From efa81754525df3ec86f22ecdae8e2dc43fad9a10 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 13 Feb 2016 18:35:33 +0000 Subject: [PATCH 01/36] added 70cm to dxcc list --- application/models/dxcc.php | 3 ++- application/views/awards/dxcc/index.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/application/models/dxcc.php b/application/models/dxcc.php index d3ec2873..aa94e53d 100644 --- a/application/models/dxcc.php +++ b/application/models/dxcc.php @@ -32,7 +32,8 @@ class DXCC extends CI_Model { "10m"=>0, "4m" =>0, "6m" =>0, - "2m" =>0); + "2m" =>0, + "70cm" =>0); $last_country = $row->COL_COUNTRY; } diff --git a/application/views/awards/dxcc/index.php b/application/views/awards/dxcc/index.php index a62472e6..53cc7dad 100644 --- a/application/views/awards/dxcc/index.php +++ b/application/views/awards/dxcc/index.php @@ -27,6 +27,7 @@ 6m 4m 2m + 70cm From d404f80e7dd2f6a161492ffee0fbbb380eb090bb Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 13 Feb 2016 18:40:44 +0000 Subject: [PATCH 02/36] migrate bits --- application/config/migration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/config/migration.php b/application/config/migration.php index 71ecf7ba..1e1b1c54 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -21,7 +21,7 @@ $config['migration_enabled'] = TRUE; | be upgraded / downgraded to. | */ -$config['migration_version'] = 6; +$config['migration_version'] = 7; /* From 7e014e504da535434b2f801cb79b9aba9b41c0af Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 17 Feb 2016 14:27:18 +0000 Subject: [PATCH 03/36] removed sensor parameter from Google Maps js script include as its no longer required --- application/views/layout/header.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/views/layout/header.php b/application/views/layout/header.php index fd378ba9..3b84f5f8 100644 --- a/application/views/layout/header.php +++ b/application/views/layout/header.php @@ -10,7 +10,7 @@ - + From 8524717b7607ea758818db885cd729ecbaf13412 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 17 Feb 2016 14:30:52 +0000 Subject: [PATCH 04/36] removed sensor parameter from Google Maps js script include as its no longer required from mini_header thats used for popups --- application/views/layout/mini_header.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/views/layout/mini_header.php b/application/views/layout/mini_header.php index 13817316..42be5105 100644 --- a/application/views/layout/mini_header.php +++ b/application/views/layout/mini_header.php @@ -10,7 +10,7 @@ - + From e589fe1c9c865bd17d09edb69d3036c33c88c2ee Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 17 Feb 2016 15:46:23 +0000 Subject: [PATCH 05/36] trying to fix merge --- application/config/migration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/config/migration.php b/application/config/migration.php index 1e1b1c54..71ecf7ba 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -21,7 +21,7 @@ $config['migration_enabled'] = TRUE; | be upgraded / downgraded to. | */ -$config['migration_version'] = 7; +$config['migration_version'] = 6; /* From 8a4beb96bb76ae0e522996444bbcfe5367f76a5b Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 17 Feb 2016 15:57:00 +0000 Subject: [PATCH 06/36] added menu item for updates --- application/views/layout/header.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/views/layout/header.php b/application/views/layout/header.php index 3b84f5f8..ad601737 100644 --- a/application/views/layout/header.php +++ b/application/views/layout/header.php @@ -90,7 +90,7 @@
  • eQSL Import
  • eQSL Export
  • LoTW Import
  • -
  • LoTW Export
  • +
  • Update Tools
  • From 2e6cb20193615cabf9f832ca96675d0a4bdf7621 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 17 Feb 2016 23:18:24 +0000 Subject: [PATCH 07/36] added some contributing guidelines to the readme --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c71c929..c47fdc54 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Website: [http://www.cloudlog.co.uk](http://www.cloudlog.co.uk) You can set up the basics of Cloudlog by opening /install in your browser. Please note theres an issue with the demo account creation (password invalid) so after this process you must disable auth via the config.php file and manually create yourself a user in till this issue is fixed. -More information can be found in the wiki. +More information can be found in the [wiki](https://github.com/magicbug/Cloudlog/wiki). ## CAT Control @@ -26,6 +26,14 @@ Its possible to interact with your radio if you have a K3, FT-950 or a ICOM radi I've started building out some scripts to generate labels for sticking on QSL Cards, at the moment it supports 24 label sheets, but theres no reason for it not to support more, these can be found at [Cloudlog-Labels](https://github.com/magicbug/cloudlog-labels) +## Contributing + +If you would like to contributing in anyway to Cloudlog then its most appreciated, this has been developed in free time, help coding new features or writing documentation is always useful. + +Please note that Cloudlog was built using [Codeigniter](https://ellislab.com/codeigniter/user-guide/) version 2 and uses a rather old version of the Bootstrap UI v1.4.0 documentation is available for this when building components. + +If you are writing code all javascript and css should be pleased within the js & css folders not within the views where possible. + ## Copyright / Licence The MIT License (MIT) From be26bf727beadf9aa587a77af722646e388d6796 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Thu, 18 Feb 2016 17:32:46 +0000 Subject: [PATCH 08/36] removed the contesting sql from install .sql file as its a defunk item --- install/assets/install.sql | 58 -------------------------------------- 1 file changed, 58 deletions(-) diff --git a/install/assets/install.sql b/install/assets/install.sql index cd70790b..b5e605a3 100755 --- a/install/assets/install.sql +++ b/install/assets/install.sql @@ -54,64 +54,6 @@ CREATE TABLE IF NOT EXISTS `cat` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; - --- -------------------------------------------------------- - --- --- Table structure for table `contests` --- - -CREATE TABLE IF NOT EXISTS `contests` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL, - `start` datetime NOT NULL, - `end` datetime NOT NULL, - `template` int(11) NOT NULL, - `serial_num` int(11) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; - --- --- Dumping data for table `contests` --- - - --- -------------------------------------------------------- - --- --- Table structure for table `contest_template` --- - -CREATE TABLE IF NOT EXISTS `contest_template` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL, - `band_160` varchar(20) NOT NULL, - `band_80` varchar(20) NOT NULL, - `band_40` varchar(20) NOT NULL, - `band_20` varchar(20) NOT NULL, - `band_15` varchar(20) NOT NULL, - `band_10` varchar(20) NOT NULL, - `band_6m` varchar(20) NOT NULL, - `band_4m` varchar(20) NOT NULL, - `band_2m` varchar(20) NOT NULL, - `band_70cm` varchar(20) NOT NULL, - `band_23cm` varchar(20) NOT NULL, - `mode_ssb` varchar(20) NOT NULL, - `mode_cw` varchar(20) NOT NULL, - `serial` varchar(20) NOT NULL, - `point_per_km` int(20) NOT NULL, - `qra` varchar(20) NOT NULL, - `other_exch` varchar(255) NOT NULL, - `scoring` varchar(255) NOT NULL, - PRIMARY KEY (`id`), - KEY `name` (`name`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; - --- --- Dumping data for table `contest_template` --- - - -- -------------------------------------------------------- -- From ad50b55cb688d20f26ae333ed342be06c00a0ccc Mon Sep 17 00:00:00 2001 From: Alexandru Csete Date: Sat, 13 Feb 2016 22:21:23 +0100 Subject: [PATCH 09/36] Import ADIF 'comment' field in addition to 'notes'. PS: Perhaps 'notes' should go into COL_NOTES instead. --- application/models/logbook_model.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/models/logbook_model.php b/application/models/logbook_model.php index 0f0e2710..73b800ef 100644 --- a/application/models/logbook_model.php +++ b/application/models/logbook_model.php @@ -735,7 +735,9 @@ class Logbook_model extends CI_Model { } // Store Notes - if(isset($record['notes'])) { + if (isset($record['comment'])) { + $comment = $record['comment']; + } else if (isset($record['notes'])) { $comment = $record['notes']; } else { $comment = ""; From e162044945619523edcf92ac3c24fe85ca438b77 Mon Sep 17 00:00:00 2001 From: Alexandru Csete Date: Sat, 13 Feb 2016 23:03:38 +0100 Subject: [PATCH 10/36] Import MY_RIG field from ADIF file. --- application/models/logbook_model.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/application/models/logbook_model.php b/application/models/logbook_model.php index 73b800ef..387ad52c 100644 --- a/application/models/logbook_model.php +++ b/application/models/logbook_model.php @@ -882,6 +882,13 @@ class Logbook_model extends CI_Model { $srx = null; } + // MY_RIG + if (isset($record['my_rig'])) { + $my_rig = $record['my_rig']; + } else { + $my_rig = null; + } + // Filter Modes if not apart of ADIF spec if($record['mode'] == "RTTY75") { // Set RTTY75 to just RTTY @@ -929,7 +936,8 @@ class Logbook_model extends CI_Model { 'COL_QSLSDATE' => $QSLSDATE, 'COL_QSL_SENT' => $QSLSENT, 'COL_LOTW_QSL_SENT' => $LOTWQSLSENT, - 'COL_LOTW_QSL_RCVD' => $LOTWQSLRCVD + 'COL_LOTW_QSL_RCVD' => $LOTWQSLRCVD, + 'COL_MY_RIG' => $my_rig ); // if eQSL username set, default SENT & RCVD to 'N' else leave as null From 0665a9b86efe32bf3b94b2a5db9d063dc0936501 Mon Sep 17 00:00:00 2001 From: Alexandru Csete Date: Sun, 14 Feb 2016 13:48:55 +0100 Subject: [PATCH 11/36] Import TX_PWR and MY_GRIDSQUARE from ADIF. --- application/models/logbook_model.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/application/models/logbook_model.php b/application/models/logbook_model.php index 387ad52c..2c03dbc3 100644 --- a/application/models/logbook_model.php +++ b/application/models/logbook_model.php @@ -889,6 +889,20 @@ class Logbook_model extends CI_Model { $my_rig = null; } + // TX_PWR + if (isset($record['tx_pwr'])) { + $tx_pwr = $record['tx_pwr']; + } else { + $tx_pwr = 0.0; + } + + // MY_GRIDSQUARE + if (isset($record['my_gridsquare'])) { + $my_gridsquare = $record['my_gridsquare']; + } else { + $my_gridsquare = null; + } + // Filter Modes if not apart of ADIF spec if($record['mode'] == "RTTY75") { // Set RTTY75 to just RTTY @@ -937,7 +951,9 @@ class Logbook_model extends CI_Model { 'COL_QSL_SENT' => $QSLSENT, 'COL_LOTW_QSL_SENT' => $LOTWQSLSENT, 'COL_LOTW_QSL_RCVD' => $LOTWQSLRCVD, - 'COL_MY_RIG' => $my_rig + 'COL_MY_RIG' => $my_rig, + 'COL_TX_PWR' => $tx_pwr, + 'COL_MY_GRIDSQUARE' => $my_gridsquare ); // if eQSL username set, default SENT & RCVD to 'N' else leave as null From 1979818fb2f7ee2a4dcf2a68354fc2f24ff14608 Mon Sep 17 00:00:00 2001 From: Alexandru Csete Date: Wed, 17 Feb 2016 23:38:07 +0100 Subject: [PATCH 12/36] Indent --- application/models/logbook_model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/models/logbook_model.php b/application/models/logbook_model.php index 0f0e2710..2e2ca4cd 100644 --- a/application/models/logbook_model.php +++ b/application/models/logbook_model.php @@ -205,7 +205,7 @@ class Logbook_model extends CI_Model { 'COL_PROP_MODE' => $this->input->post('prop_mode'), 'COL_FREQ_RX' => '0', 'COL_STX_STRING' => $this->input->post('stx_string'), - 'COL_SRX_STRING' => $this->input->post('srx_string') + 'COL_SRX_STRING' => $this->input->post('srx_string') ); $this->db->where('COL_PRIMARY_KEY', $this->input->post('id')); From 950a83ef924a53ff5764b3e124ef559238f5e0fe Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 2 Apr 2016 16:24:28 +0100 Subject: [PATCH 13/36] converting hertz to mhz on adif export #196 --- .gitignore | 1 + .idea/Cloudlog.iml | 8 + .idea/copyright/profiles_settings.xml | 3 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/workspace.xml | 280 ++++++++++++++++++++++ application/config/migration.php | 1 - application/controllers/update.php | 1 + application/views/adif/data/exportall.php | 2 +- 9 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 .idea/Cloudlog.iml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml diff --git a/.gitignore b/.gitignore index 67fe860e..3bc66988 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /uploads/*.TQ8 /install/* /updates/* +/.idea/* diff --git a/.idea/Cloudlog.iml b/.idea/Cloudlog.iml new file mode 100644 index 00000000..c956989b --- /dev/null +++ b/.idea/Cloudlog.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000..e7bedf33 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..26b8ffb4 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..a8941ef6 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1458423140445 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/config/migration.php b/application/config/migration.php index 947334c3..be559d8e 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -23,7 +23,6 @@ $config['migration_enabled'] = TRUE; */ $config['migration_version'] = 12; - /* |-------------------------------------------------------------------------- | Migrations Path diff --git a/application/controllers/update.php b/application/controllers/update.php index 5896c2e7..e24f10ff 100644 --- a/application/controllers/update.php +++ b/application/controllers/update.php @@ -14,6 +14,7 @@ class Update extends CI_Controller { $this->load->view('layout/header', $data); $this->load->view('update/index'); $this->load->view('layout/footer'); + } /* diff --git a/application/views/adif/data/exportall.php b/application/views/adif/data/exportall.php index 11538d72..bd771b72 100644 --- a/application/views/adif/data/exportall.php +++ b/application/views/adif/data/exportall.php @@ -9,6 +9,6 @@ result() as $qso) { //print_r($qso);?> - COL_CALL); ?>>COL_CALL; ?>COL_BAND); ?>>COL_BAND; ?>COL_MODE); ?>>COL_MODE; ?>COL_FREQ != "0") { ?>COL_FREQ); ?>>COL_FREQ; ?>COL_TIME_ON); $new_date = date('Ymd', $date_on); ?>>COL_TIME_ON); $new_on = date('His', $time_on); ?>>COL_TIME_OFF); $new_off = date('His', $time_off); ?>>COL_RST_RCVD); ?>>COL_RST_RCVD; ?>COL_RST_SENT); ?>>COL_RST_SENT; ?>COL_QSL_RCVD); ?>>COL_QSL_RCVD; ?>COL_QSL_SENT); ?>>COL_QSL_SENT; ?>COL_COUNTRY); ?>>COL_COUNTRY; ?>COL_GRIDSQUARE != "") { ?>COL_GRIDSQUARE); ?>>COL_GRIDSQUARE; ?>COL_SAT_NAME) { ?>COL_SAT_MODE); ?>>COL_SAT_MODE; ?>COL_SAT_NAME); ?>>COL_SAT_NAME; ?>COL_PROP_MODE) { ?>COL_PROP_MODE); ?>>COL_PROP_MODE; ?>COL_NAME) { ?>COL_NAME); ?>>COL_NAME; ?>COL_COMMENT) { ?>COL_COMMENT); ?>>COL_COMMENT; ?> + COL_CALL); ?>>COL_CALL; ?>COL_BAND); ?>>COL_BAND; ?>COL_MODE); ?>>COL_MODE; ?>COL_FREQ != "0") { ?>COL_FREQ, 0, -3); ?>>COL_TIME_ON); $new_date = date('Ymd', $date_on); ?>>COL_TIME_ON); $new_on = date('His', $time_on); ?>>COL_TIME_OFF); $new_off = date('His', $time_off); ?>>COL_RST_RCVD); ?>>COL_RST_RCVD; ?>COL_RST_SENT); ?>>COL_RST_SENT; ?>COL_QSL_RCVD); ?>>COL_QSL_RCVD; ?>COL_QSL_SENT); ?>>COL_QSL_SENT; ?>COL_COUNTRY); ?>>COL_COUNTRY; ?>COL_GRIDSQUARE != "") { ?>COL_GRIDSQUARE); ?>>COL_GRIDSQUARE; ?>COL_SAT_NAME) { ?>COL_SAT_MODE); ?>>COL_SAT_MODE; ?>COL_SAT_NAME); ?>>COL_SAT_NAME; ?>COL_PROP_MODE) { ?>COL_PROP_MODE); ?>>COL_PROP_MODE; ?>COL_NAME) { ?>COL_NAME); ?>>COL_NAME; ?>COL_COMMENT) { ?>COL_COMMENT); ?>>COL_COMMENT; ?> \ No newline at end of file From 4a13275d8525cf34d05def7379f6d7b9847eafbd Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 2 Apr 2016 16:33:04 +0100 Subject: [PATCH 14/36] helps to save the right code --- application/views/adif/data/exportall.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/views/adif/data/exportall.php b/application/views/adif/data/exportall.php index bd771b72..dcc56b2f 100644 --- a/application/views/adif/data/exportall.php +++ b/application/views/adif/data/exportall.php @@ -9,6 +9,6 @@ result() as $qso) { //print_r($qso);?> - COL_CALL); ?>>COL_CALL; ?>COL_BAND); ?>>COL_BAND; ?>COL_MODE); ?>>COL_MODE; ?>COL_FREQ != "0") { ?>COL_FREQ, 0, -3); ?>>COL_TIME_ON); $new_date = date('Ymd', $date_on); ?>>COL_TIME_ON); $new_on = date('His', $time_on); ?>>COL_TIME_OFF); $new_off = date('His', $time_off); ?>>COL_RST_RCVD); ?>>COL_RST_RCVD; ?>COL_RST_SENT); ?>>COL_RST_SENT; ?>COL_QSL_RCVD); ?>>COL_QSL_RCVD; ?>COL_QSL_SENT); ?>>COL_QSL_SENT; ?>COL_COUNTRY); ?>>COL_COUNTRY; ?>COL_GRIDSQUARE != "") { ?>COL_GRIDSQUARE); ?>>COL_GRIDSQUARE; ?>COL_SAT_NAME) { ?>COL_SAT_MODE); ?>>COL_SAT_MODE; ?>COL_SAT_NAME); ?>>COL_SAT_NAME; ?>COL_PROP_MODE) { ?>COL_PROP_MODE); ?>>COL_PROP_MODE; ?>COL_NAME) { ?>COL_NAME); ?>>COL_NAME; ?>COL_COMMENT) { ?>COL_COMMENT); ?>>COL_COMMENT; ?> + COL_CALL); ?>>COL_CALL; ?>COL_BAND); ?>>COL_BAND; ?>COL_MODE); ?>>COL_MODE; ?>COL_FREQ != "0") { ?>COL_FREQ / 1000000; ?>>COL_TIME_ON); $new_date = date('Ymd', $date_on); ?>>COL_TIME_ON); $new_on = date('His', $time_on); ?>>COL_TIME_OFF); $new_off = date('His', $time_off); ?>>COL_RST_RCVD); ?>>COL_RST_RCVD; ?>COL_RST_SENT); ?>>COL_RST_SENT; ?>COL_QSL_RCVD); ?>>COL_QSL_RCVD; ?>COL_QSL_SENT); ?>>COL_QSL_SENT; ?>COL_COUNTRY); ?>>COL_COUNTRY; ?>COL_GRIDSQUARE != "") { ?>COL_GRIDSQUARE); ?>>COL_GRIDSQUARE; ?>COL_SAT_NAME) { ?>COL_SAT_MODE); ?>>COL_SAT_MODE; ?>COL_SAT_NAME); ?>>COL_SAT_NAME; ?>COL_PROP_MODE) { ?>COL_PROP_MODE); ?>>COL_PROP_MODE; ?>COL_NAME) { ?>COL_NAME); ?>>COL_NAME; ?>COL_COMMENT) { ?>COL_COMMENT); ?>>COL_COMMENT; ?> \ No newline at end of file From 14d443ed8440ac3be40a054e85cfb623b9946209 Mon Sep 17 00:00:00 2001 From: Dinesh Cyanam Date: Fri, 8 Apr 2016 16:06:21 -0400 Subject: [PATCH 15/36] Changed the table_hrd_contacts_v01 table name to uppercase in the log.sql file. Cloudlog is expecting an uppercase table name --- sql/tables/log.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/tables/log.sql b/sql/tables/log.sql index 970d4fb8..290d2e44 100644 --- a/sql/tables/log.sql +++ b/sql/tables/log.sql @@ -22,10 +22,10 @@ SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; -- -------------------------------------------------------- -- --- Table structure for table `table_hrd_contacts_v01` +-- Table structure for table `TABLE_HRD_CONTACTS_V01` -- -CREATE TABLE IF NOT EXISTS `table_hrd_contacts_v01` ( +CREATE TABLE IF NOT EXISTS `TABLE_HRD_CONTACTS_V01` ( `COL_PRIMARY_KEY` int(11) NOT NULL AUTO_INCREMENT, `COL_ADDRESS` varchar(255) DEFAULT NULL, `COL_AGE` int(11) DEFAULT NULL, From fddecbcaf6fe1f8c2431ac4804fcac6ac597f620 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 26 Oct 2016 17:34:45 +0100 Subject: [PATCH 16/36] Added mode MSK144 for @m1bxf Added digital mode MSK144 for @m1bxf --- application/views/qso/index.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/views/qso/index.php b/application/views/qso/index.php index dc80f3e9..8fb7d095 100644 --- a/application/views/qso/index.php +++ b/application/views/qso/index.php @@ -84,6 +84,7 @@ + From 8b5899392403805375655a2891391971af3971e3 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 26 Oct 2016 17:37:13 +0100 Subject: [PATCH 17/36] Added JTMSK & QRA64 to the modes dropdown as requested by @m1bxf --- application/views/qso/index.php | 2 ++ application/views/qso/manual.php | 3 +++ 2 files changed, 5 insertions(+) diff --git a/application/views/qso/index.php b/application/views/qso/index.php index 8fb7d095..5f286077 100644 --- a/application/views/qso/index.php +++ b/application/views/qso/index.php @@ -85,6 +85,8 @@ + + diff --git a/application/views/qso/manual.php b/application/views/qso/manual.php index 65f04999..9c5fb022 100644 --- a/application/views/qso/manual.php +++ b/application/views/qso/manual.php @@ -74,6 +74,9 @@ + + + From 7600feb17dbaa69d0e696849c85c2148724c2f07 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 26 Oct 2016 17:39:40 +0100 Subject: [PATCH 18/36] Updated the default report for FSK441 requested by @m1bxf --- application/views/qso/index.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/application/views/qso/index.php b/application/views/qso/index.php index 5f286077..ada73e25 100644 --- a/application/views/qso/index.php +++ b/application/views/qso/index.php @@ -439,9 +439,12 @@ // Change report based on mode $('.mode').change(function(){ - if($(this).val() == 'JT65' || $(this).val() == 'JT65B' || $(this).val() == 'JT6C' || $(this).val() == 'JT6M' || $(this).val() == 'FSK441' || $(this).val() == 'JTMS' || $(this).val() == 'ISCAT'){ // or this.value == 'volvo' + if($(this).val() == 'JT65' || $(this).val() == 'JT65B' || $(this).val() == 'JT6C' || $(this).val() == 'JT6M' || $(this).val() == 'JTMS' || $(this).val() == 'ISCAT' || $(this).val() == 'MSK144' || $(this).val() == 'JTMSK' || $(this).val() == 'QRA64'){ $('#rst_sent').val('-5'); $('#rst_recv').val('-5'); + } else if ($(this).val() == 'FSK441') { + $('#rst_sent').val('26'); + $('#rst_recv').val('26'); } else if ($(this).val() == 'CW') { $('#rst_sent').val('599'); $('#rst_recv').val('599'); @@ -450,8 +453,6 @@ $('#rst_recv').val('59'); } }); - - }); From 4f5b8edd861b57a2bdf8a5468df040ea0a9b011a Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 26 Oct 2016 17:41:28 +0100 Subject: [PATCH 19/36] changed default report for jt6m --- application/views/qso/index.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/views/qso/index.php b/application/views/qso/index.php index ada73e25..ce9253bf 100644 --- a/application/views/qso/index.php +++ b/application/views/qso/index.php @@ -439,10 +439,10 @@ // Change report based on mode $('.mode').change(function(){ - if($(this).val() == 'JT65' || $(this).val() == 'JT65B' || $(this).val() == 'JT6C' || $(this).val() == 'JT6M' || $(this).val() == 'JTMS' || $(this).val() == 'ISCAT' || $(this).val() == 'MSK144' || $(this).val() == 'JTMSK' || $(this).val() == 'QRA64'){ + if($(this).val() == 'JT65' || $(this).val() == 'JT65B' || $(this).val() == 'JT6C' || $(this).val() == 'JTMS' || $(this).val() == 'ISCAT' || $(this).val() == 'MSK144' || $(this).val() == 'JTMSK' || $(this).val() == 'QRA64'){ $('#rst_sent').val('-5'); $('#rst_recv').val('-5'); - } else if ($(this).val() == 'FSK441') { + } else if ($(this).val() == 'FSK441' || $(this).val() == 'JT6M') { $('#rst_sent').val('26'); $('#rst_recv').val('26'); } else if ($(this).val() == 'CW') { From 6832ec031de2e29171fbb7d2c40b2b150a677b85 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 16 Nov 2016 16:59:53 +0000 Subject: [PATCH 20/36] Added Features to allow SatPC32 to act as a Radio interface with Cloudlog This required extra SQL so you'll need to run the SQL query. ALTER TABLE `cat` ADD `downlink_freq` INT(11) NOT NULL AFTER `mode`, ADD `uplink_freq` INT(11) NOT NULL AFTER `downlink_freq`, ADD `downlink_mode` VARCHAR(255) NOT NULL AFTER `uplink_freq`, ADD `uplink_mode` VARCHAR(255) NOT NULL AFTER `downlink_mode`, ADD `sat_name` VARCHAR(255) NOT NULL AFTER `uplink_mode`; --- application/config/config.sample.php | 2 +- application/controllers/radio.php | 81 ++++++++++++++++++++++++++-- application/models/cat.php | 64 ++++++++++++++++------ application/views/qso/index.php | 22 ++++++-- install/assets/install.sql | 2 + sql/tables/cat.sql | 4 +- 6 files changed, 150 insertions(+), 25 deletions(-) diff --git a/application/config/config.sample.php b/application/config/config.sample.php index 0452150c..1c501c2e 100644 --- a/application/config/config.sample.php +++ b/application/config/config.sample.php @@ -13,7 +13,7 @@ */ $config['app_name'] = "Cloudlog"; -$config['app_version'] = "1.1"; +$config['app_version'] = "1.5"; $config['directory'] = "logbook"; $config['callbook'] = "hamqth"; // Options are hamqth or qrz diff --git a/application/controllers/radio.php b/application/controllers/radio.php index 74ba5474..5e2acadf 100644 --- a/application/controllers/radio.php +++ b/application/controllers/radio.php @@ -66,7 +66,21 @@ { foreach ($query->result() as $row) { - echo $row->frequency; + if( $row->frequency == "0") { + $this->db->select('uplink_freq'); + $this->db->where('id', $id); + $query = $this->db->get('cat'); + + if ($query->num_rows() > 0) + { + foreach ($query->result() as $row) + { + echo strtoupper($row->uplink_freq); + } + } + } else { + echo $row->frequency; + } } } } @@ -78,7 +92,7 @@ if(!$this->user_model->authorize(99)) { $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); redirect('dashboard'); } //$this->db->where('radio', $result['radio']); - $this->db->select('mode'); + $this->db->select('mode, radio, uplink_mode'); $this->db->where('id', $id); $query = $this->db->get('cat'); @@ -86,7 +100,68 @@ { foreach ($query->result() as $row) { - echo strtoupper($row->mode); + if($row->radio != "SatPC32") { + echo strtoupper($row->mode); + } else { + echo strtoupper($row->uplink_mode); + } + } + } + } + + function satname($id) { + + // Check Auth + $this->load->model('user_model'); + if(!$this->user_model->authorize(99)) { $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); redirect('dashboard'); } + + //$this->db->where('radio', $result['radio']); + $this->db->select('sat_name'); + $this->db->where('id', $id); + $query = $this->db->get('cat'); + + if ($query->num_rows() > 0) + { + foreach ($query->result() as $row) + { + echo strtoupper($row->sat_name); + } + } + } + + function satmode($id) { + + // Check Auth + $this->load->model('user_model'); + if(!$this->user_model->authorize(99)) { $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); redirect('dashboard'); } + + //$this->db->where('radio', $result['radio']); + $this->db->select('uplink_freq, downlink_freq'); + $this->db->where('id', $id); + $query = $this->db->get('cat'); + + if ($query->num_rows() > 0) + { + foreach ($query->result() as $row) + { + + if ($row->uplink_freq > 144000000 && $row->uplink_freq < 147000000) { + $uplink_mode = "V"; + } elseif ($row->uplink_freq > 432000000 && $row->uplink_freq < 438000000) { + $uplink_mode = "U"; + } elseif ($row->uplink_freq > 28000000 && $row->uplink_freq < 30000000) { + $uplink_mode = "A"; + } + + if ($row->downlink_freq > 144000000 && $row->downlink_freq < 147000000) { + $downlink_mode = "V"; + } elseif ($row->downlink_freq > 432000000 && $row->downlink_freq < 438000000) { + $downlink_mode = "U"; + } elseif ($row->downlink_freq > 28000000 && $row->downlink_freq < 30000000) { + $downlink_mode = "A"; + } + + echo $uplink_mode."/".$downlink_mode; } } } diff --git a/application/models/cat.php b/application/models/cat.php index 7c65aadc..1ec8f0c3 100644 --- a/application/models/cat.php +++ b/application/models/cat.php @@ -15,27 +15,59 @@ if ($query->num_rows() > 0) { - // Update the record - foreach ($query->result() as $row) - { - $radio_id = $row->id; - - $data = array( - 'frequency' => $result['frequency'], - 'mode' => $result['mode'] - ); + if($result['radio'] == "SatPC32") { + // Update the record + foreach ($query->result() as $row) + { + $radio_id = $row->id; + + $data = array( + 'sat_name' => $result['sat_name'], + 'downlink_freq' => $result['downlink_freq'], + 'uplink_freq' => $result['uplink_freq'], + 'downlink_mode' => $result['downlink_mode'], + 'uplink_mode' => $result['uplink_mode'], + ); - $this->db->where('id', $radio_id); - $this->db->update('cat', $data); + $this->db->where('id', $radio_id); + $this->db->update('cat', $data); + } + } else { + // Update the record + foreach ($query->result() as $row) + { + $radio_id = $row->id; + + $data = array( + 'frequency' => $result['frequency'], + 'mode' => $result['mode'], + ); + + $this->db->where('id', $radio_id); + $this->db->update('cat', $data); + } } } else { // Add a new record - $data = array( - 'radio' => $result['radio'], - 'frequency' => $result['frequency'], - 'mode' => $result['mode'] - ); + if($result['radio'] == "SatPC32") { + $data = array( + 'radio' => $result['radio'], + 'frequency' => $result['frequency'], + 'mode' => $result['mode'], + 'sat_name' => $result['sat_name'], + 'downlink_freq' => $result['downlink_freq'], + 'uplink_freq' => $result['uplink_freq'], + 'downlink_mode' => $result['downlink_mode'], + 'uplink_mode' => $result['uplink_mode'], + ); + } else { + $data = array( + 'radio' => $result['radio'], + 'frequency' => $result['frequency'], + 'mode' => $result['mode'] + ); + } $this->db->insert('cat', $data); diff --git a/application/views/qso/index.php b/application/views/qso/index.php index ce9253bf..a1240da7 100644 --- a/application/views/qso/index.php +++ b/application/views/qso/index.php @@ -209,12 +209,12 @@ - + - +
    Sat Name
    Sat Mode
    @@ -303,8 +303,12 @@ if($('select.radios option:selected').val() != '0') { // Get frequency $.get('radio/frequency/' + $('select.radios option:selected').val(), function(result) { - $('#frequency').val(result); - $(".band").val(frequencyToBand(result)); + + if(result == "0") { + } else { + $('#frequency').val(result); + $(".band").val(frequencyToBand(result)); + } }); // Get Mode @@ -315,6 +319,16 @@ $(".mode").val(result); } }); + + // Get SAT_Name + $.get('radio/satname/' + $('select.radios option:selected').val(), function(result) { + $(".sat_name").val(result); + }); + + // Get SAT_Name + $.get('radio/satmode/' + $('select.radios option:selected').val(), function(result) { + $(".sat_mode").val(result); + }); } }; diff --git a/install/assets/install.sql b/install/assets/install.sql index b5e605a3..f827cdea 100755 --- a/install/assets/install.sql +++ b/install/assets/install.sql @@ -3806,3 +3806,5 @@ CREATE TABLE IF NOT EXISTS `config` ( INSERT INTO `config` (`id`, `lotw_download_url`, `lotw_upload_url`, `lotw_rcvd_mark`, `lotw_login_url`, `eqsl_download_url`, `eqsl_rcvd_mark`) VALUES (1, 'https://p1k.arrl.org/lotwuser/lotwreport.adi', 'https://p1k.arrl.org/lotwuser/upload', 'Y', 'https://p1k.arrl.org/lotwuser/default', 'http://www.eqsl.cc/qslcard/DownloadInBox.cfm', 'Y'); + +ALTER TABLE `cat` ADD `downlink_freq` INT(11) NOT NULL AFTER `mode`, ADD `uplink_freq` INT(11) NOT NULL AFTER `downlink_freq`, ADD `downlink_mode` VARCHAR(255) NOT NULL AFTER `uplink_freq`, ADD `uplink_mode` VARCHAR(255) NOT NULL AFTER `downlink_mode`, ADD `sat_name` VARCHAR(255) NOT NULL AFTER `uplink_mode`; diff --git a/sql/tables/cat.sql b/sql/tables/cat.sql index b83684d2..55b64e49 100644 --- a/sql/tables/cat.sql +++ b/sql/tables/cat.sql @@ -5,4 +5,6 @@ CREATE TABLE IF NOT EXISTS `cat` ( `mode` varchar(10) NOT NULL, `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1 ; \ No newline at end of file +) ENGINE=InnoDB DEFAULT CHARSET=latin1 ; + +ALTER TABLE `cat` ADD `downlink_freq` INT(11) NOT NULL AFTER `mode`, ADD `uplink_freq` INT(11) NOT NULL AFTER `downlink_freq`, ADD `downlink_mode` VARCHAR(255) NOT NULL AFTER `uplink_freq`, ADD `uplink_mode` VARCHAR(255) NOT NULL AFTER `downlink_mode`, ADD `sat_name` VARCHAR(255) NOT NULL AFTER `uplink_mode`; \ No newline at end of file From 9ba0bdde61409b7e79ba6793a2e9fd90ef4fa5a2 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 16 Nov 2016 17:03:55 +0000 Subject: [PATCH 21/36] Made house keeping changes to documents. --- .gitignore | 2 +- README.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3bc66988..61ae130a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ /uploads/*.TQ8 /install/* /updates/* -/.idea/* +.idea/* diff --git a/README.md b/README.md index c47fdc54..198b3fb1 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,12 @@ More information can be found in the [wiki](https://github.com/magicbug/Cloudlog Its possible to interact with your radio if you have a K3, FT-950 or a ICOM radio using a nice application that Rob (M0VFC) has put together called [CloudlogCAT](https://github.com/rmc47/CloudlogCAT). The application is still in active development and lots of new features are due. +## SatPC32 Interface + +If your into satellite operations I have written a application [SatPC32 to Cloud Interface](https://github.com/magicbug/SatPC32-To-Cloudlog) which allows automatic population of satellite fields, Cloudlog just sees this as another radio interface. + +Code for this is BETA feel free to make modifications and do PRs. + ## QSL Card Labels I've started building out some scripts to generate labels for sticking on QSL Cards, at the moment it supports 24 label sheets, but theres no reason for it not to support more, these can be found at [Cloudlog-Labels](https://github.com/magicbug/cloudlog-labels) From e84381b54ec8a92521f492f0a6bfa72a917fbadc Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 16 Nov 2016 17:04:34 +0000 Subject: [PATCH 22/36] House Keeping --- .idea/Cloudlog.iml | 8 - .idea/copyright/profiles_settings.xml | 3 - .idea/modules.xml | 8 - .idea/vcs.xml | 6 - .idea/workspace.xml | 280 -------------------------- 5 files changed, 305 deletions(-) delete mode 100644 .idea/Cloudlog.iml delete mode 100644 .idea/copyright/profiles_settings.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml diff --git a/.idea/Cloudlog.iml b/.idea/Cloudlog.iml deleted file mode 100644 index c956989b..00000000 --- a/.idea/Cloudlog.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf33..00000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 26b8ffb4..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index a8941ef6..00000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,280 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1458423140445 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 59cd8bd4c99e3cdea1ac2653fdc73a5524049ca2 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Fri, 18 Nov 2016 15:45:34 +0000 Subject: [PATCH 23/36] Set FMN to FM so correct modes selected in the UI. --- application/controllers/radio.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/application/controllers/radio.php b/application/controllers/radio.php index 5e2acadf..2547ad39 100644 --- a/application/controllers/radio.php +++ b/application/controllers/radio.php @@ -101,9 +101,17 @@ foreach ($query->result() as $row) { if($row->radio != "SatPC32") { - echo strtoupper($row->mode); + if(strtoupper($row->mode) == "FMN"){ + echo "FM"; + } else { + echo strtoupper($row->mode); + } } else { - echo strtoupper($row->uplink_mode); + if(strtoupper($row->uplink_mode) == "FMN"){ + echo "FM"; + } else { + echo strtoupper($row->uplink_mode); + } } } } From 11b000888284824235423c19a02651568f7cf815 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sun, 12 Feb 2017 22:10:17 +0000 Subject: [PATCH 24/36] Added HELL and HELL80 to Modes --- application/views/qso/index.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/views/qso/index.php b/application/views/qso/index.php index a1240da7..b9cdbfe8 100644 --- a/application/views/qso/index.php +++ b/application/views/qso/index.php @@ -89,6 +89,8 @@ + + Band From 23bb1e6eb5a2e192c965998979ca0dd213f89c9f Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Tue, 7 Mar 2017 20:46:09 +0000 Subject: [PATCH 25/36] Added DSTAR and Digital Voice to modes dropdown for M0TZO --- application/views/qso/index.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/views/qso/index.php b/application/views/qso/index.php index b9cdbfe8..d5ea7edf 100644 --- a/application/views/qso/index.php +++ b/application/views/qso/index.php @@ -91,6 +91,8 @@ + + Band From 6a146f8235f855ede8877c5affd8e8c9aaa1ef6a Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Mon, 13 Mar 2017 21:27:21 +0000 Subject: [PATCH 26/36] Update SatPC32 Sat Names to correct idents --- application/controllers/radio.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/application/controllers/radio.php b/application/controllers/radio.php index 2547ad39..f1fcba8c 100644 --- a/application/controllers/radio.php +++ b/application/controllers/radio.php @@ -132,7 +132,13 @@ { foreach ($query->result() as $row) { - echo strtoupper($row->sat_name); + if($row->sat_name == "AO-07") { + echo "AO-7"; + } elseif ($row->sat_name == "LILACSAT") { + echo "CAS-3H"; + } else { + echo strtoupper($row->sat_name); + } } } } From 39f4e83e09392beabff88af24580618c1e586709 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 26 Jul 2017 10:40:56 +0100 Subject: [PATCH 27/36] Added FT8 to the modes dropdowns --- application/views/qso/index.php | 1 + application/views/qso/manual.php | 1 + 2 files changed, 2 insertions(+) diff --git a/application/views/qso/index.php b/application/views/qso/index.php index d5ea7edf..d6fcb6f2 100644 --- a/application/views/qso/index.php +++ b/application/views/qso/index.php @@ -88,6 +88,7 @@ + diff --git a/application/views/qso/manual.php b/application/views/qso/manual.php index 9c5fb022..9a6cec42 100644 --- a/application/views/qso/manual.php +++ b/application/views/qso/manual.php @@ -77,6 +77,7 @@ + From e384826aea00558b7982d94ef75a2bf5949a8fcd Mon Sep 17 00:00:00 2001 From: Warren Volz Date: Tue, 7 Nov 2017 00:45:06 +0000 Subject: [PATCH 28/36] Codeignitor 3.1.6 and corresponding changes - fixes missing () from num_rows in authenticate user function - removes passwordhash library in favor of built in PHP password_hash and password_verify functions - uppercase all class filenames - add new CLI error templates, move HTML error templates - update mimes file to latest version - update routes to latest version --- application/config/mimes.php | 279 +- application/config/routes.php | 21 +- .../controllers/{adif.php => Adif.php} | 0 application/controllers/{api.php => Api.php} | 0 .../controllers/{awards.php => Awards.php} | 0 .../controllers/{backup.php => Backup.php} | 0 .../{dashboard.php => Dashboard.php} | 0 .../{dxcluster.php => Dxcluster.php} | 0 .../controllers/{eqsl.php => Eqsl.php} | 0 .../controllers/{export.php => Export.php} | 0 .../controllers/{install.php => Install.php} | 0 application/controllers/{kml.php => Kml.php} | 0 .../controllers/{logbook.php => Logbook.php} | 0 .../controllers/{lotw.php => Lotw.php} | 0 .../controllers/{migrate.php => Migrate.php} | 0 .../controllers/{notes.php => Notes.php} | 0 application/controllers/{qsl.php => Qsl.php} | 0 application/controllers/{qso.php => Qso.php} | 0 .../controllers/{radio.php => Radio.php} | 0 .../controllers/{search.php => Search.php} | 0 .../controllers/{setup.php => Setup.php} | 0 .../controllers/{social.php => Social.php} | 0 .../{statistics.php => Statistics.php} | 0 .../controllers/{update.php => Update.php} | 0 .../controllers/{user.php => User.php} | 0 .../controllers/{widgets.php => Widgets.php} | 0 .../{adif_parser.php => Adif_parser.php} | 0 .../libraries/{hamqth.php => Hamqth.php} | 0 application/libraries/{qrz.php => Qrz.php} | 0 .../models/{adif_data.php => Adif_data.php} | 0 .../models/{api_model.php => Api_model.php} | 0 application/models/{cat.php => Cat.php} | 0 application/models/{dxcc.php => Dxcc.php} | 0 .../{dxcc_entities.php => Dxcc_entities.php} | 0 ...xcc_exceptions.php => Dxcc_exceptions.php} | 0 .../{dxcc_prefixes.php => Dxcc_prefixes.php} | 0 .../{logbook_model.php => Logbook_model.php} | 0 application/models/{lotw.php => Lotw.php} | 0 application/models/{note.php => Note.php} | 0 application/models/{search.php => Search.php} | 0 application/models/{sota.php => Sota.php} | 0 application/models/{stats.php => Stats.php} | 0 .../models/{user_model.php => User_model.php} | 16 +- application/models/{wab.php => Wab.php} | 0 application/models/{wacral.php => Wacral.php} | 0 application/third_party/PasswordHash.php | 253 -- application/views/errors/cli/error_404.php | 8 + application/views/errors/cli/error_db.php | 8 + .../views/errors/cli/error_exception.php | 21 + .../views/errors/cli/error_general.php | 8 + application/views/errors/cli/error_php.php | 21 + application/views/errors/cli/index.html | 11 + .../errors/html}/error_404.php | 0 .../errors/html}/error_db.php | 0 .../views/errors/html/error_exception.php | 32 + .../errors/html}/error_general.php | 0 .../errors/html}/error_php.php | 0 .../{errors => views/errors/html}/index.html | 0 application/views/errors/index.html | 11 + index.php | 250 +- system/core/Benchmark.php | 106 +- system/core/CodeIgniter.php | 497 ++- system/core/Common.php | 946 ++++-- system/core/Config.php | 424 +-- system/core/Controller.php | 81 +- system/core/Exceptions.php | 274 +- system/core/Hooks.php | 219 +- system/core/Input.php | 977 +++--- system/core/Lang.php | 187 +- system/core/Loader.php | 1375 ++++---- system/core/Log.php | 296 ++ system/core/Model.php | 76 +- system/core/Output.php | 721 +++-- system/core/Router.php | 598 ++-- system/core/Security.php | 1080 +++++++ system/core/URI.php | 637 ++-- system/core/Utf8.php | 151 +- system/core/compat/hash.php | 254 ++ system/core/compat/index.html | 11 + system/core/compat/mbstring.php | 149 + system/core/compat/password.php | 251 ++ system/core/compat/standard.php | 182 ++ system/core/index.html | 3 +- system/database/DB.php | 225 +- system/database/DB_active_rec.php | 2073 ------------ system/database/DB_cache.php | 164 +- system/database/DB_driver.php | 1705 ++++++---- system/database/DB_forge.php | 982 +++++- system/database/DB_query_builder.php | 2803 +++++++++++++++++ system/database/DB_result.php | 609 +++- system/database/DB_utility.php | 380 +-- .../database/drivers/cubrid/cubrid_driver.php | 405 +++ .../database/drivers/cubrid/cubrid_forge.php | 230 ++ .../database/drivers/cubrid/cubrid_result.php | 177 ++ .../drivers/cubrid/cubrid_utility.php | 79 + system/database/drivers/cubrid/index.html | 11 + .../database/drivers/ibase/ibase_driver.php | 413 +++ system/database/drivers/ibase/ibase_forge.php | 251 ++ .../database/drivers/ibase/ibase_result.php | 161 + .../database/drivers/ibase/ibase_utility.php | 69 + system/database/drivers/ibase/index.html | 11 + system/database/drivers/index.html | 3 +- system/database/drivers/mssql/index.html | 3 +- .../database/drivers/mssql/mssql_driver.php | 730 ++--- system/database/drivers/mssql/mssql_forge.php | 321 +- .../database/drivers/mssql/mssql_result.php | 127 +- .../database/drivers/mssql/mssql_utility.php | 119 +- system/database/drivers/mysql/index.html | 3 +- .../database/drivers/mysql/mysql_driver.php | 776 ++--- system/database/drivers/mysql/mysql_forge.php | 386 ++- .../database/drivers/mysql/mysql_result.php | 130 +- .../database/drivers/mysql/mysql_utility.php | 171 +- system/database/drivers/mysqli/index.html | 3 +- .../database/drivers/mysqli/mysqli_driver.php | 810 ++--- .../database/drivers/mysqli/mysqli_forge.php | 372 ++- .../database/drivers/mysqli/mysqli_result.php | 177 +- .../drivers/mysqli/mysqli_utility.php | 250 +- system/database/drivers/oci8/index.html | 3 +- system/database/drivers/oci8/oci8_driver.php | 899 +++--- system/database/drivers/oci8/oci8_forge.php | 307 +- system/database/drivers/oci8/oci8_result.php | 252 +- system/database/drivers/oci8/oci8_utility.php | 103 +- system/database/drivers/odbc/index.html | 3 +- system/database/drivers/odbc/odbc_driver.php | 699 ++-- system/database/drivers/odbc/odbc_forge.php | 298 +- system/database/drivers/odbc/odbc_result.php | 272 +- system/database/drivers/odbc/odbc_utility.php | 120 +- system/database/drivers/pdo/index.html | 11 + system/database/drivers/pdo/pdo_driver.php | 329 ++ system/database/drivers/pdo/pdo_forge.php | 65 + system/database/drivers/pdo/pdo_result.php | 198 ++ system/database/drivers/pdo/pdo_utility.php | 63 + .../drivers/pdo/subdrivers/index.html | 11 + .../drivers/pdo/subdrivers/pdo_4d_driver.php | 200 ++ .../drivers/pdo/subdrivers/pdo_4d_forge.php | 217 ++ .../pdo/subdrivers/pdo_cubrid_driver.php | 209 ++ .../pdo/subdrivers/pdo_cubrid_forge.php | 230 ++ .../pdo/subdrivers/pdo_dblib_driver.php | 353 +++ .../pdo/subdrivers/pdo_dblib_forge.php | 149 + .../pdo/subdrivers/pdo_firebird_driver.php | 279 ++ .../pdo/subdrivers/pdo_firebird_forge.php | 237 ++ .../drivers/pdo/subdrivers/pdo_ibm_driver.php | 244 ++ .../drivers/pdo/subdrivers/pdo_ibm_forge.php | 154 + .../pdo/subdrivers/pdo_informix_driver.php | 309 ++ .../pdo/subdrivers/pdo_informix_forge.php | 163 + .../pdo/subdrivers/pdo_mysql_driver.php | 374 +++ .../pdo/subdrivers/pdo_mysql_forge.php | 256 ++ .../drivers/pdo/subdrivers/pdo_oci_driver.php | 326 ++ .../drivers/pdo/subdrivers/pdo_oci_forge.php | 176 ++ .../pdo/subdrivers/pdo_odbc_driver.php | 229 ++ .../drivers/pdo/subdrivers/pdo_odbc_forge.php | 70 + .../pdo/subdrivers/pdo_pgsql_driver.php | 384 +++ .../pdo/subdrivers/pdo_pgsql_forge.php | 210 ++ .../pdo/subdrivers/pdo_sqlite_driver.php | 219 ++ .../pdo/subdrivers/pdo_sqlite_forge.php | 238 ++ .../pdo/subdrivers/pdo_sqlsrv_driver.php | 369 +++ .../pdo/subdrivers/pdo_sqlsrv_forge.php | 149 + system/database/drivers/postgre/index.html | 3 +- .../drivers/postgre/postgre_driver.php | 794 +++-- .../drivers/postgre/postgre_forge.php | 426 +-- .../drivers/postgre/postgre_result.php | 113 +- .../drivers/postgre/postgre_utility.php | 110 +- system/database/drivers/sqlite/index.html | 3 +- .../database/drivers/sqlite/sqlite_driver.php | 623 +--- .../database/drivers/sqlite/sqlite_forge.php | 350 +- .../database/drivers/sqlite/sqlite_result.php | 133 +- .../drivers/sqlite/sqlite_utility.php | 113 +- system/database/drivers/sqlite3/index.html | 11 + .../drivers/sqlite3/sqlite3_driver.php | 350 ++ .../drivers/sqlite3/sqlite3_forge.php | 225 ++ .../drivers/sqlite3/sqlite3_result.php | 194 ++ .../drivers/sqlite3/sqlite3_utility.php | 61 + system/database/drivers/sqlsrv/index.html | 11 + .../database/drivers/sqlsrv/sqlsrv_driver.php | 543 ++++ .../database/drivers/sqlsrv/sqlsrv_forge.php | 149 + .../database/drivers/sqlsrv/sqlsrv_result.php | 193 ++ .../drivers/sqlsrv/sqlsrv_utility.php | 77 + system/database/index.html | 3 +- system/fonts/index.html | 3 +- system/fonts/texb.ttf | Bin 152992 -> 143830 bytes system/helpers/array_helper.php | 144 +- system/helpers/captcha_helper.php | 351 ++- system/helpers/cookie_helper.php | 136 +- system/helpers/date_helper.php | 805 +++-- system/helpers/directory_helper.php | 83 +- system/helpers/download_helper.php | 182 +- system/helpers/email_helper.php | 84 +- system/helpers/file_helper.php | 425 ++- system/helpers/form_helper.php | 1115 +++---- system/helpers/html_helper.php | 390 ++- system/helpers/index.html | 3 +- system/helpers/inflector_helper.php | 316 +- system/helpers/language_helper.php | 79 +- system/helpers/number_helper.php | 64 +- system/helpers/path_helper.php | 92 +- system/helpers/security_helper.php | 175 +- system/helpers/smiley_helper.php | 320 +- system/helpers/string_helper.php | 364 ++- system/helpers/text_helper.php | 506 +-- system/helpers/typography_helper.php | 110 +- system/helpers/url_helper.php | 620 ++-- system/helpers/xml_helper.php | 81 +- system/index.html | 3 +- system/language/english/calendar_lang.php | 131 +- system/language/english/date_lang.php | 77 +- system/language/english/db_lang.php | 44 +- system/language/english/email_lang.php | 78 +- .../language/english/form_validation_lang.php | 89 +- system/language/english/ftp_lang.php | 65 +- system/language/english/imglib_lang.php | 77 +- system/language/english/index.html | 3 +- system/language/english/migration_lang.php | 56 +- system/language/english/number_lang.php | 50 +- system/language/english/pagination_lang.php | 43 + system/language/english/profiler_lang.php | 76 +- system/language/english/unit_test_lang.php | 79 +- system/language/english/upload_lang.php | 73 +- system/language/index.html | 3 +- system/libraries/Cache/Cache.php | 271 +- system/libraries/Cache/drivers/Cache_apc.php | 192 +- .../libraries/Cache/drivers/Cache_dummy.php | 129 +- system/libraries/Cache/drivers/Cache_file.php | 244 +- .../Cache/drivers/Cache_memcached.php | 308 +- .../libraries/Cache/drivers/Cache_redis.php | 328 ++ .../Cache/drivers/Cache_wincache.php | 217 ++ system/libraries/Cache/drivers/index.html | 11 + system/libraries/Cache/index.html | 11 + system/libraries/Calendar.php | 471 +-- system/libraries/Cart.php | 383 +-- system/libraries/Driver.php | 299 +- system/libraries/Email.php | 2128 ++++++++----- system/libraries/Encrypt.php | 380 ++- system/libraries/Encryption.php | 943 ++++++ system/libraries/Form_validation.php | 1333 ++++---- system/libraries/Ftp.php | 413 +-- system/libraries/Image_lib.php | 1522 +++++---- system/libraries/Javascript.php | 285 +- .../{javascript => Javascript}/Jquery.php | 627 ++-- system/libraries/Javascript/index.html | 11 + system/libraries/Log.php | 114 - system/libraries/Migration.php | 441 ++- system/libraries/Pagination.php | 718 ++++- system/libraries/Parser.php | 212 +- system/libraries/Profiler.php | 486 +-- system/libraries/Security.php | 738 ----- system/libraries/Session.php | 776 ----- system/libraries/Session/Session.php | 985 ++++++ .../Session/SessionHandlerInterface.php | 59 + system/libraries/Session/Session_driver.php | 191 ++ .../drivers/Session_database_driver.php | 420 +++ .../Session/drivers/Session_files_driver.php | 406 +++ .../drivers/Session_memcached_driver.php | 376 +++ .../Session/drivers/Session_redis_driver.php | 399 +++ system/libraries/Session/drivers/index.html | 11 + system/libraries/Session/index.html | 11 + system/libraries/Sha1.php | 251 -- system/libraries/Table.php | 429 +-- system/libraries/Trackback.php | 324 +- system/libraries/Typography.php | 228 +- system/libraries/Unit_test.php | 303 +- system/libraries/Upload.php | 1127 ++++--- system/libraries/User_agent.php | 350 +- system/libraries/Xmlrpc.php | 1695 ++++++---- system/libraries/Xmlrpcs.php | 365 +-- system/libraries/Zip.php | 383 ++- system/libraries/index.html | 3 +- 266 files changed, 45274 insertions(+), 23853 deletions(-) rename application/controllers/{adif.php => Adif.php} (100%) rename application/controllers/{api.php => Api.php} (100%) rename application/controllers/{awards.php => Awards.php} (100%) rename application/controllers/{backup.php => Backup.php} (100%) rename application/controllers/{dashboard.php => Dashboard.php} (100%) rename application/controllers/{dxcluster.php => Dxcluster.php} (100%) rename application/controllers/{eqsl.php => Eqsl.php} (100%) rename application/controllers/{export.php => Export.php} (100%) rename application/controllers/{install.php => Install.php} (100%) rename application/controllers/{kml.php => Kml.php} (100%) rename application/controllers/{logbook.php => Logbook.php} (100%) rename application/controllers/{lotw.php => Lotw.php} (100%) rename application/controllers/{migrate.php => Migrate.php} (100%) rename application/controllers/{notes.php => Notes.php} (100%) rename application/controllers/{qsl.php => Qsl.php} (100%) rename application/controllers/{qso.php => Qso.php} (100%) rename application/controllers/{radio.php => Radio.php} (100%) rename application/controllers/{search.php => Search.php} (100%) rename application/controllers/{setup.php => Setup.php} (100%) rename application/controllers/{social.php => Social.php} (100%) rename application/controllers/{statistics.php => Statistics.php} (100%) rename application/controllers/{update.php => Update.php} (100%) rename application/controllers/{user.php => User.php} (100%) rename application/controllers/{widgets.php => Widgets.php} (100%) rename application/libraries/{adif_parser.php => Adif_parser.php} (100%) rename application/libraries/{hamqth.php => Hamqth.php} (100%) rename application/libraries/{qrz.php => Qrz.php} (100%) rename application/models/{adif_data.php => Adif_data.php} (100%) rename application/models/{api_model.php => Api_model.php} (100%) rename application/models/{cat.php => Cat.php} (100%) rename application/models/{dxcc.php => Dxcc.php} (100%) rename application/models/{dxcc_entities.php => Dxcc_entities.php} (100%) rename application/models/{dxcc_exceptions.php => Dxcc_exceptions.php} (100%) rename application/models/{dxcc_prefixes.php => Dxcc_prefixes.php} (100%) rename application/models/{logbook_model.php => Logbook_model.php} (100%) rename application/models/{lotw.php => Lotw.php} (100%) rename application/models/{note.php => Note.php} (100%) rename application/models/{search.php => Search.php} (100%) rename application/models/{sota.php => Sota.php} (100%) rename application/models/{stats.php => Stats.php} (100%) rename application/models/{user_model.php => User_model.php} (96%) rename application/models/{wab.php => Wab.php} (100%) rename application/models/{wacral.php => Wacral.php} (100%) delete mode 100644 application/third_party/PasswordHash.php create mode 100644 application/views/errors/cli/error_404.php create mode 100644 application/views/errors/cli/error_db.php create mode 100644 application/views/errors/cli/error_exception.php create mode 100644 application/views/errors/cli/error_general.php create mode 100644 application/views/errors/cli/error_php.php create mode 100644 application/views/errors/cli/index.html rename application/{errors => views/errors/html}/error_404.php (100%) rename application/{errors => views/errors/html}/error_db.php (100%) create mode 100644 application/views/errors/html/error_exception.php rename application/{errors => views/errors/html}/error_general.php (100%) rename application/{errors => views/errors/html}/error_php.php (100%) rename application/{errors => views/errors/html}/index.html (100%) create mode 100644 application/views/errors/index.html create mode 100644 system/core/Log.php create mode 100644 system/core/Security.php create mode 100644 system/core/compat/hash.php create mode 100644 system/core/compat/index.html create mode 100644 system/core/compat/mbstring.php create mode 100644 system/core/compat/password.php create mode 100644 system/core/compat/standard.php delete mode 100644 system/database/DB_active_rec.php create mode 100644 system/database/DB_query_builder.php create mode 100644 system/database/drivers/cubrid/cubrid_driver.php create mode 100644 system/database/drivers/cubrid/cubrid_forge.php create mode 100644 system/database/drivers/cubrid/cubrid_result.php create mode 100644 system/database/drivers/cubrid/cubrid_utility.php create mode 100644 system/database/drivers/cubrid/index.html create mode 100644 system/database/drivers/ibase/ibase_driver.php create mode 100644 system/database/drivers/ibase/ibase_forge.php create mode 100644 system/database/drivers/ibase/ibase_result.php create mode 100644 system/database/drivers/ibase/ibase_utility.php create mode 100644 system/database/drivers/ibase/index.html create mode 100644 system/database/drivers/pdo/index.html create mode 100644 system/database/drivers/pdo/pdo_driver.php create mode 100644 system/database/drivers/pdo/pdo_forge.php create mode 100644 system/database/drivers/pdo/pdo_result.php create mode 100644 system/database/drivers/pdo/pdo_utility.php create mode 100644 system/database/drivers/pdo/subdrivers/index.html create mode 100644 system/database/drivers/pdo/subdrivers/pdo_4d_driver.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_4d_forge.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_dblib_forge.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_ibm_forge.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_informix_driver.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_informix_forge.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_oci_driver.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_oci_forge.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_odbc_forge.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_pgsql_driver.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php create mode 100644 system/database/drivers/pdo/subdrivers/pdo_sqlsrv_forge.php create mode 100644 system/database/drivers/sqlite3/index.html create mode 100644 system/database/drivers/sqlite3/sqlite3_driver.php create mode 100644 system/database/drivers/sqlite3/sqlite3_forge.php create mode 100644 system/database/drivers/sqlite3/sqlite3_result.php create mode 100644 system/database/drivers/sqlite3/sqlite3_utility.php create mode 100644 system/database/drivers/sqlsrv/index.html create mode 100644 system/database/drivers/sqlsrv/sqlsrv_driver.php create mode 100644 system/database/drivers/sqlsrv/sqlsrv_forge.php create mode 100644 system/database/drivers/sqlsrv/sqlsrv_result.php create mode 100644 system/database/drivers/sqlsrv/sqlsrv_utility.php create mode 100644 system/language/english/pagination_lang.php create mode 100644 system/libraries/Cache/drivers/Cache_redis.php create mode 100644 system/libraries/Cache/drivers/Cache_wincache.php create mode 100644 system/libraries/Cache/drivers/index.html create mode 100644 system/libraries/Cache/index.html create mode 100644 system/libraries/Encryption.php rename system/libraries/{javascript => Javascript}/Jquery.php (61%) create mode 100644 system/libraries/Javascript/index.html delete mode 100644 system/libraries/Log.php delete mode 100644 system/libraries/Security.php delete mode 100644 system/libraries/Session.php create mode 100644 system/libraries/Session/Session.php create mode 100644 system/libraries/Session/SessionHandlerInterface.php create mode 100644 system/libraries/Session/Session_driver.php create mode 100644 system/libraries/Session/drivers/Session_database_driver.php create mode 100644 system/libraries/Session/drivers/Session_files_driver.php create mode 100644 system/libraries/Session/drivers/Session_memcached_driver.php create mode 100644 system/libraries/Session/drivers/Session_redis_driver.php create mode 100644 system/libraries/Session/drivers/index.html create mode 100644 system/libraries/Session/index.html delete mode 100644 system/libraries/Sha1.php diff --git a/application/config/mimes.php b/application/config/mimes.php index d8451a58..01765333 100644 --- a/application/config/mimes.php +++ b/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', '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' => 'audio/x-wav', - 'bmp' => 'image/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' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'word' => array('application/msword', 'application/octet-stream'), - 'xl' => 'application/excel', - 'eml' => 'message/rfc822', - 'json' => array('application/json', 'text/json'), - 'adi' => 'application/octet-stream', - 'adif' => 'application/octet-stream', - 'ADI' => 'application/octet-stream', - 'ADIF' => 'application/octet-stream', - 'tq8' => 'application/octet-stream', - 'TQ8' => 'application/octet-stream', - ); - - -/* 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'), + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'otf' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web' +); diff --git a/application/config/routes.php b/application/config/routes.php index 925dfd24..e9bbc49e 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -17,13 +17,13 @@ | | Please see the user guide for complete details: | -| http://codeigniter.com/user_guide/general/routing.html +| https://codeigniter.com/user_guide/general/routing.html | | ------------------------------------------------------------------------- | RESERVED ROUTES | ------------------------------------------------------------------------- | -| There area two reserved routes: +| There are three reserved routes: | | $route['default_controller'] = 'welcome'; | @@ -33,14 +33,25 @@ | | $route['404_override'] = 'errors/page_missing'; | -| This route will tell the Router what URI segments to use if those provided -| in the URL cannot be matched to a valid route. +| This route will tell the Router which controller/method to use if those +| provided in the URL cannot be matched to a valid route. | +| $route['translate_uri_dashes'] = FALSE; +| +| This is not exactly a route, but allows you to automatically route +| controller and method names that contain dashes. '-' isn't a valid +| class or method name character, so it requires translation. +| When you set this option to TRUE, it will replace ALL dashes in the +| controller and method URI segments. +| +| Examples: my-controller/index -> my_controller/index +| my-controller/my-method -> my_controller/my_method */ $route['default_controller'] = "dashboard"; $route['404_override'] = ''; +$route['translate_uri_dashes'] = FALSE; /* End of file routes.php */ -/* Location: ./application/config/routes.php */ \ No newline at end of file +/* Location: ./application/config/routes.php */ diff --git a/application/controllers/adif.php b/application/controllers/Adif.php similarity index 100% rename from application/controllers/adif.php rename to application/controllers/Adif.php diff --git a/application/controllers/api.php b/application/controllers/Api.php similarity index 100% rename from application/controllers/api.php rename to application/controllers/Api.php diff --git a/application/controllers/awards.php b/application/controllers/Awards.php similarity index 100% rename from application/controllers/awards.php rename to application/controllers/Awards.php diff --git a/application/controllers/backup.php b/application/controllers/Backup.php similarity index 100% rename from application/controllers/backup.php rename to application/controllers/Backup.php diff --git a/application/controllers/dashboard.php b/application/controllers/Dashboard.php similarity index 100% rename from application/controllers/dashboard.php rename to application/controllers/Dashboard.php diff --git a/application/controllers/dxcluster.php b/application/controllers/Dxcluster.php similarity index 100% rename from application/controllers/dxcluster.php rename to application/controllers/Dxcluster.php diff --git a/application/controllers/eqsl.php b/application/controllers/Eqsl.php similarity index 100% rename from application/controllers/eqsl.php rename to application/controllers/Eqsl.php diff --git a/application/controllers/export.php b/application/controllers/Export.php similarity index 100% rename from application/controllers/export.php rename to application/controllers/Export.php diff --git a/application/controllers/install.php b/application/controllers/Install.php similarity index 100% rename from application/controllers/install.php rename to application/controllers/Install.php diff --git a/application/controllers/kml.php b/application/controllers/Kml.php similarity index 100% rename from application/controllers/kml.php rename to application/controllers/Kml.php diff --git a/application/controllers/logbook.php b/application/controllers/Logbook.php similarity index 100% rename from application/controllers/logbook.php rename to application/controllers/Logbook.php diff --git a/application/controllers/lotw.php b/application/controllers/Lotw.php similarity index 100% rename from application/controllers/lotw.php rename to application/controllers/Lotw.php diff --git a/application/controllers/migrate.php b/application/controllers/Migrate.php similarity index 100% rename from application/controllers/migrate.php rename to application/controllers/Migrate.php diff --git a/application/controllers/notes.php b/application/controllers/Notes.php similarity index 100% rename from application/controllers/notes.php rename to application/controllers/Notes.php diff --git a/application/controllers/qsl.php b/application/controllers/Qsl.php similarity index 100% rename from application/controllers/qsl.php rename to application/controllers/Qsl.php diff --git a/application/controllers/qso.php b/application/controllers/Qso.php similarity index 100% rename from application/controllers/qso.php rename to application/controllers/Qso.php diff --git a/application/controllers/radio.php b/application/controllers/Radio.php similarity index 100% rename from application/controllers/radio.php rename to application/controllers/Radio.php diff --git a/application/controllers/search.php b/application/controllers/Search.php similarity index 100% rename from application/controllers/search.php rename to application/controllers/Search.php diff --git a/application/controllers/setup.php b/application/controllers/Setup.php similarity index 100% rename from application/controllers/setup.php rename to application/controllers/Setup.php diff --git a/application/controllers/social.php b/application/controllers/Social.php similarity index 100% rename from application/controllers/social.php rename to application/controllers/Social.php diff --git a/application/controllers/statistics.php b/application/controllers/Statistics.php similarity index 100% rename from application/controllers/statistics.php rename to application/controllers/Statistics.php diff --git a/application/controllers/update.php b/application/controllers/Update.php similarity index 100% rename from application/controllers/update.php rename to application/controllers/Update.php diff --git a/application/controllers/user.php b/application/controllers/User.php similarity index 100% rename from application/controllers/user.php rename to application/controllers/User.php diff --git a/application/controllers/widgets.php b/application/controllers/Widgets.php similarity index 100% rename from application/controllers/widgets.php rename to application/controllers/Widgets.php diff --git a/application/libraries/adif_parser.php b/application/libraries/Adif_parser.php similarity index 100% rename from application/libraries/adif_parser.php rename to application/libraries/Adif_parser.php diff --git a/application/libraries/hamqth.php b/application/libraries/Hamqth.php similarity index 100% rename from application/libraries/hamqth.php rename to application/libraries/Hamqth.php diff --git a/application/libraries/qrz.php b/application/libraries/Qrz.php similarity index 100% rename from application/libraries/qrz.php rename to application/libraries/Qrz.php diff --git a/application/models/adif_data.php b/application/models/Adif_data.php similarity index 100% rename from application/models/adif_data.php rename to application/models/Adif_data.php diff --git a/application/models/api_model.php b/application/models/Api_model.php similarity index 100% rename from application/models/api_model.php rename to application/models/Api_model.php diff --git a/application/models/cat.php b/application/models/Cat.php similarity index 100% rename from application/models/cat.php rename to application/models/Cat.php diff --git a/application/models/dxcc.php b/application/models/Dxcc.php similarity index 100% rename from application/models/dxcc.php rename to application/models/Dxcc.php diff --git a/application/models/dxcc_entities.php b/application/models/Dxcc_entities.php similarity index 100% rename from application/models/dxcc_entities.php rename to application/models/Dxcc_entities.php diff --git a/application/models/dxcc_exceptions.php b/application/models/Dxcc_exceptions.php similarity index 100% rename from application/models/dxcc_exceptions.php rename to application/models/Dxcc_exceptions.php diff --git a/application/models/dxcc_prefixes.php b/application/models/Dxcc_prefixes.php similarity index 100% rename from application/models/dxcc_prefixes.php rename to application/models/Dxcc_prefixes.php diff --git a/application/models/logbook_model.php b/application/models/Logbook_model.php similarity index 100% rename from application/models/logbook_model.php rename to application/models/Logbook_model.php diff --git a/application/models/lotw.php b/application/models/Lotw.php similarity index 100% rename from application/models/lotw.php rename to application/models/Lotw.php diff --git a/application/models/note.php b/application/models/Note.php similarity index 100% rename from application/models/note.php rename to application/models/Note.php diff --git a/application/models/search.php b/application/models/Search.php similarity index 100% rename from application/models/search.php rename to application/models/Search.php diff --git a/application/models/sota.php b/application/models/Sota.php similarity index 100% rename from application/models/sota.php rename to application/models/Sota.php diff --git a/application/models/stats.php b/application/models/Stats.php similarity index 100% rename from application/models/stats.php rename to application/models/Stats.php diff --git a/application/models/user_model.php b/application/models/User_model.php similarity index 96% rename from application/models/user_model.php rename to application/models/User_model.php index 2703224f..b1ac6d1f 100644 --- a/application/models/user_model.php +++ b/application/models/User_model.php @@ -8,7 +8,8 @@ // Uses 'phpass' from http://www.openwall.com/phpass/ to implement password hashing -require_once('application/third_party/PasswordHash.php'); +// TODO migration away from this? +//require_once('application/third_party/PasswordHash.php'); class User_Model extends CI_Model { @@ -188,7 +189,7 @@ class User_Model extends CI_Model { // Validates a username/password combination // This is really just a wrapper around User_Model::authenticate function login() { - + $username = $this->input->post('user_name'); $password = $this->input->post('user_password'); @@ -201,7 +202,7 @@ class User_Model extends CI_Model { // login session *will* be cleared, no matter what state it is in function clear_session() { - $this->session->unset_userdata(array('user_id' => '', 'user_type' => '', 'user_email' => '', 'user_hash' => '')); + $this->session->sess_destroy(); } // FUNCTION: void update_session() @@ -253,7 +254,7 @@ class User_Model extends CI_Model { // Authenticate a user against the users table function authenticate($username, $password) { $u = $this->get($username); - if($u->num_rows != 0) + if($u->num_rows() != 0) { if($this->_auth($password, $u->row()->user_password)) { return 1; @@ -310,8 +311,7 @@ class User_Model extends CI_Model { // FUNCTION: bool _auth($password, $hash) // Checks a password against the stored hash private function _auth($password, $hash) { - $h = new PasswordHash(8, FALSE); - if($h->CheckPassword($password, $hash)) { + if(password_verify($password, $hash)) { return 1; } else { return 0; @@ -323,9 +323,7 @@ class User_Model extends CI_Model { // Will return '0' in the event of problems with the // hashing function private function _hash($password) { - $h = new PasswordHash(8, FALSE); - $hash = $h->HashPassword($password); - unset($h); + $hash = password_hash($password, PASSWORD_DEFAULT); if(strlen($hash) < 20) { return EPASSWORDINVALID; diff --git a/application/models/wab.php b/application/models/Wab.php similarity index 100% rename from application/models/wab.php rename to application/models/Wab.php diff --git a/application/models/wacral.php b/application/models/Wacral.php similarity index 100% rename from application/models/wacral.php rename to application/models/Wacral.php diff --git a/application/third_party/PasswordHash.php b/application/third_party/PasswordHash.php deleted file mode 100644 index 84447b27..00000000 --- a/application/third_party/PasswordHash.php +++ /dev/null @@ -1,253 +0,0 @@ - in 2004-2006 and placed in -# the public domain. Revised in subsequent years, still public domain. -# -# There's absolutely no warranty. -# -# The homepage URL for this framework is: -# -# http://www.openwall.com/phpass/ -# -# Please be sure to update the Version line if you edit this file in any way. -# It is suggested that you leave the main version number intact, but indicate -# your project name (after the slash) and add your own revision information. -# -# Please do not change the "private" password hashing method implemented in -# here, thereby making your hashes incompatible. However, if you must, please -# change the hash type identifier (the "$P$") to something different. -# -# Obviously, since this code is in the public domain, the above are not -# requirements (there can be none), but merely suggestions. -# -class PasswordHash { - var $itoa64; - var $iteration_count_log2; - var $portable_hashes; - var $random_state; - - function PasswordHash($iteration_count_log2, $portable_hashes) - { - $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - - if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) - $iteration_count_log2 = 8; - $this->iteration_count_log2 = $iteration_count_log2; - - $this->portable_hashes = $portable_hashes; - - $this->random_state = microtime(); - if (function_exists('getmypid')) - $this->random_state .= getmypid(); - } - - function get_random_bytes($count) - { - $output = ''; - if (@is_readable('/dev/urandom') && - ($fh = @fopen('/dev/urandom', 'rb'))) { - $output = fread($fh, $count); - fclose($fh); - } - - if (strlen($output) < $count) { - $output = ''; - for ($i = 0; $i < $count; $i += 16) { - $this->random_state = - md5(microtime() . $this->random_state); - $output .= - pack('H*', md5($this->random_state)); - } - $output = substr($output, 0, $count); - } - - return $output; - } - - function encode64($input, $count) - { - $output = ''; - $i = 0; - do { - $value = ord($input[$i++]); - $output .= $this->itoa64[$value & 0x3f]; - if ($i < $count) - $value |= ord($input[$i]) << 8; - $output .= $this->itoa64[($value >> 6) & 0x3f]; - if ($i++ >= $count) - break; - if ($i < $count) - $value |= ord($input[$i]) << 16; - $output .= $this->itoa64[($value >> 12) & 0x3f]; - if ($i++ >= $count) - break; - $output .= $this->itoa64[($value >> 18) & 0x3f]; - } while ($i < $count); - - return $output; - } - - function gensalt_private($input) - { - $output = '$P$'; - $output .= $this->itoa64[min($this->iteration_count_log2 + - ((PHP_VERSION >= '5') ? 5 : 3), 30)]; - $output .= $this->encode64($input, 6); - - return $output; - } - - function crypt_private($password, $setting) - { - $output = '*0'; - if (substr($setting, 0, 2) == $output) - $output = '*1'; - - $id = substr($setting, 0, 3); - # We use "$P$", phpBB3 uses "$H$" for the same thing - if ($id != '$P$' && $id != '$H$') - return $output; - - $count_log2 = strpos($this->itoa64, $setting[3]); - if ($count_log2 < 7 || $count_log2 > 30) - return $output; - - $count = 1 << $count_log2; - - $salt = substr($setting, 4, 8); - if (strlen($salt) != 8) - return $output; - - # We're kind of forced to use MD5 here since it's the only - # cryptographic primitive available in all versions of PHP - # currently in use. To implement our own low-level crypto - # in PHP would result in much worse performance and - # consequently in lower iteration counts and hashes that are - # quicker to crack (by non-PHP code). - if (PHP_VERSION >= '5') { - $hash = md5($salt . $password, TRUE); - do { - $hash = md5($hash . $password, TRUE); - } while (--$count); - } else { - $hash = pack('H*', md5($salt . $password)); - do { - $hash = pack('H*', md5($hash . $password)); - } while (--$count); - } - - $output = substr($setting, 0, 12); - $output .= $this->encode64($hash, 16); - - return $output; - } - - function gensalt_extended($input) - { - $count_log2 = min($this->iteration_count_log2 + 8, 24); - # This should be odd to not reveal weak DES keys, and the - # maximum valid value is (2**24 - 1) which is odd anyway. - $count = (1 << $count_log2) - 1; - - $output = '_'; - $output .= $this->itoa64[$count & 0x3f]; - $output .= $this->itoa64[($count >> 6) & 0x3f]; - $output .= $this->itoa64[($count >> 12) & 0x3f]; - $output .= $this->itoa64[($count >> 18) & 0x3f]; - - $output .= $this->encode64($input, 3); - - return $output; - } - - function gensalt_blowfish($input) - { - # This one needs to use a different order of characters and a - # different encoding scheme from the one in encode64() above. - # We care because the last character in our encoded string will - # only represent 2 bits. While two known implementations of - # bcrypt will happily accept and correct a salt string which - # has the 4 unused bits set to non-zero, we do not want to take - # chances and we also do not want to waste an additional byte - # of entropy. - $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - - $output = '$2a$'; - $output .= chr(ord('0') + $this->iteration_count_log2 / 10); - $output .= chr(ord('0') + $this->iteration_count_log2 % 10); - $output .= '$'; - - $i = 0; - do { - $c1 = ord($input[$i++]); - $output .= $itoa64[$c1 >> 2]; - $c1 = ($c1 & 0x03) << 4; - if ($i >= 16) { - $output .= $itoa64[$c1]; - break; - } - - $c2 = ord($input[$i++]); - $c1 |= $c2 >> 4; - $output .= $itoa64[$c1]; - $c1 = ($c2 & 0x0f) << 2; - - $c2 = ord($input[$i++]); - $c1 |= $c2 >> 6; - $output .= $itoa64[$c1]; - $output .= $itoa64[$c2 & 0x3f]; - } while (1); - - return $output; - } - - function HashPassword($password) - { - $random = ''; - - if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) { - $random = $this->get_random_bytes(16); - $hash = - crypt($password, $this->gensalt_blowfish($random)); - if (strlen($hash) == 60) - return $hash; - } - - if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) { - if (strlen($random) < 3) - $random = $this->get_random_bytes(3); - $hash = - crypt($password, $this->gensalt_extended($random)); - if (strlen($hash) == 20) - return $hash; - } - - if (strlen($random) < 6) - $random = $this->get_random_bytes(6); - $hash = - $this->crypt_private($password, - $this->gensalt_private($random)); - if (strlen($hash) == 34) - return $hash; - - # Returning '*' on error is safe here, but would _not_ be safe - # in a crypt(3)-like function used _both_ for generating new - # hashes and for validating passwords against existing hashes. - return '*'; - } - - function CheckPassword($password, $stored_hash) - { - $hash = $this->crypt_private($password, $stored_hash); - if ($hash[0] == '*') - $hash = crypt($password, $stored_hash); - - return $hash == $stored_hash; - } -} - -?> diff --git a/application/views/errors/cli/error_404.php b/application/views/errors/cli/error_404.php new file mode 100644 index 00000000..6984b61e --- /dev/null +++ b/application/views/errors/cli/error_404.php @@ -0,0 +1,8 @@ + + +An uncaught Exception was encountered + +Type: +Message: +Filename: getFile(), "\n"; ?> +Line Number: getLine(); ?> + + + +Backtrace: +getTrace() as $error): ?> + + File: + Line: + Function: + + + + diff --git a/application/views/errors/cli/error_general.php b/application/views/errors/cli/error_general.php new file mode 100644 index 00000000..6984b61e --- /dev/null +++ b/application/views/errors/cli/error_general.php @@ -0,0 +1,8 @@ + + +A PHP Error was encountered + +Severity: +Message: +Filename: +Line Number: + + + +Backtrace: + + + File: + Line: + Function: + + + + diff --git a/application/views/errors/cli/index.html b/application/views/errors/cli/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/application/views/errors/cli/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

    Directory access is forbidden.

    + + + diff --git a/application/errors/error_404.php b/application/views/errors/html/error_404.php similarity index 100% rename from application/errors/error_404.php rename to application/views/errors/html/error_404.php diff --git a/application/errors/error_db.php b/application/views/errors/html/error_db.php similarity index 100% rename from application/errors/error_db.php rename to application/views/errors/html/error_db.php diff --git a/application/views/errors/html/error_exception.php b/application/views/errors/html/error_exception.php new file mode 100644 index 00000000..87848866 --- /dev/null +++ b/application/views/errors/html/error_exception.php @@ -0,0 +1,32 @@ + + +
    + +

    An uncaught Exception was encountered

    + +

    Type:

    +

    Message:

    +

    Filename: getFile(); ?>

    +

    Line Number: getLine(); ?>

    + + + +

    Backtrace:

    + getTrace() as $error): ?> + + + +

    + File:
    + Line:
    + Function: +

    + + + + + + +
    \ No newline at end of file diff --git a/application/errors/error_general.php b/application/views/errors/html/error_general.php similarity index 100% rename from application/errors/error_general.php rename to application/views/errors/html/error_general.php diff --git a/application/errors/error_php.php b/application/views/errors/html/error_php.php similarity index 100% rename from application/errors/error_php.php rename to application/views/errors/html/error_php.php diff --git a/application/errors/index.html b/application/views/errors/html/index.html similarity index 100% rename from application/errors/index.html rename to application/views/errors/html/index.html diff --git a/application/views/errors/index.html b/application/views/errors/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/application/views/errors/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

    Directory access is forbidden.

    + + + diff --git a/index.php b/index.php index 0c38d3fb..7d8fe56d 100644 --- a/index.php +++ b/index.php @@ -1,4 +1,40 @@ =')) + { + 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 @@ -89,24 +140,23 @@ * * 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.php + // The controller class file name. Example: mycontroller // $routing['controller'] = ''; // The controller function you wish to be called. @@ -126,7 +176,6 @@ * 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'; @@ -148,18 +197,26 @@ 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 } /* @@ -170,43 +227,90 @@ // The name of THIS file define('SELF', pathinfo(__FILE__, PATHINFO_BASENAME)); - // The PHP file extension - 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'.EXT; - -/* End of file index.php */ -/* Location: ./index.php */ \ No newline at end of file +require_once BASEPATH.'core/CodeIgniter.php'; diff --git a/system/core/Benchmark.php b/system/core/Benchmark.php index 515550e9..b3ac79c6 100644 --- a/system/core/Benchmark.php +++ b/system/core/Benchmark.php @@ -1,56 +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 @@ -58,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}'; } @@ -78,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); } // -------------------------------------------------------------------- @@ -92,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/system/core/CodeIgniter.php b/system/core/CodeIgniter.php index 99c261e7..0d03293f 100644 --- a/system/core/CodeIgniter.php +++ b/system/core/CodeIgniter.php @@ -1,19 +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'); /* * ------------------------------------------------------ @@ -76,26 +148,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); + } } /* @@ -119,33 +204,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'); /* @@ -160,14 +308,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); /* * ------------------------------------------------------ @@ -178,17 +319,21 @@ /* * ------------------------------------------------------ - * 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; } +/* + * ----------------------------------------------------- + * Load the security class for xss and csrf support + * ----------------------------------------------------- + */ + $SEC =& load_class('Security', 'core'); + /* * ------------------------------------------------------ * Load the Input class and sanitize globals @@ -210,50 +355,149 @@ * */ // Load the base controller class - require BASEPATH.'core/Controller'.EXT; + 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'.EXT)) + if (file_exists(APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller.php')) { - require APPPATH.'core/'.$CFG->config['subclass_prefix'].'Controller'.EXT; + 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().EXT)) - { - 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().EXT); - // 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')) { - show_404("{$class}/{$method}"); + $e404 = TRUE; + } + else + { + require_once(APPPATH.'controllers/'.$RTR->directory.$class.'.php'); + + if ( ! class_exists($class, FALSE) OR $method[0] === '_' OR method_exists('CI_Controller', $method)) + { + $e404 = TRUE; + } + elseif (method_exists($class, '_remap')) + { + $params = array($method, array_slice($URI->rsegments, 2)); + $method = '_remap'; + } + elseif ( ! method_exists($class, $method)) + { + $e404 = TRUE; + } + /** + * DO NOT CHANGE THIS, NOTHING ELSE WORKS! + * + * - method_exists() returns true for non-public methods, which passes the previous elseif + * - is_callable() returns false for PHP 4-style constructors, even if there's a __construct() + * - method_exists($class, '__construct') won't work because CI_Controller::__construct() is inherited + * - People will only complain if this doesn't work, even though it is documented that it shouldn't. + * + * ReflectionMethod::isConstructor() is the ONLY reliable check, + * knowing which method will be executed as a constructor. + */ + elseif ( ! is_callable(array($class, $method))) + { + $reflection = new ReflectionMethod($class, $method); + if ( ! $reflection->isPublic() OR $reflection->isConstructor()) + { + $e404 = TRUE; + } + } + } + + if ($e404) + { + if ( ! empty($RTR->routes['404_override'])) + { + if (sscanf($RTR->routes['404_override'], '%[^/]/%s', $error_class, $error_method) !== 2) + { + $error_method = 'index'; + } + + $error_class = ucfirst($error_class); + + if ( ! class_exists($error_class, FALSE)) + { + if (file_exists(APPPATH.'controllers/'.$RTR->directory.$error_class.'.php')) + { + require_once(APPPATH.'controllers/'.$RTR->directory.$error_class.'.php'); + $e404 = ! class_exists($error_class, FALSE); + } + // Were we in a directory? If so, check for a global override + elseif ( ! empty($RTR->directory) && file_exists(APPPATH.'controllers/'.$error_class.'.php')) + { + require_once(APPPATH.'controllers/'.$error_class.'.php'); + if (($e404 = ! class_exists($error_class, FALSE)) === FALSE) + { + $RTR->directory = ''; + } + } + } + else + { + $e404 = FALSE; + } + } + + // Did we reset the $e404 flag? If so, set the rsegments, starting from index 1 + if ( ! $e404) + { + $class = $error_class; + $method = $error_method; + + $URI->rsegments = array( + 1 => $class, + 2 => $method + ); + } + else + { + show_404($RTR->directory.$class.'/'.$method); + } + } + + if ($method !== '_remap') + { + $params = array_slice($URI->rsegments, 2); } /* @@ -261,7 +505,7 @@ * Is there a "pre_controller" hook? * ------------------------------------------------------ */ - $EXT->_call_hook('pre_controller'); + $EXT->call_hook('pre_controller'); /* * ------------------------------------------------------ @@ -278,53 +522,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.EXT)) - { - show_404("{$class}/{$method}"); - } - - include_once(APPPATH.'controllers/'.$class.EXT); - 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'); @@ -334,14 +539,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(); } @@ -351,18 +556,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 */ + $EXT->call_hook('post_system'); diff --git a/system/core/Common.php b/system/core/Common.php index d82a29d8..d6a1fdb4 100644 --- a/system/core/Common.php +++ b/system/core/Common.php @@ -1,19 +1,41 @@ - 5 -* we'll set a static variable. -* -* @access public -* @param string -* @return bool TRUE if the current version is $version or higher -*/ - function is_php($version = '5.0.0') +if ( ! function_exists('is_php')) +{ + /** + * Determines if the current version of PHP is equal to or greater than the supplied value + * + * @param string + * @return bool TRUE if the current version is $version or higher + */ + function is_php($version) { static $_is_php; - $version = (string)$version; + $version = (string) $version; if ( ! isset($_is_php[$version])) { - $_is_php[$version] = (version_compare(PHP_VERSION, $version) < 0) ? FALSE : TRUE; + $_is_php[$version] = version_compare(PHP_VERSION, $version, '>='); } return $_is_php[$version]; } +} // ------------------------------------------------------------------------ -/** - * Tests for file writability - * - * is_writable() returns TRUE on Windows servers when you really can't write to - * the file, based on the read-only attribute. is_writable() is also unreliable - * on Unix servers if safe_mode is on. - * - * @access private - * @return void - */ +if ( ! function_exists('is_really_writable')) +{ + /** + * 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 (($fp = @fopen($file, FOPEN_WRITE_CREATE)) === FALSE) + elseif ( ! is_file($file) OR ($fp = @fopen($file, 'ab')) === FALSE) { return FALSE; } @@ -96,27 +120,29 @@ fclose($fp); return TRUE; } +} // ------------------------------------------------------------------------ -/** -* Class registry -* -* This function acts as a singleton. If the requested class does not -* exist it is instantiated and set to a static variable. If it has -* previously been instantiated the variable is returned. -* -* @access public -* @param string the class name being requested -* @param string the directory where the class should be found -* @param string the class name prefix -* @return object -*/ - function &load_class($class, $directory = 'libraries', $prefix = 'CI_') +if ( ! function_exists('load_class')) +{ + /** + * Class registry + * + * This function acts as a singleton. If the requested class does not + * exist it is instantiated and set to a static variable. If it has + * previously been instantiated the variable is returned. + * + * @param string the class name being requested + * @param string the directory where the class should be found + * @param mixed an optional argument to pass to the class constructor + * @return object + */ + function &load_class($class, $directory = 'libraries', $param = NULL) { static $_classes = array(); - // Does the class exist? If so, we're done... + // Does the class exist? If so, we're done... if (isset($_classes[$class])) { return $_classes[$class]; @@ -124,388 +150,700 @@ $name = FALSE; - // Look for the class first in the native system/libraries folder - // thenin the local application/libraries folder - foreach (array(BASEPATH, APPPATH) as $path) + // Look for the class first in the local application/libraries folder + // then in the native system/libraries folder + foreach (array(APPPATH, BASEPATH) as $path) { - if (file_exists($path.$directory.'/'.$class.EXT)) + 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.EXT); + require_once($path.$directory.'/'.$class.'.php'); } break; } } - // Is the request a class extension? If so we load it too - if (file_exists(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.EXT)) + // 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.EXT); + 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.EXT); + // 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 -*/ - function is_loaded($class = '') +if ( ! function_exists('is_loaded')) +{ + /** + * Keeps track of which libraries have been loaded. This function is + * called by the load_class() function above + * + * @param string + * @return array + */ + function &is_loaded($class = '') { static $_is_loaded = array(); - if ($class != '') + if ($class !== '') { $_is_loaded[strtolower($class)] = $class; } return $_is_loaded; } +} // ------------------------------------------------------------------------ -/** -* Loads the main config.php file -* -* This function lets us grab the config file even if the Config class -* hasn't been instantiated yet -* -* @access private -* @return array -*/ - function &get_config($replace = array()) +if ( ! function_exists('get_config')) +{ + /** + * Loads the main config.php file + * + * This function lets us grab the config file even if the Config class + * hasn't been instantiated yet + * + * @param array + * @return array + */ + function &get_config(Array $replace = array()) + { + static $config; + + if (empty($config)) + { + $file_path = APPPATH.'config/config.php'; + $found = FALSE; + if (file_exists($file_path)) + { + $found = TRUE; + require($file_path); + } + + // Is the config file in the environment folder? + if (file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php')) + { + require($file_path); + } + elseif ( ! $found) + { + set_status_header(503); + echo 'The configuration file does not exist.'; + exit(3); // EXIT_CONFIG + } + + // Does the $config array exist in the file? + if ( ! isset($config) OR ! is_array($config)) + { + set_status_header(503); + echo 'Your config file does not appear to be formatted correctly.'; + exit(3); // EXIT_CONFIG + } + } + + // Are any values being dynamically added or replaced? + foreach ($replace as $key => $val) + { + $config[$key] = $val; + } + + return $config; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('config_item')) +{ + /** + * Returns the specified config item + * + * @param string + * @return mixed + */ + function config_item($item) { static $_config; - if (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(); } - $file_path = APPPATH.'config/'.ENVIRONMENT.'/config'.EXT; - - // Fetch the config file - if ( ! file_exists($file_path)) - { - $file_path = APPPATH.'config/config'.EXT; - - 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; - } - } - } - - # Fix irritating PHP error - # see http://stackoverflow.com/questions/28348879/only-variable-references-should-be-returned-by-reference-codeigniter - $_config[0] =& $config; - return $_config[0]; + return isset($_config[0][$item]) ? $_config[0][$item] : NULL; } +} // ------------------------------------------------------------------------ -/** -* Returns the specified config item -* -* @access public -* @return mixed -*/ - function config_item($item) +if ( ! function_exists('get_mimes')) +{ + /** + * 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(); + $_mimes = file_exists(APPPATH.'config/mimes.php') + ? include(APPPATH.'config/mimes.php') + : array(); - if ( ! isset($config[$item])) + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')) { - return FALSE; + $_mimes = array_merge($_mimes, include(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')); } - $_config_item[$item] = $config[$item]; } - return $_config_item[$item]; + return $_mimes; } +} // ------------------------------------------------------------------------ -/** -* 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('is_https')) +{ + /** + * Is HTTPS? + * + * Determines if the application is accessed via an encrypted + * (HTTPS) connection. + * + * @return bool + */ + function is_https() + { + if ( ! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') + { + return TRUE; + } + elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') + { + return TRUE; + } + elseif ( ! empty($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) !== 'off') + { + return TRUE; + } + + return FALSE; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('is_cli')) +{ + + /** + * Is CLI? + * + * Test to see if a request was made from the command line. + * + * @return bool + */ + function is_cli() + { + return (PHP_SAPI === 'cli' OR defined('STDIN')); + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('show_error')) +{ + /** + * Error Handler + * + * This function lets us invoke the exception class and + * display errors using the standard error template located + * in application/views/errors/error_general.php + * This function will send the error page directly to the + * browser and exit. + * + * @param string + * @param int + * @param string + * @return void + */ function show_error($message, $status_code = 500, $heading = 'An Error Was Encountered') { + $status_code = abs($status_code); + if ($status_code < 100) + { + $exit_status = $status_code + 9; // 9 is EXIT__AUTO_MIN + $status_code = 500; + } + else + { + $exit_status = 1; // EXIT_ERROR + } + $_error =& load_class('Exceptions', 'core'); echo $_error->show_error($heading, $message, 'error_general', $status_code); - exit; + exit($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 -*/ - function log_message($level = 'error', $message, $php_error = FALSE) +if ( ! function_exists('log_message')) +{ + /** + * Error Logging Interface + * + * We use this as a simple mechanism to access the logging + * class and send messages to be logged. + * + * @param string the error level: 'error', 'debug' or 'info' + * @param string the error message + * @return void + */ + function log_message($level, $message) { static $_log; - if (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 - */ - 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', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 511 => 'Network Authentication Required', + ); + + if (isset($stati[$code])) + { + $text = $stati[$code]; + } + else + { + show_error('No status text available. Please check your status code number or supply your own message text.', 500); + } } - if ($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); + header('Status: '.$code.' '.$text, TRUE); + return; } - $server_protocol = (isset($_SERVER['SERVER_PROTOCOL'])) ? $_SERVER['SERVER_PROTOCOL'] : FALSE; - - if (substr(php_sapi_name(), 0, 3) == 'cgi') - { - header("Status: {$code} {$text}", TRUE); - } - elseif ($server_protocol == 'HTTP/1.1' OR $server_protocol == 'HTTP/1.0') - { - header($server_protocol." {$code} {$text}", TRUE, $code); - } - else - { - header("HTTP/1.1 {$code} {$text}", TRUE, $code); - } + $server_protocol = (isset($_SERVER['SERVER_PROTOCOL']) && in_array($_SERVER['SERVER_PROTOCOL'], array('HTTP/1.0', 'HTTP/1.1', 'HTTP/2'), TRUE)) + ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1'; + header($server_protocol.' '.$code.' '.$text, TRUE, $code); } +} // -------------------------------------------------------------------- -/** -* 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 -*/ - function _exception_handler($severity, $message, $filepath, $line) +if ( ! function_exists('_error_handler')) +{ + /** + * Error Handler + * + * This is the custom error handler that is declared at the (relative) + * top of CodeIgniter.php. The main reason we use this is to permit + * PHP errors to be logged in our own log files since the user may + * not have access to server logs. Since this function effectively + * intercepts PHP errors, however, we also need to display errors + * based on the current error_reporting level. + * We do that with the use of a PHP error template. + * + * @param int $severity + * @param string $message + * @param string $filepath + * @param int $line + * @return void + */ + function _error_handler($severity, $message, $filepath, $line) { - // We don't bother with "strict" notices since they tend to fill up - // the log file with excess information that isn't normally very helpful. - // For example, if you are running PHP 5 and you use version 4 style - // class functions (without prefixes like "public", "private", etc.) - // you'll get notices telling you that these have been deprecated. - if ($severity == E_STRICT) + $is_error = (((E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR | E_USER_ERROR) & $severity) === $severity); + + // When an error occurred, set the status header to '500 Internal Server Error' + // to indicate to the client something went wrong. + // This can't be done within the $_error->show_php_error method because + // it is only called when the display_errors flag is set (which isn't usually + // the case in a production environment) or when errors are ignored because + // they are above the error_reporting threshold. + if ($is_error) + { + set_status_header(500); + } + + // Should we ignore the error? We'll get the current error_reporting + // level and add its bits with the severity bits to find out. + if (($severity & error_reporting()) !== $severity) { return; } $_error =& load_class('Exceptions', 'core'); + $_error->log_exception($severity, $message, $filepath, $line); - // Should we display the error? 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()); + + is_cli() OR set_status_header(500); + // Should we display the error? + if (str_ireplace(array('off', 'none', 'no', 'false', 'null'), '', ini_get('display_errors'))) + { + $_error->show_exception($exception); } - $_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']); + } + } +} + +// -------------------------------------------------------------------- + +if ( ! function_exists('remove_invisible_characters')) +{ /** * Remove Invisible Characters * * This prevents sandwiching null characters * between ascii characters, like Java\0script. * - * @access public * @param string + * @param bool * @return string */ - function remove_invisible_characters($str) + function remove_invisible_characters($str, $url_encoded = TRUE) { - static $non_displayables; + $non_displayables = array(); - if ( ! isset($non_displayables)) + // every control character except newline (dec 10), + // carriage return (dec 13) and horizontal tab (dec 09) + if ($url_encoded) { - // every control character except newline (dec 10), carriage return (dec 13), and horizontal tab (dec 09), - $non_displayables = array( - '/%0[0-8bcef]/', // url encoded 00-08, 11, 12, 14, 15 - '/%1[0-9a-f]/', // url encoded 16-31 - '/[\x00-\x08]/', // 00-08 - '/\x0b/', '/\x0c/', // 11, 12 - '/[\x0e-\x1f]/' // 14-31 - ); + $non_displayables[] = '/%0[0-8bcef]/i'; // url encoded 00-08, 11, 12, 14, 15 + $non_displayables[] = '/%1[0-9a-f]/i'; // url encoded 16-31 + $non_displayables[] = '/%7f/i'; // url encoded 127 } + $non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'; // 00-08, 11, 12, 14-31, 127 + do { - $cleaned = $str; - $str = preg_replace($non_displayables, '', $str); + $str = preg_replace($non_displayables, '', $str, -1, $count); } - while ($cleaned != $str); + while ($count); return $str; } +} +// ------------------------------------------------------------------------ -/* End of file Common.php */ -/* Location: ./system/core/Common.php */ +if ( ! function_exists('html_escape')) +{ + /** + * Returns HTML escaped variable. + * + * @param mixed $var The input string or array of strings to be escaped. + * @param bool $double_encode $double_encode set to FALSE prevents escaping twice. + * @return mixed The escaped string or array of strings as a result. + */ + function html_escape($var, $double_encode = TRUE) + { + if (empty($var)) + { + return $var; + } + + if (is_array($var)) + { + foreach (array_keys($var) as $key) + { + $var[$key] = html_escape($var[$key], $double_encode); + } + + return $var; + } + + return htmlspecialchars($var, ENT_QUOTES, config_item('charset'), $double_encode); + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('_stringify_attributes')) +{ + /** + * Stringify attributes for use in HTML tags. + * + * Helper function used to convert a string, array, or object + * of attributes to a string. + * + * @param mixed string, array, object + * @param bool + * @return string + */ + function _stringify_attributes($attributes, $js = FALSE) + { + $atts = NULL; + + if (empty($attributes)) + { + return $atts; + } + + if (is_string($attributes)) + { + return ' '.$attributes; + } + + $attributes = (array) $attributes; + + foreach ($attributes as $key => $val) + { + $atts .= ($js) ? $key.'='.$val.',' : ' '.$key.'="'.$val.'"'; + } + + return rtrim($atts, ','); + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('function_usable')) +{ + /** + * Function usable + * + * Executes a function_exists() check, and if the Suhosin PHP + * extension is loaded - checks whether the function that is + * checked might be disabled in there as well. + * + * This is useful as function_exists() will return FALSE for + * functions disabled via the *disable_functions* php.ini + * setting, but not for *suhosin.executor.func.blacklist* and + * *suhosin.executor.disable_eval*. These settings will just + * terminate script execution if a disabled function is executed. + * + * The above described behavior turned out to be a bug in Suhosin, + * but even though a fix was committed for 0.9.34 on 2012-02-12, + * that version is yet to be released. This function will therefore + * be just temporary, but would probably be kept for a few years. + * + * @link http://www.hardened-php.net/suhosin/ + * @param string $function_name Function to check for + * @return bool TRUE if the function exists and is safe to call, + * FALSE otherwise. + */ + function function_usable($function_name) + { + static $_suhosin_func_blacklist; + + if (function_exists($function_name)) + { + if ( ! isset($_suhosin_func_blacklist)) + { + $_suhosin_func_blacklist = extension_loaded('suhosin') + ? explode(',', trim(ini_get('suhosin.executor.func.blacklist'))) + : array(); + } + + return ! in_array($function_name, $_suhosin_func_blacklist, TRUE); + } + + return FALSE; + } +} diff --git a/system/core/Config.php b/system/core/Config.php index a2a7dd56..cda62241 100644 --- a/system/core/Config.php +++ b/system/core/Config.php @@ -1,63 +1,107 @@ -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'])) { - if (isset($_SERVER['HTTP_HOST'])) + if (isset($_SERVER['SERVER_ADDR'])) { - $base_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http'; - $base_url .= '://'. $_SERVER['HTTP_HOST']; - $base_url .= str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']); - } + 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/'; @@ -65,6 +109,8 @@ class CI_Config { $this->set_item('base_url', $base_url); } + + log_message('info', 'Config Class Initialized'); } // -------------------------------------------------------------------- @@ -72,82 +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(EXT, '', $file); + $file = ($file === '') ? 'config' : str_replace('.php', '', $file); $loaded = FALSE; foreach ($this->_config_paths as $path) - { - $file_path = $path.'config/'.ENVIRONMENT.'/'.$file.EXT; - - if (in_array($file_path, $this->is_loaded, TRUE)) + { + foreach (array($file, ENVIRONMENT.DIRECTORY_SEPARATOR.$file) as $location) { - $loaded = TRUE; - continue; - } + $file_path = $path.'config/'.$location.'.php'; + if (in_array($file_path, $this->is_loaded, TRUE)) + { + return TRUE; + } - if ( ! file_exists($file_path)) - { - log_message('debug', 'Config for '.ENVIRONMENT.' environment is not found. Trying global config.'); - $file_path = $path.'config/'.$file.EXT; - if ( ! file_exists($file_path)) { continue; } - } - - include($file_path); - if ( ! isset($config) OR ! is_array($config)) - { - if ($fail_gracefully === TRUE) + include($file_path); + + 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); + $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 '.ENVIRONMENT.'/'.$file.EXT.' and '.$file.EXT.' do not exist.'); + return TRUE; } - - return TRUE; + elseif ($fail_gracefully === TRUE) + { + return FALSE; + } + + show_error('The configuration file '.$file.'.php does not exist.'); } // -------------------------------------------------------------------- @@ -155,60 +190,37 @@ 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 + * Fetch a config file item with slash appended (if not empty) * - * The second parameter allows a slash to be added to the end of - * the item, in the case of a path. - * - * @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; + } + elseif (trim($this->config[$item]) === '') + { + return ''; } return rtrim($this->config[$item], '/').'/'; @@ -219,46 +231,121 @@ class CI_Config { /** * Site URL * - * @access public - * @param string the URI string + * Returns base_url . index_page [. uri_string] + * + * @uses CI_Config::_uri_string() + * + * @param string|string[] $uri URI string or an array of segments + * @param string $protocol * @return string */ - function site_url($uri = '') + public function site_url($uri = '', $protocol = NULL) { - if ($uri == '') - { - return $this->slash_item('base_url').$this->item('index_page'); - } + $base_url = $this->slash_item('base_url'); - if ($this->item('enable_query_strings') == FALSE) + if (isset($protocol)) { - if (is_array($uri)) + // For protocol-relative links + if ($protocol === '') { - $uri = implode('/', $uri); + $base_url = substr($base_url, strpos($base_url, '//')); } - - $index = $this->item('index_page') == '' ? '' : $this->slash_item('index_page'); - $suffix = ($this->item('url_suffix') == FALSE) ? '' : $this->item('url_suffix'); - return $this->slash_item('base_url').$index.trim($uri, '/').$suffix; - } - else - { - if (is_array($uri)) + else { - $i = 0; - $str = ''; - foreach ($uri as $key => $val) + $base_url = $protocol.substr($base_url, strpos($base_url, '://')); + } + } + + if (empty($uri)) + { + return $base_url.$this->item('index_page'); + } + + $uri = $this->_uri_string($uri); + + if ($this->item('enable_query_strings') === FALSE) + { + $suffix = isset($this->config['url_suffix']) ? $this->config['url_suffix'] : ''; + + if ($suffix !== '') + { + if (($offset = strpos($uri, '?')) !== FALSE) { - $prefix = ($i == 0) ? '' : '&'; - $str .= $prefix.$key.'='.$val; - $i++; + $uri = substr($uri, 0, $offset).$suffix.substr($uri, $offset); + } + else + { + $uri .= $suffix; } - - $uri = $str; } - return $this->slash_item('base_url').$this->item('index_page').'?'.$uri; + return $base_url.$this->slash_item('index_page').$uri; } + elseif (strpos($uri, '?') === FALSE) + { + $uri = '?'.$uri; + } + + return $base_url.$this->item('index_page').$uri; + } + + // ------------------------------------------------------------- + + /** + * Base URL + * + * Returns base_url [. uri_string] + * + * @uses CI_Config::_uri_string() + * + * @param string|string[] $uri URI string or an array of segments + * @param string $protocol + * @return string + */ + public function base_url($uri = '', $protocol = NULL) + { + $base_url = $this->slash_item('base_url'); + + if (isset($protocol)) + { + // For protocol-relative links + if ($protocol === '') + { + $base_url = substr($base_url, strpos($base_url, '//')); + } + else + { + $base_url = $protocol.substr($base_url, strpos($base_url, '://')); + } + } + + return $base_url.$this->_uri_string($uri); + } + + // ------------------------------------------------------------- + + /** + * Build URI string + * + * @used-by CI_Config::site_url() + * @used-by CI_Config::base_url() + * + * @param string|string[] $uri URI string or an array of segments + * @return string + */ + protected function _uri_string($uri) + { + if ($this->item('enable_query_strings') === FALSE) + { + is_array($uri) && $uri = implode('/', $uri); + return ltrim($uri, '/'); + } + elseif (is_array($uri)) + { + return http_build_query($uri); + } + + return $uri; } // -------------------------------------------------------------------- @@ -266,12 +353,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).'/'; } @@ -280,42 +367,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/system/core/Controller.php b/system/core/Controller.php index 469663f0..59a91673 100644 --- a/system/core/Controller.php +++ b/system/core/Controller.php @@ -1,22 +1,44 @@ -load =& load_class('Loader', 'core'); - - $this->load->_base_classes =& is_loaded(); - - $this->load->_ci_autoloader(); - - log_message('debug', "Controller Class Initialized"); - + $this->load->initialize(); + 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/system/core/Exceptions.php b/system/core/Exceptions.php index f5659561..52690960 100644 --- a/system/core/Exceptions.php +++ b/system/core/Exceptions.php @@ -1,19 +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' - ); - /** - * Constructor + * Nesting level of the output buffering mechanism + * + * @var int + */ + public $ob_level; + + /** + * List of available error levels + * + * @var array + */ + public $levels = array( + E_ERROR => 'Error', + E_WARNING => 'Warning', + E_PARSE => 'Parsing Error', + E_NOTICE => 'Notice', + E_CORE_ERROR => 'Core Error', + E_CORE_WARNING => 'Core Warning', + E_COMPILE_ERROR => 'Compile Error', + E_COMPILE_WARNING => 'Compile Warning', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice' + ); + + /** + * Class constructor + * + * @return void */ public function __construct() { $this->ob_level = ob_get_level(); - // Note: Do not log messages from this constructor. + // Note: Do not log messages from this constructor. } // -------------------------------------------------------------------- @@ -62,44 +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 - * @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 } // -------------------------------------------------------------------- @@ -107,28 +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 - * @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.EXT); + include($templates_path.$template.'.php'); $buffer = ob_get_contents(); ob_end_clean(); return $buffer; @@ -136,27 +187,77 @@ 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 + { + $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 void */ - 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) @@ -164,15 +265,10 @@ class CI_Exceptions { ob_end_flush(); } ob_start(); - include(APPPATH.'errors/error_php'.EXT); + 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/system/core/Hooks.php b/system/core/Hooks.php index 75fd811b..f2d6f21c 100644 --- a/system/core/Hooks.php +++ b/system/core/Hooks.php @@ -1,72 +1,114 @@ -_initialize(); - log_message('debug', "Hooks Class Initialized"); - } - - // -------------------------------------------------------------------- + public $enabled = FALSE; /** - * Initialize the Hooks Preferences + * List of all hooks set in config/hooks.php + * + * @var array + */ + public $hooks = array(); + + /** + * Array with class objects to use hooks methods + * + * @var array + */ + protected $_objects = array(); + + /** + * In progress flag + * + * Determines whether hook is in progress, used to prevent infinte loops + * + * @var bool + */ + protected $_in_progress = FALSE; + + /** + * Class constructor * - * @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 (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. - - @include(APPPATH.'config/hooks'.EXT); - if ( ! isset($hook) OR ! is_array($hook)) { return; @@ -81,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) { @@ -116,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; } @@ -133,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; } @@ -143,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; } @@ -155,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/system/core/Input.php b/system/core/Input.php index 62624539..af4f87c1 100644 --- a/system/core/Input.php +++ b/system/core/Input.php @@ -1,19 +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'); - // Do we need to load the security class? - if ($this->_enable_xss == TRUE OR $this->_enable_csrf == TRUE) - { - $this->security =& load_class('Security'); - } + $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'); } // -------------------------------------------------------------------- @@ -75,148 +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])) { - $_security =& load_class('Security'); - return $_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 = NULL, $httponly = NULL) { if (is_array($name)) { // always leave 'name' in last place, as the loop will break otherwise, due to $$item - foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'name') as $item) + foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'httponly', 'name') as $item) { if (isset($name[$item])) { @@ -225,22 +373,28 @@ 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) - { - $secure = config_item('cookie_secure'); - } + + $secure = ($secure === NULL && config_item('cookie_secure') !== NULL) + ? (bool) config_item('cookie_secure') + : (bool) $secure; + + $httponly = ($httponly === NULL && config_item('cookie_httponly') !== NULL) + ? (bool) config_item('cookie_httponly') + : (bool) $httponly; if ( ! is_numeric($expire)) { @@ -251,78 +405,141 @@ 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 - * - * @access public - * @return string - */ - function ip_address() + * Fetch the IP Address + * + * Determines and validates the visitor's IP address. + * + * @return string IP address + */ + public function ip_address() { if ($this->ip_address !== FALSE) { return $this->ip_address; } - if (config_item('proxy_ips') != '' && $this->server('HTTP_X_FORWARDED_FOR') && $this->server('REMOTE_ADDR')) + $proxy_ips = config_item('proxy_ips'); + if ( ! empty($proxy_ips) && ! is_array($proxy_ips)) { - $proxies = preg_split('/[\s,]/', config_item('proxy_ips'), -1, PREG_SPLIT_NO_EMPTY); - $proxies = is_array($proxies) ? $proxies : array($proxies); - - $this->ip_address = in_array($_SERVER['REMOTE_ADDR'], $proxies) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; - } - elseif ($this->server('REMOTE_ADDR') AND $this->server('HTTP_CLIENT_IP')) - { - $this->ip_address = $_SERVER['HTTP_CLIENT_IP']; - } - elseif ($this->server('REMOTE_ADDR')) - { - $this->ip_address = $_SERVER['REMOTE_ADDR']; - } - elseif ($this->server('HTTP_CLIENT_IP')) - { - $this->ip_address = $_SERVER['HTTP_CLIENT_IP']; - } - elseif ($this->server('HTTP_X_FORWARDED_FOR')) - { - $this->ip_address = $_SERVER['HTTP_X_FORWARDED_FOR']; + $proxy_ips = explode(',', str_replace(' ', '', $proxy_ips)); } - if ($this->ip_address === FALSE) - { - $this->ip_address = '0.0.0.0'; - return $this->ip_address; - } + $this->ip_address = $this->server('REMOTE_ADDR'); - if (strpos($this->ip_address, ',') !== FALSE) + if ($proxy_ips) { - $x = explode(',', $this->ip_address); - $this->ip_address = trim(end($x)); + foreach (array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP') as $header) + { + if (($spoof = $this->server($header)) !== NULL) + { + // Some proxies typically list the whole chain of IP + // addresses through which the client has reached us. + // e.g. client_ip, proxy_ip1, proxy_ip2, etc. + sscanf($spoof, '%[^,]', $spoof); + + if ( ! $this->valid_ip($spoof)) + { + $spoof = NULL; + } + else + { + break; + } + } + } + + if ($spoof) + { + for ($i = 0, $c = count($proxy_ips); $i < $c; $i++) + { + // Check if we have an IP address or a subnet + if (strpos($proxy_ips[$i], '/') === FALSE) + { + // An IP address (and not a subnet) is specified. + // We can compare right away. + if ($proxy_ips[$i] === $this->ip_address) + { + $this->ip_address = $spoof; + break; + } + + continue; + } + + // We have a subnet ... now the heavy lifting begins + isset($separator) OR $separator = $this->valid_ip($this->ip_address, 'ipv6') ? ':' : '.'; + + // If the proxy entry doesn't match the IP protocol - skip it + if (strpos($proxy_ips[$i], $separator) === FALSE) + { + continue; + } + + // Convert the REMOTE_ADDR IP address to binary, if needed + if ( ! isset($ip, $sprintf)) + { + if ($separator === ':') + { + // Make sure we're have the "full" IPv6 format + $ip = explode(':', + str_replace('::', + str_repeat(':', 9 - substr_count($this->ip_address, ':')), + $this->ip_address + ) + ); + + for ($j = 0; $j < 8; $j++) + { + $ip[$j] = intval($ip[$j], 16); + } + + $sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b'; + } + else + { + $ip = explode('.', $this->ip_address); + $sprintf = '%08b%08b%08b%08b'; + } + + $ip = vsprintf($sprintf, $ip); + } + + // Split the netmask length off the network address + sscanf($proxy_ips[$i], '%[^/]/%d', $netaddr, $masklen); + + // Again, an IPv6 address is most likely in a compressed form + if ($separator === ':') + { + $netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr)); + for ($j = 0; $j < 8; $j++) + { + $netaddr[$j] = intval($netaddr[$j], 16); + } + } + else + { + $netaddr = explode('.', $netaddr); + } + + // Convert to binary and finally compare + if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0) + { + $this->ip_address = $spoof; + break; + } + } + } } if ( ! $this->valid_ip($this->ip_address)) { - $this->ip_address = '0.0.0.0'; + return $this->ip_address = '0.0.0.0'; } return $this->ip_address; @@ -331,129 +548,72 @@ class CI_Input { // -------------------------------------------------------------------- /** - * Validate IP Address - * - * Updated version suggested by Geert De Deckere - * - * @access public - * @param string - * @return string - */ - function valid_ip($ip) + * Validate IP Address + * + * @param string $ip IP address + * @param string $which IP protocol: 'ipv4' or 'ipv6' + * @return bool + */ + public function valid_ip($ip, $which = '') { - $ip_segments = explode('.', $ip); - - // Always 4 segments needed - if (count($ip_segments) != 4) + switch (strtolower($which)) { - 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; - } + case 'ipv4': + $which = FILTER_FLAG_IPV4; + break; + case 'ipv6': + $which = FILTER_FLAG_IPV6; + break; + default: + $which = NULL; + break; } - return TRUE; + return (bool) filter_var($ip, FILTER_VALIDATE_IP, $which); } // -------------------------------------------------------------------- /** - * User Agent - * - * @access public - * @return string - */ - function user_agent() + * Fetch User Agent string + * + * @return string|null User Agent string or NULL if it doesn't exist + */ + public function user_agent($xss_clean = NULL) { - 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; + return $this->_fetch_from_array($_SERVER, 'HTTP_USER_AGENT', $xss_clean); } // -------------------------------------------------------------------- /** - * 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() + * 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() { - // 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) { @@ -462,62 +622,67 @@ 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']); + unset( + $_COOKIE['$Version'], + $_COOKIE['$Path'], + $_COOKIE['$Domain'] + ); foreach ($_COOKIE as $key => $val) { - $_COOKIE[$this->_clean_input_keys($key)] = $this->_clean_input_data($val); + if (($cookie_key = $this->_clean_input_keys($key)) !== FALSE) + { + $_COOKIE[$cookie_key] = $this->_clean_input_data($val); + } + else + { + unset($_COOKIE[$key]); + } } } // Sanitize PHP_SELF $_SERVER['PHP_SELF'] = strip_tags($_SERVER['PHP_SELF']); - - // CSRF Protection check - if ($this->_enable_csrf == TRUE) - { - $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; } - // We strip slashes if magic quotes is on to keep things consistent - if (function_exists('get_magic_quotes_gpc') AND get_magic_quotes_gpc()) + /* We strip slashes if magic quotes is on to keep things consistent + + NOTE: In PHP 5.4 get_magic_quotes_gpc() will always return 0 and + it will probably not exist in future versions at all. + */ + if ( ! is_php('5.4') && get_magic_quotes_gpc()) { $str = stripslashes($str); } @@ -528,19 +693,13 @@ class CI_Input { $str = $this->uni->clean_string($str); } - // Should we filter the input data? - if ($this->_enable_xss === TRUE) - { - $str = $this->security->xss_clean($str); - } + // Remove control characters + $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"), PHP_EOL, $str); - } + return preg_replace('/(?:\r\n|[\r\n])/', PHP_EOL, $str); } return $str; @@ -549,27 +708,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; @@ -580,41 +750,40 @@ class CI_Input { /** * Request Headers * - * In Apache, you can simply call apache_request_headers(), however for - * people running other webservers the function is undefined. - * - * @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->_fetch_from_array($this->headers, NULL, $xss_clean); + } + + // In Apache, you can simply call apache_request_headers() if (function_exists('apache_request_headers')) { - $headers = apache_request_headers(); + $this->headers = apache_request_headers(); } else { - $headers['Content-Type'] = (isset($_SERVER['CONTENT_TYPE'])) ? $_SERVER['CONTENT_TYPE'] : @getenv('CONTENT_TYPE'); + isset($_SERVER['CONTENT_TYPE']) && $this->headers['Content-Type'] = $_SERVER['CONTENT_TYPE']; foreach ($_SERVER as $key => $val) { - if (strncmp($key, 'HTTP_', 5) === 0) + if (sscanf($key, 'HTTP_%s', $header) === 1) { - $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] = $_SERVER[$key]; } } } - // 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; + return $this->_fetch_from_array($this->headers, NULL, $xss_clean); } // -------------------------------------------------------------------- @@ -624,61 +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(); - } - - if ( ! isset($this->headers[$index])) - { - return FALSE; + empty($this->headers) && $this->request_headers(); + foreach ($this->headers as $key => $value) + { + $headers[strtolower($key)] = $value; + } } - if ($xss_clean === TRUE) + $index = strtolower($index); + + if ( ! isset($headers[$index])) { - $_security =& load_class('Security'); - return $_security->xss_clean($this->headers[$index]); + return NULL; } - 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 + * Test to see if a request contains the HTTP_X_REQUESTED_WITH header. * - * @return boolean + * @return bool */ public function is_ajax_request() { - return ($this->server('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest'); + return ( ! empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'); } // -------------------------------------------------------------------- /** - * Is cli Request? + * Is CLI request? * - * Test to see if a request was made from the command line + * Test to see if a request was made from the command line. * - * @return boolean + * @deprecated 3.0.0 Use is_cli() instead + * @return bool */ public function is_cli_request() { - return (bool) 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 Input class - -/* End of file Input.php */ -/* Location: ./system/core/Input.php */ diff --git a/system/core/Lang.php b/system/core/Lang.php index fb177902..569b0236 100644 --- a/system/core/Lang.php +++ b/system/core/Lang.php @@ -1,19 +1,41 @@ -load($value, $idiom, $return, $add_suffix, $alt_path); + } + + return; } - $langfile .= EXT; + $langfile = str_replace('.php', '', $langfile); - if (in_array($langfile, $this->is_loaded, TRUE)) + if ($add_suffix === TRUE) + { + $langfile = preg_replace('/_lang$/', '', $langfile).'_lang'; + } + + $langfile .= '.php'; + + if (empty($idiom) OR ! preg_match('/^[a-z_-]+$/i', $idiom)) + { + $config =& get_config(); + $idiom = empty($config['language']) ? 'english' : $config['language']; + } + + if ($return === FALSE && isset($this->is_loaded[$langfile]) && $this->is_loaded[$langfile] === $idiom) { return; } - $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) { - $line = ($line == '' OR ! isset($this->language[$line])) ? FALSE : $this->language[$line]; - return $line; + $value = isset($this->language[$line]) ? $this->language[$line] : FALSE; + + // Because killer robots like unicorns! + if ($value === FALSE && $log_errors === TRUE) + { + log_message('error', 'Could not find the language line "'.$line.'"'); + } + + return $value; } } -// END Language Class - -/* End of file Lang.php */ -/* Location: ./system/core/Lang.php */ \ No newline at end of file diff --git a/system/core/Loader.php b/system/core/Loader.php index 7003318e..085c5b51 100644 --- a/system/core/Loader.php +++ b/system/core/Loader.php @@ -1,113 +1,222 @@ - 'unit', 'user_agent' => 'agent'); - + /** + * Nesting level of the output buffering mechanism + * + * @var int + */ + protected $_ci_ob_level; /** - * Constructor + * List of paths to load views from * - * Sets the path to the view files and gets the initial output buffering level - * - * @access public + * @var array */ - function __construct() - { - $this->_ci_view_path = APPPATH.'views/'; - $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); + protected $_ci_view_paths = array(VIEWPATH => TRUE); - log_message('debug', "Loader Class Initialized"); + /** + * List of paths to load libraries from + * + * @var array + */ + protected $_ci_library_paths = array(APPPATH, BASEPATH); + + /** + * List of paths to load models from + * + * @var array + */ + protected $_ci_model_paths = array(APPPATH); + + /** + * List of paths to load helpers from + * + * @var array + */ + protected $_ci_helper_paths = array(APPPATH, BASEPATH); + + /** + * List of cached variables + * + * @var array + */ + protected $_ci_cached_vars = array(); + + /** + * List of loaded classes + * + * @var array + */ + protected $_ci_classes = array(); + + /** + * List of loaded models + * + * @var array + */ + protected $_ci_models = array(); + + /** + * List of loaded helpers + * + * @var array + */ + protected $_ci_helpers = array(); + + /** + * List of class name mappings + * + * @var array + */ + protected $_ci_varmap = array( + 'unit_test' => 'unit', + 'user_agent' => 'agent' + ); + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * Sets component load paths, gets the initial output buffering level. + * + * @return void + */ + public function __construct() + { + $this->_ci_ob_level = ob_get_level(); + $this->_ci_classes =& is_loaded(); + + log_message('info', 'Loader Class Initialized'); } // -------------------------------------------------------------------- /** - * Class Loader + * Initializer * - * This function lets users load and instantiate classes. - * It is designed to be called from a user's app controllers. - * - * @access public - * @param string the name of the class - * @param mixed the optional parameters - * @param string an optional object name + * @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 */ - function library($library = '', $params = NULL, $object_name = NULL) + public function initialize() { - if (is_array($library)) + $this->_ci_autoloader(); + } + + // -------------------------------------------------------------------- + + /** + * Is Loaded + * + * A utility method to test if a class is in the self::$_ci_classes array. + * + * @used-by Mainly used by Form Helper function _get_validation_object(). + * + * @param string $class Class name to check for + * @return string|bool Class object name if loaded or FALSE + */ + public function is_loaded($class) + { + return array_search(ucfirst($class), $this->_ci_classes, TRUE); + } + + // -------------------------------------------------------------------- + + /** + * Library Loader + * + * Loads and instantiates libraries. + * Designed to be called from application controllers. + * + * @param mixed $library Library name + * @param array $params Optional parameters to pass to the library class constructor + * @param string $object_name An optional object name to assign to + * @return object + */ + public function library($library, $params = NULL, $object_name = NULL) + { + if (empty($library)) { - foreach ($library as $read) + return $this; + } + elseif (is_array($library)) + { + foreach ($library as $key => $value) { - $this->library($read); + 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; } - if (is_array($library)) - { - foreach ($library as $class) - { - $this->_ci_load_class($class, $params, $object_name); - } - } - else - { - $this->_ci_load_class($library, $params, $object_name); - } + $this->_ci_load_library($library, $params, $object_name); + return $this; } // -------------------------------------------------------------------- @@ -115,28 +224,27 @@ class CI_Loader { /** * Model Loader * - * This function lets users load and instantiate models. + * Loads and instantiates models. * - * @access public - * @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 */ - function model($model, $name = '', $db_conn = FALSE) + 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 = ''; @@ -145,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.EXT)) + 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.EXT); - - $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; } // -------------------------------------------------------------------- @@ -210,64 +359,71 @@ class CI_Loader { /** * Database Loader * - * @access public - * @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 */ - 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; } - require_once(BASEPATH.'database/DB'.EXT); + require_once(BASEPATH.'database/DB.php'); 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 * - * @access public - * @return string + * @param object $db Database object + * @param bool $return Whether to return the DB Utilities class object or not + * @return object */ - 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'.EXT); - require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_utility'.EXT); - $class = 'CI_DB_'.$CI->db->dbdriver.'_utility'; + require_once(BASEPATH.'database/DB_utility.php'); + 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; } // -------------------------------------------------------------------- @@ -275,62 +431,74 @@ class CI_Loader { /** * Load the Database Forge Class * - * @access public - * @return string + * @param object $db Database object + * @param bool $return Whether to return the DB Forge class object or not + * @return object */ - 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/'.$db->dbdriver.'/'.$db->dbdriver.'_forge.php'); - require_once(BASEPATH.'database/DB_forge'.EXT); - require_once(BASEPATH.'database/drivers/'.$CI->db->dbdriver.'/'.$CI->db->dbdriver.'_forge'.EXT); - $class = 'CI_DB_'.$CI->db->dbdriver.'_forge'; + 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'; + } - $CI->dbforge = new $class(); + 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. - * - * @access public - * @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 */ - function view($view, $vars = array(), $return = FALSE) + public function view($view, $vars = array(), $return = FALSE) { - return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_object_to_array($vars), '_ci_return' => $return)); + return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_prepare_view_vars($vars), '_ci_return' => $return)); } // -------------------------------------------------------------------- /** - * Load File + * Generic File Loader * - * This is a generic file loader - * - * @access public - * @param string - * @param bool - * @return string + * @param string $path File path + * @param bool $return Whether to return the file output + * @return object|string */ - function file($path, $return = FALSE) + public function file($path, $return = FALSE) { return $this->_ci_load(array('_ci_path' => $path, '_ci_return' => $return)); } @@ -343,77 +511,129 @@ class CI_Loader { * Once variables are set they become available within * the controller class and its "view" files. * - * @access public - * @param array - * @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 */ - function vars($vars = array(), $val = '') + public function vars($vars, $val = '') { - if ($val != '' AND is_string($vars)) + $vars = is_string($vars) + ? array($vars => $val) + : $this->_ci_prepare_view_vars($vars); + + foreach ($vars as $key => $val) { - $vars = array($vars => $val); + $this->_ci_cached_vars[$key] = $val; } - $vars = $this->_ci_object_to_array($vars); - - if (is_array($vars) AND count($vars) > 0) - { - foreach ($vars as $key => $val) - { - $this->_ci_cached_vars[$key] = $val; - } - } + return $this; } // -------------------------------------------------------------------- /** - * Load Helper + * Clear Cached Variables * - * This function loads the specified helper file. + * Clears the cached variables. * - * @access public - * @param mixed - * @return void + * @return CI_Loader */ - function helper($helpers = array()) + public function clear_vars() { - foreach ($this->_ci_prep_filename($helpers, '_helper') as $helper) + $this->_ci_cached_vars = array(); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Get Variable + * + * Check if a variable is set and retrieve it. + * + * @param string $key Variable name + * @return mixed The variable or NULL if not found + */ + public function get_var($key) + { + return isset($this->_ci_cached_vars[$key]) ? $this->_ci_cached_vars[$key] : NULL; + } + + // -------------------------------------------------------------------- + + /** + * Get Variables + * + * Retrieves all loaded variables. + * + * @return array + */ + public function get_vars() + { + return $this->_ci_cached_vars; + } + + // -------------------------------------------------------------------- + + /** + * Helper Loader + * + * @param string|string[] $helpers Helper name(s) + * @return object + */ + public function helper($helpers = array()) + { + is_array($helpers) OR $helpers = array($helpers); + foreach ($helpers as &$helper) { + $filename = basename($helper); + $filepath = ($filename === $helper) ? '' : substr($helper, 0, strlen($helper) - strlen($filename)); + $filename = strtolower(preg_replace('#(_helper)?(\.php)?$#i', '', $filename)).'_helper'; + $helper = $filepath.$filename; + if (isset($this->_ci_helpers[$helper])) { continue; } - $ext_helper = APPPATH.'helpers/'.config_item('subclass_prefix').$helper.EXT; - // Is this a helper extension request? - if (file_exists($ext_helper)) + $ext_helper = config_item('subclass_prefix').$filename; + $ext_loaded = FALSE; + foreach ($this->_ci_helper_paths as $path) { - $base_helper = BASEPATH.'helpers/'.$helper.EXT; + 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.EXT); + 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.EXT)) + if (file_exists($path.'helpers/'.$helper.'.php')) { - include_once($path.'helpers/'.$helper.EXT); + include_once($path.'helpers/'.$helper.'.php'); $this->_ci_helpers[$helper] = TRUE; - log_message('debug', 'Helper loaded: '.$helper); + log_message('info', 'Helper loaded: '.$helper); break; } } @@ -421,9 +641,11 @@ class CI_Loader { // unable to load the helper if ( ! isset($this->_ci_helpers[$helper])) { - show_error('Unable to load the requested file: helpers/'.$helper.EXT); + show_error('Unable to load the requested file: helpers/'.$helper.'.php'); } } + + return $this; } // -------------------------------------------------------------------- @@ -431,76 +653,94 @@ 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. * - * @access public - * @param array - * @return void + * @uses CI_Loader::helper() + * @param string|string[] $helpers Helper name(s) + * @return object */ - function helpers($helpers = array()) + public function helpers($helpers = array()) { - $this->helper($helpers); + return $this->helper($helpers); } // -------------------------------------------------------------------- /** - * Loads a language file + * Language Loader * - * @access public - * @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 */ - function language($file = array(), $lang = '') + public function language($files, $lang = '') { - $CI =& get_instance(); + get_instance()->lang->load($files, $lang); + return $this; + } - if ( ! is_array($file)) + // -------------------------------------------------------------------- + + /** + * Config Loader + * + * Loads a config file (an alias for CI_Config::load()). + * + * @uses CI_Config::load() + * @param string $file Configuration file name + * @param bool $use_sections Whether configuration values should be loaded into their own section + * @param bool $fail_gracefully Whether to just return FALSE or display an error message + * @return bool TRUE if the file was loaded correctly or FALSE on failure + */ + public function config($file, $use_sections = FALSE, $fail_gracefully = FALSE) + { + return get_instance()->config->load($file, $use_sections, $fail_gracefully); + } + + // -------------------------------------------------------------------- + + /** + * Driver Loader + * + * Loads a driver library. + * + * @param string|string[] $library Driver name(s) + * @param array $params Optional parameters to pass to the driver + * @param string $object_name An optional object name to assign to + * + * @return object|bool Object or FALSE on failure if $library is a string + * and $object_name is set. CI_Loader instance otherwise. + */ + public function driver($library, $params = NULL, $object_name = NULL) + { + if (is_array($library)) { - $file = array($file); + foreach ($library as $key => $value) + { + if (is_int($key)) + { + $this->driver($value, $params); + } + else + { + $this->driver($key, $params, $value); + } + } + + return $this; + } + elseif (empty($library)) + { + return FALSE; } - foreach ($file as $langfile) + if ( ! class_exists('CI_Driver_Library', FALSE)) { - $CI->lang->load($langfile, $lang); - } - } - - // -------------------------------------------------------------------- - - /** - * Loads a config file - * - * @access public - * @param string - * @return void - */ - function config($file = '', $use_sections = FALSE, $fail_gracefully = FALSE) - { - $CI =& get_instance(); - $CI->config->load($file, $use_sections, $fail_gracefully); - } - - // -------------------------------------------------------------------- - - /** - * Driver - * - * Loads a driver library - * - * @param string the name of the class - * @param mixed the optional parameters - * @param string an optional object name - * @return void - */ - function driver($library = '', $params = NULL, $object_name = NULL) - { - if ( ! class_exists('CI_Driver_Library')) - { - // we aren't instantiating an object here, that'll be done by the Library itself - require BASEPATH.'libraries/Driver'.EXT; + // 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, @@ -518,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. * - * @access public - * @param string - * @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 */ - function add_package_path($path) + public function add_package_path($path, $view_cascade = TRUE) { $path = rtrim($path, '/').'/'; @@ -532,9 +778,13 @@ class CI_Loader { array_unshift($this->_ci_model_paths, $path); array_unshift($this->_ci_helper_paths, $path); + $this->_ci_view_paths = array($path.'views/' => $view_cascade) + $this->_ci_view_paths; + // Add config file path $config =& $this->_ci_get_component('config'); - array_unshift($config->_config_paths, $path); + $config->_config_paths[] = $path; + + return $this; } // -------------------------------------------------------------------- @@ -542,15 +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. * - * @access public - * @param string - * @return void + * @param bool $include_base Whether to include BASEPATH (default: FALSE) + * @return array */ - function get_package_paths($include_base = FALSE) + 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; } // -------------------------------------------------------------------- @@ -558,28 +807,28 @@ 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. * - * @access public - * @param type - * @return type + * @param string $path Path to remove + * @return object */ - 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($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 { $path = rtrim($path, '/').'/'; - foreach (array('_ci_library_paths', '_ci_model_paths', '_ci_helper_paths') as $var) { if (($key = array_search($path, $this->{$var})) !== FALSE) @@ -588,6 +837,11 @@ class CI_Loader { } } + if (isset($this->_ci_view_paths[$path.'views/'])) + { + unset($this->_ci_view_paths[$path.'views/']); + } + if (($key = array_search($path, $config->_config_paths)) !== FALSE) { unset($config->_config_paths[$key]); @@ -598,51 +852,71 @@ class CI_Loader { $this->_ci_library_paths = array_unique(array_merge($this->_ci_library_paths, array(APPPATH, BASEPATH))); $this->_ci_helper_paths = array_unique(array_merge($this->_ci_helper_paths, array(APPPATH, BASEPATH))); $this->_ci_model_paths = array_unique(array_merge($this->_ci_model_paths, array(APPPATH))); + $this->_ci_view_paths = array_merge($this->_ci_view_paths, array(APPPATH.'views/' => TRUE)); $config->_config_paths = array_unique(array_merge($config->_config_paths, array(APPPATH))); + + return $this; } // -------------------------------------------------------------------- /** - * 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. * - * @access private - * @param array - * @return void + * @used-by CI_Loader::view() + * @used-by CI_Loader::file() + * @param array $_ci_data Data to load + * @return object */ - function _ci_load($_ci_data) + 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 == '') - { - $_ci_ext = pathinfo($_ci_view, PATHINFO_EXTENSION); - $_ci_file = ($_ci_ext == '') ? $_ci_view.EXT : $_ci_view; - $_ci_path = $this->_ci_view_path.$_ci_file; - } - else + if (is_string($_ci_path) && $_ci_path !== '') { $_ci_x = explode('/', $_ci_path); $_ci_file = end($_ci_x); } + else + { + $_ci_ext = pathinfo($_ci_view, PATHINFO_EXTENSION); + $_ci_file = ($_ci_ext === '') ? $_ci_view.'.php' : $_ci_view; - if ( ! file_exists($_ci_path)) + foreach ($this->_ci_view_paths as $_ci_view_file => $cascade) + { + if (file_exists($_ci_view_file.$_ci_file)) + { + $_ci_path = $_ci_view_file.$_ci_file; + $file_exists = TRUE; + break; + } + + if ( ! $cascade) + { + break; + } + } + } + + if ( ! $file_exists && ! file_exists($_ci_path)) { show_error('Unable to load the requested file: '.$_ci_file); } // This allows anything loaded using $this->load (views, files, etc.) // to become accessible from within the Controller and Model functions. - $_ci_CI =& get_instance(); foreach (get_object_vars($_ci_CI) as $_ci_key => $_ci_var) { @@ -655,15 +929,12 @@ 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)) - { - $this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars); - } + empty($_ci_vars) OR $this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars); extract($this->_ci_cached_vars); /* @@ -671,29 +942,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) { @@ -722,152 +990,208 @@ 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() * - * @access private - * @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 */ - 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, // but we don't want a leading slash - $class = str_replace(EXT, '', trim($class, '/')); + $class = str_replace('.php', '', trim($class, '/')); // Was the path included with the class name? // We look for a slash to determine this - $subdir = ''; if (($last_slash = strrpos($class, '/')) !== FALSE) { // Extract the path - $subdir = substr($class, 0, $last_slash + 1); + $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.EXT; + return $this->_ci_load_stock_library($class, $subdir, $params, $object_name); + } - // Is this a class extension request? - if (file_exists($subclass)) + // Safety: Was the class already loaded by a previous call? + if (class_exists($class, FALSE)) + { + $property = $object_name; + if (empty($property)) { - $baseclass = BASEPATH.'libraries/'.ucfirst($class).EXT; - - 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); + $property = strtolower($class); + isset($this->_ci_varmap[$property]) && $property = $this->_ci_varmap[$property]; } - // Lets search for the requested library file and load it. - $is_duplicate = FALSE; - foreach ($this->_ci_library_paths as $path) + $CI =& get_instance(); + if (isset($CI->$property)) { - $filepath = $path.'libraries/'.$subdir.$class.EXT; - - // Does the file exist? No? Bummer... - if ( ! file_exists($filepath)) - { - continue; - } - - // Safety: Was the class already loaded by a previous call? - if (in_array($filepath, $this->_ci_loaded_files)) - { - // Before we deem this to be a duplicate request, let's see - // if a custom object name is being supplied. If so, we'll - // return a new instance of the object - if ( ! is_null($object_name)) - { - $CI =& get_instance(); - if ( ! isset($CI->$object_name)) - { - return $this->_ci_init_class($class, '', $params, $object_name); - } - } - - $is_duplicate = TRUE; - log_message('debug', $class." class already loaded. Second attempt ignored."); - return; - } - - include_once($filepath); - $this->_ci_loaded_files[] = $filepath; - return $this->_ci_init_class($class, '', $params, $object_name); + log_message('debug', $class.' class already loaded. Second attempt ignored.'); + return; } - } // END FOREACH + 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 == '') + // Let's search for the requested library file and load it. + foreach ($this->_ci_library_paths as $path) { - $path = strtolower($class).'/'.$class; - return $this->_ci_load_class($path, $params); + // BASEPATH has already been checked for + if ($path === BASEPATH) + { + continue; + } + + $filepath = $path.'libraries/'.$subdir.$class.'.php'; + // Does the file exist? No? Bummer... + if ( ! file_exists($filepath)) + { + continue; + } + + include_once($filepath); + return $this->_ci_init_library($class, '', $params, $object_name); + } + + // One last attempt. Maybe the library is in a subdirectory, but it wasn't specified? + if ($subdir === '') + { + return $this->_ci_load_library($class.'/'.$class, $params, $object_name); } // If we got this far we were unable to find the requested class. - // 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 * - * @access private - * @param string - * @param string - * @param string an optional object name - * @return null + * @used-by CI_Loader::_ci_load_library() + * @uses CI_Loader::_ci_init_library() + * + * @param string $library_name Library name to load + * @param string $file_path Path to the library filename, relative to libraries/ + * @param mixed $params Optional parameters to pass to the class constructor + * @param string $object_name Optional object name to assign to + * @return void */ - 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'); + } + + $property = $object_name; + if (empty($property)) + { + $property = strtolower($library_name); + isset($this->_ci_varmap[$property]) && $property = $this->_ci_varmap[$property]; + } + + $CI =& get_instance(); + if ( ! isset($CI->$property)) + { + return $this->_ci_init_library($library_name, $prefix, $params, $object_name); + } + + log_message('debug', $library_name.' class already loaded. Second attempt ignored.'); + return; + } + + $paths = $this->_ci_library_paths; + array_pop($paths); // BASEPATH + array_pop($paths); // APPPATH (needs to be the first path checked) + array_unshift($paths, APPPATH); + + foreach ($paths as $path) + { + if (file_exists($path = $path.'libraries/'.$file_path.$library_name.'.php')) + { + // Override + include_once($path); + if (class_exists($prefix.$library_name, FALSE)) + { + return $this->_ci_init_library($library_name, $prefix, $params, $object_name); + } + + log_message('debug', $path.' exists, but does not declare '.$prefix.$library_name); + } + } + + include_once(BASEPATH.'libraries/'.$file_path.$library_name.'.php'); + + // Check for extensions + $subclass = config_item('subclass_prefix').$library_name; + foreach ($paths as $path) + { + if (file_exists($path = $path.'libraries/'.$file_path.$subclass.'.php')) + { + include_once($path); + if (class_exists($subclass, FALSE)) + { + $prefix = config_item('subclass_prefix'); + break; + } + + log_message('debug', $path.' exists, but does not declare '.$subclass); + } + } + + return $this->_ci_init_library($library_name, $prefix, $params, $object_name); + } + + // -------------------------------------------------------------------- + + /** + * Internal CI Library Instantiator + * + * @used-by CI_Loader::_ci_load_stock_library() + * @used-by CI_Loader::_ci_load_library() + * + * @param string $class Class name + * @param string $prefix Class name prefix + * @param array|null|bool $config Optional configuration to pass to the class constructor: + * FALSE to skip; + * NULL to search in config paths; + * array containing configuration data + * @param string $object_name Optional object name to assign to + * @return void + */ + protected function _ci_init_library($class, $prefix, $config = FALSE, $object_name = NULL) + { + // Is there an associated config file for this class? Note: these should always be lowercase if ($config === NULL) { // Fetch the config paths containing any package paths @@ -875,111 +1199,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 (file_exists($path .'config/'.ENVIRONMENT.'/'.strtolower($class).EXT)) + // are case-sensitive with regard to file names. Load global first, + // override with environment next + if (file_exists($path.'config/'.strtolower($class).'.php')) { - include_once($path .'config/'.ENVIRONMENT.'/'.strtolower($class).EXT); - break; + include($path.'config/'.strtolower($class).'.php'); + $found = TRUE; } - elseif (file_exists($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).EXT)) + elseif (file_exists($path.'config/'.ucfirst(strtolower($class)).'.php')) { - include_once($path .'config/'.ENVIRONMENT.'/'.ucfirst(strtolower($class)).EXT); - break; + include($path.'config/'.ucfirst(strtolower($class)).'.php'); + $found = TRUE; } - elseif (file_exists($path .'config/'.strtolower($class).EXT)) + + if (file_exists($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php')) { - include_once($path .'config/'.strtolower($class).EXT); - break; + include($path.'config/'.ENVIRONMENT.'/'.strtolower($class).'.php'); + $found = TRUE; } - elseif (file_exists($path .'config/'.ucfirst(strtolower($class)).EXT)) + 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_once($path .'config/'.ucfirst(strtolower($class)).EXT); 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. * - * @access private - * @param array + * @used-by CI_Loader::initialize() * @return void */ - function _ci_autoloader() + protected function _ci_autoloader() { - include_once(APPPATH.'config/autoload'.EXT); + 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 @@ -994,31 +1318,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'])) @@ -1028,10 +1350,7 @@ class CI_Loader { } // Load all other libraries - foreach ($autoload['libraries'] as $item) - { - $this->library($item); - } + $this->library($autoload['libraries']); } // Autoload models @@ -1044,63 +1363,47 @@ class CI_Loader { // -------------------------------------------------------------------- /** - * Object to Array + * Prepare variables for _ci_vars, to be later extract()-ed inside views * - * Takes an object as input and converts the class variables to array key/vals + * Converts objects to associative arrays and filters-out internal + * variable names (i.e. keys prefixed with '_ci_'). * - * @access private - * @param object + * @param mixed $vars * @return array */ - function _ci_object_to_array($object) + protected function _ci_prepare_view_vars($vars) { - return (is_object($object)) ? get_object_vars($object) : $object; + if ( ! is_array($vars)) + { + $vars = is_object($vars) + ? get_object_vars($vars) + : array(); + } + + foreach (array_keys($vars) as $key) + { + if (strncmp($key, '_ci_', 4) === 0) + { + unset($vars[$key]); + } + } + + return $vars; } // -------------------------------------------------------------------- /** - * Get a reference to a specific library or model + * CI Component getter * - * @access private + * Get a reference to a specific library or model. + * + * @param string $component Component name * @return bool */ - function &_ci_get_component($component) + protected function &_ci_get_component($component) { $CI =& get_instance(); return $CI->$component; } - - // -------------------------------------------------------------------- - - /** - * Prep filename - * - * This function preps the name of various items to make loading them more reliable. - * - * @access private - * @param mixed - * @return array - */ - function _ci_prep_filename($filename, $extension) - { - if ( ! is_array($filename)) - { - return array(strtolower(str_replace(EXT, '', str_replace($extension, '', $filename)).$extension)); - } - else - { - foreach ($filename as $key => $val) - { - $filename[$key] = strtolower(str_replace(EXT, '', str_replace($extension, '', $val)).$extension); - } - - return $filename; - } - } - - } - -/* End of file Loader.php */ -/* Location: ./system/core/Loader.php */ \ No newline at end of file diff --git a/system/core/Log.php b/system/core/Log.php new file mode 100644 index 00000000..d443aedb --- /dev/null +++ b/system/core/Log.php @@ -0,0 +1,296 @@ + 1, 'DEBUG' => 2, 'INFO' => 3, 'ALL' => 4); + + /** + * mbstring.func_overload flag + * + * @var bool + */ + protected static $func_overload; + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * @return void + */ + public function __construct() + { + $config =& get_config(); + + isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); + + $this->_log_path = ($config['log_path'] !== '') ? $config['log_path'] : APPPATH.'logs/'; + $this->_file_ext = (isset($config['log_file_extension']) && $config['log_file_extension'] !== '') + ? ltrim($config['log_file_extension'], '.') : 'php'; + + file_exists($this->_log_path) OR mkdir($this->_log_path, 0755, TRUE); + + if ( ! is_dir($this->_log_path) OR ! is_really_writable($this->_log_path)) + { + $this->_enabled = FALSE; + } + + if (is_numeric($config['log_threshold'])) + { + $this->_threshold = (int) $config['log_threshold']; + } + elseif (is_array($config['log_threshold'])) + { + $this->_threshold = 0; + $this->_threshold_array = array_flip($config['log_threshold']); + } + + if ( ! empty($config['log_date_format'])) + { + $this->_date_fmt = $config['log_date_format']; + } + + if ( ! empty($config['log_file_permissions']) && is_int($config['log_file_permissions'])) + { + $this->_file_permissions = $config['log_file_permissions']; + } + } + + // -------------------------------------------------------------------- + + /** + * Write Log File + * + * Generally this function will be called using the global log_message() function + * + * @param string $level The error level: 'error', 'debug' or 'info' + * @param string $msg The error message + * @return bool + */ + public function write_log($level, $msg) + { + if ($this->_enabled === FALSE) + { + return FALSE; + } + + $level = strtoupper($level); + + if (( ! isset($this->_levels[$level]) OR ($this->_levels[$level] > $this->_threshold)) + && ! isset($this->_threshold_array[$this->_levels[$level]])) + { + return FALSE; + } + + $filepath = $this->_log_path.'log-'.date('Y-m-d').'.'.$this->_file_ext; + $message = ''; + + if ( ! file_exists($filepath)) + { + $newfile = TRUE; + // Only add protection to php files + if ($this->_file_ext === 'php') + { + $message .= "\n\n"; + } + } + + if ( ! $fp = @fopen($filepath, 'ab')) + { + return FALSE; + } + + flock($fp, LOCK_EX); + + // Instantiating DateTime with microseconds appended to initial date is needed for proper support of this format + if (strpos($this->_date_fmt, 'u') !== FALSE) + { + $microtime_full = microtime(TRUE); + $microtime_short = sprintf("%06d", ($microtime_full - floor($microtime_full)) * 1000000); + $date = new DateTime(date('Y-m-d H:i:s.'.$microtime_short, $microtime_full)); + $date = $date->format($this->_date_fmt); + } + else + { + $date = date($this->_date_fmt); + } + + $message .= $this->_format_line($level, $date, $msg); + + for ($written = 0, $length = self::strlen($message); $written < $length; $written += $result) + { + if (($result = fwrite($fp, self::substr($message, $written))) === FALSE) + { + break; + } + } + + flock($fp, LOCK_UN); + fclose($fp); + + if (isset($newfile) && $newfile === TRUE) + { + chmod($filepath, $this->_file_permissions); + } + + return is_int($result); + } + + // -------------------------------------------------------------------- + + /** + * Format the log line. + * + * This is for extensibility of log formatting + * If you want to change the log format, extend the CI_Log class and override this method + * + * @param string $level The error level + * @param string $date Formatted date string + * @param string $message The log message + * @return string Formatted log line with a new line character '\n' at the end + */ + protected function _format_line($level, $date, $message) + { + return $level.' - '.$date.' --> '.$message."\n"; + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return (self::$func_overload) + ? mb_strlen($str, '8bit') + : strlen($str); + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe substr() + * + * @param string $str + * @param int $start + * @param int $length + * @return string + */ + protected static function substr($str, $start, $length = NULL) + { + if (self::$func_overload) + { + // mb_substr($str, $start, null, '8bit') returns an empty + // string on PHP 5.3 + isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start); + return mb_substr($str, $start, $length, '8bit'); + } + + return isset($length) + ? substr($str, $start, $length) + : substr($str, $start); + } +} diff --git a/system/core/Model.php b/system/core/Model.php index 8566a0b6..c809e7b8 100644 --- a/system/core/Model.php +++ b/system/core/Model.php @@ -1,56 +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/system/core/Output.php b/system/core/Output.php index 6644b3bf..a3155fec 100644 --- a/system/core/Output.php +++ b/system/core/Output.php @@ -1,52 +1,156 @@ -_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') + ); + + isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); // Get mime types for later - include APPPATH.'config/mimes'.EXT; - $this->mime_types = $mimes; - - log_message('debug', "Output Class Initialized"); + $this->mimes =& get_mimes(); + + log_message('info', 'Output Class Initialized'); } // -------------------------------------------------------------------- @@ -54,12 +158,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; } @@ -69,16 +172,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; } @@ -87,23 +188,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; } @@ -112,51 +204,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 - * @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)) { @@ -165,28 +255,89 @@ 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 + * @return string + */ + public function get_header($header) + { + // Combine headers already sent with our batched headers + $headers = array_merge( + // We only need [x][0] from our multi-dimensional array + array_map('array_shift', $this->headers), + headers_list() + ); + + if (empty($headers) OR empty($header)) + { + return NULL; + } + + // Count backwards, in order to get the last matching header + for ($c = count($headers) - 1; $c > -1; $c--) + { + if (strncasecmp($header, $headers[$c], $l = self::strlen($header)) === 0) + { + return trim(self::substr($headers[$c], $l+1)); + } + } + + return NULL; + } + + // -------------------------------------------------------------------- + + /** + * Set HTTP Status Header + * + * As of version 1.7.2, this is an alias for common function + * set_status_header(). + * + * @param int $code Status code (default: 200) + * @param string $text Optional message + * @return CI_Output + */ + public function set_status_header($code = 200, $text = '') { set_status_header($code, $text); - return $this; } @@ -195,14 +346,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; } @@ -211,17 +360,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; @@ -232,14 +387,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; } @@ -248,26 +401,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 - * @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(); } @@ -275,14 +429,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')) @@ -299,24 +453,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'); } // -------------------------------------------------------------------- @@ -337,20 +485,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: '.self::strlen($output)); + } + else + { + // User agent doesn't support gzip compression, + // so we'll have to decompress our cache + $output = gzinflate(self::substr($output, 10, -8)); + } + } + echo $output; - log_message('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); @@ -358,20 +520,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')) @@ -380,130 +535,308 @@ 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 $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; } + if ( ! flock($fp, LOCK_EX)) + { + log_message('error', 'Unable to secure a file lock for file at: '.$cache_path); + fclose($fp); + return; + } + + // If output compression is enabled, compress the cache + // itself, so that we don't have to do that each time + // we're serving it + if ($this->_compress_output === TRUE) + { + $output = gzencode($output); + + if ($this->get_header('content-type') === NULL) + { + $this->set_content_type($this->mime_type); + } + } + $expire = time() + ($this->cache_expiration * 60); - if (flock($fp, LOCK_EX)) + // Put together our serialized info. + $cache_info = serialize(array( + 'expire' => $expire, + 'headers' => $this->headers + )); + + $output = $cache_info.'ENDCI--->'.$output; + + for ($written = 0, $length = self::strlen($output); $written < $length; $written += $result) { - fwrite($fp, $expire.'TS--->'.$output); - flock($fp, LOCK_UN); + if (($result = fwrite($fp, self::substr($output, $written))) === FALSE) + { + break; + } } - else + + flock($fp, LOCK_UN); + fclose($fp); + + if ( ! is_int($result)) { - log_message('error', "Unable to secure a file lock for file at: ".$cache_path); + @unlink($cache_path); + log_message('error', 'Unable to write the complete cache content at: '.$cache_path); return; } - fclose($fp); - @chmod($cache_path, FILE_WRITE_MODE); - log_message('debug', "Cache file written: ".$cache_path); + chmod($cache_path, 0640); + log_message('debug', 'Cache file written: '.$cache_path); + + // Send HTTP cache-control headers to browser to match file cache settings. + $this->set_cache_header($_SERVER['REQUEST_TIME'], $expire); } // -------------------------------------------------------------------- /** - * Update/serve a cached file + * Update/serve cached output * - * @access public - * @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; + } + + // 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(self::substr($cache, self::strlen($match[0]))); + log_message('debug', 'Cache file is current. Sending it to browser.'); return TRUE; } + // -------------------------------------------------------------------- + /** + * Delete cache + * + * @param string $uri URI string + * @return bool + */ + public function delete_cache($uri = '') + { + $CI =& get_instance(); + $cache_path = $CI->config->item('cache_path'); + if ($cache_path === '') + { + $cache_path = APPPATH.'cache/'; + } + + if ( ! is_dir($cache_path)) + { + log_message('error', 'Unable to find cache path: '.$cache_path); + return FALSE; + } + + if (empty($uri)) + { + $uri = $CI->uri->uri_string(); + + if (($cache_query_string = $CI->config->item('cache_query_string')) && ! empty($_SERVER['QUERY_STRING'])) + { + if (is_array($cache_query_string)) + { + $uri .= '?'.http_build_query(array_intersect_key($_GET, array_flip($cache_query_string))); + } + else + { + $uri .= '?'.$_SERVER['QUERY_STRING']; + } + } + } + + $cache_path .= md5($CI->config->item('base_url').$CI->config->item('index_page').ltrim($uri, '/')); + + if ( ! @unlink($cache_path)) + { + log_message('error', 'Unable to delete cache file for '.$uri); + return FALSE; + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Set Cache Header + * + * Set the HTTP headers to match the server-side file cache settings + * in order to reduce bandwidth. + * + * @param int $last_modified Timestamp of when the page was last modified + * @param int $expiration Timestamp of when should the requested page expire from cache + * @return void + */ + public function set_cache_header($last_modified, $expiration) + { + $max_age = $expiration - $_SERVER['REQUEST_TIME']; + + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $last_modified <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) + { + $this->set_status_header(304); + exit; + } + + header('Pragma: public'); + header('Cache-Control: max-age='.$max_age.', public'); + header('Expires: '.gmdate('D, d M Y H:i:s', $expiration).' GMT'); + header('Last-modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT'); + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return (self::$func_overload) + ? mb_strlen($str, '8bit') + : strlen($str); + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe substr() + * + * @param string $str + * @param int $start + * @param int $length + * @return string + */ + protected static function substr($str, $start, $length = NULL) + { + if (self::$func_overload) + { + // mb_substr($str, $start, null, '8bit') returns an empty + // string on PHP 5.3 + isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start); + return mb_substr($str, $start, $length, '8bit'); + } + + return isset($length) + ? substr($str, $start, $length) + : substr($str, $start); + } } -// END Output Class - -/* End of file Output.php */ -/* Location: ./system/core/Output.php */ \ No newline at end of file diff --git a/system/core/Router.php b/system/core/Router.php index 6893e6e9..1abe4c4e 100644 --- a/system/core/Router.php +++ b/system/core/Router.php @@ -1,19 +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 + // Load the routes.php file. It would be great if we could + // skip this for enable_query_strings = TRUE, but then + // default_controller would be empty ... + if (file_exists(APPPATH.'config/routes.php')) + { + include(APPPATH.'config/routes.php'); + } + + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/routes.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/routes.php'); + } + + // Validate & get reserved routes + if (isset($route) && is_array($route)) + { + isset($route['default_controller']) && $this->default_controller = $route['default_controller']; + isset($route['translate_uri_dashes']) && $this->translate_uri_dashes = $route['translate_uri_dashes']; + unset($route['default_controller'], $route['translate_uri_dashes']); + $this->routes = $route; + } + + // Are query strings enabled in the config file? Normally CI doesn't utilize query strings // since URI segments are more search-engine friendly, but they can optionally be used. // If this feature is enabled, we will gather the directory/class/method a little differently - $segments = array(); - if ($this->config->item('enable_query_strings') === TRUE AND isset($_GET[$this->config->item('controller_trigger')])) + if ($this->enable_query_strings) { - if (isset($_GET[$this->config->item('directory_trigger')])) + // If the directory is set at this time, it means an override exists, so skip the checks + if ( ! isset($this->directory)) { - $this->set_directory(trim($this->uri->_filter_uri($_GET[$this->config->item('directory_trigger')]))); - $segments[] = $this->fetch_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); + } } - if (isset($_GET[$this->config->item('controller_trigger')])) + $_c = trim($this->config->item('controller_trigger')); + if ( ! empty($_GET[$_c])) { - $this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')]))); - $segments[] = $this->fetch_class(); + $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(); } - 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(); - } + // Routing rules don't apply to query strings and we don't need to detect + // directories, so we're done here + return; } - // Load the routes.php file. - @include(APPPATH.'config/routes'.EXT); - $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) + // Is there anything to parse? + if ($this->uri->uri_string !== '') { - return $this->_validate_request($segments); - } - - // Fetch the complete URI string - $this->uri->_fetch_uri_string(); - - // Is there a URI string? If not, the default controller specified in the "routes" file will be shown. - if ($this->uri->uri_string == '') - { - return $this->_set_default_controller(); - } - - // Do we need to remove the URL suffix? - $this->uri->_remove_url_suffix(); - - // Compile the segments into an array - $this->uri->_explode_segments(); - - // Parse any custom routing that may exist - $this->_parse_routes(); - - // Re-index the segment array so that it starts with 1 rather than 0 - $this->uri->_reindex_segments(); - } - - // -------------------------------------------------------------------- - - /** - * Set the default controller - * - * @access private - * @return void - */ - function _set_default_controller() - { - if ($this->default_controller === FALSE) - { - show_error("Unable to determine what should be displayed. A default route has not been specified in the routing file."); - } - // Is the method being specified? - if (strpos($this->default_controller, '/') !== FALSE) - { - $x = explode('/', $this->default_controller); - - $this->set_class($x[0]); - $this->set_method($x[1]); - $this->_set_request($x); + $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].EXT)) + // 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].EXT)) - { - 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.EXT)) - { - $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); } @@ -351,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; } @@ -362,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; } @@ -376,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/system/core/Security.php b/system/core/Security.php new file mode 100644 index 00000000..082ffa96 --- /dev/null +++ b/system/core/Security.php @@ -0,0 +1,1080 @@ +', '<', '>', + "'", '"', '&', '$', '#', + '{', '}', '[', ']', '=', + ';', '?', '%20', '%22', + '%3c', // < + '%253c', // < + '%3e', // > + '%0e', // > + '%28', // ( + '%29', // ) + '%2528', // ( + '%26', // & + '%24', // $ + '%3f', // ? + '%3b', // ; + '%3d' // = + ); + + /** + * Character set + * + * Will be overridden by the constructor. + * + * @var string + */ + public $charset = 'UTF-8'; + + /** + * XSS Hash + * + * Random Hash for protecting URLs. + * + * @var string + */ + protected $_xss_hash; + + /** + * CSRF Hash + * + * Random hash for Cross Site Request Forgery protection cookie + * + * @var string + */ + protected $_csrf_hash; + + /** + * CSRF Expire time + * + * Expiration time for Cross Site Request Forgery protection cookie. + * Defaults to two hours (in seconds). + * + * @var int + */ + protected $_csrf_expire = 7200; + + /** + * CSRF Token name + * + * Token name for Cross Site Request Forgery protection cookie. + * + * @var string + */ + protected $_csrf_token_name = 'ci_csrf_token'; + + /** + * CSRF Cookie name + * + * Cookie name for Cross Site Request Forgery protection cookie. + * + * @var string + */ + protected $_csrf_cookie_name = 'ci_csrf_token'; + + /** + * List of never allowed strings + * + * @var array + */ + protected $_never_allowed_str = array( + 'document.cookie' => '[removed]', + 'document.write' => '[removed]', + '.parentNode' => '[removed]', + '.innerHTML' => '[removed]', + '-moz-binding' => '[removed]', + '' => '-->', + ' '<![CDATA[', + '' => '<comment>', + '<%' => '<%' + ); + + /** + * List of never allowed regex replacements + * + * @var array + */ + protected $_never_allowed_regex = array( + 'javascript\s*:', + '(document|(document\.)?window)\.(location|on\w*)', + 'expression\s*(\(|&\#40;)', // CSS and IE + 'vbscript\s*:', // IE, surprise! + 'wscript\s*:', // IE + 'jscript\s*:', // IE + 'vbs\s*:', // IE + 'Redirect\s+30\d', + "([\"'])?data\s*:[^\\1]*?base64[^\\1]*?,[^\\1]*?\\1?" + ); + + /** + * Class constructor + * + * @return void + */ + public function __construct() + { + // Is CSRF protection enabled? + if (config_item('csrf_protection')) + { + // CSRF config + foreach (array('csrf_expire', 'csrf_token_name', 'csrf_cookie_name') as $key) + { + if (NULL !== ($val = config_item($key))) + { + $this->{'_'.$key} = $val; + } + } + + // Append application specific cookie prefix + if ($cookie_prefix = config_item('cookie_prefix')) + { + $this->_csrf_cookie_name = $cookie_prefix.$this->_csrf_cookie_name; + } + + // Set the CSRF hash + $this->_csrf_set_hash(); + } + + $this->charset = strtoupper(config_item('charset')); + + log_message('info', 'Security Class Initialized'); + } + + // -------------------------------------------------------------------- + + /** + * CSRF Verify + * + * @return CI_Security + */ + public function csrf_verify() + { + // If it's not a POST request we will set the CSRF cookie + if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') + { + return $this->csrf_set_cookie(); + } + + // Check if URI has been whitelisted from CSRF checks + if ($exclude_uris = config_item('csrf_exclude_uris')) + { + $uri = load_class('URI', 'core'); + foreach ($exclude_uris as $excluded) + { + if (preg_match('#^'.$excluded.'$#i'.(UTF8_ENABLED ? 'u' : ''), $uri->uri_string())) + { + return $this; + } + } + } + + // Check CSRF token validity, but don't error on mismatch just yet - we'll want to regenerate + $valid = isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name]) + && hash_equals($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name]); + + // We kill this since we're done and we don't want to pollute the _POST array + unset($_POST[$this->_csrf_token_name]); + + // Regenerate on every submission? + if (config_item('csrf_regenerate')) + { + // Nothing should last forever + unset($_COOKIE[$this->_csrf_cookie_name]); + $this->_csrf_hash = NULL; + } + + $this->_csrf_set_hash(); + $this->csrf_set_cookie(); + + if ($valid !== TRUE) + { + $this->csrf_show_error(); + } + + log_message('info', 'CSRF token verified'); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * CSRF Set Cookie + * + * @codeCoverageIgnore + * @return CI_Security + */ + public function csrf_set_cookie() + { + $expire = time() + $this->_csrf_expire; + $secure_cookie = (bool) config_item('cookie_secure'); + + if ($secure_cookie && ! is_https()) + { + return FALSE; + } + + setcookie( + $this->_csrf_cookie_name, + $this->_csrf_hash, + $expire, + config_item('cookie_path'), + config_item('cookie_domain'), + $secure_cookie, + config_item('cookie_httponly') + ); + log_message('info', 'CSRF cookie sent'); + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Show CSRF Error + * + * @return void + */ + public function csrf_show_error() + { + show_error('The action you have requested is not allowed.', 403); + } + + // -------------------------------------------------------------------- + + /** + * Get CSRF Hash + * + * @see CI_Security::$_csrf_hash + * @return string CSRF hash + */ + public function get_csrf_hash() + { + return $this->_csrf_hash; + } + + // -------------------------------------------------------------------- + + /** + * Get CSRF Token Name + * + * @see CI_Security::$_csrf_token_name + * @return string CSRF token name + */ + public function get_csrf_token_name() + { + return $this->_csrf_token_name; + } + + // -------------------------------------------------------------------- + + /** + * XSS Clean + * + * Sanitizes data so that Cross Site Scripting Hacks can be + * prevented. This method does a fair amount of work but + * it is extremely thorough, designed to prevent even the + * most obscure XSS attempts. Nothing is ever 100% foolproof, + * of course, but I haven't been able to get anything passed + * the filter. + * + * Note: Should only be used to deal with data upon submission. + * It's not something that should be used for general + * runtime processing. + * + * @link http://channel.bitflux.ch/wiki/XSS_Prevention + * Based in part on some code and ideas from Bitflux. + * + * @link http://ha.ckers.org/xss.html + * To help develop this script I used this great list of + * vulnerabilities along with a few other hacks I've + * harvested from examining vulnerabilities in other programs. + * + * @param string|string[] $str Input data + * @param bool $is_image Whether the input is an image + * @return string + */ + public function xss_clean($str, $is_image = FALSE) + { + // Is the string an array? + if (is_array($str)) + { + foreach ($str as $key => &$value) + { + $str[$key] = $this->xss_clean($value); + } + + return $str; + } + + // Remove Invisible Characters + $str = remove_invisible_characters($str); + + /* + * URL Decode + * + * Just in case stuff like this is submitted: + * + * Google + * + * Note: Use rawurldecode() so it does not remove plus signs + */ + if (stripos($str, '%') !== false) + { + do + { + $oldstr = $str; + $str = rawurldecode($str); + $str = preg_replace_callback('#%(?:\s*[0-9a-f]){2,}#i', array($this, '_urldecodespaces'), $str); + } + while ($oldstr !== $str); + unset($oldstr); + } + + /* + * Convert character entities to ASCII + * + * This permits our tests below to work reliably. + * We only convert entities that are within tags since + * these are the ones that will pose security problems. + */ + $str = preg_replace_callback("/[^a-z0-9>]+[a-z0-9]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str); + $str = preg_replace_callback('/<\w+.*/si', array($this, '_decode_entity'), $str); + + // Remove Invisible Characters Again! + $str = remove_invisible_characters($str); + + /* + * Convert all tabs to spaces + * + * This prevents strings like this: ja vascript + * NOTE: we deal with spaces between characters later. + * NOTE: preg_replace was found to be amazingly slow here on + * large blocks of data, so we use str_replace. + */ + $str = str_replace("\t", ' ', $str); + + // Capture converted string for later comparison + $converted_string = $str; + + // Remove Strings that are never allowed + $str = $this->_do_never_allowed($str); + + /* + * Makes PHP tags safe + * + * Note: XML tags are inadvertently replaced too: + * + * '), array('<?', '?>'), $str); + } + + /* + * Compact any exploded words + * + * This corrects words like: j a v a s c r i p t + * These words are compacted back to their correct state. + */ + $words = array( + 'javascript', 'expression', 'vbscript', 'jscript', 'wscript', + 'vbs', 'script', 'base64', 'applet', 'alert', 'document', + 'write', 'cookie', 'window', 'confirm', 'prompt', 'eval' + ); + + foreach ($words as $word) + { + $word = implode('\s*', str_split($word)).'\s*'; + + // We only want to do this when it is followed by a non-word character + // That way valid stuff like "dealer to" does not become "dealerto" + $str = preg_replace_callback('#('.substr($word, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str); + } + + /* + * Remove disallowed Javascript in links or img tags + * We used to do some version comparisons and use of stripos(), + * but it is dog slow compared to these simplified non-capturing + * preg_match(), especially if the pattern exists in the string + * + * Note: It was reported that not only space characters, but all in + * the following pattern can be parsed as separators between a tag name + * and its attributes: [\d\s"\'`;,\/\=\(\x00\x0B\x09\x0C] + * ... however, remove_invisible_characters() above already strips the + * hex-encoded ones, so we'll skip them below. + */ + do + { + $original = $str; + + if (preg_match('/]+([^>]*?)(?:>|$)#si', array($this, '_js_link_removal'), $str); + } + + if (preg_match('/]*?)(?:\s?/?>|$)#si', array($this, '_js_img_removal'), $str); + } + + if (preg_match('/script|xss/i', $str)) + { + $str = preg_replace('##si', '[removed]', $str); + } + } + while ($original !== $str); + unset($original); + + /* + * Sanitize naughty HTML elements + * + * If a tag containing any of the words in the list + * below is found, the tag gets converted to entities. + * + * So this: + * Becomes: <blink> + */ + $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 + * code, it simply converts the parenthesis to entities + * rendering the code un-executable. + * + * For example: eval('some code') + * Becomes: eval('some code') + */ + $str = preg_replace( + '#(alert|prompt|confirm|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', + '\\1\\2(\\3)', + $str + ); + + // Final clean up + // This adds a bit of extra precaution in case + // something got through the above filters + $str = $this->_do_never_allowed($str); + + /* + * Images are Handled in a Special Way + * - Essentially, we want to know that after all of the character + * conversion is done whether any unwanted, likely XSS, code was found. + * If not, we return TRUE, as the image is clean. + * However, if the string post-conversion does not matched the + * string post-removal of XSS, then it fails, as there was unwanted XSS + * code found and removed/changed during processing. + */ + if ($is_image === TRUE) + { + return ($str === $converted_string); + } + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * XSS Hash + * + * Generates the XSS hash if needed and returns it. + * + * @see CI_Security::$_xss_hash + * @return string XSS hash + */ + public function xss_hash() + { + if ($this->_xss_hash === NULL) + { + $rand = $this->get_random_bytes(16); + $this->_xss_hash = ($rand === FALSE) + ? md5(uniqid(mt_rand(), TRUE)) + : bin2hex($rand); + } + + return $this->_xss_hash; + } + + // -------------------------------------------------------------------- + + /** + * Get random bytes + * + * @param int $length Output length + * @return string + */ + public function get_random_bytes($length) + { + if (empty($length) OR ! ctype_digit((string) $length)) + { + return FALSE; + } + + if (function_exists('random_bytes')) + { + try + { + // The cast is required to avoid TypeError + return random_bytes((int) $length); + } + catch (Exception $e) + { + // If random_bytes() can't do the job, we can't either ... + // There's no point in using fallbacks. + log_message('error', $e->getMessage()); + return FALSE; + } + } + + // Unfortunately, none of the following PRNGs is guaranteed to exist ... + if (defined('MCRYPT_DEV_URANDOM') && ($output = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)) !== FALSE) + { + return $output; + } + + + if (is_readable('/dev/urandom') && ($fp = fopen('/dev/urandom', 'rb')) !== FALSE) + { + // Try not to waste entropy ... + is_php('5.4') && stream_set_chunk_size($fp, $length); + $output = fread($fp, $length); + fclose($fp); + if ($output !== FALSE) + { + return $output; + } + } + + if (function_exists('openssl_random_pseudo_bytes')) + { + return openssl_random_pseudo_bytes($length); + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * HTML Entities Decode + * + * A replacement for html_entity_decode() + * + * The reason we are not using html_entity_decode() by itself is because + * while it is not technically correct to leave out the semicolon + * at the end of an entity most browsers will still interpret the entity + * correctly. html_entity_decode() does not convert entities without + * semicolons, so we are left with our own little solution here. Bummer. + * + * @link http://php.net/html-entity-decode + * + * @param string $str Input + * @param string $charset Character set + * @return string + */ + public function entity_decode($str, $charset = NULL) + { + if (strpos($str, '&') === FALSE) + { + return $str; + } + + static $_entities; + + isset($charset) OR $charset = $this->charset; + $flag = is_php('5.4') + ? ENT_COMPAT | ENT_HTML5 + : ENT_COMPAT; + + if ( ! isset($_entities)) + { + $_entities = array_map('strtolower', get_html_translation_table(HTML_ENTITIES, $flag, $charset)); + + // If we're not on PHP 5.4+, add the possibly dangerous HTML 5 + // entities to the array manually + if ($flag === ENT_COMPAT) + { + $_entities[':'] = ':'; + $_entities['('] = '('; + $_entities[')'] = ')'; + $_entities["\n"] = ' '; + $_entities["\t"] = ' '; + } + } + + do + { + $str_compare = $str; + + // Decode standard entities, avoiding false positives + if (preg_match_all('/&[a-z]{2,}(?![a-z;])/i', $str, $matches)) + { + $replace = array(); + $matches = array_unique(array_map('strtolower', $matches[0])); + foreach ($matches as &$match) + { + if (($char = array_search($match.';', $_entities, TRUE)) !== FALSE) + { + $replace[$match] = $char; + } + } + + $str = str_replace(array_keys($replace), array_values($replace), $str); + } + + // Decode numeric & UTF16 two byte entities + $str = html_entity_decode( + preg_replace('/(&#(?:x0*[0-9a-f]{2,5}(?![0-9a-f;])|(?:0*\d{2,4}(?![0-9;]))))/iS', '$1;', $str), + $flag, + $charset + ); + + if ($flag === ENT_COMPAT) + { + $str = str_replace(array_values($_entities), array_keys($_entities), $str); + } + } + while ($str_compare !== $str); + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Sanitize Filename + * + * @param string $str Input file name + * @param bool $relative_path Whether to preserve paths + * @return string + */ + public function sanitize_filename($str, $relative_path = FALSE) + { + $bad = $this->filename_bad_chars; + + if ( ! $relative_path) + { + $bad[] = './'; + $bad[] = '/'; + } + + $str = remove_invisible_characters($str, FALSE); + + do + { + $old = $str; + $str = str_replace($bad, '', $str); + } + while ($old !== $str); + + return stripslashes($str); + } + + // ---------------------------------------------------------------- + + /** + * Strip Image Tags + * + * @param string $str + * @return string + */ + public function strip_image_tags($str) + { + return preg_replace( + array( + '##i', + '#`]+)).*?\>#i' + ), + '\\2', + $str + ); + } + + // ---------------------------------------------------------------- + + /** + * URL-decode taking spaces into account + * + * @see https://github.com/bcit-ci/CodeIgniter/issues/4877 + * @param array $matches + * @return string + */ + protected function _urldecodespaces($matches) + { + $input = $matches[0]; + $nospaces = preg_replace('#\s+#', '', $input); + return ($nospaces === $input) + ? $input + : rawurldecode($nospaces); + } + + // ---------------------------------------------------------------- + + /** + * Compact Exploded Words + * + * Callback method for xss_clean() to remove whitespace from + * things like 'j a v a s c r i p t'. + * + * @used-by CI_Security::xss_clean() + * @param array $matches + * @return string + */ + protected function _compact_exploded_words($matches) + { + return preg_replace('/\s+/s', '', $matches[1]).$matches[2]; + } + + // -------------------------------------------------------------------- + + /** + * Sanitize Naughty HTML + * + * Callback method for xss_clean() to remove naughty HTML elements. + * + * @used-by CI_Security::xss_clean() + * @param array $matches + * @return string + */ + protected function _sanitize_naughty_html($matches) + { + static $naughty_tags = array( + 'alert', 'area', 'prompt', 'confirm', 'applet', 'audio', 'basefont', 'base', 'behavior', 'bgsound', + 'blink', 'body', 'embed', 'expression', 'form', 'frameset', 'frame', 'head', 'html', 'ilayer', + 'iframe', 'input', 'button', 'select', 'isindex', 'layer', 'link', 'meta', 'keygen', 'object', + 'plaintext', 'style', 'script', 'textarea', 'title', 'math', 'video', 'svg', 'xml', 'xss' + ); + + static $evil_attributes = array( + 'on\w+', 'style', 'xmlns', 'formaction', 'form', 'xlink:href', 'FSCommand', 'seekSegmentTime' + ); + + // First, escape unclosed tags + if (empty($matches['closeTag'])) + { + return '<'.$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 precede an attribute. + // Browsers often parse these incorrectly and that has been a + // of numerous XSS issues we've had. + $matches['attributes'] = preg_replace('#^[^a-z]+#i', '', $matches['attributes']); + + if ( ! preg_match($attributes_pattern, $matches['attributes'], $attribute, PREG_OFFSET_CAPTURE)) + { + // No (valid) attribute found? Discard everything else inside the tag + break; + } + + if ( + // Is it indeed an "evil" attribute? + preg_match($is_evil_pattern, $attribute['name'][0]) + // Or does it have an equals sign, but no value and not quoted? Strip that too! + OR (trim($attribute['value'][0]) === '') + ) + { + $attributes[] = 'xss=removed'; + } + else + { + $attributes[] = $attribute[0][0]; + } + + $matches['attributes'] = substr($matches['attributes'], $attribute[0][1] + strlen($attribute[0][0])); + } + while ($matches['attributes'] !== ''); + + $attributes = empty($attributes) + ? '' + : ' '.implode(' ', $attributes); + return '<'.$matches['slash'].$matches['tagName'].$attributes.'>'; + } + + return $matches[0]; + } + + // -------------------------------------------------------------------- + + /** + * JS Link Removal + * + * Callback method for xss_clean() to sanitize links. + * + * This limits the PCRE backtracks, making it more performance friendly + * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in + * PHP 5.2+ on link-heavy strings. + * + * @used-by CI_Security::xss_clean() + * @param array $match + * @return string + */ + protected function _js_link_removal($match) + { + return str_replace( + $match[1], + preg_replace( + '#href=.*?(?:(?:alert|prompt|confirm)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|_filter_attributes($match[1]) + ), + $match[0] + ); + } + + // -------------------------------------------------------------------- + + /** + * JS Image Removal + * + * Callback method for xss_clean() to sanitize image tags. + * + * This limits the PCRE backtracks, making it more performance friendly + * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in + * PHP 5.2+ on image tag heavy strings. + * + * @used-by CI_Security::xss_clean() + * @param array $match + * @return string + */ + protected function _js_img_removal($match) + { + return str_replace( + $match[1], + preg_replace( + '#src=.*?(?:(?:alert|prompt|confirm|eval)(?:\(|&\#40;)|javascript:|livescript:|mocha:|charset=|window\.|document\.|\.cookie|_filter_attributes($match[1]) + ), + $match[0] + ); + } + + // -------------------------------------------------------------------- + + /** + * Attribute Conversion + * + * @used-by CI_Security::xss_clean() + * @param array $match + * @return string + */ + protected function _convert_attribute($match) + { + return str_replace(array('>', '<', '\\'), array('>', '<', '\\\\'), $match[0]); + } + + // -------------------------------------------------------------------- + + /** + * Filter Attributes + * + * Filters tag attributes for consistency and safety. + * + * @used-by CI_Security::_js_img_removal() + * @used-by CI_Security::_js_link_removal() + * @param string $str + * @return string + */ + protected function _filter_attributes($str) + { + $out = ''; + if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches)) + { + foreach ($matches[0] as $match) + { + $out .= preg_replace('#/\*.*?\*/#s', '', $match); + } + } + + return $out; + } + + // -------------------------------------------------------------------- + + /** + * HTML Entity Decode Callback + * + * @used-by CI_Security::xss_clean() + * @param array $match + * @return string + */ + protected function _decode_entity($match) + { + // Protect GET variables in URLs + // 901119URL5918AMP18930PROTECT8198 + $match = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-/]+)|i', $this->xss_hash().'\\1=\\2', $match[0]); + + // Decode, then un-protect URL GET vars + return str_replace( + $this->xss_hash(), + '&', + $this->entity_decode($match, $this->charset) + ); + } + + // -------------------------------------------------------------------- + + /** + * Do Never Allowed + * + * @used-by CI_Security::xss_clean() + * @param string + * @return string + */ + protected function _do_never_allowed($str) + { + $str = str_replace(array_keys($this->_never_allowed_str), $this->_never_allowed_str, $str); + + foreach ($this->_never_allowed_regex as $regex) + { + $str = preg_replace('#'.$regex.'#is', '[removed]', $str); + } + + return $str; + } + + // -------------------------------------------------------------------- + + /** + * Set CSRF Hash and Cookie + * + * @return string + */ + protected function _csrf_set_hash() + { + if ($this->_csrf_hash === NULL) + { + // If the cookie exists we will use its value. + // We don't necessarily want to regenerate it with + // each page load since a page could contain embedded + // sub-pages causing this feature to fail + if (isset($_COOKIE[$this->_csrf_cookie_name]) && is_string($_COOKIE[$this->_csrf_cookie_name]) + && preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->_csrf_cookie_name]) === 1) + { + return $this->_csrf_hash = $_COOKIE[$this->_csrf_cookie_name]; + } + + $rand = $this->get_random_bytes(16); + $this->_csrf_hash = ($rand === FALSE) + ? md5(uniqid(mt_rand(), TRUE)) + : bin2hex($rand); + } + + return $this->_csrf_hash; + } + +} diff --git a/system/core/URI.php b/system/core/URI.php index c43cde00..3ccdfa7b 100644 --- a/system/core/URI.php +++ b/system/core/URI.php @@ -1,19 +1,41 @@ -config =& load_class('Config', 'core'); - log_message('debug', "URI Class Initialized"); - } + // If query strings are enabled, we don't need to parse any segments. + // However, they don't make sense under CLI. + if (is_cli() OR $this->config->item('enable_query_strings') !== TRUE) + { + $this->_permitted_uri_chars = $this->config->item('permitted_uri_chars'); + + // If it's a CLI request, ignore the configuration + if (is_cli()) + { + $uri = $this->_parse_argv(); + } + else + { + $protocol = $this->config->item('uri_protocol'); + empty($protocol) && $protocol = 'REQUEST_URI'; + + switch ($protocol) + { + case 'AUTO': // For BC purposes only + case 'REQUEST_URI': + $uri = $this->_parse_request_uri(); + break; + case 'QUERY_STRING': + $uri = $this->_parse_query_string(); + break; + case 'PATH_INFO': + default: + $uri = isset($_SERVER[$protocol]) + ? $_SERVER[$protocol] + : $this->_parse_request_uri(); + break; + } + } + + $this->_set_uri_string($uri); + } + + log_message('info', 'URI Class Initialized'); + } // -------------------------------------------------------------------- /** - * Get the URI String + * Set URI String * - * @access private - * @return string + * @param string $str + * @return void */ - function _fetch_uri_string() + protected function _set_uri_string($str) { - if (strtoupper($this->config->item('uri_protocol')) == 'AUTO') + // Filter out control characters and trim slashes + $this->uri_string = trim(remove_invisible_characters($str, FALSE), '/'); + + if ($this->uri_string !== '') { - // Is the request coming from the command line? - if (defined('STDIN')) + // Remove the URL suffix, if present + if (($suffix = (string) $this->config->item('url_suffix')) !== '') { - $this->uri_string = $this->_parse_cli_args(); - return; + $slen = strlen($suffix); + + if (substr($this->uri_string, -$slen) === $suffix) + { + $this->uri_string = substr($this->uri_string, 0, -$slen); + } } - // Let's try the REQUEST_URI first, this will work in most situations - if ($uri = $this->_detect_uri()) + $this->segments[0] = NULL; + // Populate the segments array + foreach (explode('/', trim($this->uri_string, '/')) as $val) { - $this->uri_string = $uri; - return; + $val = trim($val); + // Filter segments for security + $this->filter_uri($val); + + if ($val !== '') + { + $this->segments[] = $val; + } } - // 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->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->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->uri_string = key($_GET); - return; - } - - // We've exhausted all our options... - $this->uri_string = ''; - } - else - { - $uri = strtoupper($this->config->item('uri_protocol')); - - if ($uri == 'REQUEST_URI') - { - $this->uri_string = $this->_detect_uri(); - return; - } - elseif ($uri == 'CLI') - { - $this->uri_string = $this->_parse_cli_args(); - return; - } - - $this->uri_string = (isset($_SERVER[$uri])) ? $_SERVER[$uri] : @getenv($uri); - } - - // If the URI contains only a slash we'll kill it - if ($this->uri_string == '/') - { - $this->uri_string = ''; + 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'])) + 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 * + * 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'); } @@ -364,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) @@ -423,7 +459,7 @@ class CI_URI { } else { - $retval[$seg] = FALSE; + $retval[$seg] = NULL; $lastval = $seg; } @@ -436,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; @@ -471,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'); } @@ -486,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'); } @@ -501,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 = ''; } @@ -531,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; } @@ -544,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; } @@ -557,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); } @@ -570,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); } @@ -581,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/system/core/Utf8.php b/system/core/Utf8.php index 5d5a7ef7..dfbbfff2 100644 --- a/system/core/Utf8.php +++ b/system/core/Utf8.php @@ -1,19 +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'); } // -------------------------------------------------------------------- @@ -76,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; @@ -99,15 +111,14 @@ 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 preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $str); + return remove_invisible_characters($str, FALSE); } // -------------------------------------------------------------------- @@ -115,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; } // -------------------------------------------------------------------- @@ -145,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/system/core/compat/hash.php b/system/core/compat/hash.php new file mode 100644 index 00000000..c65203aa --- /dev/null +++ b/system/core/compat/hash.php @@ -0,0 +1,254 @@ + 32, + 'haval128,3' => 128, + 'haval160,3' => 128, + 'haval192,3' => 128, + 'haval224,3' => 128, + 'haval256,3' => 128, + 'haval128,4' => 128, + 'haval160,4' => 128, + 'haval192,4' => 128, + 'haval224,4' => 128, + 'haval256,4' => 128, + 'haval128,5' => 128, + 'haval160,5' => 128, + 'haval192,5' => 128, + 'haval224,5' => 128, + 'haval256,5' => 128, + 'md2' => 16, + 'md4' => 64, + 'md5' => 64, + 'ripemd128' => 64, + 'ripemd160' => 64, + 'ripemd256' => 64, + 'ripemd320' => 64, + 'salsa10' => 64, + 'salsa20' => 64, + 'sha1' => 64, + 'sha224' => 64, + 'sha256' => 64, + 'sha384' => 128, + 'sha512' => 128, + 'snefru' => 32, + 'snefru256' => 32, + 'tiger128,3' => 64, + 'tiger160,3' => 64, + 'tiger192,3' => 64, + 'tiger128,4' => 64, + 'tiger160,4' => 64, + 'tiger192,4' => 64, + 'whirlpool' => 64 + ); + + if (isset($block_sizes[$algo], $password[$block_sizes[$algo]])) + { + $password = hash($algo, $password, TRUE); + } + + $hash = ''; + // Note: Blocks are NOT 0-indexed + for ($bc = (int) ceil($length / $hash_length), $bi = 1; $bi <= $bc; $bi++) + { + $key = $derived_key = hash_hmac($algo, $salt.pack('N', $bi), $password, TRUE); + for ($i = 1; $i < $iterations; $i++) + { + $derived_key ^= $key = hash_hmac($algo, $key, $password, TRUE); + } + + $hash .= $derived_key; + } + + // This is not RFC-compatible, but we're aiming for natural PHP compatibility + if ( ! $raw_output) + { + $hash = bin2hex($hash); + } + + return defined('MB_OVERLOAD_STRING') + ? mb_substr($hash, 0, $length, '8bit') + : substr($hash, 0, $length); + } +} diff --git a/system/core/compat/index.html b/system/core/compat/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/system/core/compat/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

    Directory access is forbidden.

    + + + diff --git a/system/core/compat/mbstring.php b/system/core/compat/mbstring.php new file mode 100644 index 00000000..1b2f2c63 --- /dev/null +++ b/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_overload; + isset($func_overload) OR $func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); + + if ($algo !== 1) + { + trigger_error('password_hash(): Unknown hashing algorithm: '.(int) $algo, E_USER_WARNING); + return NULL; + } + + if (isset($options['cost']) && ($options['cost'] < 4 OR $options['cost'] > 31)) + { + trigger_error('password_hash(): Invalid bcrypt cost parameter specified: '.(int) $options['cost'], E_USER_WARNING); + return NULL; + } + + if (isset($options['salt']) && ($saltlen = ($func_overload ? mb_strlen($options['salt'], '8bit') : strlen($options['salt']))) < 22) + { + trigger_error('password_hash(): Provided salt is too short: '.$saltlen.' expecting 22', E_USER_WARNING); + return NULL; + } + elseif ( ! isset($options['salt'])) + { + if (function_exists('random_bytes')) + { + try + { + $options['salt'] = random_bytes(16); + } + catch (Exception $e) + { + log_message('error', 'compat/password: Error while trying to use random_bytes(): '.$e->getMessage()); + return FALSE; + } + } + elseif (defined('MCRYPT_DEV_URANDOM')) + { + $options['salt'] = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM); + } + elseif (DIRECTORY_SEPARATOR === '/' && (is_readable($dev = '/dev/arandom') OR is_readable($dev = '/dev/urandom'))) + { + if (($fp = fopen($dev, 'rb')) === FALSE) + { + log_message('error', 'compat/password: Unable to open '.$dev.' for reading.'); + return FALSE; + } + + // Try not to waste entropy ... + is_php('5.4') && stream_set_chunk_size($fp, 16); + + $options['salt'] = ''; + for ($read = 0; $read < 16; $read = ($func_overload) ? mb_strlen($options['salt'], '8bit') : strlen($options['salt'])) + { + if (($read = fread($fp, 16 - $read)) === FALSE) + { + log_message('error', 'compat/password: Error while reading from '.$dev.'.'); + return FALSE; + } + $options['salt'] .= $read; + } + + fclose($fp); + } + elseif (function_exists('openssl_random_pseudo_bytes')) + { + $is_secure = NULL; + $options['salt'] = openssl_random_pseudo_bytes(16, $is_secure); + if ($is_secure !== TRUE) + { + log_message('error', 'compat/password: openssl_random_pseudo_bytes() set the $cryto_strong flag to FALSE'); + return FALSE; + } + } + else + { + log_message('error', 'compat/password: No CSPRNG available.'); + return FALSE; + } + + $options['salt'] = str_replace('+', '.', rtrim(base64_encode($options['salt']), '=')); + } + elseif ( ! preg_match('#^[a-zA-Z0-9./]+$#D', $options['salt'])) + { + $options['salt'] = str_replace('+', '.', rtrim(base64_encode($options['salt']), '=')); + } + + isset($options['cost']) OR $options['cost'] = 10; + + return (strlen($password = crypt($password, sprintf('$2y$%02d$%s', $options['cost'], $options['salt']))) === 60) + ? $password + : FALSE; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('password_needs_rehash')) +{ + /** + * password_needs_rehash() + * + * @link http://php.net/password_needs_rehash + * @param string $hash + * @param int $algo + * @param array $options + * @return bool + */ + function password_needs_rehash($hash, $algo, array $options = array()) + { + $info = password_get_info($hash); + + if ($algo !== $info['algo']) + { + return TRUE; + } + elseif ($algo === 1) + { + $options['cost'] = isset($options['cost']) ? (int) $options['cost'] : 10; + return ($info['options']['cost'] !== $options['cost']); + } + + // Odd at first glance, but according to a comment in PHP's own unit tests, + // because it is an unknown algorithm - it's valid and therefore doesn't + // need rehashing. + return FALSE; + } +} + +// ------------------------------------------------------------------------ + +if ( ! function_exists('password_verify')) +{ + /** + * password_verify() + * + * @link http://php.net/password_verify + * @param string $password + * @param string $hash + * @return bool + */ + function password_verify($password, $hash) + { + if (strlen($hash) !== 60 OR strlen($password = crypt($password, $hash)) !== 60) + { + return FALSE; + } + + $compare = 0; + for ($i = 0; $i < 60; $i++) + { + $compare |= (ord($password[$i]) ^ ord($hash[$i])); + } + + return ($compare === 0); + } +} diff --git a/system/core/compat/standard.php b/system/core/compat/standard.php new file mode 100644 index 00000000..7db2efb5 --- /dev/null +++ b/system/core/compat/standard.php @@ -0,0 +1,182 @@ + 403 Forbidden @@ -7,4 +8,4 @@

    Directory access is forbidden.

    - \ No newline at end of file + diff --git a/system/database/DB.php b/system/database/DB.php index 93ee3922..c19eef72 100644 --- a/system/database/DB.php +++ b/system/database/DB.php @@ -1,103 +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; @@ -105,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'.EXT); + 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'.EXT); - - 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'.EXT); + // 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/system/database/DB_active_rec.php b/system/database/DB_active_rec.php deleted file mode 100644 index ee72dbbf..00000000 --- a/system/database/DB_active_rec.php +++ /dev/null @@ -1,2073 +0,0 @@ -_protect_identifiers = $escape; - } - - if (is_string($select)) - { - $select = explode(',', $select); - } - - foreach ($select as $val) - { - $val = trim($val); - - if ($val != '') - { - $this->ar_select[] = $val; - - if ($this->ar_caching === TRUE) - { - $this->ar_cache_select[] = $val; - $this->ar_cache_exists[] = 'select'; - } - } - } - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Select Max - * - * Generates a SELECT MAX(field) portion of a query - * - * @access public - * @param string the field - * @param string an alias - * @return object - */ - 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 - * - * @access public - * @param string the field - * @param string an alias - * @return object - */ - 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 - * - * @access public - * @param string the field - * @param string an alias - * @return object - */ - 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 - * - * @access public - * @param string the field - * @param string an alias - * @return object - */ - 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() - * - * @access public - * @param string the field - * @param string an alias - * @return object - */ - 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 - * - * @access private - * @param string - * @return string - */ - 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 - * - * @access public - * @param bool - * @return object - */ - function distinct($val = TRUE) - { - $this->ar_distinct = (is_bool($val)) ? $val : TRUE; - return $this; - } - - // -------------------------------------------------------------------- - - /** - * From - * - * Generates the FROM portion of the query - * - * @access public - * @param mixed can be a string or array - * @return object - */ - 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 - * - * @access public - * @param string - * @param string the join condition - * @param string the type of join - * @return object - */ - 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 - * - * @access public - * @param mixed - * @param mixed - * @return object - */ - 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 - * - * @access public - * @param mixed - * @param mixed - * @return object - */ - function or_where($key, $value = NULL, $escape = TRUE) - { - return $this->_where($key, $value, 'OR ', $escape); - } - - // -------------------------------------------------------------------- - - /** - * Where - * - * Called by where() or orwhere() - * - * @access private - * @param mixed - * @param mixed - * @param string - * @return object - */ - 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 - * - * @access public - * @param string The field to search - * @param array The values searched on - * @return object - */ - 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 - * - * @access public - * @param string The field to search - * @param array The values searched on - * @return object - */ - 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 - * - * @access public - * @param string The field to search - * @param array The values searched on - * @return object - */ - 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 - * - * @access public - * @param string The field to search - * @param array The values searched on - * @return object - */ - 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 - * - * @access public - * @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 - */ - 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 - * - * @access public - * @param mixed - * @param mixed - * @return object - */ - 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 - * - * @access public - * @param mixed - * @param mixed - * @return object - */ - 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 - * - * @access public - * @param mixed - * @param mixed - * @return object - */ - 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 - * - * @access public - * @param mixed - * @param mixed - * @return object - */ - function or_not_like($field, $match = '', $side = 'both') - { - return $this->_like($field, $match, 'OR ', $side, 'NOT'); - } - - // -------------------------------------------------------------------- - - /** - * Like - * - * Called by like() or orlike() - * - * @access private - * @param mixed - * @param mixed - * @param string - * @return object - */ - 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 == '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 - * - * @access public - * @param string - * @return object - */ - 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 - * - * @access public - * @param string - * @param string - * @return object - */ - function having($key, $value = '', $escape = TRUE) - { - return $this->_having($key, $value, 'AND ', $escape); - } - - // -------------------------------------------------------------------- - - /** - * Sets the OR HAVING value - * - * Separates multiple calls with OR - * - * @access public - * @param string - * @param string - * @return object - */ - function or_having($key, $value = '', $escape = TRUE) - { - return $this->_having($key, $value, 'OR ', $escape); - } - - // -------------------------------------------------------------------- - - /** - * Sets the HAVING values - * - * Called by having() or or_having() - * - * @access private - * @param string - * @param string - * @return object - */ - 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_str($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 - * - * @access public - * @param string - * @param string direction: asc or desc - * @return object - */ - 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 - * - * @access public - * @param integer the limit value - * @param integer the offset value - * @return object - */ - function limit($value, $offset = '') - { - $this->ar_limit = $value; - - if ($offset != '') - { - $this->ar_offset = $offset; - } - - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Sets the OFFSET value - * - * @access public - * @param integer the offset value - * @return object - */ - function offset($offset) - { - $this->ar_offset = $offset; - return $this; - } - - // -------------------------------------------------------------------- - - /** - * The "set" function. Allows key/value pairs to be set for inserting or updating - * - * @access public - * @param mixed - * @param string - * @param boolean - * @return object - */ - 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 - * - * @access public - * @param string the table - * @param string the limit clause - * @param string the offset clause - * @return object - */ - 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. - * - * @access public - * @param string - * @return string - */ - 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 $row->numrows; - } - - // -------------------------------------------------------------------- - - /** - * Get_Where - * - * Allows the where clause, limit and offset to be added directly - * - * @access public - * @param string the where clause - * @param string the limit clause - * @param string the offset clause - * @return object - */ - 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 - * - * @access public - * @param string the table to retrieve the results from - * @param array an associative array of insert values - * @return object - */ - 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 - * - * @access public - * @param mixed - * @param string - * @param boolean - * @return object - */ - - 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 - * - * @access public - * @param string the table to retrieve the results from - * @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); - } - - 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 - * - * @access public - * @param string the table to retrieve the results from - * @param array an associative array of update values - * @param mixed the where clause - * @return object - */ - 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 - * - * @access public - * @param string the table to retrieve the results from - * @param array an associative array of update values - * @param string the where key - * @return object - */ - 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_myst_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 - * - * @access public - * @param array - * @param string - * @param boolean - * @return object - */ - - function set_update_batch($key, $index = '', $escape = TRUE) - { - $key = $this->_object_to_array_batch($key); - - if ( ! is_array($key)) - { - // @todo error - } - - foreach ($key as $k => $v) - { - $index_set = FALSE; - $clean = array(); - - foreach ($v as $k2 => $v2) - { - if ($k2 == $index) - { - $index_set = TRUE; - } - else - { - $not[] = $k.'-'.$v; - } - - if ($escape === FALSE) - { - $clean[$this->_protect_identifiers($k2)] = $v2; - } - else - { - $clean[$this->_protect_identifiers($k2)] = $this->escape($v2); - } - } - - if ($index_set == FALSE) - { - return $this->display_error('db_batch_missing_index'); - } - - $this->ar_set[] = $clean; - } - - return $this; - } - - // -------------------------------------------------------------------- - - /** - * Empty Table - * - * Compiles a delete string and runs "DELETE FROM table" - * - * @access public - * @param string the table to empty - * @return object - */ - 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" - * - * @access public - * @param string the table to truncate - * @return object - */ - 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 - * - * @access public - * @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 - */ - 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 - * - * @access public - * @param string the table - * @return string - */ - function dbprefix($table = '') - { - if ($table == '') - { - $this->display_error('db_table_name_required'); - } - - return $this->dbprefix.$table; - } - - // -------------------------------------------------------------------- - - /** - * Track Aliases - * - * Used to track SQL statements written with aliased tables. - * - * @access private - * @param string The table to inspect - * @return string - */ - 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('/ AS /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. - * - * @access private - * @return string - */ - 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) - { - $this->ar_select[$key] = $this->_protect_identifiers($val); - } - - $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 .= "\n"; - - $sql .= "WHERE "; - } - - $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 - * - * @access public - * @param object - * @return array - */ - 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 - * - * @access public - * @param object - * @return array - */ - 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 - * - * @access public - * @return void - */ - function start_cache() - { - $this->ar_caching = TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Stop Cache - * - * Stops AR caching - * - * @access public - * @return void - */ - function stop_cache() - { - $this->ar_caching = FALSE; - } - - // -------------------------------------------------------------------- - - /** - * Flush Cache - * - * Empties the AR cache - * - * @access public - * @return void - */ - 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() - ) - ); - } - - // -------------------------------------------------------------------- - - /** - * Merge Cache - * - * When called, this function merges any cached AR arrays with - * locally called ones. - * - * @access private - * @return void - */ - 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); - } - } - - // -------------------------------------------------------------------- - - /** - * Resets the active record values. Called by the get() function - * - * @access private - * @param array An array of fields to reset - * @return void - */ - 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 - * - * @access private - * @return void - */ - 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_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 - * - * @access private - * @return void - */ - function _reset_write() - { - $ar_reset_items = array( - 'ar_set' => array(), - 'ar_from' => array(), - 'ar_where' => array(), - 'ar_like' => array(), - 'ar_orderby' => array(), - 'ar_keys' => array(), - 'ar_limit' => FALSE, - 'ar_order' => FALSE - ); - - $this->_reset_run($ar_reset_items); - } - -} - -/* End of file DB_active_rec.php */ -/* Location: ./system/database/DB_active_rec.php */ \ No newline at end of file diff --git a/system/database/DB_cache.php b/system/database/DB_cache.php index 3bf065ca..7c8ee5fc 100644 --- a/system/database/DB_cache.php +++ b/system/database/DB_cache.php @@ -1,45 +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(); } // -------------------------------------------------------------------- @@ -47,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(); } @@ -64,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; } @@ -82,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 ( ! is_file($filepath) OR FALSE === ($cachedata = file_get_contents($filepath))) { return FALSE; } @@ -113,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) @@ -146,7 +177,7 @@ class CI_DB_Cache { return FALSE; } - @chmod($dir_path.$filename, FILE_WRITE_MODE); + chmod($dir_path.$filename, 0640); return TRUE; } @@ -155,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); } @@ -180,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/system/database/DB_driver.php b/system/database/DB_driver.php index e7a9de47..0b13a2f8 100644 --- a/system/database/DB_driver.php +++ b/system/database/DB_driver.php @@ -1,19 +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; } @@ -112,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; @@ -188,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; } @@ -199,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'); + $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'; } // -------------------------------------------------------------------- @@ -237,47 +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); - } - - // Is query caching enabled? If the query is a "read type" - // we will load the caching class and return the previously - // cached query if it exists - if ($this->cache_on == TRUE AND stristr($sql, 'SELECT')) - { - if ($this->_cache_init()) - { - $this->load_rdriver(); - if (FALSE !== ($cache = $this->CACHE->read($sql))) - { - return $cache; - } - } + $sql = preg_replace('/(\W)'.$this->swap_pre.'(\S+?)/', '\\1'.$this->dbprefix.'\\2', $sql); } // Compile binds if needed @@ -286,72 +629,88 @@ class CI_DB_driver { $sql = $this->compile_binds($sql, $binds); } - // Save the query for debugging - if ($this->save_queries == TRUE) + // Is query caching enabled? If the query is a "read type" + // we will load the caching class and return the previously + // cached query if it exists + if ($this->cache_on === TRUE && $return_object === TRUE && $this->_cache_init()) + { + $this->load_rdriver(); + if (FALSE !== ($cache = $this->CACHE->read($sql))) + { + return $cache; + } + } + + // Save the query for debugging + if ($this->save_queries === TRUE) { $this->queries[] = $sql; } // Start the Query Timer - $time_start = 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(); } @@ -359,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 @@ -395,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; @@ -415,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'.EXT); - include_once(BASEPATH.'database/drivers/'.$this->dbdriver.'/'.$this->dbdriver.'_result'.EXT); + require_once(BASEPATH.'database/DB_result.php'); + require_once(BASEPATH.'database/drivers/'.$this->dbdriver.'/'.$this->dbdriver.'_result.php'); } return $driver; @@ -435,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); @@ -459,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; } @@ -471,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; } @@ -489,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); } // -------------------------------------------------------------------- @@ -514,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; @@ -548,8 +873,7 @@ class CI_DB_driver { return FALSE; } - $this->trans_commit(); - return TRUE; + return $this->trans_commit(); } // -------------------------------------------------------------------- @@ -557,55 +881,158 @@ 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_status = TRUE; + $this->_trans_depth++; + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + public function trans_commit() + { + if ( ! $this->trans_enabled OR $this->_trans_depth === 0) + { + return FALSE; + } + // When transactions are nested we only begin/commit/rollback the outermost ones + elseif ($this->_trans_depth > 1 OR $this->_trans_commit()) + { + $this->_trans_depth--; + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + public function trans_rollback() + { + if ( ! $this->trans_enabled OR $this->_trans_depth === 0) + { + return FALSE; + } + // When transactions are nested we only begin/commit/rollback the outermost ones + elseif ($this->_trans_depth > 1 OR $this->_trans_rollback()) + { + $this->_trans_depth--; + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + /** * Compile Bindings * - * @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($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; } // -------------------------------------------------------------------- @@ -613,17 +1040,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|MERGE)\s/i', $sql); } // -------------------------------------------------------------------- @@ -631,11 +1053,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); } @@ -645,10 +1066,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; } @@ -658,10 +1078,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); } @@ -674,23 +1093,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; @@ -704,11 +1163,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); } @@ -716,25 +1174,60 @@ class CI_DB_driver { // -------------------------------------------------------------------- /** - * Primary + * Platform-dependent 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, FALSE)); + } + + // -------------------------------------------------------------------- + + /** + * Primary + * + * Retrieves the primary key. It assumes that the row in the first + * position is the primary key + * + * @param string $table Table name + * @return string + */ + public function primary($table) { $fields = $this->list_fields($table); + return is_array($fields) ? current($fields) : FALSE; + } - 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; } // -------------------------------------------------------------------- @@ -742,10 +1235,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'])) @@ -755,32 +1248,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']; } @@ -788,24 +1289,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])) @@ -813,40 +1314,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]; } @@ -854,14 +1352,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)); } // -------------------------------------------------------------------- @@ -869,24 +1367,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); } // -------------------------------------------------------------------- @@ -894,23 +1443,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).')'; } // -------------------------------------------------------------------- @@ -918,51 +1482,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 : ''); } // -------------------------------------------------------------------- @@ -970,19 +1536,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; } // -------------------------------------------------------------------- @@ -990,14 +1587,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)) { @@ -1006,18 +1601,12 @@ class CI_DB_driver { if ( ! function_exists($function)) { - if ($this->db_debug) - { - return $this->display_error('db_unsupported_function'); - } - return FALSE; + return ($this->db_debug) ? $this->display_error('db_unsupported_function') : FALSE; } - else - { - $args = (func_num_args() > 1) ? array_splice(func_get_args(), 1) : null; - return call_user_func_array($function, $args); - } + return (func_num_args() > 1) + ? call_user_func_array($function, array_slice(func_get_args(), 1)) + : call_user_func($function); } // -------------------------------------------------------------------- @@ -1025,11 +1614,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; } @@ -1039,13 +1627,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; } // -------------------------------------------------------------------- @@ -1053,31 +1639,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; } // -------------------------------------------------------------------- @@ -1085,17 +1667,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; } // -------------------------------------------------------------------- @@ -1103,24 +1681,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'.EXT)) - { - 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; } @@ -1130,15 +1703,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; } @@ -1147,49 +1733,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 } // -------------------------------------------------------------------- @@ -1197,29 +1788,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: * @@ -1230,14 +1805,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)) { @@ -1247,34 +1821,48 @@ 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; } - // Convert tabs or multiple spaces into single spaces - $item = preg_replace('/[\t ]+/', ' ', $item); - - // If the item has an alias declaration we remove it and set it aside. - // Basically we remove everything to the right of the first space - $alias = ''; - if (strpos($item, ' ') !== FALSE) - { - $alias = strstr($item, " "); - $item = substr($item, 0, - strlen($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 + // 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) + // + // Added exception for single quotes as well, we don't want to alter + // literal strings. -- Narf + if (strcspn($item, "()'") !== strlen($item)) { - return $item.$alias; + return $item; + } + + // Convert tabs or multiple spaces into single spaces + $item = preg_replace('/\s+/', ' ', trim($item)); + + // If the item has an alias declaration we remove it and set it aside. + // Note: strripos() is used in order to support spaces in table names + if ($offset = strripos($item, ' AS ')) + { + $alias = ($protect_identifiers) + ? substr($item, $offset, 4).$this->escape_identifiers(substr($item, $offset + 4)) + : substr($item, $offset); + $item = substr($item, 0, $offset); + } + elseif ($offset = strrpos($item, ' ')) + { + $alias = ($protect_identifiers) + ? ' '.$this->escape_identifiers(substr($item, $offset + 1)) + : substr($item, $offset); + $item = substr($item, 0, $offset); + } + else + { + $alias = ''; } // Break the string apart if it contains periods, then insert the table prefix @@ -1282,12 +1870,15 @@ class CI_DB_driver { // 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) { @@ -1295,17 +1886,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)? @@ -1329,19 +1921,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]; } @@ -1352,39 +1943,45 @@ 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; } + // -------------------------------------------------------------------- + + /** + * Dummy method that allows Query Builder class to be disabled + * and keep count_all() working. + * + * @return void + */ + protected function _reset_select() + { + } } - - -/* End of file DB_driver.php */ -/* Location: ./system/database/DB_driver.php */ \ No newline at end of file diff --git a/system/database/DB_forge.php b/system/database/DB_forge.php index a71fca78..3cb02ca4 100644 --- a/system/database/DB_forge.php +++ b/system/database/DB_forge.php @@ -1,46 +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'); } // -------------------------------------------------------------------- @@ -48,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, $this->db->escape_identifiers($db_name), $this->db->char_set, $this->db->dbcollat))) + { + return ($this->db->db_debug) ? $this->db->display_error('db_unable_to_drop') : FALSE; } - return $this->db->query($sql); + if ( ! empty($this->db->data_cache['db_names'])) + { + $this->db->data_cache['db_names'][] = $db_name; + } + + return TRUE; } // -------------------------------------------------------------------- @@ -69,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, $this->db->escape_identifiers($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; } // -------------------------------------------------------------------- @@ -90,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) @@ -120,6 +263,8 @@ class CI_DB_forge { { $this->keys[] = $key; } + + return $this; } // -------------------------------------------------------------------- @@ -127,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 @@ -167,6 +306,7 @@ class CI_DB_forge { $this->fields = array_merge($this->fields, $field); } + return $this; } // -------------------------------------------------------------------- @@ -174,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) + { + isset($this->db->data_cache['table_names']) && $this->db->data_cache['table_names'][] = $table; + + // Most databases don't support creating indexes from within the CREATE TABLE statement + if ( ! empty($this->keys)) + { + for ($i = 0, $sqls = $this->_process_indexes($table), $c = count($sqls); $i < $c; $i++) + { + $this->db->query($sqls[$i]); + } + } + } $this->_reset(); - return $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; } // -------------------------------------------------------------------- @@ -201,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 mixed (Returns a platform-specific DROP table string, or TRUE to indicate there's nothing to do) + */ + protected function _drop_table($table, $if_exists) + { + $sql = 'DROP TABLE'; + + if ($if_exists) + { + if ($this->_drop_table_if === FALSE) + { + if ( ! $this->db->table_exists($table)) + { + return TRUE; + } + } + else + { + $sql = sprintf($this->_drop_table_if, $this->db->escape_identifiers($table)); + } + } + + return $sql.' '.$this->db->escape_identifiers($table); } // -------------------------------------------------------------------- @@ -222,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($table_name, $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; } // -------------------------------------------------------------------- @@ -243,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; - } // -------------------------------------------------------------------- @@ -287,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); } @@ -315,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; } @@ -361,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 Table name + * @return string[] list of SQL statements + */ + protected function _process_indexes($table) + { + $sqls = array(); + + for ($i = 0, $c = count($this->keys); $i < $c; $i++) + { + if (is_array($this->keys[$i])) + { + for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) + { + if ( ! isset($this->fields[$this->keys[$i][$i2]])) + { + unset($this->keys[$i][$i2]); + continue; + } + } + } + elseif ( ! isset($this->fields[$this->keys[$i]])) + { + unset($this->keys[$i]); + continue; + } + + is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]); + + $sqls[] = 'CREATE INDEX '.$this->db->escape_identifiers($table.'_'.implode('_', $this->keys[$i])) + .' ON '.$this->db->escape_identifiers($table) + .' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).');'; + } + + return $sqls; + } + + // -------------------------------------------------------------------- + /** * Reset * * Resets table creation vars * - * @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/system/database/DB_query_builder.php b/system/database/DB_query_builder.php new file mode 100644 index 00000000..81603bf3 --- /dev/null +++ b/system/database/DB_query_builder.php @@ -0,0 +1,2803 @@ +_protect_identifiers; + + foreach ($select as $val) + { + $val = trim($val); + + if ($val !== '') + { + $this->qb_select[] = $val; + $this->qb_no_escape[] = $escape; + + if ($this->qb_caching === TRUE) + { + $this->qb_cache_select[] = $val; + $this->qb_cache_exists[] = 'select'; + $this->qb_cache_no_escape[] = $escape; + } + } + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Select Max + * + * Generates a SELECT MAX(field) portion of a query + * + * @param string the field + * @param string an alias + * @return CI_DB_query_builder + */ + public function select_max($select = '', $alias = '') + { + return $this->_max_min_avg_sum($select, $alias, 'MAX'); + } + + // -------------------------------------------------------------------- + + /** + * Select Min + * + * Generates a SELECT MIN(field) portion of a query + * + * @param string the field + * @param string an alias + * @return CI_DB_query_builder + */ + public function select_min($select = '', $alias = '') + { + return $this->_max_min_avg_sum($select, $alias, 'MIN'); + } + + // -------------------------------------------------------------------- + + /** + * Select Average + * + * Generates a SELECT AVG(field) portion of a query + * + * @param string the field + * @param string an alias + * @return CI_DB_query_builder + */ + public function select_avg($select = '', $alias = '') + { + return $this->_max_min_avg_sum($select, $alias, 'AVG'); + } + + // -------------------------------------------------------------------- + + /** + * Select Sum + * + * Generates a SELECT SUM(field) portion of a query + * + * @param string the field + * @param string an alias + * @return CI_DB_query_builder + */ + public function select_sum($select = '', $alias = '') + { + return $this->_max_min_avg_sum($select, $alias, 'SUM'); + } + + // -------------------------------------------------------------------- + + /** + * SELECT [MAX|MIN|AVG|SUM]() + * + * @used-by select_max() + * @used-by select_min() + * @used-by select_avg() + * @used-by select_sum() + * + * @param string $select Field name + * @param string $alias + * @param string $type + * @return CI_DB_query_builder + */ + protected function _max_min_avg_sum($select = '', $alias = '', $type = 'MAX') + { + if ( ! is_string($select) OR $select === '') + { + $this->display_error('db_invalid_query'); + } + + $type = strtoupper($type); + + if ( ! in_array($type, array('MAX', 'MIN', 'AVG', 'SUM'))) + { + show_error('Invalid function type: '.$type); + } + + if ($alias === '') + { + $alias = $this->_create_alias_from_table(trim($select)); + } + + $sql = $type.'('.$this->protect_identifiers(trim($select)).') AS '.$this->escape_identifiers(trim($alias)); + + $this->qb_select[] = $sql; + $this->qb_no_escape[] = NULL; + + if ($this->qb_caching === TRUE) + { + $this->qb_cache_select[] = $sql; + $this->qb_cache_exists[] = 'select'; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Determines the alias name based on the table + * + * @param string $item + * @return string + */ + protected function _create_alias_from_table($item) + { + if (strpos($item, '.') !== FALSE) + { + $item = explode('.', $item); + return end($item); + } + + return $item; + } + + // -------------------------------------------------------------------- + + /** + * DISTINCT + * + * Sets a flag which tells the query string compiler to add DISTINCT + * + * @param bool $val + * @return CI_DB_query_builder + */ + public function distinct($val = TRUE) + { + $this->qb_distinct = is_bool($val) ? $val : TRUE; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * From + * + * Generates the FROM portion of the query + * + * @param mixed $from can be a string or array + * @return CI_DB_query_builder + */ + public function from($from) + { + foreach ((array) $from as $val) + { + if (strpos($val, ',') !== FALSE) + { + foreach (explode(',', $val) as $v) + { + $v = trim($v); + $this->_track_aliases($v); + + $this->qb_from[] = $v = $this->protect_identifiers($v, TRUE, NULL, FALSE); + + if ($this->qb_caching === TRUE) + { + $this->qb_cache_from[] = $v; + $this->qb_cache_exists[] = 'from'; + } + } + } + else + { + $val = trim($val); + + // Extract any aliases that might exist. We use this information + // in the protect_identifiers to know whether to add a table prefix + $this->_track_aliases($val); + + $this->qb_from[] = $val = $this->protect_identifiers($val, TRUE, NULL, FALSE); + + if ($this->qb_caching === TRUE) + { + $this->qb_cache_from[] = $val; + $this->qb_cache_exists[] = 'from'; + } + } + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * JOIN + * + * Generates the JOIN portion of the query + * + * @param string + * @param string the join condition + * @param string the type of join + * @param string whether not to try to escape identifiers + * @return CI_DB_query_builder + */ + public function join($table, $cond, $type = '', $escape = NULL) + { + if ($type !== '') + { + $type = strtoupper(trim($type)); + + if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE)) + { + $type = ''; + } + else + { + $type .= ' '; + } + } + + // Extract any aliases that might exist. We use this information + // in the protect_identifiers to know whether to add a table prefix + $this->_track_aliases($table); + + is_bool($escape) OR $escape = $this->_protect_identifiers; + + if ( ! $this->_has_operator($cond)) + { + $cond = ' USING ('.($escape ? $this->escape_identifiers($cond) : $cond).')'; + } + elseif ($escape === FALSE) + { + $cond = ' ON '.$cond; + } + else + { + // Split multiple conditions + if (preg_match_all('/\sAND\s|\sOR\s/i', $cond, $joints, PREG_OFFSET_CAPTURE)) + { + $conditions = array(); + $joints = $joints[0]; + array_unshift($joints, array('', 0)); + + for ($i = count($joints) - 1, $pos = strlen($cond); $i >= 0; $i--) + { + $joints[$i][1] += strlen($joints[$i][0]); // offset + $conditions[$i] = substr($cond, $joints[$i][1], $pos - $joints[$i][1]); + $pos = $joints[$i][1] - strlen($joints[$i][0]); + $joints[$i] = $joints[$i][0]; + } + } + else + { + $conditions = array($cond); + $joints = array(''); + } + + $cond = ' ON '; + for ($i = 0, $c = count($conditions); $i < $c; $i++) + { + $operator = $this->_get_operator($conditions[$i]); + $cond .= $joints[$i]; + $cond .= preg_match("/(\(*)?([\[\]\w\.'-]+)".preg_quote($operator)."(.*)/i", $conditions[$i], $match) + ? $match[1].$this->protect_identifiers($match[2]).$operator.$this->protect_identifiers($match[3]) + : $conditions[$i]; + } + } + + // Do we want to escape the table name? + if ($escape === TRUE) + { + $table = $this->protect_identifiers($table, TRUE, NULL, FALSE); + } + + // Assemble the JOIN statement + $this->qb_join[] = $join = $type.'JOIN '.$table.$cond; + + if ($this->qb_caching === TRUE) + { + $this->qb_cache_join[] = $join; + $this->qb_cache_exists[] = 'join'; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * WHERE + * + * Generates the WHERE portion of the query. + * Separates multiple calls with 'AND'. + * + * @param mixed + * @param mixed + * @param bool + * @return CI_DB_query_builder + */ + public function where($key, $value = NULL, $escape = NULL) + { + return $this->_wh('qb_where', $key, $value, 'AND ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR WHERE + * + * Generates the WHERE portion of the query. + * Separates multiple calls with 'OR'. + * + * @param mixed + * @param mixed + * @param bool + * @return CI_DB_query_builder + */ + public function or_where($key, $value = NULL, $escape = NULL) + { + return $this->_wh('qb_where', $key, $value, 'OR ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * WHERE, HAVING + * + * @used-by where() + * @used-by or_where() + * @used-by having() + * @used-by or_having() + * + * @param string $qb_key 'qb_where' or 'qb_having' + * @param mixed $key + * @param mixed $value + * @param string $type + * @param bool $escape + * @return CI_DB_query_builder + */ + protected function _wh($qb_key, $key, $value = NULL, $type = 'AND ', $escape = NULL) + { + $qb_cache_key = ($qb_key === 'qb_having') ? 'qb_cache_having' : 'qb_cache_where'; + + if ( ! is_array($key)) + { + $key = array($key => $value); + } + + // If the escape value was not set will base it on the global setting + is_bool($escape) OR $escape = $this->_protect_identifiers; + + foreach ($key as $k => $v) + { + $prefix = (count($this->$qb_key) === 0 && count($this->$qb_cache_key) === 0) + ? $this->_group_get_type('') + : $this->_group_get_type($type); + + if ($v !== NULL) + { + if ($escape === TRUE) + { + $v = ' '.$this->escape($v); + } + + if ( ! $this->_has_operator($k)) + { + $k .= ' = '; + } + } + elseif ( ! $this->_has_operator($k)) + { + // value appears not to have been set, assign the test to IS NULL + $k .= ' IS NULL'; + } + elseif (preg_match('/\s*(!?=|<>|\sIS(?:\s+NOT)?\s)\s*$/i', $k, $match, PREG_OFFSET_CAPTURE)) + { + $k = substr($k, 0, $match[0][1]).($match[1][0] === '=' ? ' IS NULL' : ' IS NOT NULL'); + } + + $this->{$qb_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape); + if ($this->qb_caching === TRUE) + { + $this->{$qb_cache_key}[] = array('condition' => $prefix.$k.$v, 'escape' => $escape); + $this->qb_cache_exists[] = substr($qb_key, 3); + } + + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * WHERE IN + * + * Generates a WHERE field IN('item', 'item') SQL query, + * joined with 'AND' if appropriate. + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $escape + * @return CI_DB_query_builder + */ + public function where_in($key = NULL, $values = NULL, $escape = NULL) + { + return $this->_where_in($key, $values, FALSE, 'AND ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR WHERE IN + * + * Generates a WHERE field IN('item', 'item') SQL query, + * joined with 'OR' if appropriate. + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $escape + * @return CI_DB_query_builder + */ + public function or_where_in($key = NULL, $values = NULL, $escape = NULL) + { + return $this->_where_in($key, $values, FALSE, 'OR ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * WHERE NOT IN + * + * Generates a WHERE field NOT IN('item', 'item') SQL query, + * joined with 'AND' if appropriate. + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $escape + * @return CI_DB_query_builder + */ + public function where_not_in($key = NULL, $values = NULL, $escape = NULL) + { + return $this->_where_in($key, $values, TRUE, 'AND ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR WHERE NOT IN + * + * Generates a WHERE field NOT IN('item', 'item') SQL query, + * joined with 'OR' if appropriate. + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $escape + * @return CI_DB_query_builder + */ + public function or_where_not_in($key = NULL, $values = NULL, $escape = NULL) + { + return $this->_where_in($key, $values, TRUE, 'OR ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * Internal WHERE IN + * + * @used-by where_in() + * @used-by or_where_in() + * @used-by where_not_in() + * @used-by or_where_not_in() + * + * @param string $key The field to search + * @param array $values The values searched on + * @param bool $not If the statement would be IN or NOT IN + * @param string $type + * @param bool $escape + * @return CI_DB_query_builder + */ + protected function _where_in($key = NULL, $values = NULL, $not = FALSE, $type = 'AND ', $escape = NULL) + { + if ($key === NULL OR $values === NULL) + { + return $this; + } + + if ( ! is_array($values)) + { + $values = array($values); + } + + is_bool($escape) OR $escape = $this->_protect_identifiers; + + $not = ($not) ? ' NOT' : ''; + + if ($escape === TRUE) + { + $where_in = array(); + foreach ($values as $value) + { + $where_in[] = $this->escape($value); + } + } + else + { + $where_in = array_values($values); + } + + $prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0) + ? $this->_group_get_type('') + : $this->_group_get_type($type); + + $where_in = array( + 'condition' => $prefix.$key.$not.' IN('.implode(', ', $where_in).')', + 'escape' => $escape + ); + + $this->qb_where[] = $where_in; + if ($this->qb_caching === TRUE) + { + $this->qb_cache_where[] = $where_in; + $this->qb_cache_exists[] = 'where'; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * LIKE + * + * Generates a %LIKE% portion of the query. + * Separates multiple calls with 'AND'. + * + * @param mixed $field + * @param string $match + * @param string $side + * @param bool $escape + * @return CI_DB_query_builder + */ + public function like($field, $match = '', $side = 'both', $escape = NULL) + { + return $this->_like($field, $match, 'AND ', $side, '', $escape); + } + + // -------------------------------------------------------------------- + + /** + * NOT LIKE + * + * Generates a NOT LIKE portion of the query. + * Separates multiple calls with 'AND'. + * + * @param mixed $field + * @param string $match + * @param string $side + * @param bool $escape + * @return CI_DB_query_builder + */ + public function not_like($field, $match = '', $side = 'both', $escape = NULL) + { + return $this->_like($field, $match, 'AND ', $side, 'NOT', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR LIKE + * + * Generates a %LIKE% portion of the query. + * Separates multiple calls with 'OR'. + * + * @param mixed $field + * @param string $match + * @param string $side + * @param bool $escape + * @return CI_DB_query_builder + */ + public function or_like($field, $match = '', $side = 'both', $escape = NULL) + { + return $this->_like($field, $match, 'OR ', $side, '', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR NOT LIKE + * + * Generates a NOT LIKE portion of the query. + * Separates multiple calls with 'OR'. + * + * @param mixed $field + * @param string $match + * @param string $side + * @param bool $escape + * @return CI_DB_query_builder + */ + public function or_not_like($field, $match = '', $side = 'both', $escape = NULL) + { + return $this->_like($field, $match, 'OR ', $side, 'NOT', $escape); + } + + // -------------------------------------------------------------------- + + /** + * Internal LIKE + * + * @used-by like() + * @used-by or_like() + * @used-by not_like() + * @used-by or_not_like() + * + * @param mixed $field + * @param string $match + * @param string $type + * @param string $side + * @param string $not + * @param bool $escape + * @return CI_DB_query_builder + */ + protected function _like($field, $match = '', $type = 'AND ', $side = 'both', $not = '', $escape = NULL) + { + if ( ! is_array($field)) + { + $field = array($field => $match); + } + + is_bool($escape) OR $escape = $this->_protect_identifiers; + // lowercase $side in case somebody writes e.g. 'BEFORE' instead of 'before' (doh) + $side = strtolower($side); + + foreach ($field as $k => $v) + { + $prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0) + ? $this->_group_get_type('') : $this->_group_get_type($type); + + if ($escape === TRUE) + { + $v = $this->escape_like_str($v); + } + + if ($side === 'none') + { + $like_statement = "{$prefix} {$k} {$not} LIKE '{$v}'"; + } + elseif ($side === 'before') + { + $like_statement = "{$prefix} {$k} {$not} LIKE '%{$v}'"; + } + elseif ($side === 'after') + { + $like_statement = "{$prefix} {$k} {$not} LIKE '{$v}%'"; + } + else + { + $like_statement = "{$prefix} {$k} {$not} LIKE '%{$v}%'"; + } + + // some platforms require an escape sequence definition for LIKE wildcards + if ($escape === TRUE && $this->_like_escape_str !== '') + { + $like_statement .= sprintf($this->_like_escape_str, $this->_like_escape_chr); + } + + $this->qb_where[] = array('condition' => $like_statement, 'escape' => $escape); + if ($this->qb_caching === TRUE) + { + $this->qb_cache_where[] = array('condition' => $like_statement, 'escape' => $escape); + $this->qb_cache_exists[] = 'where'; + } + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Starts a query group. + * + * @param string $not (Internal use only) + * @param string $type (Internal use only) + * @return CI_DB_query_builder + */ + public function group_start($not = '', $type = 'AND ') + { + $type = $this->_group_get_type($type); + + $this->qb_where_group_started = TRUE; + $prefix = (count($this->qb_where) === 0 && count($this->qb_cache_where) === 0) ? '' : $type; + $where = array( + 'condition' => $prefix.$not.str_repeat(' ', ++$this->qb_where_group_count).' (', + 'escape' => FALSE + ); + + $this->qb_where[] = $where; + if ($this->qb_caching) + { + $this->qb_cache_where[] = $where; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Starts a query group, but ORs the group + * + * @return CI_DB_query_builder + */ + public function or_group_start() + { + return $this->group_start('', 'OR '); + } + + // -------------------------------------------------------------------- + + /** + * Starts a query group, but NOTs the group + * + * @return CI_DB_query_builder + */ + public function not_group_start() + { + return $this->group_start('NOT ', 'AND '); + } + + // -------------------------------------------------------------------- + + /** + * Starts a query group, but OR NOTs the group + * + * @return CI_DB_query_builder + */ + public function or_not_group_start() + { + return $this->group_start('NOT ', 'OR '); + } + + // -------------------------------------------------------------------- + + /** + * Ends a query group + * + * @return CI_DB_query_builder + */ + public function group_end() + { + $this->qb_where_group_started = FALSE; + $where = array( + 'condition' => str_repeat(' ', $this->qb_where_group_count--).')', + 'escape' => FALSE + ); + + $this->qb_where[] = $where; + if ($this->qb_caching) + { + $this->qb_cache_where[] = $where; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Group_get_type + * + * @used-by group_start() + * @used-by _like() + * @used-by _wh() + * @used-by _where_in() + * + * @param string $type + * @return string + */ + protected function _group_get_type($type) + { + if ($this->qb_where_group_started) + { + $type = ''; + $this->qb_where_group_started = FALSE; + } + + return $type; + } + + // -------------------------------------------------------------------- + + /** + * GROUP BY + * + * @param string $by + * @param bool $escape + * @return CI_DB_query_builder + */ + public function group_by($by, $escape = NULL) + { + is_bool($escape) OR $escape = $this->_protect_identifiers; + + if (is_string($by)) + { + $by = ($escape === TRUE) + ? explode(',', $by) + : array($by); + } + + foreach ($by as $val) + { + $val = trim($val); + + if ($val !== '') + { + $val = array('field' => $val, 'escape' => $escape); + + $this->qb_groupby[] = $val; + if ($this->qb_caching === TRUE) + { + $this->qb_cache_groupby[] = $val; + $this->qb_cache_exists[] = 'groupby'; + } + } + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * HAVING + * + * Separates multiple calls with 'AND'. + * + * @param string $key + * @param string $value + * @param bool $escape + * @return CI_DB_query_builder + */ + public function having($key, $value = NULL, $escape = NULL) + { + return $this->_wh('qb_having', $key, $value, 'AND ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * OR HAVING + * + * Separates multiple calls with 'OR'. + * + * @param string $key + * @param string $value + * @param bool $escape + * @return CI_DB_query_builder + */ + public function or_having($key, $value = NULL, $escape = NULL) + { + return $this->_wh('qb_having', $key, $value, 'OR ', $escape); + } + + // -------------------------------------------------------------------- + + /** + * ORDER BY + * + * @param string $orderby + * @param string $direction ASC, DESC or RANDOM + * @param bool $escape + * @return CI_DB_query_builder + */ + public function order_by($orderby, $direction = '', $escape = NULL) + { + $direction = strtoupper(trim($direction)); + + if ($direction === 'RANDOM') + { + $direction = ''; + + // Do we have a seed value? + $orderby = ctype_digit((string) $orderby) + ? sprintf($this->_random_keyword[1], $orderby) + : $this->_random_keyword[0]; + } + elseif (empty($orderby)) + { + return $this; + } + elseif ($direction !== '') + { + $direction = in_array($direction, array('ASC', 'DESC'), TRUE) ? ' '.$direction : ''; + } + + is_bool($escape) OR $escape = $this->_protect_identifiers; + + if ($escape === FALSE) + { + $qb_orderby[] = array('field' => $orderby, 'direction' => $direction, 'escape' => FALSE); + } + else + { + $qb_orderby = array(); + foreach (explode(',', $orderby) as $field) + { + $qb_orderby[] = ($direction === '' && preg_match('/\s+(ASC|DESC)$/i', rtrim($field), $match, PREG_OFFSET_CAPTURE)) + ? array('field' => ltrim(substr($field, 0, $match[0][1])), 'direction' => ' '.$match[1][0], 'escape' => TRUE) + : array('field' => trim($field), 'direction' => $direction, 'escape' => TRUE); + } + } + + $this->qb_orderby = array_merge($this->qb_orderby, $qb_orderby); + if ($this->qb_caching === TRUE) + { + $this->qb_cache_orderby = array_merge($this->qb_cache_orderby, $qb_orderby); + $this->qb_cache_exists[] = 'orderby'; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * @param int $value LIMIT value + * @param int $offset OFFSET value + * @return CI_DB_query_builder + */ + public function limit($value, $offset = 0) + { + is_null($value) OR $this->qb_limit = (int) $value; + empty($offset) OR $this->qb_offset = (int) $offset; + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Sets the OFFSET value + * + * @param int $offset OFFSET value + * @return CI_DB_query_builder + */ + public function offset($offset) + { + empty($offset) OR $this->qb_offset = (int) $offset; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * LIMIT string + * + * Generates a platform-specific LIMIT clause. + * + * @param string $sql SQL Query + * @return string + */ + protected function _limit($sql) + { + return $sql.' LIMIT '.($this->qb_offset ? $this->qb_offset.', ' : '').(int) $this->qb_limit; + } + + // -------------------------------------------------------------------- + + /** + * The "set" function. + * + * Allows key/value pairs to be set for inserting or updating + * + * @param mixed + * @param string + * @param bool + * @return CI_DB_query_builder + */ + public function set($key, $value = '', $escape = NULL) + { + $key = $this->_object_to_array($key); + + if ( ! is_array($key)) + { + $key = array($key => $value); + } + + is_bool($escape) OR $escape = $this->_protect_identifiers; + + foreach ($key as $k => $v) + { + $this->qb_set[$this->protect_identifiers($k, FALSE, $escape)] = ($escape) + ? $this->escape($v) : $v; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Get SELECT query string + * + * Compiles a SELECT query string and returns the sql. + * + * @param string the table name to select from (optional) + * @param bool TRUE: resets QB values; FALSE: leave QB values alone + * @return string + */ + public function get_compiled_select($table = '', $reset = TRUE) + { + if ($table !== '') + { + $this->_track_aliases($table); + $this->from($table); + } + + $select = $this->_compile_select(); + + if ($reset === TRUE) + { + $this->_reset_select(); + } + + return $select; + } + + // -------------------------------------------------------------------- + + /** + * Get + * + * Compiles the select statement based on the other functions called + * and runs the query + * + * @param string the table + * @param string the limit clause + * @param string the offset clause + * @return CI_DB_result + */ + public function get($table = '', $limit = NULL, $offset = NULL) + { + if ($table !== '') + { + $this->_track_aliases($table); + $this->from($table); + } + + if ( ! empty($limit)) + { + $this->limit($limit, $offset); + } + + $result = $this->query($this->_compile_select()); + $this->_reset_select(); + return $result; + } + + // -------------------------------------------------------------------- + + /** + * "Count All Results" query + * + * Generates a platform-specific query string that counts all records + * returned by an Query Builder query. + * + * @param string + * @param bool the reset clause + * @return int + */ + public function count_all_results($table = '', $reset = TRUE) + { + if ($table !== '') + { + $this->_track_aliases($table); + $this->from($table); + } + + // ORDER BY usage is often problematic here (most notably + // on Microsoft SQL Server) and ultimately unnecessary + // for selecting COUNT(*) ... + $qb_orderby = $this->qb_orderby; + $qb_cache_orderby = $this->qb_cache_orderby; + $this->qb_orderby = $this->qb_cache_orderby = NULL; + + $result = ($this->qb_distinct === TRUE OR ! empty($this->qb_groupby) OR ! empty($this->qb_cache_groupby) OR $this->qb_limit OR $this->qb_offset) + ? $this->query($this->_count_string.$this->protect_identifiers('numrows')."\nFROM (\n".$this->_compile_select()."\n) CI_count_all_results") + : $this->query($this->_compile_select($this->_count_string.$this->protect_identifiers('numrows'))); + + if ($reset === TRUE) + { + $this->_reset_select(); + } + else + { + $this->qb_orderby = $qb_orderby; + $this->qb_cache_orderby = $qb_cache_orderby; + } + + if ($result->num_rows() === 0) + { + return 0; + } + + $row = $result->row(); + return (int) $row->numrows; + } + + // -------------------------------------------------------------------- + + /** + * Get_Where + * + * Allows the where clause, limit and offset to be added directly + * + * @param string $table + * @param string $where + * @param int $limit + * @param int $offset + * @return CI_DB_result + */ + public function get_where($table = '', $where = NULL, $limit = NULL, $offset = NULL) + { + if ($table !== '') + { + $this->from($table); + } + + if ($where !== NULL) + { + $this->where($where); + } + + if ( ! empty($limit)) + { + $this->limit($limit, $offset); + } + + $result = $this->query($this->_compile_select()); + $this->_reset_select(); + return $result; + } + + // -------------------------------------------------------------------- + + /** + * Insert_Batch + * + * Compiles batch insert strings and runs the queries + * + * @param string $table Table to insert into + * @param array $set An associative array of insert values + * @param bool $escape Whether to escape values and identifiers + * @return int Number of rows inserted or FALSE on failure + */ + public function insert_batch($table, $set = NULL, $escape = NULL, $batch_size = 100) + { + if ($set === NULL) + { + if (empty($this->qb_set)) + { + return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE; + } + } + else + { + if (empty($set)) + { + return ($this->db_debug) ? $this->display_error('insert_batch() called with no data') : FALSE; + } + + $this->set_insert_batch($set, '', $escape); + } + + if (strlen($table) === 0) + { + if ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + $table = $this->qb_from[0]; + } + + // Batch this baby + $affected_rows = 0; + for ($i = 0, $total = count($this->qb_set); $i < $total; $i += $batch_size) + { + if ($this->query($this->_insert_batch($this->protect_identifiers($table, TRUE, $escape, FALSE), $this->qb_keys, array_slice($this->qb_set, $i, $batch_size)))) + { + $affected_rows += $this->affected_rows(); + } + } + + $this->_reset_write(); + return $affected_rows; + } + + // -------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string + */ + protected function _insert_batch($table, $keys, $values) + { + return 'INSERT INTO '.$table.' ('.implode(', ', $keys).') VALUES '.implode(', ', $values); + } + + // -------------------------------------------------------------------- + + /** + * The "set_insert_batch" function. Allows key/value pairs to be set for batch inserts + * + * @param mixed + * @param string + * @param bool + * @return CI_DB_query_builder + */ + public function set_insert_batch($key, $value = '', $escape = NULL) + { + $key = $this->_object_to_array_batch($key); + + if ( ! is_array($key)) + { + $key = array($key => $value); + } + + is_bool($escape) OR $escape = $this->_protect_identifiers; + + $keys = array_keys($this->_object_to_array(reset($key))); + sort($keys); + + foreach ($key as $row) + { + $row = $this->_object_to_array($row); + if (count(array_diff($keys, array_keys($row))) > 0 OR count(array_diff(array_keys($row), $keys)) > 0) + { + // batch function above returns an error on an empty array + $this->qb_set[] = array(); + return; + } + + ksort($row); // puts $row in the same order as our keys + + if ($escape !== FALSE) + { + $clean = array(); + foreach ($row as $value) + { + $clean[] = $this->escape($value); + } + + $row = $clean; + } + + $this->qb_set[] = '('.implode(',', $row).')'; + } + + foreach ($keys as $k) + { + $this->qb_keys[] = $this->protect_identifiers($k, FALSE, $escape); + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Get INSERT query string + * + * Compiles an insert query and returns the sql + * + * @param string the table to insert into + * @param bool TRUE: reset QB values; FALSE: leave QB values alone + * @return string + */ + public function get_compiled_insert($table = '', $reset = TRUE) + { + if ($this->_validate_insert($table) === FALSE) + { + return FALSE; + } + + $sql = $this->_insert( + $this->protect_identifiers( + $this->qb_from[0], TRUE, NULL, FALSE + ), + array_keys($this->qb_set), + array_values($this->qb_set) + ); + + if ($reset === TRUE) + { + $this->_reset_write(); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Insert + * + * Compiles an insert string and runs the query + * + * @param string the table to insert data into + * @param array an associative array of insert values + * @param bool $escape Whether to escape values and identifiers + * @return bool TRUE on success, FALSE on failure + */ + public function insert($table = '', $set = NULL, $escape = NULL) + { + if ($set !== NULL) + { + $this->set($set, '', $escape); + } + + if ($this->_validate_insert($table) === FALSE) + { + return FALSE; + } + + $sql = $this->_insert( + $this->protect_identifiers( + $this->qb_from[0], TRUE, $escape, FALSE + ), + array_keys($this->qb_set), + array_values($this->qb_set) + ); + + $this->_reset_write(); + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Validate Insert + * + * This method is used by both insert() and get_compiled_insert() to + * validate that the there data is actually being set and that table + * has been chosen to be inserted into. + * + * @param string the table to insert data into + * @return string + */ + protected function _validate_insert($table = '') + { + if (count($this->qb_set) === 0) + { + return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE; + } + + if ($table !== '') + { + $this->qb_from[0] = $table; + } + elseif ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Replace + * + * Compiles an replace into string and runs the query + * + * @param string the table to replace data into + * @param array an associative array of insert values + * @return bool TRUE on success, FALSE on failure + */ + public function replace($table = '', $set = NULL) + { + if ($set !== NULL) + { + $this->set($set); + } + + if (count($this->qb_set) === 0) + { + return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE; + } + + if ($table === '') + { + if ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + $table = $this->qb_from[0]; + } + + $sql = $this->_replace($this->protect_identifiers($table, TRUE, NULL, FALSE), array_keys($this->qb_set), array_values($this->qb_set)); + + $this->_reset_write(); + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Replace statement + * + * Generates a platform-specific replace string from the supplied data + * + * @param string the table name + * @param array the insert keys + * @param array the insert values + * @return string + */ + protected function _replace($table, $keys, $values) + { + return 'REPLACE INTO '.$table.' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')'; + } + + // -------------------------------------------------------------------- + + /** + * FROM tables + * + * Groups tables in FROM clauses if needed, so there is no confusion + * about operator precedence. + * + * Note: This is only used (and overridden) by MySQL and CUBRID. + * + * @return string + */ + protected function _from_tables() + { + return implode(', ', $this->qb_from); + } + + // -------------------------------------------------------------------- + + /** + * Get UPDATE query string + * + * Compiles an update query and returns the sql + * + * @param string the table to update + * @param bool TRUE: reset QB values; FALSE: leave QB values alone + * @return string + */ + public function get_compiled_update($table = '', $reset = TRUE) + { + // Combine any cached components with the current statements + $this->_merge_cache(); + + if ($this->_validate_update($table) === FALSE) + { + return FALSE; + } + + $sql = $this->_update($this->qb_from[0], $this->qb_set); + + if ($reset === TRUE) + { + $this->_reset_write(); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * UPDATE + * + * Compiles an update string and runs the query. + * + * @param string $table + * @param array $set An associative array of update values + * @param mixed $where + * @param int $limit + * @return bool TRUE on success, FALSE on failure + */ + public function update($table = '', $set = NULL, $where = NULL, $limit = NULL) + { + // Combine any cached components with the current statements + $this->_merge_cache(); + + if ($set !== NULL) + { + $this->set($set); + } + + if ($this->_validate_update($table) === FALSE) + { + return FALSE; + } + + if ($where !== NULL) + { + $this->where($where); + } + + if ( ! empty($limit)) + { + $this->limit($limit); + } + + $sql = $this->_update($this->qb_from[0], $this->qb_set); + $this->_reset_write(); + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Validate Update + * + * This method is used by both update() and get_compiled_update() to + * validate that data is actually being set and that a table has been + * chosen to be update. + * + * @param string the table to update data on + * @return bool + */ + protected function _validate_update($table) + { + if (count($this->qb_set) === 0) + { + return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE; + } + + if ($table !== '') + { + $this->qb_from = array($this->protect_identifiers($table, TRUE, NULL, FALSE)); + } + elseif ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Update_Batch + * + * Compiles an update string and runs the query + * + * @param string the table to retrieve the results from + * @param array an associative array of update values + * @param string the where key + * @return int number of rows affected or FALSE on failure + */ + public function update_batch($table, $set = NULL, $index = NULL, $batch_size = 100) + { + // Combine any cached components with the current statements + $this->_merge_cache(); + + if ($index === NULL) + { + return ($this->db_debug) ? $this->display_error('db_must_use_index') : FALSE; + } + + if ($set === NULL) + { + if (empty($this->qb_set_ub)) + { + return ($this->db_debug) ? $this->display_error('db_must_use_set') : FALSE; + } + } + else + { + if (empty($set)) + { + return ($this->db_debug) ? $this->display_error('update_batch() called with no data') : FALSE; + } + + $this->set_update_batch($set, $index); + } + + if (strlen($table) === 0) + { + if ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + $table = $this->qb_from[0]; + } + + // Batch this baby + $affected_rows = 0; + for ($i = 0, $total = count($this->qb_set_ub); $i < $total; $i += $batch_size) + { + if ($this->query($this->_update_batch($this->protect_identifiers($table, TRUE, NULL, FALSE), array_slice($this->qb_set_ub, $i, $batch_size), $index))) + { + $affected_rows += $this->affected_rows(); + } + + $this->qb_where = array(); + } + + $this->_reset_write(); + return $affected_rows; + } + + // -------------------------------------------------------------------- + + /** + * Update_Batch statement + * + * Generates a platform-specific batch update string from the supplied data + * + * @param string $table Table name + * @param array $values Update data + * @param string $index WHERE key + * @return string + */ + protected function _update_batch($table, $values, $index) + { + $ids = array(); + foreach ($values as $key => $val) + { + $ids[] = $val[$index]['value']; + + foreach (array_keys($val) as $field) + { + if ($field !== $index) + { + $final[$val[$field]['field']][] = 'WHEN '.$val[$index]['field'].' = '.$val[$index]['value'].' THEN '.$val[$field]['value']; + } + } + } + + $cases = ''; + foreach ($final as $k => $v) + { + $cases .= $k." = CASE \n" + .implode("\n", $v)."\n" + .'ELSE '.$k.' END, '; + } + + $this->where($val[$index]['field'].' IN('.implode(',', $ids).')', NULL, FALSE); + + return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where'); + } + + // -------------------------------------------------------------------- + + /** + * The "set_update_batch" function. Allows key/value pairs to be set for batch updating + * + * @param array + * @param string + * @param bool + * @return CI_DB_query_builder + */ + public function set_update_batch($key, $index = '', $escape = NULL) + { + $key = $this->_object_to_array_batch($key); + + if ( ! is_array($key)) + { + // @todo error + } + + is_bool($escape) OR $escape = $this->_protect_identifiers; + + foreach ($key as $k => $v) + { + $index_set = FALSE; + $clean = array(); + foreach ($v as $k2 => $v2) + { + if ($k2 === $index) + { + $index_set = TRUE; + } + + $clean[$k2] = array( + 'field' => $this->protect_identifiers($k2, FALSE, $escape), + 'value' => ($escape === FALSE ? $v2 : $this->escape($v2)) + ); + } + + if ($index_set === FALSE) + { + return $this->display_error('db_batch_missing_index'); + } + + $this->qb_set_ub[] = $clean; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Empty Table + * + * Compiles a delete string and runs "DELETE FROM table" + * + * @param string the table to empty + * @return bool TRUE on success, FALSE on failure + */ + public function empty_table($table = '') + { + if ($table === '') + { + if ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + $table = $this->qb_from[0]; + } + else + { + $table = $this->protect_identifiers($table, TRUE, NULL, FALSE); + } + + $sql = $this->_delete($table); + $this->_reset_write(); + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Truncate + * + * Compiles a truncate string and runs the query + * If the database does not support the truncate() command + * This function maps to "DELETE FROM table" + * + * @param string the table to truncate + * @return bool TRUE on success, FALSE on failure + */ + public function truncate($table = '') + { + if ($table === '') + { + if ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + $table = $this->qb_from[0]; + } + else + { + $table = $this->protect_identifiers($table, TRUE, NULL, FALSE); + } + + $sql = $this->_truncate($table); + $this->_reset_write(); + return $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the truncate() command, + * then this method maps to 'DELETE FROM table' + * + * @param string the table name + * @return string + */ + protected function _truncate($table) + { + return 'TRUNCATE '.$table; + } + + // -------------------------------------------------------------------- + + /** + * Get DELETE query string + * + * Compiles a delete query string and returns the sql + * + * @param string the table to delete from + * @param bool TRUE: reset QB values; FALSE: leave QB values alone + * @return string + */ + public function get_compiled_delete($table = '', $reset = TRUE) + { + $this->return_delete_sql = TRUE; + $sql = $this->delete($table, '', NULL, $reset); + $this->return_delete_sql = FALSE; + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Delete + * + * Compiles a delete string and runs the query + * + * @param mixed the table(s) to delete from. String or array + * @param mixed the where clause + * @param mixed the limit clause + * @param bool + * @return mixed + */ + public function delete($table = '', $where = '', $limit = NULL, $reset_data = TRUE) + { + // Combine any cached components with the current statements + $this->_merge_cache(); + + if ($table === '') + { + if ( ! isset($this->qb_from[0])) + { + return ($this->db_debug) ? $this->display_error('db_must_set_table') : FALSE; + } + + $table = $this->qb_from[0]; + } + elseif (is_array($table)) + { + empty($where) && $reset_data = FALSE; + + foreach ($table as $single_table) + { + $this->delete($single_table, $where, $limit, $reset_data); + } + + return; + } + else + { + $table = $this->protect_identifiers($table, TRUE, NULL, FALSE); + } + + if ($where !== '') + { + $this->where($where); + } + + if ( ! empty($limit)) + { + $this->limit($limit); + } + + if (count($this->qb_where) === 0) + { + return ($this->db_debug) ? $this->display_error('db_del_must_use_where') : FALSE; + } + + $sql = $this->_delete($table); + if ($reset_data) + { + $this->_reset_write(); + } + + return ($this->return_delete_sql === TRUE) ? $sql : $this->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string the table name + * @return string + */ + protected function _delete($table) + { + return 'DELETE FROM '.$table.$this->_compile_wh('qb_where') + .($this->qb_limit ? ' LIMIT '.$this->qb_limit : ''); + } + + // -------------------------------------------------------------------- + + /** + * DB Prefix + * + * Prepends a database prefix if one exists in configuration + * + * @param string the table + * @return string + */ + public function dbprefix($table = '') + { + if ($table === '') + { + $this->display_error('db_table_name_required'); + } + + return $this->dbprefix.$table; + } + + // -------------------------------------------------------------------- + + /** + * Set DB Prefix + * + * Set's the DB Prefix to something new without needing to reconnect + * + * @param string the prefix + * @return string + */ + public function set_dbprefix($prefix = '') + { + return $this->dbprefix = $prefix; + } + + // -------------------------------------------------------------------- + + /** + * Track Aliases + * + * Used to track SQL statements written with aliased tables. + * + * @param string The table to inspect + * @return string + */ + protected function _track_aliases($table) + { + if (is_array($table)) + { + foreach ($table as $t) + { + $this->_track_aliases($t); + } + return; + } + + // Does the string contain a comma? If so, we need to separate + // the string into discreet statements + if (strpos($table, ',') !== FALSE) + { + return $this->_track_aliases(explode(',', $table)); + } + + // if a table alias is used we can recognize it by a space + if (strpos($table, ' ') !== FALSE) + { + // if the alias is written with the AS keyword, remove it + $table = preg_replace('/\s+AS\s+/i', ' ', $table); + + // Grab the alias + $table = trim(strrchr($table, ' ')); + + // Store the alias, if it doesn't already exist + if ( ! in_array($table, $this->qb_aliased_tables, TRUE)) + { + $this->qb_aliased_tables[] = $table; + if ($this->qb_caching === TRUE && ! in_array($table, $this->qb_cache_aliased_tables, TRUE)) + { + $this->qb_cache_aliased_tables[] = $table; + $this->qb_cache_exists[] = 'aliased_tables'; + } + } + } + } + + // -------------------------------------------------------------------- + + /** + * Compile the SELECT statement + * + * Generates a query string based on which functions were used. + * Should not be called directly. + * + * @param bool $select_override + * @return string + */ + protected function _compile_select($select_override = FALSE) + { + // Combine any cached components with the current statements + $this->_merge_cache(); + + // Write the "select" portion of the query + if ($select_override !== FALSE) + { + $sql = $select_override; + } + else + { + $sql = ( ! $this->qb_distinct) ? 'SELECT ' : 'SELECT DISTINCT '; + + if (count($this->qb_select) === 0) + { + $sql .= '*'; + } + else + { + // Cycle through the "select" portion of the query and prep each column name. + // The reason we protect identifiers here rather than in the select() function + // is because until the user calls the from() function we don't know if there are aliases + foreach ($this->qb_select as $key => $val) + { + $no_escape = isset($this->qb_no_escape[$key]) ? $this->qb_no_escape[$key] : NULL; + $this->qb_select[$key] = $this->protect_identifiers($val, FALSE, $no_escape); + } + + $sql .= implode(', ', $this->qb_select); + } + } + + // Write the "FROM" portion of the query + if (count($this->qb_from) > 0) + { + $sql .= "\nFROM ".$this->_from_tables(); + } + + // Write the "JOIN" portion of the query + if (count($this->qb_join) > 0) + { + $sql .= "\n".implode("\n", $this->qb_join); + } + + $sql .= $this->_compile_wh('qb_where') + .$this->_compile_group_by() + .$this->_compile_wh('qb_having') + .$this->_compile_order_by(); // ORDER BY + + // LIMIT + if ($this->qb_limit OR $this->qb_offset) + { + return $this->_limit($sql."\n"); + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Compile WHERE, HAVING statements + * + * Escapes identifiers in WHERE and HAVING statements at execution time. + * + * Required so that aliases are tracked properly, regardless of whether + * where(), or_where(), having(), or_having are called prior to from(), + * join() and dbprefix is added only if needed. + * + * @param string $qb_key 'qb_where' or 'qb_having' + * @return string SQL statement + */ + protected function _compile_wh($qb_key) + { + if (count($this->$qb_key) > 0) + { + for ($i = 0, $c = count($this->$qb_key); $i < $c; $i++) + { + // Is this condition already compiled? + if (is_string($this->{$qb_key}[$i])) + { + continue; + } + elseif ($this->{$qb_key}[$i]['escape'] === FALSE) + { + $this->{$qb_key}[$i] = $this->{$qb_key}[$i]['condition']; + continue; + } + + // Split multiple conditions + $conditions = preg_split( + '/((?:^|\s+)AND\s+|(?:^|\s+)OR\s+)/i', + $this->{$qb_key}[$i]['condition'], + -1, + PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY + ); + + for ($ci = 0, $cc = count($conditions); $ci < $cc; $ci++) + { + if (($op = $this->_get_operator($conditions[$ci])) === FALSE + OR ! preg_match('/^(\(?)(.*)('.preg_quote($op, '/').')\s*(.*(? '(test <= foo)', /* the whole thing */ + // 1 => '(', /* optional */ + // 2 => 'test', /* the field name */ + // 3 => ' <= ', /* $op */ + // 4 => 'foo', /* optional, if $op is e.g. 'IS NULL' */ + // 5 => ')' /* optional */ + // ); + + if ( ! empty($matches[4])) + { + $this->_is_literal($matches[4]) OR $matches[4] = $this->protect_identifiers(trim($matches[4])); + $matches[4] = ' '.$matches[4]; + } + + $conditions[$ci] = $matches[1].$this->protect_identifiers(trim($matches[2])) + .' '.trim($matches[3]).$matches[4].$matches[5]; + } + + $this->{$qb_key}[$i] = implode('', $conditions); + } + + return ($qb_key === 'qb_having' ? "\nHAVING " : "\nWHERE ") + .implode("\n", $this->$qb_key); + } + + return ''; + } + + // -------------------------------------------------------------------- + + /** + * Compile GROUP BY + * + * Escapes identifiers in GROUP BY statements at execution time. + * + * Required so that aliases are tracked properly, regardless of whether + * group_by() is called prior to from(), join() and dbprefix is added + * only if needed. + * + * @return string SQL statement + */ + protected function _compile_group_by() + { + if (count($this->qb_groupby) > 0) + { + for ($i = 0, $c = count($this->qb_groupby); $i < $c; $i++) + { + // Is it already compiled? + if (is_string($this->qb_groupby[$i])) + { + continue; + } + + $this->qb_groupby[$i] = ($this->qb_groupby[$i]['escape'] === FALSE OR $this->_is_literal($this->qb_groupby[$i]['field'])) + ? $this->qb_groupby[$i]['field'] + : $this->protect_identifiers($this->qb_groupby[$i]['field']); + } + + return "\nGROUP BY ".implode(', ', $this->qb_groupby); + } + + return ''; + } + + // -------------------------------------------------------------------- + + /** + * Compile ORDER BY + * + * Escapes identifiers in ORDER BY statements at execution time. + * + * Required so that aliases are tracked properly, regardless of whether + * order_by() is called prior to from(), join() and dbprefix is added + * only if needed. + * + * @return string SQL statement + */ + protected function _compile_order_by() + { + if (empty($this->qb_orderby)) + { + return ''; + } + + for ($i = 0, $c = count($this->qb_orderby); $i < $c; $i++) + { + if (is_string($this->qb_orderby[$i])) + { + continue; + } + + if ($this->qb_orderby[$i]['escape'] !== FALSE && ! $this->_is_literal($this->qb_orderby[$i]['field'])) + { + $this->qb_orderby[$i]['field'] = $this->protect_identifiers($this->qb_orderby[$i]['field']); + } + + $this->qb_orderby[$i] = $this->qb_orderby[$i]['field'].$this->qb_orderby[$i]['direction']; + } + + return "\nORDER BY ".implode(', ', $this->qb_orderby); + } + + // -------------------------------------------------------------------- + + /** + * Object to Array + * + * Takes an object as input and converts the class variables to array key/vals + * + * @param object + * @return array + */ + protected function _object_to_array($object) + { + if ( ! is_object($object)) + { + return $object; + } + + $array = array(); + foreach (get_object_vars($object) as $key => $val) + { + // There are some built in keys we need to ignore for this conversion + if ( ! is_object($val) && ! is_array($val) && $key !== '_parent_name') + { + $array[$key] = $val; + } + } + + return $array; + } + + // -------------------------------------------------------------------- + + /** + * Object to Array + * + * Takes an object as input and converts the class variables to array key/vals + * + * @param object + * @return array + */ + protected function _object_to_array_batch($object) + { + if ( ! is_object($object)) + { + return $object; + } + + $array = array(); + $out = get_object_vars($object); + $fields = array_keys($out); + + foreach ($fields as $val) + { + // There are some built in keys we need to ignore for this conversion + if ($val !== '_parent_name') + { + $i = 0; + foreach ($out[$val] as $data) + { + $array[$i++][$val] = $data; + } + } + } + + return $array; + } + + // -------------------------------------------------------------------- + + /** + * Start Cache + * + * Starts QB caching + * + * @return CI_DB_query_builder + */ + public function start_cache() + { + $this->qb_caching = TRUE; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Stop Cache + * + * Stops QB caching + * + * @return CI_DB_query_builder + */ + public function stop_cache() + { + $this->qb_caching = FALSE; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Flush Cache + * + * Empties the QB cache + * + * @return CI_DB_query_builder + */ + public function flush_cache() + { + $this->_reset_run(array( + 'qb_cache_select' => array(), + 'qb_cache_from' => array(), + 'qb_cache_join' => array(), + 'qb_cache_where' => array(), + 'qb_cache_groupby' => array(), + 'qb_cache_having' => array(), + 'qb_cache_orderby' => array(), + 'qb_cache_set' => array(), + 'qb_cache_exists' => array(), + 'qb_cache_no_escape' => array(), + 'qb_cache_aliased_tables' => array() + )); + + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Merge Cache + * + * When called, this function merges any cached QB arrays with + * locally called ones. + * + * @return void + */ + protected function _merge_cache() + { + if (count($this->qb_cache_exists) === 0) + { + return; + } + elseif (in_array('select', $this->qb_cache_exists, TRUE)) + { + $qb_no_escape = $this->qb_cache_no_escape; + } + + foreach (array_unique($this->qb_cache_exists) as $val) // select, from, etc. + { + $qb_variable = 'qb_'.$val; + $qb_cache_var = 'qb_cache_'.$val; + $qb_new = $this->$qb_cache_var; + + for ($i = 0, $c = count($this->$qb_variable); $i < $c; $i++) + { + if ( ! in_array($this->{$qb_variable}[$i], $qb_new, TRUE)) + { + $qb_new[] = $this->{$qb_variable}[$i]; + if ($val === 'select') + { + $qb_no_escape[] = $this->qb_no_escape[$i]; + } + } + } + + $this->$qb_variable = $qb_new; + if ($val === 'select') + { + $this->qb_no_escape = $qb_no_escape; + } + } + } + + // -------------------------------------------------------------------- + + /** + * Is literal + * + * Determines if a string represents a literal value or a field name + * + * @param string $str + * @return bool + */ + protected function _is_literal($str) + { + $str = trim($str); + + if (empty($str) OR ctype_digit($str) OR (string) (float) $str === $str OR in_array(strtoupper($str), array('TRUE', 'FALSE'), TRUE)) + { + return TRUE; + } + + static $_str; + + if (empty($_str)) + { + $_str = ($this->_escape_char !== '"') + ? array('"', "'") : array("'"); + } + + return in_array($str[0], $_str, TRUE); + } + + // -------------------------------------------------------------------- + + /** + * Reset Query Builder values. + * + * Publicly-visible method to reset the QB values. + * + * @return CI_DB_query_builder + */ + public function reset_query() + { + $this->_reset_select(); + $this->_reset_write(); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Resets the query builder values. Called by the get() function + * + * @param array An array of fields to reset + * @return void + */ + protected function _reset_run($qb_reset_items) + { + foreach ($qb_reset_items as $item => $default_value) + { + $this->$item = $default_value; + } + } + + // -------------------------------------------------------------------- + + /** + * Resets the query builder values. Called by the get() function + * + * @return void + */ + protected function _reset_select() + { + $this->_reset_run(array( + 'qb_select' => array(), + 'qb_from' => array(), + 'qb_join' => array(), + 'qb_where' => array(), + 'qb_groupby' => array(), + 'qb_having' => array(), + 'qb_orderby' => array(), + 'qb_aliased_tables' => array(), + 'qb_no_escape' => array(), + 'qb_distinct' => FALSE, + 'qb_limit' => FALSE, + 'qb_offset' => FALSE + )); + } + + // -------------------------------------------------------------------- + + /** + * Resets the query builder "write" values. + * + * Called by the insert() update() insert_batch() update_batch() and delete() functions + * + * @return void + */ + protected function _reset_write() + { + $this->_reset_run(array( + 'qb_set' => array(), + 'qb_set_ub' => array(), + 'qb_from' => array(), + 'qb_join' => array(), + 'qb_where' => array(), + 'qb_orderby' => array(), + 'qb_keys' => array(), + 'qb_limit' => FALSE + )); + } + +} diff --git a/system/database/DB_result.php b/system/database/DB_result.php index 76e1d6ab..98d8876a 100644 --- a/system/database/DB_result.php +++ b/system/database/DB_result.php @@ -1,19 +1,41 @@ -result_array(); - else if ($type == 'object') return $this->result_object(); - else return $this->custom_result_object($type); + $this->conn_id = $driver_object->conn_id; + $this->result_id = $driver_object->result_id; } // -------------------------------------------------------------------- - /** - * Custom query result. - * - * @param class_name A string that represents the type of object you want back - * @return array of objects - */ - function custom_result_object($class_name) - { - if (array_key_exists($class_name, $this->custom_result_object)) - { - return $this->custom_result_object[$class_name]; - } - - if ($this->result_id === FALSE OR $this->num_rows() == 0) - { - return array(); - } - - // add the data to the object - $this->_data_seek(0); - $result_object = array(); - while ($row = $this->_fetch_object()) - { - $object = new $class_name(); - foreach ($row as $key => $value) - { - $object->$key = $value; - } - $result_object[] = $object; + /** + * 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 the array - return $this->custom_result_object[$class_name] = $result_object; - } + return $this->num_rows = count($this->result_array()); + } + + // -------------------------------------------------------------------- /** - * Query result. "object" version. + * Query result. Acts as a wrapper function for the following functions. * - * @access public - * @return object + * @param string $type 'object', 'array' or a custom class name + * @return array */ - function result_object() + public function result($type = 'object') + { + if ($type === 'array') + { + return $this->result_array(); + } + elseif ($type === 'object') + { + return $this->result_object(); + } + else + { + return $this->custom_result_object($type); + } + } + + // -------------------------------------------------------------------- + + /** + * Custom query result. + * + * @param string $class_name + * @return array + */ + public function custom_result_object($class_name) + { + if (isset($this->custom_result_object[$class_name])) + { + return $this->custom_result_object[$class_name]; + } + elseif ( ! $this->result_id OR $this->num_rows === 0) + { + return array(); + } + + // Don't fetch the result set again if we already have it + $_data = NULL; + if (($c = count($this->result_array)) > 0) + { + $_data = 'result_array'; + } + elseif (($c = count($this->result_object)) > 0) + { + $_data = 'result_object'; + } + + if ($_data !== NULL) + { + for ($i = 0; $i < $c; $i++) + { + $this->custom_result_object[$class_name][$i] = new $class_name(); + + foreach ($this->{$_data}[$i] as $key => $value) + { + $this->custom_result_object[$class_name][$i]->$key = $value; + } + } + + return $this->custom_result_object[$class_name]; + } + + is_null($this->row_data) OR $this->data_seek(0); + $this->custom_result_object[$class_name] = array(); + + while ($row = $this->_fetch_object($class_name)) + { + $this->custom_result_object[$class_name][] = $row; + } + + return $this->custom_result_object[$class_name]; + } + + // -------------------------------------------------------------------- + + /** + * Query result. "object" version. + * + * @return array + */ + public function result_object() { if (count($this->result_object) > 0) { return $this->result_object; } - // In the event that query caching is on the result_id variable - // will 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; @@ -122,27 +269,36 @@ class CI_DB_result { // -------------------------------------------------------------------- /** - * Query result. "array" version. + * Query result. "array" version. * - * @access public * @return array */ - function result_array() + public function result_array() { if (count($this->result_array) > 0) { return $this->result_array; } - // In the event that query caching is on the result_id variable - // will return FALSE since there isn't a valid SQL resource so - // we'll simply return an empty array. - if ($this->result_id === FALSE OR $this->num_rows() == 0) + // 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; @@ -154,35 +310,33 @@ 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 */ - function row($n = 0, $type = 'object') + 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); - else return $this->custom_row_object($n, $type); + if ($type === 'object') return $this->row_object($n); + elseif ($type === 'array') return $this->row_array($n); + else return $this->custom_row_object($n, $type); } // -------------------------------------------------------------------- @@ -190,10 +344,11 @@ class CI_DB_result { /** * Assigns an item into a particular column slot * - * @access public - * @return object + * @param mixed $key + * @param mixed $value + * @return void */ - function set_row($key, $value = NULL) + public function set_row($key, $value = NULL) { // We cache the row data for subsequent uses if ( ! is_array($this->row_data)) @@ -207,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; } @@ -219,45 +373,47 @@ class CI_DB_result { // -------------------------------------------------------------------- - /** + /** * Returns a single result row - custom object version * - * @access public + * @param int $n + * @param string $type * @return object */ - function custom_row_object($n, $type) + 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 */ - function row_object($n = 0) + 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; } @@ -270,19 +426,18 @@ class CI_DB_result { /** * Returns a single result row - array version * - * @access public + * @param int $n * @return array */ - function row_array($n = 0) + 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; } @@ -290,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 */ - function first_row($type = 'object') + 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]; } // -------------------------------------------------------------------- @@ -315,18 +464,13 @@ class CI_DB_result { /** * Returns the "last" row * - * @access public - * @return object + * @param string $type + * @return mixed */ - function last_row($type = 'object') + 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]; } // -------------------------------------------------------------------- @@ -334,24 +478,20 @@ class CI_DB_result { /** * Returns the "next" row * - * @access public - * @return object + * @param string $type + * @return mixed */ - function next_row($type = 'object') + 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; } // -------------------------------------------------------------------- @@ -359,16 +499,15 @@ class CI_DB_result { /** * Returns the "previous" row * - * @access public - * @return object + * @param string $type + * @return mixed */ - function previous_row($type = 'object') + 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])) @@ -381,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. */ - function num_rows() { return $this->num_rows; } - function num_fields() { return 0; } - function list_fields() { return array(); } - function field_data() { return array(); } - function free_result() { return TRUE; } - function _data_seek() { return TRUE; } - function _fetch_assoc() { return array(); } - 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 new $class_name(); + } } -// END DB_result class - -/* End of file DB_result.php */ -/* Location: ./system/database/DB_result.php */ diff --git a/system/database/DB_utility.php b/system/database/DB_utility.php index a5f174f0..25d842c0 100644 --- a/system/database/DB_utility.php +++ b/system/database/DB_utility.php @@ -1,45 +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'); } // -------------------------------------------------------------------- @@ -47,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']; } // -------------------------------------------------------------------- @@ -77,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; } // -------------------------------------------------------------------- @@ -128,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); @@ -165,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); } // -------------------------------------------------------------------- @@ -191,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')) { @@ -206,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; @@ -235,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')) { @@ -260,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; } // -------------------------------------------------------------------- @@ -285,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 @@ -298,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) @@ -323,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/system/database/drivers/cubrid/cubrid_driver.php b/system/database/drivers/cubrid/cubrid_driver.php new file mode 100644 index 00000000..6e8aff7c --- /dev/null +++ b/system/database/drivers/cubrid/cubrid_driver.php @@ -0,0 +1,405 @@ +dsn, $matches)) + { + if (stripos($matches[2], 'autocommit=off') !== FALSE) + { + $this->auto_commit = FALSE; + } + } + else + { + // If no port is defined by the user, use the default value + empty($this->port) OR $this->port = 33000; + } + } + + // -------------------------------------------------------------------- + + /** + * Non-persistent database connection + * + * @param bool $persistent + * @return resource + */ + public function db_connect($persistent = FALSE) + { + if (preg_match('/^CUBRID:[^:]+(:[0-9][1-9]{0,4})?:[^:]+:([^:]*):([^:]*):(\?.+)?$/', $this->dsn, $matches)) + { + $func = ($persistent !== TRUE) ? 'cubrid_connect_with_url' : 'cubrid_pconnect_with_url'; + return ($matches[2] === '' && $matches[3] === '' && $this->username !== '' && $this->password !== '') + ? $func($this->dsn, $this->username, $this->password) + : $func($this->dsn); + } + + $func = ($persistent !== TRUE) ? 'cubrid_connect' : 'cubrid_pconnect'; + return ($this->username !== '') + ? $func($this->hostname, $this->port, $this->database, $this->username, $this->password) + : $func($this->hostname, $this->port, $this->database); + } + + // -------------------------------------------------------------------- + + /** + * Reconnect + * + * Keep / reestablish the db connection if no queries have been + * sent for a length of time exceeding the server's idle timeout + * + * @return void + */ + public function reconnect() + { + if (cubrid_ping($this->conn_id) === FALSE) + { + $this->conn_id = FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Database version number + * + * @return string + */ + public function version() + { + if (isset($this->data_cache['version'])) + { + return $this->data_cache['version']; + } + + return ( ! $this->conn_id OR ($version = cubrid_get_server_info($this->conn_id)) === FALSE) + ? FALSE + : $this->data_cache['version'] = $version; + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @param string $sql an SQL query + * @return resource + */ + protected function _execute($sql) + { + return cubrid_query($sql, $this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return bool + */ + protected function _trans_begin() + { + if (($autocommit = cubrid_get_autocommit($this->conn_id)) === NULL) + { + return FALSE; + } + elseif ($autocommit === TRUE) + { + return cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_FALSE); + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + protected function _trans_commit() + { + if ( ! cubrid_commit($this->conn_id)) + { + return FALSE; + } + + if ($this->auto_commit && ! cubrid_get_autocommit($this->conn_id)) + { + return cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_TRUE); + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + protected function _trans_rollback() + { + if ( ! cubrid_rollback($this->conn_id)) + { + return FALSE; + } + + if ($this->auto_commit && ! cubrid_get_autocommit($this->conn_id)) + { + cubrid_set_autocommit($this->conn_id, CUBRID_AUTOCOMMIT_TRUE); + } + + return TRUE; + } + + // -------------------------------------------------------------------- + + /** + * Platform-dependent string escape + * + * @param string + * @return string + */ + protected function _escape_str($str) + { + return cubrid_real_escape_string($str, $this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @return int + */ + public function affected_rows() + { + return cubrid_affected_rows(); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @return int + */ + public function insert_id() + { + return cubrid_insert_id($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * List table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool $prefix_limit + * @return string + */ + protected function _list_tables($prefix_limit = FALSE) + { + $sql = 'SHOW TABLES'; + + if ($prefix_limit !== FALSE && $this->dbprefix !== '') + { + return $sql." LIKE '".$this->escape_like_str($this->dbprefix)."%'"; + } + + return $sql; + } + + // -------------------------------------------------------------------- + + /** + * Show column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + return 'SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE); + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + if (($query = $this->query('SHOW COLUMNS FROM '.$this->protect_identifiers($table, TRUE, NULL, FALSE))) === FALSE) + { + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->Field; + + sscanf($query[$i]->Type, '%[a-z](%d)', + $retval[$i]->type, + $retval[$i]->max_length + ); + + $retval[$i]->default = $query[$i]->Default; + $retval[$i]->primary_key = (int) ($query[$i]->Key === 'PRI'); + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Error + * + * Returns an array containing code and message of the last + * database error that has occurred. + * + * @return array + */ + public function error() + { + return array('code' => cubrid_errno($this->conn_id), 'message' => cubrid_error($this->conn_id)); + } + + // -------------------------------------------------------------------- + + /** + * FROM tables + * + * Groups tables in FROM clauses if needed, so there is no confusion + * about operator precedence. + * + * @return string + */ + protected function _from_tables() + { + if ( ! empty($this->qb_join) && count($this->qb_from) > 1) + { + return '('.implode(', ', $this->qb_from).')'; + } + + return implode(', ', $this->qb_from); + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @return void + */ + protected function _close() + { + cubrid_close($this->conn_id); + } + +} diff --git a/system/database/drivers/cubrid/cubrid_forge.php b/system/database/drivers/cubrid/cubrid_forge.php new file mode 100644 index 00000000..27bfc146 --- /dev/null +++ b/system/database/drivers/cubrid/cubrid_forge.php @@ -0,0 +1,230 @@ + 'INTEGER', + 'SMALLINT' => 'INTEGER', + 'INT' => 'BIGINT', + 'INTEGER' => 'BIGINT', + 'BIGINT' => 'NUMERIC', + 'FLOAT' => 'DOUBLE', + 'REAL' => 'DOUBLE' + ); + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('DROP', 'ADD'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table); + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + if ($field[$i]['_literal'] !== FALSE) + { + $sqls[] = $sql.' CHANGE '.$field[$i]['_literal']; + } + else + { + $alter_type = empty($field[$i]['new_name']) ? ' MODIFY ' : ' CHANGE '; + $sqls[] = $sql.$alter_type.$this->_process_column($field[$i]); + } + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _process_column($field) + { + $extra_clause = isset($field['after']) + ? ' AFTER '.$this->db->escape_identifiers($field['after']) : ''; + + if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE) + { + $extra_clause = ' FIRST'; + } + + return $this->db->escape_identifiers($field['name']) + .(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name'])) + .' '.$field['type'].$field['length'] + .$field['unsigned'] + .$field['null'] + .$field['default'] + .$field['auto_increment'] + .$field['unique'] + .$extra_clause; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'LONGTEXT': + $attributes['TYPE'] = 'STRING'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Process indexes + * + * @param string $table (ignored) + * @return string + */ + protected function _process_indexes($table) + { + $sql = ''; + + for ($i = 0, $c = count($this->keys); $i < $c; $i++) + { + if (is_array($this->keys[$i])) + { + for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) + { + if ( ! isset($this->fields[$this->keys[$i][$i2]])) + { + unset($this->keys[$i][$i2]); + continue; + } + } + } + elseif ( ! isset($this->fields[$this->keys[$i]])) + { + unset($this->keys[$i]); + continue; + } + + is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]); + + $sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i])) + .' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')'; + } + + $this->keys = array(); + + return $sql; + } + +} diff --git a/system/database/drivers/cubrid/cubrid_result.php b/system/database/drivers/cubrid/cubrid_result.php new file mode 100644 index 00000000..251b70a6 --- /dev/null +++ b/system/database/drivers/cubrid/cubrid_result.php @@ -0,0 +1,177 @@ +num_rows) + ? $this->num_rows + : $this->num_rows = cubrid_num_rows($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @return int + */ + public function num_fields() + { + return cubrid_num_fields($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @return array + */ + public function list_fields() + { + return cubrid_column_names($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @return array + */ + public function field_data() + { + $retval = array(); + + for ($i = 0, $c = $this->num_fields(); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = cubrid_field_name($this->result_id, $i); + $retval[$i]->type = cubrid_field_type($this->result_id, $i); + $retval[$i]->max_length = cubrid_field_len($this->result_id, $i); + $retval[$i]->primary_key = (int) (strpos(cubrid_field_flags($this->result_id, $i), 'primary_key') !== FALSE); + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return void + */ + public function free_result() + { + if (is_resource($this->result_id) OR + (get_resource_type($this->result_id) === 'Unknown' && preg_match('/Resource id #/', strval($this->result_id)))) + { + cubrid_close_request($this->result_id); + $this->result_id = FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Data Seek + * + * Moves the internal pointer to the desired offset. We call + * this internally before fetching results to make sure the + * result set starts at zero. + * + * @param int $n + * @return bool + */ + public function data_seek($n = 0) + { + return cubrid_data_seek($this->result_id, $n); + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @return array + */ + protected function _fetch_assoc() + { + return cubrid_fetch_assoc($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @param string $class_name + * @return object + */ + protected function _fetch_object($class_name = 'stdClass') + { + return cubrid_fetch_object($this->result_id, $class_name); + } + +} diff --git a/system/database/drivers/cubrid/cubrid_utility.php b/system/database/drivers/cubrid/cubrid_utility.php new file mode 100644 index 00000000..555ae7a9 --- /dev/null +++ b/system/database/drivers/cubrid/cubrid_utility.php @@ -0,0 +1,79 @@ +db->data_cache['db_names'])) + { + return $this->db->data_cache['db_names']; + } + + return $this->db->data_cache['db_names'] = cubrid_list_dbs($this->db->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * CUBRID Export + * + * @param array Preferences + * @return mixed + */ + protected function _backup($params = array()) + { + // No SQL based support in CUBRID as of version 8.4.0. Database or + // table backup can be performed using CUBRID Manager + // database administration tool. + return $this->db->display_error('db_unsupported_feature'); + } +} diff --git a/system/database/drivers/cubrid/index.html b/system/database/drivers/cubrid/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/system/database/drivers/cubrid/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

    Directory access is forbidden.

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

    Directory access is forbidden.

    + + + diff --git a/system/database/drivers/index.html b/system/database/drivers/index.html index c942a79c..b702fbc3 100644 --- a/system/database/drivers/index.html +++ b/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/system/database/drivers/mssql/index.html b/system/database/drivers/mssql/index.html index c942a79c..b702fbc3 100644 --- a/system/database/drivers/mssql/index.html +++ b/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/system/database/drivers/mssql/mssql_driver.php b/system/database/drivers/mssql/mssql_driver.php index 5048c0b4..cb89f3b3 100644 --- a/system/database/drivers/mssql/mssql_driver.php +++ b/system/database/drivers/mssql/mssql_driver.php @@ -1,100 +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; } // -------------------------------------------------------------------- @@ -102,30 +143,26 @@ 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; + $this->data_cache = array(); + 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; } // -------------------------------------------------------------------- @@ -133,30 +170,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); } // -------------------------------------------------------------------- @@ -164,29 +183,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'); } // -------------------------------------------------------------------- @@ -194,24 +195,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'); } // -------------------------------------------------------------------- @@ -219,60 +207,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'); } // -------------------------------------------------------------------- @@ -280,92 +219,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(); - 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"; } // -------------------------------------------------------------------- @@ -375,22 +278,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'); } // -------------------------------------------------------------------- @@ -400,138 +303,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 occurred. * - * @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; } // -------------------------------------------------------------------- @@ -541,51 +380,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; } // -------------------------------------------------------------------- @@ -595,51 +416,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 OR strpos(implode(',', $this->qb_select), '*') !== FALSE) + { + $select = '*'; // Inevitable + } + else + { + // Use only field names and their aliases, everything else is out of our scope. + $select = array(); + $field_regexp = ($this->_quoted_identifier) + ? '("[^\"]+")' : '(\[[^\]]+\])'; + for ($i = 0, $c = count($this->qb_select); $i < $c; $i++) + { + $select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m) + ? $m[1] : $this->qb_select[$i]; + } + $select = implode(', ', $select); + } + + return 'SELECT '.$select." FROM (\n\n" + .preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql) + ."\n\n) ".$this->escape_identifiers('CI_subquery') + ."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit; + } + + return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql); + } + + // -------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string|bool + */ + protected function _insert_batch($table, $keys, $values) + { + // Multiple-value inserts are only supported as of SQL Server 2008 + if (version_compare($this->version(), '10', '>=')) + { + return parent::_insert_batch($table, $keys, $values); + } + + return ($this->db_debug) ? $this->display_error('db_unsupported_feature') : FALSE; } // -------------------------------------------------------------------- @@ -647,18 +508,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/system/database/drivers/mssql/mssql_forge.php b/system/database/drivers/mssql/mssql_forge.php index 70b20ecf..6b610986 100644 --- a/system/database/drivers/mssql/mssql_forge.php +++ b/system/database/drivers/mssql/mssql_forge.php @@ -1,248 +1,151 @@ - '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; + if (isset($attributes['CONSTRAINT']) && strpos($attributes['TYPE'], 'INT') !== FALSE) + { + unset($attributes['CONSTRAINT']); + } + + switch (strtoupper($attributes['TYPE'])) + { + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'INTEGER': + $attributes['TYPE'] = 'INT'; + return; + default: return; + } } // -------------------------------------------------------------------- /** - * 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/system/database/drivers/mssql/mssql_result.php b/system/database/drivers/mssql/mssql_result.php index 2897ca5a..38a0a057 100644 --- a/system/database/drivers/mssql/mssql_result.php +++ b/system/database/drivers/mssql/mssql_result.php @@ -1,40 +1,65 @@ -result_id); + return is_int($this->num_rows) + ? $this->num_rows + : $this->num_rows = mssql_num_rows($this->result_id); } // -------------------------------------------------------------------- @@ -42,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); } // -------------------------------------------------------------------- @@ -57,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; @@ -78,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; @@ -104,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)) { @@ -120,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); } @@ -139,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); } @@ -154,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/system/database/drivers/mssql/mssql_utility.php b/system/database/drivers/mssql/mssql_utility.php index 48ecbc72..95ce88f1 100644 --- a/system/database/drivers/mssql/mssql_utility.php +++ b/system/database/drivers/mssql/mssql_utility.php @@ -1,88 +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/system/database/drivers/mysql/index.html b/system/database/drivers/mysql/index.html index c942a79c..b702fbc3 100644 --- a/system/database/drivers/mysql/index.html +++ b/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/system/database/drivers/mysql/mysql_driver.php b/system/database/drivers/mysql/mysql_driver.php index 4ff9b0a1..71dad676 100644 --- a/system/database/drivers/mysql/mysql_driver.php +++ b/system/database/drivers/mysql/mysql_driver.php @@ -1,91 +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; } // -------------------------------------------------------------------- @@ -96,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) { @@ -112,12 +195,24 @@ 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; + $this->data_cache = array(); + return TRUE; + } + + return FALSE; } // -------------------------------------------------------------------- @@ -125,27 +220,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) { - return @mysql_query("SET NAMES '".$this->escape_str($charset)."' COLLATE '".$this->escape_str($collation)."'", $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; } // -------------------------------------------------------------------- @@ -153,14 +255,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); } // -------------------------------------------------------------------- @@ -170,20 +270,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; @@ -194,30 +290,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 } // -------------------------------------------------------------------- @@ -225,25 +303,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; } // -------------------------------------------------------------------- @@ -251,69 +321,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-dependent 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('mysql_real_escape_string') AND is_resource($this->conn_id)) - { - $str = mysql_real_escape_string($str, $this->conn_id); - } - elseif (function_exists('mysql_escape_string')) - { - $str = mysql_escape_string($str); - } - else - { - $str = addslashes($str); - } - - // escape LIKE condition wildcards - if ($like === TRUE) - { - $str = str_replace(array('%', '_'), array('\\%', '\\_'), $str); - } - - return $str; + return mysql_real_escape_string($str, $this->conn_id); } // -------------------------------------------------------------------- @@ -321,12 +352,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); } // -------------------------------------------------------------------- @@ -334,42 +364,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(); - return (int) $row->numrows; + return mysql_insert_id($this->conn_id); } // -------------------------------------------------------------------- @@ -379,17 +378,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; @@ -402,343 +400,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 "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 occurred. + * + * @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); } // -------------------------------------------------------------------- @@ -746,17 +482,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/system/database/drivers/mysql/mysql_forge.php b/system/database/drivers/mysql/mysql_forge.php index 529ec980..7ed8f8d3 100644 --- a/system/database/drivers/mysql/mysql_forge.php +++ b/system/database/drivers/mysql/mysql_forge.php @@ -1,269 +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']).' '; - } - - if (array_key_exists('TYPE', $attributes)) - { - $sql .= ' '.$attributes['TYPE']; - - if (array_key_exists('CONSTRAINT', $attributes)) - { - switch ($attributes['TYPE']) - { - case 'decimal': - case 'float': - case 'numeric': - $sql .= '('.implode(',', $attributes['CONSTRAINT']).')'; - break; - - case 'enum': - case 'set': - $sql .= '("'.implode('","', $attributes['CONSTRAINT']).'")'; - break; - - default: - $sql .= '('.$attributes['CONSTRAINT'].')'; - } - } - } - - if (array_key_exists('UNSIGNED', $attributes) && $attributes['UNSIGNED'] === TRUE) - { - $sql .= ' UNSIGNED'; - } - - if (array_key_exists('DEFAULT', $attributes)) - { - $sql .= ' DEFAULT \''.$attributes['DEFAULT'].'\''; - } - - if (array_key_exists('NULL', $attributes)) - { - $sql .= ($attributes['NULL'] === TRUE) ? ' NULL' : ' NOT NULL'; - } - - if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE) - { - $sql .= ' AUTO_INCREMENT'; - } - } - - // don't add a comma on the end of the last field - if (++$current_field_count < count($fields)) - { - $sql .= ','; - } - } - - return $sql; - } - - // -------------------------------------------------------------------- - - /** - * Create Table - * - * @access private - * @param string the table name - * @param mixed the fields - * @param mixed primary key(s) - * @param mixed key(s) - * @param boolean should 'IF NOT EXISTS' be added to the SQL - * @return bool - */ - function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists) - { - $sql = 'CREATE TABLE '; - - if ($if_not_exists === TRUE) - { - $sql .= 'IF NOT EXISTS '; - } - - $sql .= $this->db->_escape_identifiers($table)." ("; - - $sql .= $this->_process_fields($fields); - - if (count($primary_keys) > 0) - { - $key_name = $this->db->_protect_identifiers(implode('_', $primary_keys)); - $primary_keys = $this->db->_protect_identifiers($primary_keys); - $sql .= ",\n\tPRIMARY KEY ".$key_name." (" . implode(', ', $primary_keys) . ")"; - } - - if (is_array($keys) && count($keys) > 0) - { - foreach ($keys as $key) - { - if (is_array($key)) - { - $key_name = $this->db->_protect_identifiers(implode('_', $key)); - $key = $this->db->_protect_identifiers($key); + $field[$i]['_literal'] = "\n\tADD "; } else { - $key_name = $this->db->_protect_identifiers($key); - $key = array($key_name); + $field[$i]['_literal'] = empty($field[$i]['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE "; } - $sql .= ",\n\tKEY {$key_name} (" . implode(', ', $key) . ")"; + $field[$i] = $field[$i]['_literal'].$this->_process_column($field[$i]); } } - $sql .= "\n) DEFAULT CHARACTER SET {$this->db->char_set} COLLATE {$this->db->dbcollat};"; - - return $sql; + return array($sql.implode(',', $field)); } // -------------------------------------------------------------------- /** - * Drop Table + * Process column * - * @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 mysql_forge.php */ -/* Location: ./system/database/drivers/mysql/mysql_forge.php */ \ No newline at end of file diff --git a/system/database/drivers/mysql/mysql_result.php b/system/database/drivers/mysql/mysql_result.php index 50738960..7aa265eb 100644 --- a/system/database/drivers/mysql/mysql_result.php +++ b/system/database/drivers/mysql/mysql_result.php @@ -1,19 +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; } // -------------------------------------------------------------------- @@ -42,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); } // -------------------------------------------------------------------- @@ -57,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; @@ -78,22 +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_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; - $F->primary_key = $field->primary_key; - - $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; @@ -104,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)) { @@ -120,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; } // -------------------------------------------------------------------- @@ -139,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); } @@ -154,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/system/database/drivers/mysql/mysql_utility.php b/system/database/drivers/mysql/mysql_utility.php index e9747c54..bc01fc58 100644 --- a/system/database/drivers/mysql/mysql_utility.php +++ b/system/database/drivers/mysql/mysql_utility.php @@ -1,83 +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; } @@ -87,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) @@ -107,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; @@ -123,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; @@ -146,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) @@ -177,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 @@ -193,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/system/database/drivers/mysqli/index.html b/system/database/drivers/mysqli/index.html index c942a79c..b702fbc3 100644 --- a/system/database/drivers/mysqli/index.html +++ b/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/system/database/drivers/mysqli/mysqli_driver.php b/system/database/drivers/mysqli/mysqli_driver.php index ccdabce1..b59e8949 100644 --- a/system/database/drivers/mysqli/mysqli_driver.php +++ b/system/database/drivers/mysqli/mysqli_driver.php @@ -1,91 +1,222 @@ -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); + $hostname = ($persistent === TRUE) + ? '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')) + { + $client_flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT; + } + } + + $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_debug) ? $this->display_error($message, '', TRUE) : FALSE; + } + + return $this->_mysqli; + } + + return FALSE; } // -------------------------------------------------------------------- @@ -96,12 +227,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; } @@ -112,12 +242,24 @@ 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; + $this->data_cache = array(); + return TRUE; + } + + return FALSE; } // -------------------------------------------------------------------- @@ -125,27 +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) { - return @mysqli_query($this->conn_id, "SET NAMES '".$this->escape_str($charset)."' COLLATE '".$this->escape_str($collation)."'"); + 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; } // -------------------------------------------------------------------- @@ -153,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)); } // -------------------------------------------------------------------- @@ -171,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; @@ -195,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 } // -------------------------------------------------------------------- @@ -226,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; } // -------------------------------------------------------------------- @@ -252,69 +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-dependent 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('mysqli_real_escape_string') AND is_object($this->conn_id)) - { - $str = mysqli_real_escape_string($this->conn_id, $str); - } - elseif (function_exists('mysql_escape_string')) - { - $str = mysql_escape_string($str); - } - else - { - $str = addslashes($str); - } - - // escape LIKE condition wildcards - if ($like === TRUE) - { - $str = str_replace(array('%', '_'), array('\\%', '\\_'), $str); - } - - return $str; + return $this->conn_id->real_escape_string($str); } // -------------------------------------------------------------------- @@ -322,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; } // -------------------------------------------------------------------- @@ -335,42 +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(); - return (int) $row->numrows; + return $this->conn_id->insert_id; } // -------------------------------------------------------------------- @@ -380,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; @@ -403,319 +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 "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 occurred. + * + * @return array + */ + public function error() + { + if ( ! empty($this->_mysqli->connect_errno)) + { + return array( + 'code' => $this->_mysqli->connect_errno, + 'message' => $this->_mysqli->connect_error + ); + } + + return array('code' => $this->conn_id->errno, 'message' => $this->conn_id->error); + } + + // -------------------------------------------------------------------- + + /** + * 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 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 ( ! 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).")"; - } - - // -------------------------------------------------------------------- - - /** - * 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) - { - $sql .= "LIMIT ".$limit; - - if ($offset > 0) - { - $sql .= " OFFSET ".$offset; - } - - return $sql; + return implode(', ', $this->qb_from); } // -------------------------------------------------------------------- @@ -723,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/system/database/drivers/mysqli/mysqli_forge.php b/system/database/drivers/mysqli/mysqli_forge.php index d5097335..c5b23b6c 100644 --- a/system/database/drivers/mysqli/mysqli_forge.php +++ b/system/database/drivers/mysqli/mysqli_forge.php @@ -1,254 +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)) - { - $sql .= ($attributes['NULL'] === TRUE) ? ' NULL' : ' NOT NULL'; - } - - if (array_key_exists('AUTO_INCREMENT', $attributes) && $attributes['AUTO_INCREMENT'] === TRUE) - { - $sql .= ' AUTO_INCREMENT'; - } - } - - // don't add a comma on the end of the last field - if (++$current_field_count < count($fields)) - { - $sql .= ','; - } - } - - return $sql; - } - - // -------------------------------------------------------------------- - - /** - * Create Table - * - * @access private - * @param string the table name - * @param mixed the fields - * @param mixed primary key(s) - * @param mixed key(s) - * @param boolean should 'IF NOT EXISTS' be added to the SQL - * @return bool - */ - function _create_table($table, $fields, $primary_keys, $keys, $if_not_exists) - { - $sql = 'CREATE TABLE '; - - if ($if_not_exists === TRUE) - { - $sql .= 'IF NOT EXISTS '; - } - - $sql .= $this->db->_escape_identifiers($table)." ("; - - $sql .= $this->_process_fields($fields); - - if (count($primary_keys) > 0) - { - $key_name = $this->db->_protect_identifiers(implode('_', $primary_keys)); - $primary_keys = $this->db->_protect_identifiers($primary_keys); - $sql .= ",\n\tPRIMARY KEY ".$key_name." (" . implode(', ', $primary_keys) . ")"; - } - - if (is_array($keys) && count($keys) > 0) - { - foreach ($keys as $key) - { - if (is_array($key)) - { - $key_name = $this->db->_protect_identifiers(implode('_', $key)); - $key = $this->db->_protect_identifiers($key); + $field[$i]['_literal'] = "\n\tADD "; } else { - $key_name = $this->db->_protect_identifiers($key); - $key = array($key_name); + $field[$i]['_literal'] = empty($field[$i]['new_name']) ? "\n\tMODIFY " : "\n\tCHANGE "; } - $sql .= ",\n\tKEY {$key_name} (" . implode(', ', $key) . ")"; + $field[$i] = $field[$i]['_literal'].$this->_process_column($field[$i]); } } - $sql .= "\n) DEFAULT CHARACTER SET {$this->db->char_set} COLLATE {$this->db->dbcollat};"; - - return $sql; + return array($sql.implode(',', $field)); } // -------------------------------------------------------------------- /** - * Drop Table + * Process column * - * @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/system/database/drivers/mysqli/mysqli_result.php b/system/database/drivers/mysqli/mysqli_result.php index c4d8f5d5..0b3d9c2b 100644 --- a/system/database/drivers/mysqli/mysqli_result.php +++ b/system/database/drivers/mysqli/mysqli_result.php @@ -1,40 +1,65 @@ -result_id); + return is_int($this->num_rows) + ? $this->num_rows + : $this->num_rows = $this->result_id->num_rows; } // -------------------------------------------------------------------- @@ -42,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; } // -------------------------------------------------------------------- @@ -57,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; } @@ -78,22 +102,20 @@ 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_field($this->result_id)) + $field_data = $this->result_id->fetch_fields(); + for ($i = 0, $c = count($field_data); $i < $c; $i++) { - $F = new stdClass(); - $F->name = $field->name; - $F->type = $field->type; - $F->default = $field->def; - $F->max_length = $field->max_length; - $F->primary_key = ($field->flags & MYSQLI_PRI_KEY_FLAG) ? 1 : 0; - - $retval[] = $F; + $retval[$i] = new stdClass(); + $retval[$i]->name = $field_data[$i]->name; + $retval[$i]->type = static::_get_field_type($field_data[$i]->type); + $retval[$i]->max_length = $field_data[$i]->max_length; + $retval[$i]->primary_key = (int) ($field_data[$i]->flags & MYSQLI_PRI_KEY_FLAG); + $retval[$i]->default = $field_data[$i]->def; } return $retval; @@ -101,16 +123,70 @@ class CI_DB_mysqli_result extends CI_DB_result { // -------------------------------------------------------------------- + /** + * Get field type + * + * Extracts field type info from the bitflags returned by + * mysqli_result::fetch_fields() + * + * @used-by CI_DB_mysqli_result::field_data() + * @param int $flags + * @return string + */ + private static function _get_field_type($flags) + { + static $map; + isset($map) OR $map = array( + MYSQLI_TYPE_DECIMAL => 'decimal', + MYSQLI_TYPE_BIT => 'bit', + MYSQLI_TYPE_TINY => 'tinyint', + MYSQLI_TYPE_SHORT => 'smallint', + MYSQLI_TYPE_INT24 => 'mediumint', + MYSQLI_TYPE_LONG => 'int', + MYSQLI_TYPE_LONGLONG => 'bigint', + MYSQLI_TYPE_FLOAT => 'float', + MYSQLI_TYPE_DOUBLE => 'double', + MYSQLI_TYPE_TIMESTAMP => 'timestamp', + MYSQLI_TYPE_DATE => 'date', + MYSQLI_TYPE_TIME => 'time', + MYSQLI_TYPE_DATETIME => 'datetime', + MYSQLI_TYPE_YEAR => 'year', + MYSQLI_TYPE_NEWDATE => 'date', + MYSQLI_TYPE_INTERVAL => 'interval', + MYSQLI_TYPE_ENUM => 'enum', + MYSQLI_TYPE_SET => 'set', + MYSQLI_TYPE_TINY_BLOB => 'tinyblob', + MYSQLI_TYPE_MEDIUM_BLOB => 'mediumblob', + MYSQLI_TYPE_BLOB => 'blob', + MYSQLI_TYPE_LONG_BLOB => 'longblob', + MYSQLI_TYPE_STRING => 'char', + MYSQLI_TYPE_VAR_STRING => 'varchar', + MYSQLI_TYPE_GEOMETRY => 'geometry' + ); + + foreach ($map as $flag => $name) + { + if ($flags & $flag) + { + return $name; + } + } + + return $flags; + } + + // -------------------------------------------------------------------- + /** * 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; } } @@ -120,16 +196,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); } // -------------------------------------------------------------------- @@ -139,12 +215,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(); } // -------------------------------------------------------------------- @@ -154,16 +229,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/system/database/drivers/mysqli/mysqli_utility.php b/system/database/drivers/mysqli/mysqli_utility.php index e17889b8..1699b611 100644 --- a/system/database/drivers/mysqli/mysqli_utility.php +++ b/system/database/drivers/mysqli/mysqli_utility.php @@ -1,87 +1,215 @@ -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] = ($field->type & MYSQLI_TYPE_TINY) + OR ($field->type & MYSQLI_TYPE_SHORT) + OR ($field->type & MYSQLI_TYPE_INT24) + OR ($field->type & MYSQLI_TYPE_LONG) + OR ($field->type & MYSQLI_TYPE_LONGLONG); + + // 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/system/database/drivers/oci8/index.html b/system/database/drivers/oci8/index.html index c942a79c..b702fbc3 100644 --- a/system/database/drivers/oci8/index.html +++ b/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/system/database/drivers/oci8/oci8_driver.php b/system/database/drivers/oci8/oci8_driver.php index 14df104f..fb2f6b31 100644 --- a/system/database/drivers/oci8/oci8_driver.php +++ b/system/database/drivers/oci8/oci8_driver.php @@ -1,32 +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 - */ - function db_connect() - { - return @ocilogon($this->username, $this->password, $this->hostname); - } - - // -------------------------------------------------------------------- - - /** - * Persistent database connection - * - * @access private called by the base class - * @return resource - */ - function db_pconnect() - { - return @ociplogon($this->username, $this->password, $this->hostname); - } - - // -------------------------------------------------------------------- - - /** - * 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 oracle - } - - // -------------------------------------------------------------------- - - /** - * 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 + * @param bool $persistent * @return resource */ - 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 public - * @return string + * @return string */ - function _version() + public function version() { - return ociserverversion($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; } // -------------------------------------------------------------------- @@ -158,63 +269,33 @@ class CI_DB_oci8_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 resource */ - function _execute($sql) + 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); - ocisetprefetch($this->stmt_id, 1000); - return @ociexecute($this->stmt_id, $this->_commit); - } - - /** - * Generate a statement ID - * - * @access private - * @param string an SQL query - * @return none - */ - 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 = ociparse($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 + * @return resource */ - function _prep_query($sql) + public function get_cursor() { - return $sql; - } - - // -------------------------------------------------------------------- - - /** - * getCursor. Returns a cursor from the datbase - * - * @access public - * @return cursor id - */ - function get_cursor() - { - $this->curs_id = ocinewcursor($this->conn_id); - return $this->curs_id; + return $this->curs_id = oci_new_cursor($this->conn_id); } // -------------------------------------------------------------------- @@ -222,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 */ - 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; } // -------------------------------------------------------------------- @@ -275,10 +353,10 @@ class CI_DB_oci8_driver extends CI_DB { /** * Bind parameters * - * @access private - * @return none + * @param array $params + * @return void */ - function _bind_params($params) + protected function _bind_params($params) { if ( ! is_array($params) OR ! is_resource($this->stmt_id)) { @@ -295,7 +373,7 @@ class CI_DB_oci8_driver extends CI_DB { } } - ocibindbyname($this->stmt_id, $param['name'], $param['value'], $param['length'], $param['type']); + oci_bind_by_name($this->stmt_id, $param['name'], $param['value'], $param['length'], $param['type']); } } @@ -304,28 +382,11 @@ class CI_DB_oci8_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->_commit = OCI_DEFAULT; + $this->commit_mode = OCI_NO_AUTO_COMMIT; return TRUE; } @@ -334,25 +395,13 @@ class CI_DB_oci8_driver extends CI_DB { /** * Commit Transaction * - * @access public * @return bool */ - 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 = OCIcommit($this->conn_id); - $this->_commit = OCI_COMMIT_ON_SUCCESS; - return $ret; + return oci_commit($this->conn_id); } // -------------------------------------------------------------------- @@ -360,60 +409,12 @@ class CI_DB_oci8_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 = OCIrollback($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 - */ - 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); } // -------------------------------------------------------------------- @@ -421,12 +422,11 @@ class CI_DB_oci8_driver extends CI_DB { /** * Affected Rows * - * @access public - * @return integer + * @return int */ - function affected_rows() + public function affected_rows() { - return @ocirowcount($this->stmt_id); + return oci_num_rows($this->stmt_id); } // -------------------------------------------------------------------- @@ -434,10 +434,9 @@ class CI_DB_oci8_driver extends CI_DB { /** * Insert ID * - * @access public - * @return integer + * @return int */ - function insert_id() + public function insert_id() { // not supported in oracle return $this->display_error('db_unsupported_function'); @@ -445,52 +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 - */ - 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(); - 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 + * @param bool $prefix_limit + * @return string */ - function _list_tables($prefix_limit = FALSE) + 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; @@ -503,174 +472,141 @@ class CI_DB_oci8_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 - */ - 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 - */ - function _field_data($table) - { - return "SELECT * FROM ".$table." where rownum = 1"; - } - - // -------------------------------------------------------------------- - - /** - * The error message string - * - * @access private - * @return string - */ - function _error_message() - { - $error = ocierror($this->conn_id); - return $error['message']; - } - - // -------------------------------------------------------------------- - - /** - * The error message number - * - * @access private - * @return integer - */ - function _error_number() - { - $error = ocierror($this->conn_id); - return $error['code']; - } - - // -------------------------------------------------------------------- - - /** - * Escape the SQL Identifiers - * - * This function escapes column and table names - * - * @access private - * @param string + * @param string $table * @return string */ - function _escape_identifiers($item) + protected function _list_columns($table = '') { - 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 public - * @param type - * @return type + * @param string $table + * @return array */ - 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 + * + * Returns an array containing code and message of the last + * database error that has occurred. + * + * @return array + */ + public function error() + { + // oci_error() returns an array that already contains + // 'code' and 'message' keys, but it can return false + // if there was no error .... + if (is_resource($this->curs_id)) + { + $error = oci_error($this->curs_id); + } + elseif (is_resource($this->stmt_id)) + { + $error = oci_error($this->stmt_id); + } + elseif (is_resource($this->conn_id)) + { + $error = oci_error($this->conn_id); + } + else + { + $error = oci_error(); + } + + return is_array($error) + ? $error + : array('code' => '', 'message' => ''); + } + + // -------------------------------------------------------------------- + + /** + * 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($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 + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values * @return string */ - function _update($table, $values, $where, $orderby = array(), $limit = FALSE) + protected function _insert_batch($table, $keys, $values) { - foreach ($values as $key => $val) + $keys = implode(', ', $keys); + $sql = "INSERT ALL\n"; + + for ($i = 0, $c = count($values); $i < $c; $i++) { - $valstr[] = $key." = ".$val; + $sql .= ' INTO '.$table.' ('.$keys.') VALUES '.$values[$i]."\n"; } - $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'; } // -------------------------------------------------------------------- @@ -679,16 +615,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 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; } // -------------------------------------------------------------------- @@ -698,60 +634,43 @@ class CI_DB_oci8_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); + $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 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 + * @param string $sql SQL Query + * @return string */ - 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) : ''); } // -------------------------------------------------------------------- @@ -759,19 +678,11 @@ class CI_DB_oci8_driver extends CI_DB { /** * Close DB Connection * - * @access public - * @param resource - * @return void + * @return void */ - function _close($conn_id) + protected function _close() { - @ocilogoff($conn_id); + oci_close($this->conn_id); } - } - - - -/* End of file oci8_driver.php */ -/* Location: ./system/database/drivers/oci8/oci8_driver.php */ \ No newline at end of file diff --git a/system/database/drivers/oci8/oci8_forge.php b/system/database/drivers/oci8/oci8_forge.php index 3cd17585..724a76df 100644 --- a/system/database/drivers/oci8/oci8_forge.php +++ b/system/database/drivers/oci8/oci8_forge.php @@ -1,248 +1,187 @@ -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']) + .' TO '.$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 .= ','; + $field[$i] = "\n\t".$field[$i]['_literal']; } } - if (count($primary_keys) > 0) - { - $primary_keys = $this->db->_protect_identifiers($primary_keys); - $sql .= ",\n\tPRIMARY KEY (" . implode(', ', $primary_keys) . ")"; - } + $sql .= ' '.$alter_type.' '; + $sql .= (count($field) === 1) + ? $field[0] + : '('.implode(',', $field).')'; - if (is_array($keys) && count($keys) > 0) - { - foreach ($keys as $key) - { - if (is_array($key)) - { - $key = $this->db->_protect_identifiers($key); - } - else - { - $key = array($this->db->_protect_identifiers($key)); - } - - $sql .= ",\n\tUNIQUE COLUMNS (" . implode(', ', $key) . ")"; - } - } - - $sql .= "\n)"; - - return $sql; + // RENAME COLUMN must be executed after MODIFY + array_unshift($sqls, $sql); + return $sqls; } // -------------------------------------------------------------------- /** - * 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 + * Field attribute TYPE * - * Generates a platform-specific query so that a table can be altered - * Called by add_column(), drop_column(), and column_alter(), + * Performs a data type mapping between different databases. * - * @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 + * @param array &$attributes + * @return void */ - function _alter_table($alter_type, $table, $column_name, $column_definition = '', $default_value = '', $null = '', $after_field = '') + protected function _attr_type(&$attributes) { - $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') + switch (strtoupper($attributes['TYPE'])) { - return $sql; + case 'TINYINT': + $attributes['TYPE'] = 'NUMBER'; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'NUMBER'; + return; + case 'INT': + $attributes['TYPE'] = 'NUMBER'; + return; + case 'BIGINT': + $attributes['TYPE'] = 'NUMBER'; + return; + default: return; } - - $sql .= " $column_definition"; - - if ($default_value != '') - { - $sql .= " DEFAULT \"$default_value\""; - } - - if ($null === NULL) - { - $sql .= ' NULL'; - } - else - { - $sql .= ' NOT NULL'; - } - - if ($after_field != '') - { - $sql .= ' AFTER ' . $this->db->_protect_identifiers($after_field); - } - - return $sql; - } - - // -------------------------------------------------------------------- - - /** - * Rename a table - * - * Generates a platform-specific query so that a table can be renamed - * - * @access private - * @param string the old table name - * @param string the new table name - * @return string - */ - function _rename_table($table_name, $new_table_name) - { - $sql = 'ALTER TABLE '.$this->db->_protect_identifiers($table_name)." RENAME TO ".$this->db->_protect_identifiers($new_table_name); - return $sql; - } - - } - -/* End of file oci8_forge.php */ -/* Location: ./system/database/drivers/oci8/oci8_forge.php */ \ No newline at end of file diff --git a/system/database/drivers/oci8/oci8_result.php b/system/database/drivers/oci8/oci8_result.php index 88531b43..0c354333 100644 --- a/system/database/drivers/oci8/oci8_result.php +++ b/system/database/drivers/oci8/oci8_result.php @@ -1,19 +1,41 @@ -result_array()); - @ociexecute($this->stmt_id); + parent::__construct($driver_object); - if ($this->curs_id) - { - @ociexecute($this->curs_id); - } - - return $rowcount; + $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; } // -------------------------------------------------------------------- @@ -58,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 */ - function num_fields() + public function num_fields() { - $count = @ocinumcols($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; } // -------------------------------------------------------------------- @@ -81,16 +117,14 @@ class CI_DB_oci8_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(); - $fieldCount = $this->num_fields(); - for ($c = 1; $c <= $fieldCount; $c++) + for ($c = 1, $fieldCount = $this->num_fields(); $c <= $fieldCount; $c++) { - $field_names[] = ocicolumnname($this->stmt_id, $c); + $field_names[] = oci_field_name($this->stmt_id, $c); } return $field_names; } @@ -102,19 +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 */ - function field_data() + public function field_data() { $retval = array(); - $fieldCount = $this->num_fields(); - for ($c = 1; $c <= $fieldCount; $c++) + for ($c = 1, $fieldCount = $this->num_fields(); $c <= $fieldCount; $c++) { - $F = new stdClass(); - $F->name = ocicolumnname($this->stmt_id, $c); - $F->type = ocicolumntype($this->stmt_id, $c); - $F->max_length = ocicolumnsize($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,15 +159,26 @@ class CI_DB_oci8_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)) { - ocifreestatement($this->result_id); + 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,14 +188,12 @@ class CI_DB_oci8_result extends CI_DB_result { * * Returns the result set as an array * - * @access private - * @return array + * @return array */ - function _fetch_assoc(&$row) + protected function _fetch_assoc() { $id = ($this->curs_id) ? $this->curs_id : $this->stmt_id; - - return ocifetchinto($id, $row, OCI_ASSOC + OCI_RETURN_NULLS); + return oci_fetch_assoc($id); } // -------------------------------------------------------------------- @@ -162,88 +203,27 @@ class CI_DB_oci8_result extends CI_DB_result { * * Returns the result set as an object * - * @access private - * @return object + * @param string $class_name + * @return object */ - function _fetch_object() + protected function _fetch_object($class_name = 'stdClass') { - $result = array(); + $row = ($this->curs_id) + ? oci_fetch_object($this->curs_id) + : oci_fetch_object($this->stmt_id); - // If PHP 5 is being used we can fetch an result object - if (function_exists('oci_fetch_object')) + if ($class_name === 'stdClass' OR ! $row) { - $id = ($this->curs_id) ? $this->curs_id : $this->stmt_id; - - return @oci_fetch_object($id); + return $row; } - // If PHP 4 is being used we have to build our own result - foreach ($this->result_array() as $key => $val) + $class_name = new $class_name(); + foreach ($row as $key => $value) { - $obj = new stdClass(); - if (is_array($val)) - { - foreach ($val as $k => $v) - { - $obj->$k = $v; - } - } - else - { - $obj->$key = $val; - } - - $result[] = $obj; + $class_name->$key = $value; } - return $result; - } - - // -------------------------------------------------------------------- - - /** - * Query result. "array" version. - * - * @access public - * @return array - */ - function result_array() - { - if (count($this->result_array) > 0) - { - return $this->result_array; - } - - // oracle's fetch functions do not return arrays. - // The information is returned in reference parameters - $row = NULL; - while ($this->_fetch_assoc($row)) - { - $this->result_array[] = $row; - } - - return $this->result_array; - } - - // -------------------------------------------------------------------- - - /** - * Data Seek - * - * Moves the internal pointer to the desired offset. We call - * this internally before fetching results to make sure the - * result set starts at zero - * - * @access private - * @return array - */ - 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 */ \ No newline at end of file diff --git a/system/database/drivers/oci8/oci8_utility.php b/system/database/drivers/oci8/oci8_utility.php index 854b467e..ce0dfc5f 100644 --- a/system/database/drivers/oci8/oci8_utility.php +++ b/system/database/drivers/oci8/oci8_utility.php @@ -1,87 +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/system/database/drivers/odbc/index.html b/system/database/drivers/odbc/index.html index c942a79c..b702fbc3 100644 --- a/system/database/drivers/odbc/index.html +++ b/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/system/database/drivers/odbc/odbc_driver.php b/system/database/drivers/odbc/odbc_driver.php index 81e0d7cf..ef982fc6 100644 --- a/system/database/drivers/odbc/odbc_driver.php +++ b/system/database/drivers/odbc/odbc_driver.php @@ -1,141 +1,212 @@ -_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); + return ($persistent === TRUE) + ? odbc_pconnect($this->dsn, $this->username, $this->password) + : odbc_connect($this->dsn, $this->username, $this->password); } // -------------------------------------------------------------------- /** - * Persistent database connection + * Compile Bindings * - * @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 + * @param string $sql SQL statement + * @param array $binds An array of values to bind * @return string */ - function _version() + public function compile_binds($sql, $binds) { - return "SELECT version() AS ver"; + 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 ($this->bind_marker !== '?') + { + do + { + $c--; + $sql = substr_replace($sql, '?', $matches[0][$c][1], $ml); + } + while ($c !== 0); + } + + if (FALSE !== ($this->odbc_result = odbc_prepare($this->conn_id, $sql))) + { + $this->binds = array_values($binds); + } + + return $sql; } // -------------------------------------------------------------------- @@ -143,30 +214,30 @@ 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); - } + if ( ! isset($this->odbc_result)) + { + return odbc_exec($this->conn_id, $sql); + } + elseif ($this->odbc_result === FALSE) + { + return FALSE; + } - // -------------------------------------------------------------------- + if (TRUE === ($success = odbc_execute($this->odbc_result, $this->binds))) + { + // For queries that return result sets, return the result_id resource on success + $this->is_write_type($sql) OR $success = $this->odbc_result; + } - /** - * Prep the query - * - * If needed, each database adapter can prep the query string - * - * @access private called by execute() - * @param string an SQL query - * @return string - */ - function _prep_query($sql) - { - return $sql; + $this->odbc_result = NULL; + $this->binds = array(); + + return $success; } // -------------------------------------------------------------------- @@ -174,27 +245,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); } @@ -203,25 +257,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; } // -------------------------------------------------------------------- @@ -229,61 +275,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?.+)*$#is', $sql)) + { + return FALSE; + } + + return parent::is_write_type($sql); + } + + // -------------------------------------------------------------------- + + /** + * Platform-dependent 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; + $this->display_error('db_unsupported_feature'); } // -------------------------------------------------------------------- @@ -291,12 +324,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); } // -------------------------------------------------------------------- @@ -304,42 +336,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(); - return (int) $row->numrows; + return ($this->db_debug) ? $this->display_error('db_unsupported_feature') : FALSE; } // -------------------------------------------------------------------- @@ -349,18 +350,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; @@ -373,13 +373,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; } // -------------------------------------------------------------------- @@ -389,228 +388,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 occurred. + * + * @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).")"; - } - - // -------------------------------------------------------------------- - - /** - * Update statement - * - * Generates a platform-specific update string from the supplied data - * - * @access public - * @param string the table name - * @param array the update data - * @param array the where clause - * @param array the orderby clause - * @param array the limit clause - * @return string - */ - function _update($table, $values, $where, $orderby = array(), $limit = FALSE) - { - foreach ($values as $key => $val) - { - $valstr[] = $key." = ".$val; - } - - $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; - - $orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):''; - - $sql = "UPDATE ".$table." SET ".implode(', ', $valstr); - - $sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : ''; - - $sql .= $orderby.$limit; - - return $sql; - } - - - // -------------------------------------------------------------------- - - /** - * Truncate statement - * - * Generates a platform-specific truncate string from the supplied data - * If the database does not support the truncate() command - * This function maps to "DELETE FROM table" - * - * @access public - * @param string the table name - * @return string - */ - function _truncate($table) - { - return $this->_delete($table); - } - - // -------------------------------------------------------------------- - - /** - * Delete statement - * - * Generates a platform-specific delete string from the supplied data - * - * @access public - * @param string the table name - * @param array the where clause - * @param string the limit clause - * @return string - */ - function _delete($table, $where = array(), $like = array(), $limit = FALSE) - { - $conditions = ''; - - if (count($where) > 0 OR count($like) > 0) - { - $conditions = "\nWHERE "; - $conditions .= implode("\n", $this->ar_where); - - if (count($where) > 0 && count($like) > 0) - { - $conditions .= " AND "; - } - $conditions .= implode("\n", $like); - } - - $limit = ( ! $limit) ? '' : ' LIMIT '.$limit; - - return "DELETE FROM ".$table.$conditions.$limit; - } - - // -------------------------------------------------------------------- - - /** - * Limit string - * - * Generates a platform-specific LIMIT clause - * - * @access public - * @param string the sql query string - * @param integer the number of rows to limit the query to - * @param integer the offset value - * @return string - */ - function _limit($sql, $limit, $offset) - { - // Does ODBC doesn't use the LIMIT clause? - return $sql; + return array('code' => odbc_error($this->conn_id), 'message' => odbc_errormsg($this->conn_id)); } // -------------------------------------------------------------------- @@ -618,19 +416,10 @@ 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/system/database/drivers/odbc/odbc_forge.php b/system/database/drivers/odbc/odbc_forge.php index 3ec86b4e..77b2fdf6 100644 --- a/system/database/drivers/odbc/odbc_forge.php +++ b/system/database/drivers/odbc/odbc_forge.php @@ -1,266 +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/system/database/drivers/odbc/odbc_result.php b/system/database/drivers/odbc/odbc_result.php index 5d64a464..845aa9c7 100644 --- a/system/database/drivers/odbc/odbc_result.php +++ b/system/database/drivers/odbc/odbc_result.php @@ -1,40 +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()); } // -------------------------------------------------------------------- @@ -42,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); } // -------------------------------------------------------------------- @@ -57,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; @@ -78,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; @@ -104,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)) { @@ -117,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); } // -------------------------------------------------------------------- @@ -161,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/system/database/drivers/odbc/odbc_utility.php b/system/database/drivers/odbc/odbc_utility.php index d335bed9..643f6ec0 100644 --- a/system/database/drivers/odbc/odbc_utility.php +++ b/system/database/drivers/odbc/odbc_utility.php @@ -1,103 +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/system/database/drivers/pdo/index.html b/system/database/drivers/pdo/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/system/database/drivers/pdo/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

    Directory access is forbidden.

    + + + diff --git a/system/database/drivers/pdo/pdo_driver.php b/system/database/drivers/pdo/pdo_driver.php new file mode 100644 index 00000000..6afc999c --- /dev/null +++ b/system/database/drivers/pdo/pdo_driver.php @@ -0,0 +1,329 @@ +dsn, $match) && count($match) === 2) + { + // 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'); + + if ($this->db_debug) + { + show_error('Invalid or non-existent PDO subdriver'); + } + } + + $this->dsn = NULL; + } + + // -------------------------------------------------------------------- + + /** + * Database connection + * + * @param bool $persistent + * @return object + */ + public function db_connect($persistent = FALSE) + { + if ($persistent === TRUE) + { + $this->options[PDO::ATTR_PERSISTENT] = TRUE; + } + + 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); + } + + return FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Database version number + * + * @return string + */ + public function 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(); + } + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @param string $sql SQL query + * @return mixed + */ + protected function _execute($sql) + { + return $this->conn_id->query($sql); + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return bool + */ + protected function _trans_begin() + { + return $this->conn_id->beginTransaction(); + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + protected function _trans_commit() + { + return $this->conn_id->commit(); + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + protected function _trans_rollback() + { + return $this->conn_id->rollBack(); + } + + // -------------------------------------------------------------------- + + /** + * Platform-dependent string escape + * + * @param string + * @return string + */ + protected function _escape_str($str) + { + // Escape the string + $str = $this->conn_id->quote($str); + + // If there are duplicated quotes, trim them away + return ($str[0] === "'") + ? substr($str, 1, -1) + : $str; + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @return int + */ + public function affected_rows() + { + return is_object($this->result_id) ? $this->result_id->rowCount() : 0; + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * @param string $name + * @return int + */ + public function insert_id($name = NULL) + { + return $this->conn_id->lastInsertId($name); + } + + // -------------------------------------------------------------------- + + /** + * 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 TOP 1 * FROM '.$this->protect_identifiers($table); + } + + // -------------------------------------------------------------------- + + /** + * Error + * + * Returns an array containing code and message of the last + * database error that has occurred. + * + * @return array + */ + public function error() + { + $error = array('code' => '00000', 'message' => ''); + $pdo_error = $this->conn_id->errorInfo(); + + if (empty($pdo_error[0])) + { + return $error; + } + + $error['code'] = isset($pdo_error[1]) ? $pdo_error[0].'/'.$pdo_error[1] : $pdo_error[0]; + if (isset($pdo_error[2])) + { + $error['message'] = $pdo_error[2]; + } + + return $error; + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table + * @return string + */ + protected function _truncate($table) + { + return 'TRUNCATE TABLE '.$table; + } + +} diff --git a/system/database/drivers/pdo/pdo_forge.php b/system/database/drivers/pdo/pdo_forge.php new file mode 100644 index 00000000..685b6776 --- /dev/null +++ b/system/database/drivers/pdo/pdo_forge.php @@ -0,0 +1,65 @@ +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); + } + elseif (($num_rows = $this->result_id->rowCount()) > 0) + { + return $this->num_rows = $num_rows; + } + + return $this->num_rows = count($this->result_array()); + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @return int + */ + public function num_fields() + { + return $this->result_id->columnCount(); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @return bool + */ + public function list_fields() + { + $field_names = array(); + for ($i = 0, $c = $this->num_fields(); $i < $c; $i++) + { + // 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 $field_names; + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @return array + */ + public function field_data() + { + try + { + $retval = array(); + + for ($i = 0, $c = $this->num_fields(); $i < $c; $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 $retval; + } + catch (Exception $e) + { + if ($this->db->db_debug) + { + return $this->db->display_error('db_unsupported_feature'); + } + + return FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return void + */ + public function free_result() + { + if (is_object($this->result_id)) + { + $this->result_id = FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @return array + */ + protected function _fetch_assoc() + { + return $this->result_id->fetch(PDO::FETCH_ASSOC); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @param string $class_name + * @return object + */ + protected function _fetch_object($class_name = 'stdClass') + { + return $this->result_id->fetchObject($class_name); + } + +} diff --git a/system/database/drivers/pdo/pdo_utility.php b/system/database/drivers/pdo/pdo_utility.php new file mode 100644 index 00000000..5029cac9 --- /dev/null +++ b/system/database/drivers/pdo/pdo_utility.php @@ -0,0 +1,63 @@ +db->display_error('db_unsupported_feature'); + } + +} diff --git a/system/database/drivers/pdo/subdrivers/index.html b/system/database/drivers/pdo/subdrivers/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/system/database/drivers/pdo/subdrivers/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

    Directory access is forbidden.

    + + + diff --git a/system/database/drivers/pdo/subdrivers/pdo_4d_driver.php b/system/database/drivers/pdo/subdrivers/pdo_4d_driver.php new file mode 100644 index 00000000..7eaeaa1f --- /dev/null +++ b/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/system/database/drivers/pdo/subdrivers/pdo_4d_forge.php b/system/database/drivers/pdo/subdrivers/pdo_4d_forge.php new file mode 100644 index 00000000..3f636d3b --- /dev/null +++ b/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/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php b/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php new file mode 100644 index 00000000..fc49e0dd --- /dev/null +++ b/system/database/drivers/pdo/subdrivers/pdo_cubrid_driver.php @@ -0,0 +1,209 @@ +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; + } + + // -------------------------------------------------------------------- + + /** + * 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/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php b/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php new file mode 100644 index 00000000..276cbb6b --- /dev/null +++ b/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php @@ -0,0 +1,230 @@ + 'INTEGER', + 'SMALLINT' => 'INTEGER', + 'INT' => 'BIGINT', + 'INTEGER' => 'BIGINT', + 'BIGINT' => 'NUMERIC', + 'FLOAT' => 'DOUBLE', + 'REAL' => 'DOUBLE' + ); + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('DROP', 'ADD'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table); + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + if ($field[$i]['_literal'] !== FALSE) + { + $sqls[] = $sql.' CHANGE '.$field[$i]['_literal']; + } + else + { + $alter_type = empty($field[$i]['new_name']) ? ' MODIFY ' : ' CHANGE '; + $sqls[] = $sql.$alter_type.$this->_process_column($field[$i]); + } + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Process column + * + * @param array $field + * @return string + */ + protected function _process_column($field) + { + $extra_clause = isset($field['after']) + ? ' AFTER '.$this->db->escape_identifiers($field['after']) : ''; + + if (empty($extra_clause) && isset($field['first']) && $field['first'] === TRUE) + { + $extra_clause = ' FIRST'; + } + + return $this->db->escape_identifiers($field['name']) + .(empty($field['new_name']) ? '' : ' '.$this->db->escape_identifiers($field['new_name'])) + .' '.$field['type'].$field['length'] + .$field['unsigned'] + .$field['null'] + .$field['default'] + .$field['auto_increment'] + .$field['unique'] + .$extra_clause; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + switch (strtoupper($attributes['TYPE'])) + { + case 'TINYINT': + $attributes['TYPE'] = 'SMALLINT'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'LONGTEXT': + $attributes['TYPE'] = 'STRING'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Process indexes + * + * @param string $table (ignored) + * @return string + */ + protected function _process_indexes($table) + { + $sql = ''; + + for ($i = 0, $c = count($this->keys); $i < $c; $i++) + { + if (is_array($this->keys[$i])) + { + for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++) + { + if ( ! isset($this->fields[$this->keys[$i][$i2]])) + { + unset($this->keys[$i][$i2]); + continue; + } + } + } + elseif ( ! isset($this->fields[$this->keys[$i]])) + { + unset($this->keys[$i]); + continue; + } + + is_array($this->keys[$i]) OR $this->keys[$i] = array($this->keys[$i]); + + $sql .= ",\n\tKEY ".$this->db->escape_identifiers(implode('_', $this->keys[$i])) + .' ('.implode(', ', $this->db->escape_identifiers($this->keys[$i])).')'; + } + + $this->keys = array(); + + return $sql; + } + +} diff --git a/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php b/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php new file mode 100644 index 00000000..b9b86f78 --- /dev/null +++ b/system/database/drivers/pdo/subdrivers/pdo_dblib_driver.php @@ -0,0 +1,353 @@ +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) + { + if ($persistent === TRUE) + { + log_message('debug', "dblib driver doesn't support persistent connections"); + } + + $this->conn_id = parent::db_connect(FALSE); + + 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 OR strpos(implode(',', $this->qb_select), '*') !== FALSE) + { + $select = '*'; // Inevitable + } + else + { + // Use only field names and their aliases, everything else is out of our scope. + $select = array(); + $field_regexp = ($this->_quoted_identifier) + ? '("[^\"]+")' : '(\[[^\]]+\])'; + for ($i = 0, $c = count($this->qb_select); $i < $c; $i++) + { + $select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m) + ? $m[1] : $this->qb_select[$i]; + } + $select = implode(', ', $select); + } + + return 'SELECT '.$select." FROM (\n\n" + .preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql) + ."\n\n) ".$this->escape_identifiers('CI_subquery') + ."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit; + } + + return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql); + } + + // -------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string|bool + */ + protected function _insert_batch($table, $keys, $values) + { + // Multiple-value inserts are only supported as of SQL Server 2008 + if (version_compare($this->version(), '10', '>=')) + { + return parent::_insert_batch($table, $keys, $values); + } + + return ($this->db_debug) ? $this->display_error('db_unsupported_feature') : FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Database version number + * + * @return string + */ + public function version() + { + if (isset($this->data_cache['version'])) + { + return $this->data_cache['version']; + } + + return $this->data_cache['version'] = $this->conn_id->query("SELECT SERVERPROPERTY('ProductVersion') AS ver")->fetchColumn(0); + } +} diff --git a/system/database/drivers/pdo/subdrivers/pdo_dblib_forge.php b/system/database/drivers/pdo/subdrivers/pdo_dblib_forge.php new file mode 100644 index 00000000..d0cca38d --- /dev/null +++ b/system/database/drivers/pdo/subdrivers/pdo_dblib_forge.php @@ -0,0 +1,149 @@ + 'SMALLINT', + 'SMALLINT' => 'INT', + 'INT' => 'BIGINT', + 'REAL' => 'FLOAT' + ); + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('ADD', 'DROP'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN '; + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + $sqls[] = $sql.$this->_process_column($field[$i]); + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + if (isset($attributes['CONSTRAINT']) && strpos($attributes['TYPE'], 'INT') !== FALSE) + { + unset($attributes['CONSTRAINT']); + } + + switch (strtoupper($attributes['TYPE'])) + { + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'INTEGER': + $attributes['TYPE'] = 'INT'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE) + { + $field['auto_increment'] = ' IDENTITY(1,1)'; + } + } + +} diff --git a/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php b/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php new file mode 100644 index 00000000..cb93f19b --- /dev/null +++ b/system/database/drivers/pdo/subdrivers/pdo_firebird_driver.php @@ -0,0 +1,279 @@ +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); + } + + // -------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string|bool + */ + protected function _insert_batch($table, $keys, $values) + { + return ($this->db_debug) ? $this->display_error('db_unsupported_feature') : FALSE; + } +} diff --git a/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php b/system/database/drivers/pdo/subdrivers/pdo_firebird_forge.php new file mode 100644 index 00000000..20c5a689 --- /dev/null +++ b/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/system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php b/system/database/drivers/pdo/subdrivers/pdo_ibm_driver.php new file mode 100644 index 00000000..26b556a7 --- /dev/null +++ b/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/system/database/drivers/pdo/subdrivers/pdo_ibm_forge.php b/system/database/drivers/pdo/subdrivers/pdo_ibm_forge.php new file mode 100644 index 00000000..4238ca08 --- /dev/null +++ b/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/system/database/drivers/pdo/subdrivers/pdo_informix_driver.php b/system/database/drivers/pdo/subdrivers/pdo_informix_driver.php new file mode 100644 index 00000000..050171f6 --- /dev/null +++ b/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/system/database/drivers/pdo/subdrivers/pdo_informix_forge.php b/system/database/drivers/pdo/subdrivers/pdo_informix_forge.php new file mode 100644 index 00000000..2ddc2a93 --- /dev/null +++ b/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/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php b/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php new file mode 100644 index 00000000..64b13d82 --- /dev/null +++ b/system/database/drivers/pdo/subdrivers/pdo_mysql_driver.php @@ -0,0 +1,374 @@ +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) + { + $this->dsn .= ';charset='.$this->char_set; + } + } + + // -------------------------------------------------------------------- + + /** + * Database connection + * + * @param bool $persistent + * @return object + */ + public function db_connect($persistent = FALSE) + { + 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; + } + + if (is_array($this->encrypt)) + { + $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_debug) ? $this->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; + $this->data_cache = array(); + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return bool + */ + protected function _trans_begin() + { + $this->conn_id->setAttribute(PDO::ATTR_AUTOCOMMIT, FALSE); + return $this->conn_id->beginTransaction(); + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + protected function _trans_commit() + { + if ($this->conn_id->commit()) + { + $this->conn_id->setAttribute(PDO::ATTR_AUTOCOMMIT, TRUE); + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + protected function _trans_rollback() + { + if ($this->conn_id->rollBack()) + { + $this->conn_id->setAttribute(PDO::ATTR_AUTOCOMMIT, TRUE); + 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/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php b/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php new file mode 100644 index 00000000..c7a92b82 --- /dev/null +++ b/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/system/database/drivers/pdo/subdrivers/pdo_oci_driver.php b/system/database/drivers/pdo/subdrivers/pdo_oci_driver.php new file mode 100644 index 00000000..abf9167d --- /dev/null +++ b/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/system/database/drivers/pdo/subdrivers/pdo_oci_forge.php b/system/database/drivers/pdo/subdrivers/pdo_oci_forge.php new file mode 100644 index 00000000..813207b8 --- /dev/null +++ b/system/database/drivers/pdo/subdrivers/pdo_oci_forge.php @@ -0,0 +1,176 @@ +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']) + .' TO '.$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 + } + + /** + * 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'] = 'NUMBER'; + return; + case 'MEDIUMINT': + $attributes['TYPE'] = 'NUMBER'; + return; + case 'INT': + $attributes['TYPE'] = 'NUMBER'; + return; + case 'BIGINT': + $attributes['TYPE'] = 'NUMBER'; + return; + default: return; + } + } +} diff --git a/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php b/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php new file mode 100644 index 00000000..066dd961 --- /dev/null +++ b/system/database/drivers/pdo/subdrivers/pdo_odbc_driver.php @@ -0,0 +1,229 @@ +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;'); + } + } + + // -------------------------------------------------------------------- + + /** + * Platform-dependent string escape + * + * @param string + * @return string + */ + protected function _escape_str($str) + { + $this->display_error('db_unsupported_feature'); + } + + // -------------------------------------------------------------------- + + /** + * 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?.+)*$#is', $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); + } +} diff --git a/system/database/drivers/pdo/subdrivers/pdo_odbc_forge.php b/system/database/drivers/pdo/subdrivers/pdo_odbc_forge.php new file mode 100644 index 00000000..a2a3bada --- /dev/null +++ b/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?.+)*$#is', $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]['value']; + + foreach (array_keys($val) as $field) + { + if ($field !== $index) + { + $final[$val[$field]['field']][] = 'WHEN '.$val[$index]['value'].' THEN '.$val[$field]['value']; + } + } + } + + $cases = ''; + foreach ($final as $k => $v) + { + $cases .= $k.' = (CASE '.$val[$index]['field']."\n" + .implode("\n", $v)."\n" + .'ELSE '.$k.' END), '; + } + + $this->where($val[$index]['field'].' IN('.implode(',', $ids).')', NULL, FALSE); + + return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where'); + } + + // -------------------------------------------------------------------- + + /** + * 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/system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php b/system/database/drivers/pdo/subdrivers/pdo_pgsql_forge.php new file mode 100644 index 00000000..b00af4ad --- /dev/null +++ b/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 lengths 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/system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php b/system/database/drivers/pdo/subdrivers/pdo_sqlite_driver.php new file mode 100644 index 00000000..9b70f3ea --- /dev/null +++ b/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/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php b/system/database/drivers/pdo/subdrivers/pdo_sqlite_forge.php new file mode 100644 index 00000000..18c475b1 --- /dev/null +++ b/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/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php b/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_driver.php new file mode 100644 index 00000000..a9fb4d14 --- /dev/null +++ b/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 OR strpos(implode(',', $this->qb_select), '*') !== FALSE) + { + $select = '*'; // Inevitable + } + else + { + // Use only field names and their aliases, everything else is out of our scope. + $select = array(); + $field_regexp = ($this->_quoted_identifier) + ? '("[^\"]+")' : '(\[[^\]]+\])'; + for ($i = 0, $c = count($this->qb_select); $i < $c; $i++) + { + $select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m) + ? $m[1] : $this->qb_select[$i]; + } + $select = implode(', ', $select); + } + + return 'SELECT '.$select." FROM (\n\n" + .preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql) + ."\n\n) ".$this->escape_identifiers('CI_subquery') + ."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit; + } + + return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql); + } + + // -------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string|bool + */ + protected function _insert_batch($table, $keys, $values) + { + // Multiple-value inserts are only supported as of SQL Server 2008 + if (version_compare($this->version(), '10', '>=')) + { + return parent::_insert_batch($table, $keys, $values); + } + + return ($this->db_debug) ? $this->display_error('db_unsupported_feature') : FALSE; + } + +} diff --git a/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_forge.php b/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_forge.php new file mode 100644 index 00000000..82a0d515 --- /dev/null +++ b/system/database/drivers/pdo/subdrivers/pdo_sqlsrv_forge.php @@ -0,0 +1,149 @@ + 'SMALLINT', + 'SMALLINT' => 'INT', + 'INT' => 'BIGINT', + 'REAL' => 'FLOAT' + ); + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('ADD', 'DROP'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN '; + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + $sqls[] = $sql.$this->_process_column($field[$i]); + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + if (isset($attributes['CONSTRAINT']) && strpos($attributes['TYPE'], 'INT') !== FALSE) + { + unset($attributes['CONSTRAINT']); + } + + switch (strtoupper($attributes['TYPE'])) + { + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'INTEGER': + $attributes['TYPE'] = 'INT'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE) + { + $field['auto_increment'] = ' IDENTITY(1,1)'; + } + } + +} diff --git a/system/database/drivers/postgre/index.html b/system/database/drivers/postgre/index.html index c942a79c..b702fbc3 100644 --- a/system/database/drivers/postgre/index.html +++ b/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/system/database/drivers/postgre/postgre_driver.php b/system/database/drivers/postgre/postgre_driver.php index 47ff3624..bcdfc060 100644 --- a/system/database/drivers/postgre/postgre_driver.php +++ b/system/database/drivers/postgre/postgre_driver.php @@ -1,102 +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; } // -------------------------------------------------------------------- @@ -107,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) { @@ -120,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(); } // -------------------------------------------------------------------- @@ -166,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); } // -------------------------------------------------------------------- @@ -197,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'); } // -------------------------------------------------------------------- @@ -226,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'); } // -------------------------------------------------------------------- @@ -250,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?.+)*$#is', $sql)) + { + return FALSE; + } + + return parent::is_write_type($sql); + } + + // -------------------------------------------------------------------- + + /** + * Platform-dependent 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); } // -------------------------------------------------------------------- @@ -309,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); } // -------------------------------------------------------------------- @@ -322,70 +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(); - return (int) $row->numrows; + $query = $query->row(); + return (int) $query->ins_id; } // -------------------------------------------------------------------- @@ -395,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; @@ -414,141 +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 occurred. + * + * @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).")"; + return parent::order_by($orderby, $direction, $escape); } // -------------------------------------------------------------------- @@ -558,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]['value']; + + foreach (array_keys($val) as $field) + { + if ($field !== $index) + { + $final[$val[$field]['field']][] = 'WHEN '.$val[$index]['value'].' THEN '.$val[$field]['value']; + } + } + } + + $cases = ''; + foreach ($final as $k => $v) + { + $cases .= $k.' = (CASE '.$val[$index]['field']."\n" + .implode("\n", $v)."\n" + .'ELSE '.$k.' END), '; + } + + $this->where($val[$index]['field'].' IN('.implode(',', $ids).')', NULL, FALSE); + + return 'UPDATE '.$table.' SET '.substr($cases, 0, -2).$this->_compile_wh('qb_where'); } // -------------------------------------------------------------------- @@ -611,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 : ''); } // -------------------------------------------------------------------- @@ -667,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/system/database/drivers/postgre/postgre_forge.php b/system/database/drivers/postgre/postgre_forge.php index 91a1c686..cdbff4c4 100644 --- a/system/database/drivers/postgre/postgre_forge.php +++ b/system/database/drivers/postgre/postgre_forge.php @@ -1,299 +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 lengths 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/system/database/drivers/postgre/postgre_result.php b/system/database/drivers/postgre/postgre_result.php index e9a1d160..57864a7f 100644 --- a/system/database/drivers/postgre/postgre_result.php +++ b/system/database/drivers/postgre/postgre_result.php @@ -1,40 +1,65 @@ -result_id); + return is_int($this->num_rows) + ? $this->num_rows + : $this->num_rows = pg_num_rows($this->result_id); } // -------------------------------------------------------------------- @@ -42,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); } // -------------------------------------------------------------------- @@ -57,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); } @@ -78,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; @@ -104,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)) { @@ -120,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); } @@ -139,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); } @@ -154,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/system/database/drivers/postgre/postgre_utility.php b/system/database/drivers/postgre/postgre_utility.php index 741c52ea..5ca358da 100644 --- a/system/database/drivers/postgre/postgre_utility.php +++ b/system/database/drivers/postgre/postgre_utility.php @@ -1,88 +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/system/database/drivers/sqlite/index.html b/system/database/drivers/sqlite/index.html index c942a79c..b702fbc3 100644 --- a/system/database/drivers/sqlite/index.html +++ b/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/system/database/drivers/sqlite/sqlite_driver.php b/system/database/drivers/sqlite/sqlite_driver.php index eb4e585b..03c96e44 100644 --- a/system/database/drivers/sqlite/sqlite_driver.php +++ b/system/database/drivers/sqlite/sqlite_driver.php @@ -1,73 +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; } @@ -75,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(); } // -------------------------------------------------------------------- @@ -160,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); } // -------------------------------------------------------------------- @@ -191,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'); } // -------------------------------------------------------------------- @@ -221,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'); } // -------------------------------------------------------------------- @@ -246,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); } // -------------------------------------------------------------------- @@ -306,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); } @@ -319,42 +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(); - return (int) $row->numrows; + return sqlite_last_insert_rowid($this->conn_id); } // -------------------------------------------------------------------- @@ -364,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; } @@ -386,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; @@ -399,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; } // -------------------------------------------------------------------- @@ -640,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/system/database/drivers/sqlite/sqlite_forge.php b/system/database/drivers/sqlite/sqlite_forge.php index 56904082..a0fc0cdb 100644 --- a/system/database/drivers/sqlite/sqlite_forge.php +++ b/system/database/drivers/sqlite/sqlite_forge.php @@ -1,37 +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/system/database/drivers/sqlite/sqlite_result.php b/system/database/drivers/sqlite/sqlite_result.php index 7bd30db7..34d3ac3c 100644 --- a/system/database/drivers/sqlite/sqlite_result.php +++ b/system/database/drivers/sqlite/sqlite_result.php @@ -1,19 +1,41 @@ -result_id); + return is_int($this->num_rows) + ? $this->num_rows + : $this->num_rows = @sqlite_num_rows($this->result_id); } // -------------------------------------------------------------------- @@ -42,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); } @@ -57,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; @@ -78,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; @@ -101,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); } @@ -135,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); } @@ -150,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/system/database/drivers/sqlite/sqlite_utility.php b/system/database/drivers/sqlite/sqlite_utility.php index 508023e2..90ca4b16 100644 --- a/system/database/drivers/sqlite/sqlite_utility.php +++ b/system/database/drivers/sqlite/sqlite_utility.php @@ -1,96 +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/system/database/drivers/sqlite3/index.html b/system/database/drivers/sqlite3/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/system/database/drivers/sqlite3/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

    Directory access is forbidden.

    + + + diff --git a/system/database/drivers/sqlite3/sqlite3_driver.php b/system/database/drivers/sqlite3/sqlite3_driver.php new file mode 100644 index 00000000..d131baad --- /dev/null +++ b/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-dependent 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 occurred. + * + * @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/system/database/drivers/sqlite3/sqlite3_forge.php b/system/database/drivers/sqlite3/sqlite3_forge.php new file mode 100644 index 00000000..5ee6daae --- /dev/null +++ b/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/system/database/drivers/sqlite3/sqlite3_result.php b/system/database/drivers/sqlite3/sqlite3_result.php new file mode 100644 index 00000000..03751f0d --- /dev/null +++ b/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/system/database/drivers/sqlite3/sqlite3_utility.php b/system/database/drivers/sqlite3/sqlite3_utility.php new file mode 100644 index 00000000..20d562f9 --- /dev/null +++ b/system/database/drivers/sqlite3/sqlite3_utility.php @@ -0,0 +1,61 @@ +db->display_error('db_unsupported_feature'); + } + +} diff --git a/system/database/drivers/sqlsrv/index.html b/system/database/drivers/sqlsrv/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/system/database/drivers/sqlsrv/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

    Directory access is forbidden.

    + + + diff --git a/system/database/drivers/sqlsrv/sqlsrv_driver.php b/system/database/drivers/sqlsrv/sqlsrv_driver.php new file mode 100644 index 00000000..4edcc7fb --- /dev/null +++ b/system/database/drivers/sqlsrv/sqlsrv_driver.php @@ -0,0 +1,543 @@ +scrollable === NULL) + { + $this->scrollable = defined('SQLSRV_CURSOR_CLIENT_BUFFERED') + ? SQLSRV_CURSOR_CLIENT_BUFFERED + : FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Database connection + * + * @param bool $pooling + * @return resource + */ + public function db_connect($pooling = FALSE) + { + $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 === 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'])) + { + unset($connection['UID'], $connection['PWD']); + } + + 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('[', ']'); + } + + return $this->conn_id; + } + + // -------------------------------------------------------------------- + + /** + * Select the database + * + * @param string $database + * @return bool + */ + public function db_select($database = '') + { + if ($database === '') + { + $database = $this->database; + } + + if ($this->_execute('USE '.$this->escape_identifiers($database))) + { + $this->database = $database; + $this->data_cache = array(); + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Execute the query + * + * @param string $sql an SQL query + * @return resource + */ + protected function _execute($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)); + } + + // -------------------------------------------------------------------- + + /** + * Begin Transaction + * + * @return bool + */ + protected function _trans_begin() + { + return sqlsrv_begin_transaction($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Commit Transaction + * + * @return bool + */ + protected function _trans_commit() + { + return sqlsrv_commit($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Rollback Transaction + * + * @return bool + */ + protected function _trans_rollback() + { + return sqlsrv_rollback($this->conn_id); + } + + // -------------------------------------------------------------------- + + /** + * Affected Rows + * + * @return int + */ + public function affected_rows() + { + return sqlsrv_rows_affected($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Insert ID + * + * Returns the last id created in the Identity column. + * + * @return string + */ + public function insert_id() + { + 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 $this->data_cache['version']; + } + + if ( ! $this->conn_id OR ($info = sqlsrv_server_info($this->conn_id)) === FALSE) + { + return FALSE; + } + + return $this->data_cache['version'] = $info['SQLServerVersion']; + } + + // -------------------------------------------------------------------- + + /** + * List table query + * + * Generates a platform-specific query string so that the table names can be fetched + * + * @param bool + * @return string $prefix_limit + */ + 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->_escape_like_str, $this->_escape_like_chr); + } + + return $sql.' ORDER BY '.$this->escape_identifiers('name'); + } + + // -------------------------------------------------------------------- + + /** + * List column query + * + * Generates a platform-specific query string so that the column names can be fetched + * + * @param string $table + * @return string + */ + protected function _list_columns($table = '') + { + return 'SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.Columns + WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); + } + + // -------------------------------------------------------------------- + + /** + * Returns an object with field data + * + * @param string $table + * @return array + */ + public function field_data($table) + { + $sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT + FROM INFORMATION_SCHEMA.Columns + WHERE UPPER(TABLE_NAME) = '.$this->escape(strtoupper($table)); + + if (($query = $this->query($sql)) === FALSE) + { + return FALSE; + } + $query = $query->result_object(); + + $retval = array(); + for ($i = 0, $c = count($query); $i < $c; $i++) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $query[$i]->COLUMN_NAME; + $retval[$i]->type = $query[$i]->DATA_TYPE; + $retval[$i]->max_length = ($query[$i]->CHARACTER_MAXIMUM_LENGTH > 0) ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION; + $retval[$i]->default = $query[$i]->COLUMN_DEFAULT; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Error + * + * Returns an array containing code and message of the last + * database error that has occurred. + * + * @return array + */ + public function error() + { + $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; + } + + // -------------------------------------------------------------------- + + /** + * Update statement + * + * Generates a platform-specific update string from the supplied data + * + * @param string $table + * @param array $values + * @return string + */ + protected function _update($table, $values) + { + $this->qb_limit = FALSE; + $this->qb_orderby = array(); + return parent::_update($table, $values); + } + + // -------------------------------------------------------------------- + + /** + * Truncate statement + * + * Generates a platform-specific truncate string from the supplied data + * + * If the database does not support the TRUNCATE statement, + * then this method maps to 'DELETE FROM table' + * + * @param string $table + * @return string + */ + protected function _truncate($table) + { + return 'TRUNCATE TABLE '.$table; + } + + // -------------------------------------------------------------------- + + /** + * Delete statement + * + * Generates a platform-specific delete string from the supplied data + * + * @param string $table + * @return string + */ + protected function _delete($table) + { + if ($this->qb_limit) + { + return 'WITH ci_delete AS (SELECT TOP '.$this->qb_limit.' * FROM '.$table.$this->_compile_wh('qb_where').') DELETE FROM ci_delete'; + } + + return parent::_delete($table); + } + + // -------------------------------------------------------------------- + + /** + * LIMIT + * + * Generates a platform-specific LIMIT clause + * + * @param string $sql SQL Query + * @return string + */ + protected function _limit($sql) + { + // 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 OR strpos(implode(',', $this->qb_select), '*') !== FALSE) + { + $select = '*'; // Inevitable + } + else + { + // Use only field names and their aliases, everything else is out of our scope. + $select = array(); + $field_regexp = ($this->_quoted_identifier) + ? '("[^\"]+")' : '(\[[^\]]+\])'; + for ($i = 0, $c = count($this->qb_select); $i < $c; $i++) + { + $select[] = preg_match('/(?:\s|\.)'.$field_regexp.'$/i', $this->qb_select[$i], $m) + ? $m[1] : $this->qb_select[$i]; + } + $select = implode(', ', $select); + } + + return 'SELECT '.$select." FROM (\n\n" + .preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER('.trim($orderby).') AS '.$this->escape_identifiers('CI_rownum').', ', $sql) + ."\n\n) ".$this->escape_identifiers('CI_subquery') + ."\nWHERE ".$this->escape_identifiers('CI_rownum').' BETWEEN '.($this->qb_offset + 1).' AND '.$limit; + } + + return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP '.$limit.' ', $sql); + } + + // -------------------------------------------------------------------- + + /** + * Insert batch statement + * + * Generates a platform-specific insert string from the supplied data. + * + * @param string $table Table name + * @param array $keys INSERT keys + * @param array $values INSERT values + * @return string|bool + */ + protected function _insert_batch($table, $keys, $values) + { + // Multiple-value inserts are only supported as of SQL Server 2008 + if (version_compare($this->version(), '10', '>=')) + { + return parent::_insert_batch($table, $keys, $values); + } + + return ($this->db_debug) ? $this->display_error('db_unsupported_feature') : FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Close DB Connection + * + * @return void + */ + protected function _close() + { + sqlsrv_close($this->conn_id); + } + +} diff --git a/system/database/drivers/sqlsrv/sqlsrv_forge.php b/system/database/drivers/sqlsrv/sqlsrv_forge.php new file mode 100644 index 00000000..aa8490ee --- /dev/null +++ b/system/database/drivers/sqlsrv/sqlsrv_forge.php @@ -0,0 +1,149 @@ + 'SMALLINT', + 'SMALLINT' => 'INT', + 'INT' => 'BIGINT', + 'REAL' => 'FLOAT' + ); + + // -------------------------------------------------------------------- + + /** + * ALTER TABLE + * + * @param string $alter_type ALTER type + * @param string $table Table name + * @param mixed $field Column definition + * @return string|string[] + */ + protected function _alter_table($alter_type, $table, $field) + { + if (in_array($alter_type, array('ADD', 'DROP'), TRUE)) + { + return parent::_alter_table($alter_type, $table, $field); + } + + $sql = 'ALTER TABLE '.$this->db->escape_identifiers($table).' ALTER COLUMN '; + $sqls = array(); + for ($i = 0, $c = count($field); $i < $c; $i++) + { + $sqls[] = $sql.$this->_process_column($field[$i]); + } + + return $sqls; + } + + // -------------------------------------------------------------------- + + /** + * Field attribute TYPE + * + * Performs a data type mapping between different databases. + * + * @param array &$attributes + * @return void + */ + protected function _attr_type(&$attributes) + { + if (isset($attributes['CONSTRAINT']) && strpos($attributes['TYPE'], 'INT') !== FALSE) + { + unset($attributes['CONSTRAINT']); + } + + switch (strtoupper($attributes['TYPE'])) + { + case 'MEDIUMINT': + $attributes['TYPE'] = 'INTEGER'; + $attributes['UNSIGNED'] = FALSE; + return; + case 'INTEGER': + $attributes['TYPE'] = 'INT'; + return; + default: return; + } + } + + // -------------------------------------------------------------------- + + /** + * Field attribute AUTO_INCREMENT + * + * @param array &$attributes + * @param array &$field + * @return void + */ + protected function _attr_auto_increment(&$attributes, &$field) + { + if ( ! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === TRUE && stripos($field['type'], 'int') !== FALSE) + { + $field['auto_increment'] = ' IDENTITY(1,1)'; + } + } + +} diff --git a/system/database/drivers/sqlsrv/sqlsrv_result.php b/system/database/drivers/sqlsrv/sqlsrv_result.php new file mode 100644 index 00000000..f784ebea --- /dev/null +++ b/system/database/drivers/sqlsrv/sqlsrv_result.php @@ -0,0 +1,193 @@ +scrollable = $driver_object->scrollable; + } + + // -------------------------------------------------------------------- + + /** + * Number of rows in the result set + * + * @return int + */ + public function num_rows() + { + // 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); + } + + // -------------------------------------------------------------------- + + /** + * Number of fields in the result set + * + * @return int + */ + public function num_fields() + { + return @sqlsrv_num_fields($this->result_id); + } + + // -------------------------------------------------------------------- + + /** + * Fetch Field Names + * + * Generates an array of column names + * + * @return array + */ + public function list_fields() + { + $field_names = array(); + foreach (sqlsrv_field_metadata($this->result_id) as $offset => $field) + { + $field_names[] = $field['Name']; + } + + return $field_names; + } + + // -------------------------------------------------------------------- + + /** + * Field data + * + * Generates an array of objects containing field meta-data + * + * @return array + */ + public function field_data() + { + $retval = array(); + foreach (sqlsrv_field_metadata($this->result_id) as $i => $field) + { + $retval[$i] = new stdClass(); + $retval[$i]->name = $field['Name']; + $retval[$i]->type = $field['Type']; + $retval[$i]->max_length = $field['Size']; + } + + return $retval; + } + + // -------------------------------------------------------------------- + + /** + * Free the result + * + * @return void + */ + public function free_result() + { + if (is_resource($this->result_id)) + { + sqlsrv_free_stmt($this->result_id); + $this->result_id = FALSE; + } + } + + // -------------------------------------------------------------------- + + /** + * Result - associative array + * + * Returns the result set as an array + * + * @return array + */ + protected function _fetch_assoc() + { + return sqlsrv_fetch_array($this->result_id, SQLSRV_FETCH_ASSOC); + } + + // -------------------------------------------------------------------- + + /** + * Result - object + * + * Returns the result set as an object + * + * @param string $class_name + * @return object + */ + protected function _fetch_object($class_name = 'stdClass') + { + return sqlsrv_fetch_object($this->result_id, $class_name); + } + +} diff --git a/system/database/drivers/sqlsrv/sqlsrv_utility.php b/system/database/drivers/sqlsrv/sqlsrv_utility.php new file mode 100644 index 00000000..19c93d0c --- /dev/null +++ b/system/database/drivers/sqlsrv/sqlsrv_utility.php @@ -0,0 +1,77 @@ +db->display_error('db_unsupported_feature'); + } + +} diff --git a/system/database/index.html b/system/database/index.html index c942a79c..b702fbc3 100644 --- a/system/database/index.html +++ b/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/system/fonts/index.html b/system/fonts/index.html index c942a79c..b702fbc3 100644 --- a/system/fonts/index.html +++ b/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/system/fonts/texb.ttf b/system/fonts/texb.ttf index 6792342a33277106ecd28a130476b2aeb0983af2..383c88b86b7c17e2e284732af48b2bfc359647ae 100644 GIT binary patch delta 59402 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 E%A)ovwIx96Fph>n4U`fq29X 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 delta 68545 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 Hbw>R!N diff --git a/system/helpers/array_helper.php b/system/helpers/array_helper.php index 075a31fd..74c7c15a 100644 --- a/system/helpers/array_helper.php +++ b/system/helpers/array_helper.php @@ -1,19 +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; } @@ -82,21 +105,15 @@ 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 (in_array(substr($filename, -4), array('.jpg', '.png')) + && (str_replace(array('.jpg', '.png'), '', $filename) + $expiration) < $now) { - $name = str_replace(".jpg", "", $filename); - - if (($name + $expiration) < $now) - { - @unlink($img_path.$filename); - } + @unlink($img_path.$filename); } } @@ -106,141 +123,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/system/helpers/cookie_helper.php b/system/helpers/cookie_helper.php index 7cee0282..b943edba 100644 --- a/system/helpers/cookie_helper.php +++ b/system/helpers/cookie_helper.php @@ -1,19 +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/system/helpers/date_helper.php b/system/helpers/date_helper.php index 553e8d7e..bb150426 100644 --- a/system/helpers/date_helper.php +++ b/system/helpers/date_helper.php @@ -1,19 +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:%m:%i 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; @@ -206,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; @@ -218,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; @@ -230,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; } @@ -291,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); } @@ -410,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; @@ -421,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); @@ -490,122 +490,253 @@ 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. + * + * @deprecated 3.1.3 Use DateTime::createFromFormat($input_format, $input)->format($output_format); + * @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{8}$/i', $bad_date, $matches)) + { + return DateTime::createFromFormat('Ymd', $bad_date)->format($format); + } - 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(); + + $from = new DateTime(); + $from->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; + } +} diff --git a/system/helpers/directory_helper.php b/system/helpers/directory_helper.php index 38347fa6..2785241e 100644 --- a/system/helpers/directory_helper.php +++ b/system/helpers/directory_helper.php @@ -1,19 +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 { @@ -74,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/system/helpers/download_helper.php b/system/helpers/download_helper.php index 6cecd0d1..b2a1458d 100644 --- a/system/helpers/download_helper.php +++ b/system/helpers/download_helper.php @@ -1,19 +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)) { @@ -93,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)) { @@ -130,49 +136,44 @@ 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)) + $filepath = $path.DIRECTORY_SEPARATOR.$filename; + + if (is_dir($filepath) && $filename[0] !== '.' && ! is_link($filepath)) { - // Ignore empty folders - if (substr($filename, 0, 1) != '.') - { - delete_files($path.DIRECTORY_SEPARATOR.$filename, $del_dir, $level + 1); - } + delete_files($filepath, $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($filepath); } } } - @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(); @@ -188,42 +189,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(); @@ -238,49 +238,46 @@ if ( ! function_exists('get_dir_file_info')) $source_dir = rtrim(realpath($source_dir), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; } - // foreach (scandir($source_dir, 1) as $file) // In addition to being PHP5+, scandir() is simply not as fast + // 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; @@ -296,7 +293,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; @@ -311,8 +308,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); @@ -329,95 +325,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 ( ! require_once(APPPATH.'config/mimes.php')) + $mimes = get_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 } @@ -427,19 +415,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; } @@ -447,24 +435,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/system/helpers/form_helper.php b/system/helpers/form_helper.php index 53230979..13f19631 100644 --- a/system/helpers/form_helper.php +++ b/system/helpers/form_helper.php @@ -1,19 +1,41 @@ -config->site_url($CI->uri->uri_string()); } - - if ($action && strpos($action, '://') === FALSE) + // If an action is not a full URL then turn it into one + elseif (strpos($action, '://') === FALSE) { $action = $CI->config->site_url($action); } - $form = '
    config->item('csrf_protection') === TRUE) + if (stripos($attributes, 'method=') === FALSE) { - $hidden[$CI->security->csrf_token_name] = $CI->security->csrf_hash; + $attributes .= ' method="post"'; } - if (is_array($hidden) AND count($hidden) > 0) + if (stripos($attributes, 'accept-charset=') === FALSE) { - $form .= sprintf("\n
    %s
    ", form_hidden($hidden)); + $attributes .= ' accept-charset="'.strtolower(config_item('charset')).'"'; + } + + $form = '\n"; + + if (is_array($hidden)) + { + foreach ($hidden as $name => $value) + { + $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 && strpos($action, $CI->config->base_url()) !== FALSE && ! stripos($form, 'method="get"')) + { + // Prepend/append random-length "white noise" around the CSRF + // token input, as a form of protection against BREACH attacks + if (FALSE !== ($noise = $CI->security->get_random_bytes(1))) + { + list(, $noise) = unpack('c', $noise); + } + else + { + $noise = mt_rand(-128, 127); + } + + // Prepend if $noise has a negative value, append if positive, do nothing for zero + $prepend = $append = ''; + if ($noise < 0) + { + $prepend = str_repeat(" ", abs($noise)); + } + elseif ($noise > 0) + { + $append = str_repeat(" ", $noise); + } + + $form .= sprintf( + '%s%s%s', + $prepend, + $CI->security->get_csrf_token_name(), + $CI->security->get_csrf_hash(), + $append, + "\n" + ); } return $form; @@ -77,20 +139,19 @@ 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')) { - function form_open_multipart($action, $attributes = array(), $hidden = array()) + /** + * 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)) { @@ -107,19 +168,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; @@ -135,18 +196,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); } } @@ -157,47 +219,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); } @@ -205,47 +265,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' => '90', 'rows' => '12'); + $defaults = array( + 'name' => is_array($data) ? '' : $data, + 'cols' => '40', + 'rows' => '10' + ); if ( ! is_array($data) OR ! isset($data['value'])) { @@ -257,28 +317,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"'; } @@ -289,91 +350,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']; @@ -396,167 +483,159 @@ 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 mixed Additional attributes + * @return string + */ function form_label($label_text = '', $id = '', $attributes = array()) { $label = ' 0) - { - foreach ($attributes as $key => $val) - { - $label .= ' '.$key.'="'.$val.'"'; - } - } + $label .= _attributes_to_string($attributes); - $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; @@ -565,306 +644,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())) @@ -878,19 +901,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())) @@ -904,18 +926,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)) @@ -939,12 +960,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; @@ -953,54 +978,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.'"'; @@ -1008,45 +1011,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 + // We set this as a variable since we're returning by reference. $return = FALSE; - if ( ! isset($CI->load->_ci_classes) OR ! isset($CI->load->_ci_classes['form_validation'])) + if (FALSE !== ($object = $CI->load->is_loaded('Form_validation'))) { - return $return; + if ( ! isset($CI->$object) OR ! is_object($CI->$object)) + { + return $return; + } + + return $CI->$object; } - $object = $CI->load->_ci_classes['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 */ \ No newline at end of file diff --git a/system/helpers/html_helper.php b/system/helpers/html_helper.php index 68c6f590..87a5f9b2 100644 --- a/system/helpers/html_helper.php +++ b/system/helpers/html_helper.php @@ -1,19 +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); @@ -68,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); @@ -88,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)) @@ -111,21 +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; - } - - // Write the opening list tag - $out .= "<".$type.$attributes.">\n"; // Cycle through the list elements. If an array is // encountered we will recursively call _list() @@ -135,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)) { @@ -144,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) ) { @@ -207,103 +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('#^(data:[a-z,;])|(([a-z]+:)?(?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 ( ! require_once(APPPATH.'config/doctypes.php')) + if (file_exists(APPPATH.'config/doctypes.php')) { + include(APPPATH.'config/doctypes.php'); + } + + 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) { @@ -316,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.'" '; } @@ -339,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 @@ -376,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; } @@ -402,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/system/helpers/index.html b/system/helpers/index.html index c942a79c..b702fbc3 100644 --- a/system/helpers/index.html +++ b/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/system/helpers/inflector_helper.php b/system/helpers/inflector_helper.php index 4cd7486b..4a6805fb 100644 --- a/system/helpers/inflector_helper.php +++ b/system/helpers/inflector_helper.php @@ -1,19 +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', + '/((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', + '/(quiz)zes$/' => '\1', + '/([^us])s$/' => '\1' + ); + + foreach ($singular_rules as $rule => $replacement) + { + if (preg_match($rule, $result)) { - $str = substr($str, 0, strlen($str)-1); + $result = preg_replace($rule, $replacement, $result); + break; } } - return $str; + return $result; } } // -------------------------------------------------------------------- -/** - * 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) { - $str = strtolower(trim($str)); - $end = substr($str, -1); + $result = strval($str); - if ($end == 'y') + if ( ! is_countable($result)) { - // Y preceded by vowel => regular plural - $vowels = array('a', 'e', 'i', 'o', 'u'); - $str = in_array(substr($str, -2, 1), $vowels) ? $str.'s' : substr($str, 0, -1).'ies'; - } - elseif ($end == 'h') - { - if (substr($str, -2) == 'ch' OR substr($str, -2) == 'sh') - { - $str .= 'es'; - } - else - { - $str .= 's'; - } - } - elseif ($end == 's') - { - if ($force == TRUE) - { - $str .= 'es'; - } - } - else - { - $str .= 's'; + return $result; } - return $str; + $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 + '/(x|ch|ss|sh)$/' => '\1es', // search, switch, fix, box, process, address + '/([^aeiouy]|qu)y$/' => '\1ies', // query, ability, agency + '/(hive)$/' => '\1s', // archive, hive + '/(?:([^f])fe|([lr])f)$/' => '\1\2ves', // half, safe, wife + '/sis$/' => 'ses', // basis, diagnosis + '/([ti])um$/' => '\1a', // datum, medium + '/(p)erson$/' => '\1eople', // person, salesperson + '/(m)an$/' => '\1en', // man, woman, spokesman + '/(c)hild$/' => '\1hildren', // child + '/(buffal|tomat)o$/' => '\1\2oes', // buffalo, tomato + '/(bu|campu)s$/' => '\1\2ses', // bus, campus + '/(alias|status|virus)$/' => '\1es', // alias + '/(octop)us$/' => '\1i', // octopus + '/(ax|cris|test)is$/' => '\1es', // axis, crisis + '/s$/' => 's', // no change (compatibility) + '/$/' => 's', + ); + + foreach ($plural_rules as $rule => $replacement) + { + if (preg_match($rule, $result)) + { + $result = preg_replace($rule, $replacement, $result); + break; + } + } + + return $result; } } // -------------------------------------------------------------------- -/** - * 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( + 'audio', + 'bison', + 'chassis', + 'compensation', + 'coreopsis', + 'data', + 'deer', + 'education', + 'emoji', + 'equipment', + 'fish', + 'furniture', + 'gold', + 'information', + 'knowledge', + 'love', + 'rain', + 'money', + 'moose', + 'nutrition', + 'offspring', + 'plankton', + 'pokemon', + 'police', + 'rice', + 'series', + 'sheep', + 'species', + 'swine', + 'traffic', + 'wheat' + ) + ); + } +} diff --git a/system/helpers/language_helper.php b/system/helpers/language_helper.php index ac0d69da..d26cf5b8 100644 --- a/system/helpers/language_helper.php +++ b/system/helpers/language_helper.php @@ -1,19 +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/system/helpers/number_helper.php b/system/helpers/number_helper.php index 61177755..cc8a7760 100644 --- a/system/helpers/number_helper.php +++ b/system/helpers/number_helper.php @@ -1,19 +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 - * - * This is simply an alias for do_hash() - * dohash() is now deprecated - */ -if ( ! function_exists('dohash')) -{ - function dohash($str, $type = 'sha1') - { - return do_hash($str, $type); - } -} - -// -------------------------------------------------------------------- - -/** - * 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())) { - if ( ! function_exists('sha1')) - { - if ( ! function_exists('mhash')) - { - require_once(BASEPATH.'libraries/Sha1'.EXT); - $SH = new CI_SHA; - return $SH->generate($str); - } - else - { - return bin2hex(mhash(MHASH_SHA1, $str)); - } - } - else - { - 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/system/helpers/smiley_helper.php b/system/helpers/smiley_helper.php index 463881f5..2c9a3b4a 100644 --- a/system/helpers/smiley_helper.php +++ b/system/helpers/smiley_helper.php @@ -1,19 +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 @@ -157,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]])) @@ -165,8 +173,7 @@ if ( ! function_exists('get_clickable_smileys')) continue; } - $link[] = "
    \"".$smileys[$key][3]."\""; - + $link[] = ''.$smileys[$key][3].''; $used[$smileys[$key][0]] = TRUE; } @@ -176,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; @@ -217,63 +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 ( ! file_exists(APPPATH.'config/smileys'.EXT)) + static $_smileys; + + if ( ! is_array($_smileys)) { - return FALSE; + 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; } - include(APPPATH.'config/smileys'.EXT); - - if ( ! isset($smileys) OR ! is_array($smileys)) - { - return FALSE; - } - - return $smileys; + 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/system/helpers/string_helper.php b/system/helpers/string_helper.php index 7765bba3..93446b82 100644 --- a/system/helpers/string_helper.php +++ b/system/helpers/string_helper.php @@ -1,19 +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; @@ -83,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); @@ -102,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); @@ -121,147 +140,144 @@ 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 + * + * @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)); } } } // ------------------------------------------------------------------------ -/** - * Alternator - * - * Allows strings to be alternated. See docs... - * - * @access public - * @param string (as many parameters as needed) - * @return string - */ +if ( ! function_exists('increment_string')) +{ + /** + * 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; + } +} + +// ------------------------------------------------------------------------ + 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))]; } @@ -269,22 +285,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/system/helpers/text_helper.php b/system/helpers/text_helper.php index 96afd4ce..217729b7 100644 --- a/system/helpers/text_helper.php +++ b/system/helpers/text_helper.php @@ -1,19 +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; } } } @@ -104,24 +125,23 @@ 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 = ''; + $length = defined('MB_OVERLOAD_STRING') + ? mb_strlen($str, '8bit') - 1 + : strlen($str) - 1; + for ($i = 0, $count = 1, $temp = array(); $i <= $length; $i++) { $ordinal = ord($str[$i]); @@ -131,9 +151,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; } @@ -141,21 +161,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 === $length) + { + $out .= '&#'.implode(';', $temp).';'; + } } } @@ -165,26 +192,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) @@ -194,25 +219,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; @@ -221,21 +247,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)) @@ -253,13 +278,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 + ); + } } } @@ -269,143 +309,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 = ''; // tags - // so we'll replace them with tags. - - if (abs(PHP_VERSION) < 5) - { - $str = str_replace(array(''), array(''), $str); - $str = preg_replace('#color="(.*?)"#', 'style="color: \\1"', $str); - } + $str = highlight_string('', 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 ( ! file_exists(APPPATH.'config/foreign_chars'.EXT)) + static $array_from, $array_to; + + if ( ! is_array($array_from)) { - return $str; + 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); } - include APPPATH.'config/foreign_chars'.EXT; - - 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) @@ -416,58 +459,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 @@ -475,59 +516,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/system/helpers/typography_helper.php b/system/helpers/typography_helper.php index 302bf45c..183e117b 100644 --- a/system/helpers/typography_helper.php +++ b/system/helpers/typography_helper.php @@ -1,19 +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) { - $CI =& get_instance(); - $CI->load->library('security'); - return $CI->security->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/system/helpers/url_helper.php b/system/helpers/url_helper.php index d0516cee..84023aff 100644 --- a/system/helpers/url_helper.php +++ b/system/helpers/url_helper.php @@ -1,19 +1,41 @@ -config->site_url($uri); + return get_instance()->config->site_url($uri, $protocol); } } // ------------------------------------------------------------------------ -/** - * Base URL - * - * Returns the "base_url" item from your config file - * - * @access public - * @return string - */ if ( ! function_exists('base_url')) { - function base_url() + /** + * 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->slash_item('base_url'); + 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(); @@ -86,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.''; @@ -166,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]; } } } @@ -306,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(); } @@ -335,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])); } } } @@ -428,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 ''; } @@ -450,7 +452,7 @@ if ( ! function_exists('prep_url')) if ( ! $url OR ! isset($url['scheme'])) { - $str = 'http://'.$str; + return 'http://'.$str; } return $str; @@ -459,49 +461,46 @@ if ( ! function_exists('prep_url')) // ------------------------------------------------------------------------ -/** - * Create URL Title - * - * Takes a "title" string as input and creates a - * human-friendly URL string with either a dash - * or an underscore as the word separator. - * - * @access public - * @param string the string - * @param string the separator: dash, or underscore - * @return string - */ if ( ! function_exists('url_title')) { - function url_title($str, $separator = 'dash', $lowercase = FALSE) + /** + * 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') { - $search = '_'; - $replace = '-'; + $separator = '-'; } - else + elseif ($separator === 'underscore') { - $search = '-'; - $replace = '_'; + $separator = '_'; } + $q_separator = preg_quote($separator, '#'); + $trans = array( - '&\#\d+?;' => '', - '&\S+?;' => '', - '\s+' => $replace, - '[^a-z0-9\-\._]' => '', - $replace.'+' => $replace, - $replace.'$' => $replace, - '^'.$replace => $replace, - '\.+$' => '' - ); + '&.+?;' => '', + '[^\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(stripslashes($str)); + 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/system/helpers/xml_helper.php b/system/helpers/xml_helper.php index cdd81ad7..a12ee25d 100644 --- a/system/helpers/xml_helper.php +++ b/system/helpers/xml_helper.php @@ -1,19 +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/system/index.html b/system/index.html index c942a79c..b702fbc3 100644 --- a/system/index.html +++ b/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/system/language/english/calendar_lang.php b/system/language/english/calendar_lang.php index 3e631236..77911e98 100644 --- a/system/language/english/calendar_lang.php +++ b/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/system/language/english/migration_lang.php b/system/language/english/migration_lang.php index fdf36ee9..16849609 100644 --- a/system/language/english/migration_lang.php +++ b/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/system/libraries/Cache/Cache.php b/system/libraries/Cache/Cache.php index d3f6105e..267dffb0 100644 --- a/system/libraries/Cache/Cache.php +++ b/system/libraries/Cache/Cache.php @@ -1,68 +1,141 @@ -_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; + } } } // ------------------------------------------------------------------------ /** - * Get + * 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); } // ------------------------------------------------------------------------ @@ -70,15 +143,15 @@ class 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); } // ------------------------------------------------------------------------ @@ -86,12 +159,40 @@ class 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); } // ------------------------------------------------------------------------ @@ -99,7 +200,7 @@ class 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() { @@ -111,8 +212,8 @@ class 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') { @@ -120,52 +221,16 @@ class 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); } // ------------------------------------------------------------------------ @@ -173,44 +238,18 @@ class 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; - } - - return $obj; - } - - // ------------------------------------------------------------------------ } -// End Class - -/* End of file Cache.php */ -/* Location: ./system/libraries/Cache/Cache.php */ \ No newline at end of file diff --git a/system/libraries/Cache/drivers/Cache_apc.php b/system/libraries/Cache/drivers/Cache_apc.php index 4b995c79..c873eb64 100644 --- a/system/libraries/Cache/drivers/Cache_apc.php +++ b/system/libraries/Cache/drivers/Cache_apc.php @@ -1,71 +1,111 @@ -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 * 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; + return ($success === TRUE) ? $data : FALSE; } - // ------------------------------------------------------------------------ - + // ------------------------------------------------------------------------ + /** * 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 Length of time (in seconds) to cache the data + * @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) { - return apc_store($id, array($data, time(), $ttl), $ttl); + return apc_store($id, $data, (int) $ttl); } - + // ------------------------------------------------------------------------ /** * 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 +114,38 @@ class 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,8 +157,8 @@ class 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) { @@ -102,25 +170,35 @@ class Cache_apc extends CI_Driver { /** * 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); - - if (count($stored) !== 3) + $cache_info = apc_cache_info('user', FALSE); + if (empty($cache_info) OR empty($cache_info['cache_list'])) { return FALSE; } - list($data, $time, $ttl) = $stored; + foreach ($cache_info['cache_list'] as &$entry) + { + if ($entry['info'] !== $id) + { + continue; + } - return array( - 'expire' => $time + $ttl, - 'mtime' => $time, - 'data' => $data - ); + $success = FALSE; + $metadata = array( + 'expire' => ($entry['ttl'] ? $entry['mtime'] + $entry['ttl'] : 0), + 'mtime' => $entry['ttl'], + 'data' => apc_fetch($id, $success) + ); + + return ($success === TRUE) ? $metadata : FALSE; + } + + return FALSE; } // ------------------------------------------------------------------------ @@ -129,23 +207,11 @@ class 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 ! function_exists('apc_store')) - { - 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 Class - -/* End of file Cache_apc.php */ -/* Location: ./system/libraries/Cache/drivers/Cache_apc.php */ \ No newline at end of file diff --git a/system/libraries/Cache/drivers/Cache_dummy.php b/system/libraries/Cache/drivers/Cache_dummy.php index 74f68924..c6d9a61f 100644 --- a/system/libraries/Cache/drivers/Cache_dummy.php +++ b/system/libraries/Cache/drivers/Cache_dummy.php @@ -1,68 +1,89 @@ -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 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,24 +87,24 @@ class 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); - return TRUE; + chmod($this->_cache_path.$id, 0640); + return TRUE; } return FALSE; @@ -102,12 +115,68 @@ class 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 is_file($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 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 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,35 +211,33 @@ class 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) { - if ( ! file_exists($this->_cache_path.$id)) + if ( ! is_file($this->_cache_path.$id)) { 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)) { - $data = $data['data']; $mtime = filemtime($this->_cache_path.$id); - if ( ! isset($data['ttl'])) + if ( ! isset($data['ttl'], $data['time'])) { return FALSE; } return array( - 'expire' => $mtime + $data['ttl'], - 'mtime' => $mtime + 'expire' => $data['time'] + $data['ttl'], + 'mtime' => $mtime ); } - + return FALSE; } @@ -180,8 +247,8 @@ class Cache_file extends CI_Driver { * Is supported * * In the file driver, check to see that the cache directory is indeed writable - * - * @return boolean + * + * @return bool */ public function is_supported() { @@ -189,8 +256,31 @@ class Cache_file extends CI_Driver { } // ------------------------------------------------------------------------ -} -// End Class -/* 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/system/libraries/Cache/drivers/Cache_memcached.php b/system/libraries/Cache/drivers/Cache_memcached.php index 5f5a3159..b642a2c0 100644 --- a/system/libraries/Cache/drivers/Cache_memcached.php +++ b/system/libraries/Cache/drivers/Cache_memcached.php @@ -1,55 +1,147 @@ - array( + 'host' => '127.0.0.1', + 'port' => 11211, + 'weight' => 1 + ) + ); - protected $_memcache_conf = array( - 'default' => array( - 'default_host' => '127.0.0.1', - 'default_port' => 11211, - 'default_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 persistence 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,23 +149,38 @@ class 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) { - return $this->_memcached->add($id, array($data, time(), $ttl), $ttl); + if ($raw !== TRUE) + { + $data = array($data, time(), $ttl); + } + + if ($this->_memcached instanceof Memcached) + { + return $this->_memcached->set($id, $data, $ttl); + } + elseif ($this->_memcached instanceof Memcache) + { + return $this->_memcached->set($id, $data, 0, $ttl); + } + + return FALSE; } // ------------------------------------------------------------------------ - + /** * 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) { @@ -81,11 +188,39 @@ class 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() { @@ -97,21 +232,20 @@ class 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(); } // ------------------------------------------------------------------------ - + /** * 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) { @@ -133,77 +267,37 @@ class 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')); } // ------------------------------------------------------------------------ + /** + * 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 && method_exists($this->_memcached, 'quit')) + { + $this->_memcached->quit(); + } + } } -// End Class - -/* End of file Cache_memcached.php */ -/* Location: ./system/libraries/Cache/drivers/Cache_memcached.php */ \ No newline at end of file diff --git a/system/libraries/Cache/drivers/Cache_redis.php b/system/libraries/Cache/drivers/Cache_redis.php new file mode 100644 index 00000000..ac67be07 --- /dev/null +++ b/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/system/libraries/Cache/drivers/Cache_wincache.php b/system/libraries/Cache/drivers/Cache_wincache.php new file mode 100644 index 00000000..f296a5e2 --- /dev/null +++ b/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/system/libraries/Cache/drivers/index.html b/system/libraries/Cache/drivers/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/system/libraries/Cache/drivers/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

    Directory access is forbidden.

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

    Directory access is forbidden.

    + + + diff --git a/system/libraries/Calendar.php b/system/libraries/Calendar.php index 72d228e7..edb0fb4d 100644 --- a/system/libraries/Calendar.php +++ b/system/libraries/Calendar.php @@ -1,19 +1,41 @@ -CI =& get_instance(); + $this->CI->lang->load('calendar'); - if ( ! in_array('calendar_lang'.EXT, $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'); } // -------------------------------------------------------------------- @@ -69,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) { @@ -82,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; } // -------------------------------------------------------------------- @@ -89,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); @@ -123,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) { @@ -137,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']; } // -------------------------------------------------------------------- @@ -256,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'); } @@ -271,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]); } // -------------------------------------------------------------------- @@ -287,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'); } @@ -312,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; @@ -329,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(); @@ -353,7 +438,7 @@ class CI_Calendar { $date['year']--; } - if (strlen($date['month']) == 1) + if (strlen($date['month']) === 1) { $date['month'] = '0'.$date['month']; } @@ -366,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); } // -------------------------------------------------------------------- @@ -399,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' => '
    ' + ); } // -------------------------------------------------------------------- @@ -437,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/system/libraries/Cart.php b/system/libraries/Cart.php index 7f65b48b..734c4342 100644 --- a/system/libraries/Cart.php +++ b/system/libraries/Cart.php @@ -1,19 +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'); } // -------------------------------------------------------------------- @@ -78,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; @@ -99,7 +143,7 @@ class CI_Cart { $save_cart = FALSE; if (isset($items['id'])) { - if ($this->_insert($items) == TRUE) + if (($rowid = $this->_insert($items))) { $save_cart = TRUE; } @@ -108,9 +152,9 @@ 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) == TRUE) + if ($this->_insert($val)) { $save_cart = TRUE; } @@ -119,10 +163,10 @@ 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; + return isset($rowid) ? $rowid : TRUE; } return FALSE; @@ -133,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; @@ -149,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; @@ -157,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; } @@ -173,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; @@ -183,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; @@ -191,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. @@ -215,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 { @@ -230,21 +260,15 @@ 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 TRUE; + return $rowid; } // -------------------------------------------------------------------- @@ -257,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; } @@ -286,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; } @@ -297,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; @@ -311,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; @@ -363,36 +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; + // 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']); - - // 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'] = count($this->_cart_contents); - $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'); @@ -414,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']; } @@ -444,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']); @@ -460,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); } // -------------------------------------------------------------------- @@ -486,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(); } // -------------------------------------------------------------------- @@ -506,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, '.', ','); } // -------------------------------------------------------------------- @@ -529,22 +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 Cart Class - -/* End of file Cart.php */ -/* Location: ./system/libraries/Cart.php */ \ No newline at end of file diff --git a/system/libraries/Driver.php b/system/libraries/Driver.php index 02e093d7..00e8416f 100644 --- a/system/libraries/Driver.php +++ b/system/libraries/Driver.php @@ -1,19 +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; - - if (in_array(strtolower($child_class), 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 (array(APPPATH, BASEPATH) as $path) - { - // and check for case sensitivity of both the parent and child libs - foreach (array(ucfirst($this->lib_name), strtolower($this->lib_name)) as $lib) - { - // loves me some nesting! - foreach (array(ucfirst($child_class), strtolower($child_class)) as $class) - { - $filepath = $path.'libraries/'.$this->lib_name.'/drivers/'.$child_class.EXT; - - 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 @@ -107,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 @@ -124,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); @@ -139,7 +261,7 @@ class CI_Driver { { if ($method->isPublic()) { - $this->methods[] = $method->getName(); + $this->_methods[] = $method->getName(); } } @@ -147,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]; } } @@ -166,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.'()'); } // -------------------------------------------------------------------- @@ -195,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; } } @@ -214,16 +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/system/libraries/Email.php b/system/libraries/Email.php index 6c21f114..0e9cf057 100644 --- a/system/libraries/Email.php +++ b/system/libraries/Email.php @@ -1,19 +1,41 @@ - '1 (Highest)', + 2 => '2 (High)', + 3 => '3 (Normal)', + 4 => '4 (Low)', + 5 => '5 (Lowest)' + ); + + /** + * mbstring.func_overload flag + * + * @var bool + */ + protected static $func_overload; + + // -------------------------------------------------------------------- /** * 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()) { - 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->charset = config_item('charset'); + $this->initialize($config); + $this->_safe_mode = ( ! is_php('5.4') && ini_get('safe_mode')); - log_message('debug', "Email Class Initialized"); + isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); + + log_message('info', 'Email Class Initialized'); } // -------------------------------------------------------------------- @@ -102,12 +407,13 @@ class CI_Email { /** * Initialize preferences * - * @access public - * @param array - * @return void + * @param array $config + * @return CI_Email */ - public function initialize($config = array()) + public function initialize(array $config = array()) { + $this->clear(); + foreach ($config as $key => $val) { if (isset($this->$key)) @@ -124,10 +430,9 @@ 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->charset = strtoupper($this->charset); + $this->_smtp_auth = isset($this->smtp_user[0], $this->smtp_pass[0]); return $this; } @@ -137,30 +442,27 @@ 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('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; @@ -171,25 +473,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)) @@ -199,12 +505,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; } @@ -214,16 +522,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) @@ -231,17 +538,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; @@ -252,9 +563,8 @@ class CI_Email { /** * Set Recipients * - * @access public * @param string - * @return void + * @return CI_Email */ public function to($to) { @@ -266,21 +576,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; } @@ -290,23 +591,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; } @@ -319,34 +618,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; @@ -357,14 +654,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; } @@ -373,13 +669,24 @@ class CI_Email { /** * Set Body * - * @access public * @param string - * @return void + * @return CI_Email */ public function message($body) { - $this->_body = stripslashes(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. + + NOTE: In PHP 5.4 get_magic_quotes_gpc() will always return 0 and + it will probably not exist in future versions at all. + */ + if ( ! is_php('5.4') && get_magic_quotes_gpc()) + { + $this->_body = stripslashes($this->_body); + } + return $this; } @@ -388,31 +695,86 @@ 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(next(explode('.', basename($filename)))); - $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)), + 'multipart' => 'mixed' + ); + return $this; } // -------------------------------------------------------------------- + /** + * Set and return attachment Content-ID + * + * Useful for attached inline pictures + * + * @param string $filename + * @return string + */ + public function attachment_cid($filename) + { + for ($i = 0, $c = count($this->_attachments); $i < $c; $i++) + { + if ($this->_attachments[$i]['name'][0] === $filename) + { + $this->_attachments[$i]['multipart'] = 'related'; + $this->_attachments[$i]['cid'] = uniqid(basename($this->_attachments[$i]['name'][0]).'@'); + return $this->_attachments[$i]['cid']; + } + } + + return FALSE; + } + + // -------------------------------------------------------------------- + /** * Add a Header Item * - * @access private * @param string * @param string - * @return void + * @return CI_Email */ - private 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; } // -------------------------------------------------------------------- @@ -420,24 +782,18 @@ class CI_Email { /** * Convert a String to an Array * - * @access private * @param string * @return array */ - private function _str_to_array($email) + protected function _str_to_array($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; } @@ -446,13 +802,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 == '') ? '' : $str; + $this->alt_message = (string) $str; return $this; } @@ -461,13 +816,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; } @@ -476,13 +830,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; } @@ -491,13 +844,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; } @@ -506,25 +858,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; } @@ -533,20 +872,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; } @@ -555,52 +886,26 @@ 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; } // -------------------------------------------------------------------- - /** - * Set Message Boundary - * - * @access private - * @return void - */ - private function _set_boundaries() - { - $this->_alt_boundary = "B_ALT_".uniqid(''); // multipart/alternative - $this->_atc_boundary = "B_ATC_".uniqid(''); // attachment boundary - } - - // -------------------------------------------------------------------- - /** * Get the Message ID * - * @access private * @return string */ - private function _get_message_id() + 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, '@').'>'; } // -------------------------------------------------------------------- @@ -608,19 +913,13 @@ class CI_Email { /** * Get Mail Protocol * - * @access private - * @param bool - * @return string + * @return mixed */ - private function _get_protocol($return = TRUE) + protected function _get_protocol() { $this->protocol = strtolower($this->protocol); - $this->protocol = ( ! in_array($this->protocol, $this->_protocols, TRUE)) ? 'mail' : $this->protocol; - - if ($return == TRUE) - { - return $this->protocol; - } + in_array($this->protocol, $this->_protocols, TRUE) OR $this->protocol = 'mail'; + return $this->protocol; } // -------------------------------------------------------------------- @@ -628,26 +927,21 @@ class CI_Email { /** * Get Mail Encoding * - * @access private - * @param bool * @return string */ - private function _get_encoding($return = TRUE) + protected function _get_encoding() { - $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($this->charset, $charset) === 0) { $this->_encoding = '7bit'; } } - if ($return == TRUE) - { - return $this->_encoding; - } + return $this->_encoding; } // -------------------------------------------------------------------- @@ -655,20 +949,15 @@ class CI_Email { /** * Get content type (text/html/attachment) * - * @access private * @return string */ - private function _get_content_type() + protected function _get_content_type() { - if ($this->mailtype == 'html' && count($this->_attach_name) == 0) + if ($this->mailtype === 'html') { - return 'html'; + return empty($this->_attachments) ? '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' && ! empty($this->_attachments)) { return 'plain-attach'; } @@ -683,17 +972,16 @@ class CI_Email { /** * Set RFC 822 Date * - * @access private * @return string */ - private function _set_date() + 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); } // -------------------------------------------------------------------- @@ -701,12 +989,11 @@ class CI_Email { /** * Mime message * - * @access private * @return string */ - private function _get_mime_message() + 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.'; } // -------------------------------------------------------------------- @@ -714,7 +1001,6 @@ class CI_Email { /** * Validate Email Address * - * @access public * @param string * @return bool */ @@ -722,7 +1008,7 @@ class CI_Email { { if ( ! is_array($email)) { - $this->_set_error_message('email_must_be_array'); + $this->_set_error_message('lang:email_must_be_array'); return FALSE; } @@ -730,7 +1016,7 @@ class CI_Email { { if ( ! $this->valid_email($val)) { - $this->_set_error_message('email_invalid_address', $val); + $this->_set_error_message('lang:email_invalid_address', $val); return FALSE; } } @@ -743,13 +1029,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 = self::substr($email, 0, ++$atpos).idn_to_ascii(self::substr($email, $atpos)); + } + + return (bool) filter_var($email, FILTER_VALIDATE_EMAIL); } // -------------------------------------------------------------------- @@ -757,7 +1047,6 @@ class CI_Email { /** * Clean Extended Email Address: Joe Smith * - * @access public * @param string * @return string */ @@ -765,28 +1054,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; @@ -797,47 +1072,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 private * @return string */ - private function _get_alt_message() + 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/system/libraries/Migration.php b/system/libraries/Migration.php index b0b3f2c5..2a87d9d7 100644 --- a/system/libraries/Migration.php +++ b/system/libraries/Migration.php @@ -1,19 +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) @@ -57,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, '/').'/'; @@ -68,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()); } } @@ -89,154 +190,166 @@ 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; + } + elseif ( ! is_callable(array($class, $method))) + { + $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->line->lang('migration_none_found'); - return false; + $this->_error_string = $this->lang->line('migration_none_found'); + 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() { @@ -258,28 +371,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); } // -------------------------------------------------------------------- @@ -287,12 +438,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'; } // -------------------------------------------------------------------- @@ -300,13 +451,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 )); } @@ -315,14 +466,12 @@ class CI_Migration { /** * Enable the use of CI super-global * - * @param mixed $var + * @param string $var * @return mixed */ public function __get($var) { return get_instance()->$var; } -} -/* End of file Migration.php */ -/* Location: ./system/libraries/Migration.php */ +} diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php index 1113f862..f26f8a4e 100644 --- a/system/libraries/Pagination.php +++ b/system/libraries/Pagination.php @@ -1,19 +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'); } // -------------------------------------------------------------------- @@ -85,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; } // -------------------------------------------------------------------- @@ -108,111 +394,200 @@ 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 ''; } - // Determine the current page number. - $CI =& get_instance(); + // Check the user defined number of links. + $this->num_links = (int) $this->num_links; - if ($CI->config->item('enable_query_strings') === TRUE OR $this->page_query_string === TRUE) + if ($this->num_links < 0) { - if ($CI->input->get($this->query_string_segment) != 0) - { - $this->cur_page = $CI->input->get($this->query_string_segment); + show_error('Your number of links must be a non-negative number.'); + } - // Prep the current page - no funny business! - $this->cur_page = (int) $this->cur_page; + // 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 control, method, old-school routing options + unset($get['c'], $get['m'], $get[$this->query_string_segment]); + } + else + { + $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. + $base_page = ($this->use_page_numbers) ? 1 : 0; + + // Are we using query strings? + if ($this->page_query_string === TRUE) + { + $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->uri_segment = count($this->CI->uri->segment_array()); + } + + $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) != 0) - { - $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; } - $this->num_links = (int)$this->num_links; - - if ($this->num_links < 1) + // 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)) { - show_error('Your number of links must be a positive number.'); + $this->cur_page = $base_page; } - - if ( ! is_numeric($this->cur_page)) + else { - $this->cur_page = 0; + // 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 ($this->cur_page > $this->total_rows) + // If so, we show the last page. + if ($this->use_page_numbers) + { + if ($this->cur_page > $num_pages) + { + $this->cur_page = $num_pages; + } + } + elseif ($this->cur_page > $this->total_rows) { $this->cur_page = ($num_pages - 1) * $this->per_page; } $uri_page_number = $this->cur_page; - $this->cur_page = floor(($this->cur_page/$this->per_page) + 1); + + // 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 = (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) { - $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; } } @@ -221,59 +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++) { - $i = ($loop * $this->per_page) - $this->per_page; + $i = ($this->use_page_numbers) ? $loop : ($loop * $this->per_page) - $this->per_page; - if ($i >= 0) + $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 == 0) ? '' : $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) { - $output .= $this->next_tag_open.'anchor_class.'href="'.$this->base_url.$this->prefix.($this->cur_page * $this->per_page).$this->suffix.'">'.$this->next_link.''.$this->next_tag_close; + $i = ($this->use_page_numbers) ? $this->cur_page + 1 : $this->cur_page * $this->per_page; + + $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) { - $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/system/libraries/Parser.php b/system/libraries/Parser.php index d223da02..fdd958b2 100644 --- a/system/libraries/Parser.php +++ b/system/libraries/Parser.php @@ -1,19 +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 @@ -44,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); } @@ -53,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); } @@ -72,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; @@ -114,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; @@ -130,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/system/libraries/Profiler.php b/system/libraries/Profiler.php index 8a1f18ce..cb3eaed7 100644 --- a/system/libraries/Profiler.php +++ b/system/libraries/Profiler.php @@ -1,19 +1,41 @@ -CI =& get_instance(); @@ -61,6 +110,7 @@ class CI_Profiler { } $this->set_sections($config); + log_message('info', 'Profiler Class Initialized'); } // -------------------------------------------------------------------- @@ -70,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); } } } @@ -103,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"; } // -------------------------------------------------------------------- @@ -147,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 @@ -177,45 +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) { - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_database').':  '.$db->database.'   '.$this->CI->lang->line('profiler_queries').': '.count($db->queries).'   '; - $output .= "\n"; - $output .= "\n\n\n"; + $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'); - if (count($db->queries) == 0) + $show_hide_js = '('.$this->CI->lang->line('profiler_section_hide').')'; + + if ($hide_queries !== '') { - $output .= "\n"; + $show_hide_js = '('.$this->CI->lang->line('profiler_section_show').')'; + } + + $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" + .'
    ".$this->CI->lang->line('profiler_no_queries')."
    \n"; + + if (count($db->queries) === 0) + { + $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')."
    ".$time."  ".$val."
    ' + .$time.'  ' + .$val."
    \n"; - $output .= "
    "; - + $output .= "\n"; + $count++; } return $output; } - // -------------------------------------------------------------------- /** @@ -225,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.'
    '; } // -------------------------------------------------------------------- @@ -274,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.'
    '; } // -------------------------------------------------------------------- @@ -323,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) + .'
    '; } // -------------------------------------------------------------------- @@ -352,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 + .'
    '; } // -------------------------------------------------------------------- @@ -376,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_usage')."
    "; - } - - $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')) + .'
    '; } // -------------------------------------------------------------------- @@ -407,24 +448,21 @@ class CI_Profiler { */ protected function _compile_http_headers() { - $output = "\n\n"; - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_headers').'  '; - $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 .= "
    ".$header."  ".$val."
    ' + .$header.'  '.$val."
    \n"; - $output .= "
    "; - - return $output; + return $output."\n
    "; } // -------------------------------------------------------------------- @@ -438,28 +476,68 @@ class CI_Profiler { */ protected function _compile_config() { - $output = "\n\n"; - $output .= '
    '; - $output .= "\n"; - $output .= '  '.$this->CI->lang->line('profiler_config').'  '; - $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)) + $pre = ''; + $pre_close = ''; + + if (is_array($val) OR is_object($val)) { $val = print_r($val, TRUE); + + $pre = '
    ' ;
    + 				$pre_close = '
    '; } - $output .= "\n"; + $output .= '\n"; } - $output .= "
    ".$config."  ".htmlspecialchars($val)."
    ' + .$config.'  '.$pre.htmlspecialchars($val, ENT_QUOTES, config_item('charset')).$pre_close."
    \n"; - $output .= "
    "; + return $output."\n
    "; + } - return $output; + // -------------------------------------------------------------------- + + /** + * Compile session userdata + * + * @return string + */ + protected function _compile_session_data() + { + if ( ! isset($this->CI->session)) + { + return; + } + + $output = '
    ' + .'  '.$this->CI->lang->line('profiler_session_data').'  ('.$this->CI->lang->line('profiler_section_show').')' + .''; + + foreach ($this->CI->session->userdata() as $key => $val) + { + $pre = ''; + $pre_close = ''; + + if (is_array($val) OR is_object($val)) + { + $val = print_r($val, TRUE); + + $pre = '
    ' ;
    + 				$pre_close = '
    '; + } + + $output .= '\n"; + } + + return $output."\n
    "; } // -------------------------------------------------------------------- @@ -471,32 +549,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/system/libraries/Security.php b/system/libraries/Security.php deleted file mode 100644 index 58db4e79..00000000 --- a/system/libraries/Security.php +++ /dev/null @@ -1,738 +0,0 @@ - '[removed]', - 'document.write' => '[removed]', - '.parentNode' => '[removed]', - '.innerHTML' => '[removed]', - 'window.location' => '[removed]', - '-moz-binding' => '[removed]', - '' => '-->', - ' '<![CDATA[' - ); - /* never allowed, regex replacement */ - public $never_allowed_regex = array( - "javascript\s*:" => '[removed]', - "expression\s*(\(|&\#40;)" => '[removed]', // CSS and IE - "vbscript\s*:" => '[removed]', // IE, surprise! - "Redirect\s+302" => '[removed]' - ); - - public function __construct() - { - $this->csrf_token_name = (config_item('csrf_token_name')) ? config_item('csrf_token_name') : 'csrf_token_name'; - $this->csrf_cookie_name = (config_item('csrf_cookie_name')) ? config_item('csrf_cookie_name') : 'csrf_cookie_name'; - $this->csrf_expire = (config_item('csrf_expire')) ? config_item('csrf_expire') : 7200; - - // Append application specific cookie prefix to token name - $this->csrf_cookie_name = (config_item('cookie_prefix')) ? config_item('cookie_prefix').$this->csrf_token_name : $this->csrf_token_name; - - // Set the CSRF hash - $this->_csrf_set_hash(); - - log_message('debug', "Security Class Initialized"); - } - - // -------------------------------------------------------------------- - - /** - * Verify Cross Site Request Forgery Protection - * - * @access public - * @return null - */ - public function csrf_verify() - { - // If no POST data exists we will set the CSRF cookie - if (count($_POST) == 0) - { - return $this->csrf_set_cookie(); - } - - // Do the tokens exist in both the _POST and _COOKIE arrays? - if ( ! isset($_POST[$this->csrf_token_name]) OR ! isset($_COOKIE[$this->csrf_cookie_name])) - { - $this->csrf_show_error(); - } - - // Do the tokens match? - if ($_POST[$this->csrf_token_name] != $_COOKIE[$this->csrf_cookie_name]) - { - $this->csrf_show_error(); - } - - // We kill this since we're done and we don't want to polute the _POST array - unset($_POST[$this->csrf_token_name]); - - // Nothing should last forever - unset($_COOKIE[$this->csrf_cookie_name]); - $this->_csrf_set_hash(); - $this->csrf_set_cookie(); - - log_message('debug', "CSRF token verified "); - } - - // -------------------------------------------------------------------- - - /** - * Set Cross Site Request Forgery Protection Cookie - * - * @access public - * @return null - */ - public function csrf_set_cookie() - { - $expire = time() + $this->csrf_expire; - $secure_cookie = (config_item('cookie_secure') === TRUE) ? 1 : 0; - - 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"); - } - - // -------------------------------------------------------------------- - - /** - * Set Cross Site Request Forgery Protection Cookie - * - * @access private - * @return null - */ - private function _csrf_set_hash() - { - if ($this->csrf_hash == '') - { - // If the cookie exists we will use it's value. We don't necessarily want to regenerate it with - // each page load since a page could contain embedded sub-pages causing this feature to fail - if (isset($_COOKIE[$this->csrf_cookie_name]) AND $_COOKIE[$this->csrf_cookie_name] != '') - { - $this->csrf_hash = $_COOKIE[$this->csrf_cookie_name]; - } - else - { - $this->csrf_hash = md5(uniqid(rand(), TRUE)); - } - } - - return $this->csrf_hash; - } - - // -------------------------------------------------------------------- - - /** - * Show CSRF Error - * - * @access public - * @return null - */ - public function csrf_show_error() - { - show_error('The action you have requested is not allowed.'); - } - - // -------------------------------------------------------------------- - - /** - * XSS Clean - * - * Sanitizes data so that Cross Site Scripting Hacks can be - * prevented. This function does a fair amount of work but - * it is extremely thorough, designed to prevent even the - * most obscure XSS attempts. Nothing is ever 100% foolproof, - * of course, but I haven't been able to get anything passed - * the filter. - * - * Note: This function should only be used to deal with data - * upon submission. It's not something that should - * be used for general runtime processing. - * - * This function was based in part on some code and ideas I - * got from Bitflux: http://channel.bitflux.ch/wiki/XSS_Prevention - * - * To help develop this script I used this great list of - * vulnerabilities along with a few other hacks I've - * harvested from examining vulnerabilities in other programs: - * http://ha.ckers.org/xss.html - * - * @access public - * @param mixed string or array - * @return string - */ - public function xss_clean($str, $is_image = FALSE) - { - /* - * Is the string an array? - * - */ - if (is_array($str)) - { - while (list($key) = each($str)) - { - $str[$key] = $this->xss_clean($str[$key]); - } - - return $str; - } - - /* - * Remove Invisible Characters - */ - $str = remove_invisible_characters($str); - - /* - * Protect GET variables in URLs - */ - - // 901119URL5918AMP18930PROTECT8198 - - $str = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-]+)|i', $this->xss_hash()."\\1=\\2", $str); - - /* - * Validate standard character entities - * - * Add a semicolon if missing. We do this to enable - * the conversion of entities to ASCII later. - * - */ - $str = preg_replace('#(&\#?[0-9a-z]{2,})([\x00-\x20])*;?#i', "\\1;\\2", $str); - - /* - * Validate UTF16 two byte encoding (x00) - * - * Just as above, adds a semicolon if missing. - * - */ - $str = preg_replace('#(&\#x?)([0-9A-F]+);?#i',"\\1\\2;",$str); - - /* - * Un-Protect GET variables in URLs - */ - $str = str_replace($this->xss_hash(), '&', $str); - - /* - * URL Decode - * - * Just in case stuff like this is submitted: - * - * Google - * - * Note: Use rawurldecode() so it does not remove plus signs - * - */ - $str = rawurldecode($str); - - /* - * Convert character entities to ASCII - * - * This permits our tests below to work reliably. - * We only convert entities that are within tags since - * these are the ones that will pose security problems. - * - */ - - $str = preg_replace_callback("/[a-z]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str); - - $str = preg_replace_callback("/<\w+.*?(?=>|<|$)/si", array($this, '_decode_entity'), $str); - - /* - * Remove Invisible Characters Again! - */ - $str = remove_invisible_characters($str); - - /* - * Convert all tabs to spaces - * - * This prevents strings like this: ja vascript - * NOTE: we deal with spaces between characters later. - * NOTE: preg_replace was found to be amazingly slow here on large blocks of data, - * so we use str_replace. - * - */ - - if (strpos($str, "\t") !== FALSE) - { - $str = str_replace("\t", ' ', $str); - } - - /* - * Capture converted string for later comparison - */ - $converted_string = $str; - - /* - * Not Allowed Under Any Conditions - */ - - foreach ($this->never_allowed_str as $key => $val) - { - $str = str_replace($key, $val, $str); - } - - foreach ($this->never_allowed_regex as $key => $val) - { - $str = preg_replace("#".$key."#i", $val, $str); - } - - /* - * Makes PHP tags safe - * - * Note: XML tags are inadvertently replaced too: - * - * '), array('<?', '?>'), $str); - } - - /* - * Compact any exploded words - * - * This corrects words like: j a v a s c r i p t - * These words are compacted back to their correct state. - * - */ - $words = array('javascript', 'expression', 'vbscript', 'script', 'applet', 'alert', 'document', 'write', 'cookie', 'window'); - foreach ($words as $word) - { - $temp = ''; - - for ($i = 0, $wordlen = strlen($word); $i < $wordlen; $i++) - { - $temp .= substr($word, $i, 1)."\s*"; - } - - // We only want to do this when it is followed by a non-word character - // That way valid stuff like "dealer to" does not become "dealerto" - $str = preg_replace_callback('#('.substr($temp, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str); - } - - /* - * Remove disallowed Javascript in links or img tags - * We used to do some version comparisons and use of stripos for PHP5, but it is dog slow compared - * to these simplified non-capturing preg_match(), especially if the pattern exists in the string - */ - do - { - $original = $str; - - if (preg_match("/]*?)(>|$)#si", array($this, '_js_link_removal'), $str); - } - - if (preg_match("/]*?)(\s?/?>|$)#si", array($this, '_js_img_removal'), $str); - } - - if (preg_match("/script/i", $str) OR preg_match("/xss/i", $str)) - { - $str = preg_replace("#<(/*)(script|xss)(.*?)\>#si", '[removed]', $str); - } - } - while ($original != $str); - - unset($original); - - /* - * Remove JavaScript Event Handlers - * - * Note: This code is a little blunt. It removes - * the event handler and anything up to the closing >, - * but it's unlikely to be a problem. - * - */ - $event_handlers = array('[^a-z_\-]on\w*','xmlns'); - - if ($is_image === TRUE) - { - /* - * Adobe Photoshop puts XML metadata into JFIF images, including namespacing, - * so we have to allow this for images. -Paul - */ - unset($event_handlers[array_search('xmlns', $event_handlers)]); - } - - $str = preg_replace("#<([^><]+?)(".implode('|', $event_handlers).")(\s*=\s*[^><]*)([><]*)#i", "<\\1\\4", $str); - - /* - * Sanitize naughty HTML elements - * - * If a tag containing any of the words in the list - * below is found, the tag gets converted to entities. - * - * So this: - * Becomes: <blink> - * - */ - $naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|object|plaintext|style|script|textarea|title|video|xml|xss'; - $str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str); - - /* - * Sanitize naughty scripting elements - * - * Similar to above, only instead of looking for - * tags it looks for PHP and JavaScript commands - * that are disallowed. Rather than removing the - * code, it simply converts the parenthesis to entities - * rendering the code un-executable. - * - * For example: eval('some code') - * Becomes: eval('some code') - * - */ - $str = preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', "\\1\\2(\\3)", $str); - - /* - * Final clean up - * - * This adds a bit of extra precaution in case - * something got through the above filters - * - */ - foreach ($this->never_allowed_str as $key => $val) - { - $str = str_replace($key, $val, $str); - } - - foreach ($this->never_allowed_regex as $key => $val) - { - $str = preg_replace("#".$key."#i", $val, $str); - } - - /* - * Images are Handled in a Special Way - * - Essentially, we want to know that after all of the character conversion is done whether - * any unwanted, likely XSS, code was found. If not, we return TRUE, as the image is clean. - * However, if the string post-conversion does not matched the string post-removal of XSS, - * then it fails, as there was unwanted XSS code found and removed/changed during processing. - */ - - if ($is_image === TRUE) - { - if ($str == $converted_string) - { - return TRUE; - } - else - { - return FALSE; - } - } - - log_message('debug', "XSS Filtering completed"); - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Random Hash for protecting URLs - * - * @access public - * @return string - */ - public function xss_hash() - { - if ($this->xss_hash == '') - { - if (phpversion() >= 4.2) - mt_srand(); - else - mt_srand(hexdec(substr(md5(microtime()), -8)) & 0x7fffffff); - - $this->xss_hash = md5(time() + mt_rand(0, 1999999999)); - } - - return $this->xss_hash; - } - - // -------------------------------------------------------------------- - - /** - * Compact Exploded Words - * - * Callback function for xss_clean() to remove whitespace from - * things like j a v a s c r i p t - * - * @access private - * @param type - * @return type - */ - private function _compact_exploded_words($matches) - { - return preg_replace('/\s+/s', '', $matches[1]).$matches[2]; - } - - // -------------------------------------------------------------------- - - /** - * Sanitize Naughty HTML - * - * Callback function for xss_clean() to remove naughty HTML elements - * - * @access private - * @param array - * @return string - */ - private function _sanitize_naughty_html($matches) - { - // encode opening brace - $str = '<'.$matches[1].$matches[2].$matches[3]; - - // encode captured opening or closing brace to prevent recursive vectors - $str .= str_replace(array('>', '<'), array('>', '<'), $matches[4]); - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * JS Link Removal - * - * Callback function for xss_clean() to sanitize links - * This limits the PCRE backtracks, making it more performance friendly - * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in - * PHP 5.2+ on link-heavy strings - * - * @access private - * @param array - * @return string - */ - private function _js_link_removal($match) - { - $attributes = $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1])); - return str_replace($match[1], preg_replace("#href=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|_filter_attributes(str_replace(array('<', '>'), '', $match[1])); - return str_replace($match[1], preg_replace("#src=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|', '<', '\\'), array('>', '<', '\\\\'), $match[0]); - } - - // -------------------------------------------------------------------- - - /** - * Filter Attributes - * - * Filters tag attributes for consistency and safety - * - * @access private - * @param string - * @return string - */ - private function _filter_attributes($str) - { - $out = ''; - - if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches)) - { - foreach ($matches[0] as $match) - { - $out .= preg_replace("#/\*.*?\*/#s", '', $match); - } - } - - return $out; - } - - // -------------------------------------------------------------------- - - /** - * HTML Entity Decode Callback - * - * Used as a callback for XSS Clean - * - * @access private - * @param array - * @return string - */ - private function _decode_entity($match) - { - return $this->entity_decode($match[0], strtoupper(config_item('charset'))); - } - - // -------------------------------------------------------------------- - - /** - * HTML Entities Decode - * - * This function is a replacement for html_entity_decode() - * - * In some versions of PHP the native function does not work - * when UTF-8 is the specified character set, so this gives us - * a work-around. More info here: - * http://bugs.php.net/bug.php?id=25670 - * - * NOTE: html_entity_decode() has a bug in some PHP versions when UTF-8 is the - * character set, and the PHP developers said they were not back porting the - * fix to versions other than PHP 5.x. - * - * @access public - * @param string - * @param string - * @return string - */ - public function entity_decode($str, $charset='UTF-8') - { - if (stristr($str, '&') === FALSE) return $str; - - // The reason we are not using html_entity_decode() by itself is because - // while it is not technically correct to leave out the semicolon - // at the end of an entity most browsers will still interpret the entity - // correctly. html_entity_decode() does not convert entities without - // semicolons, so we are left with our own little solution here. Bummer. - - if (function_exists('html_entity_decode') && (strtolower($charset) != 'utf-8' OR is_php('5.0.0'))) - { - $str = html_entity_decode($str, ENT_COMPAT, $charset); - $str = preg_replace('~&#x(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str); - return preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str); - } - - // Numeric Entities - $str = preg_replace('~&#x(0*[0-9a-f]{2,5});{0,1}~ei', 'chr(hexdec("\\1"))', $str); - $str = preg_replace('~&#([0-9]{2,4});{0,1}~e', 'chr(\\1)', $str); - - // Literal Entities - Slightly slow so we do another check - if (stristr($str, '&') === FALSE) - { - $str = strtr($str, array_flip(get_html_translation_table(HTML_ENTITIES))); - } - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Filename Security - * - * @access public - * @param string - * @return string - */ - public function sanitize_filename($str, $relative_path = FALSE) - { - $bad = array( - "../", - "", - "<", - ">", - "'", - '"', - '&', - '$', - '#', - '{', - '}', - '[', - ']', - '=', - ';', - '?', - "%20", - "%22", - "%3c", // < - "%253c", // < - "%3e", // > - "%0e", // > - "%28", // ( - "%29", // ) - "%2528", // ( - "%26", // & - "%24", // $ - "%3f", // ? - "%3b", // ; - "%3d" // = - ); - - if ( ! $relative_path) - { - $bad[] = './'; - $bad[] = '/'; - } - - return stripslashes(str_replace($bad, '', $str)); - } - -} -// END Security Class - -/* End of file Security.php */ -/* Location: ./system/libraries/Security.php */ \ No newline at end of file diff --git a/system/libraries/Session.php b/system/libraries/Session.php deleted file mode 100644 index 18229405..00000000 --- a/system/libraries/Session.php +++ /dev/null @@ -1,776 +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; - } - - // Decrypt the cookie data - if ($this->sess_encrypt_cookie == TRUE) - { - $session = $this->CI->encrypt->decode($session); - } - else - { - // encryption was not used, so we need to check the md5 hash - $hash = substr($session, strlen($session)-32); // get last 32 chars - $session = substr($session, 0, strlen($session)-32); - - // Does the md5 hash match? This is to prevent manipulation of session data in userspace - if ($hash !== md5($session.$this->encryption_key)) - { - log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.'); - $this->sess_destroy(); - return FALSE; - } - } - - // 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, 50))) - { - $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, 50), - 'last_activity' => $this->now - ); - - - // 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->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 AND 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 - ); - } - - // -------------------------------------------------------------------- - - /** - * 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 mixed - */ - function all_userdata() - { - return ( ! isset($this->userdata)) ? FALSE : $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); - } - else - { - // if encryption is not used, we provide an md5 hash to prevent userside tampering - $cookie_data = $cookie_data.md5($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 */ \ No newline at end of file diff --git a/system/libraries/Session/Session.php b/system/libraries/Session/Session.php new file mode 100644 index 00000000..eb433de6 --- /dev/null +++ b/system/libraries/Session/Session.php @@ -0,0 +1,985 @@ +_driver = $params['driver']; + unset($params['driver']); + } + elseif ($driver = config_item('sess_driver')) + { + $this->_driver = $driver; + } + // Note: BC workaround + elseif (config_item('sess_use_database')) + { + log_message('debug', 'Session: "sess_driver" is empty; using BC fallback to "sess_use_database".'); + $this->_driver = 'database'; + } + + $class = $this->_ci_load_classes($this->_driver); + + // Configuration ... + $this->_configure($params); + $this->_config['_sid_regexp'] = $this->_sid_regexp; + + $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('#\A'.$this->_sid_regexp.'\z#', $_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); + + $this->_configure_sid_length(); + } + + // ------------------------------------------------------------------------ + + /** + * Configure session ID length + * + * To make life easier, we used to force SHA-1 and 4 bits per + * character on everyone. And of course, someone was unhappy. + * + * Then PHP 7.1 broke backwards-compatibility because ext/session + * is such a mess that nobody wants to touch it with a pole stick, + * and the one guy who does, nobody has the energy to argue with. + * + * So we were forced to make changes, and OF COURSE something was + * going to break and now we have this pile of shit. -- Narf + * + * @return void + */ + protected function _configure_sid_length() + { + if (PHP_VERSION_ID < 70100) + { + $hash_function = ini_get('session.hash_function'); + if (ctype_digit($hash_function)) + { + if ($hash_function !== '1') + { + ini_set('session.hash_function', 1); + } + + $bits = 160; + } + elseif ( ! in_array($hash_function, hash_algos(), TRUE)) + { + ini_set('session.hash_function', 1); + $bits = 160; + } + elseif (($bits = strlen(hash($hash_function, 'dummy', false)) * 4) < 160) + { + ini_set('session.hash_function', 1); + $bits = 160; + } + + $bits_per_character = (int) ini_get('session.hash_bits_per_character'); + $sid_length = (int) ceil($bits / $bits_per_character); + } + else + { + $bits_per_character = (int) ini_get('session.sid_bits_per_character'); + $sid_length = (int) ini_get('session.sid_length'); + if (($bits = $sid_length * $bits_per_character) < 160) + { + // Add as many more characters as necessary to reach at least 160 bits + $sid_length += (int) ceil((160 % $bits) / $bits_per_character); + ini_set('session.sid_length', $sid_length); + } + } + + // Yes, 4,5,6 are the only known possible values as of 2016-10-27 + switch ($bits_per_character) + { + case 4: + $this->_sid_regexp = '[0-9a-f]'; + break; + case 5: + $this->_sid_regexp = '[0-9a-v]'; + break; + case 6: + $this->_sid_regexp = '[0-9a-zA-Z,-]'; + break; + } + + $this->_sid_regexp .= '{'.$sid_length.'}'; + } + + // ------------------------------------------------------------------------ + + /** + * 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 $key 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/system/libraries/Session/SessionHandlerInterface.php b/system/libraries/Session/SessionHandlerInterface.php new file mode 100644 index 00000000..2eef61db --- /dev/null +++ b/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/system/libraries/Session/drivers/Session_database_driver.php b/system/libraries/Session/drivers/Session_database_driver.php new file mode 100644 index 00000000..b519b782 --- /dev/null +++ b/system/libraries/Session/drivers/Session_database_driver.php @@ -0,0 +1,420 @@ +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. + if ( ! isset($this->_config['save_path']) && ($this->_config['save_path'] = config_item('sess_table_name'))) + { + log_message('debug', 'Session: "sess_save_path" is empty; using BC fallback to "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 (isset($this->_session_id) && $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 = md5($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(); + } +} diff --git a/system/libraries/Session/drivers/Session_files_driver.php b/system/libraries/Session/drivers/Session_files_driver.php new file mode 100644 index 00000000..8860ef66 --- /dev/null +++ b/system/libraries/Session/drivers/Session_files_driver.php @@ -0,0 +1,406 @@ +_config['save_path'])) + { + $this->_config['save_path'] = rtrim($this->_config['save_path'], '/\\'); + ini_set('session.save_path', $this->_config['save_path']); + } + else + { + log_message('debug', 'Session: "sess_save_path" is empty; using "session.save_path" value from php.ini.'); + $this->_config['save_path'] = rtrim(ini_get('session.save_path'), '/\\'); + } + + $this->_sid_regexp = $this->_config['_sid_regexp']; + + isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload')); + } + + // ------------------------------------------------------------------------ + + /** + * 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) + { + $this->_file_new = ! file_exists($this->_file_path.$session_id); + + if (($this->_file_handle = fopen($this->_file_path.$session_id, 'c+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 += self::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 = ($this->_config['match_ip'] === TRUE) + ? '[0-9a-f]{32}' + : ''; + + $pattern = sprintf( + '#\A%s'.$pattern.$this->_sid_regexp.'\z#', + preg_quote($this->_config['cookie_name']) + ); + + 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; + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return (self::$func_overload) + ? mb_strlen($str, '8bit') + : strlen($str); + } +} diff --git a/system/libraries/Session/drivers/Session_memcached_driver.php b/system/libraries/Session/drivers/Session_memcached_driver.php new file mode 100644 index 00000000..5e90539d --- /dev/null +++ b/system/libraries/Session/drivers/Session_memcached_driver.php @@ -0,0 +1,376 @@ +_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, $this->_lock_key)) + { + 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; + } + + $key = $this->_key_prefix.$session_id; + + $this->_memcached->replace($this->_lock_key, time(), 300); + if ($this->_fingerprint !== ($fingerprint = md5($session_data))) + { + if ($this->_memcached->set($key, $session_data, $this->_config['expiration'])) + { + $this->_fingerprint = $fingerprint; + return $this->_success; + } + + return $this->_fail(); + } + elseif ( + $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->add($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; + } + + $method = ($this->_memcached->getResultCode() === Memcached::RES_NOTFOUND) ? 'add' : 'set'; + if ( ! $this->_memcached->$method($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; + } +} diff --git a/system/libraries/Session/drivers/Session_redis_driver.php b/system/libraries/Session/drivers/Session_redis_driver.php new file mode 100644 index 00000000..a9e655a8 --- /dev/null +++ b/system/libraries/Session/drivers/Session_redis_driver.php @@ -0,0 +1,399 @@ +_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, $this->_lock_key)) + { + 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; + } + + $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(); + } + + // ------------------------------------------------------------------------ + + /** + * 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() === FALSE) + { + 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; + } + + $result = ($ttl === -2) + ? $this->_redis->set($lock_key, time(), array('nx', 'ex' => 300)) + : $this->_redis->setex($lock_key, 300, time()); + + if ( ! $result) + { + 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/system/libraries/Session/drivers/index.html b/system/libraries/Session/drivers/index.html new file mode 100644 index 00000000..b702fbc3 --- /dev/null +++ b/system/libraries/Session/drivers/index.html @@ -0,0 +1,11 @@ + + + + 403 Forbidden + + + +

    Directory access is forbidden.

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

    Directory access is forbidden.

    + + + diff --git a/system/libraries/Sha1.php b/system/libraries/Sha1.php deleted file mode 100644 index 1a657572..00000000 --- a/system/libraries/Sha1.php +++ /dev/null @@ -1,251 +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/system/libraries/Table.php b/system/libraries/Table.php index def69677..50c5e358 100644 --- a/system/libraries/Table.php +++ b/system/libraries/Table.php @@ -1,19 +1,41 @@ - $val) + { + $this->template[$key] = $val; + } + + log_message('info', 'Table Class Initialized'); } // -------------------------------------------------------------------- @@ -47,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)) { @@ -59,6 +139,7 @@ class CI_Table { } $this->template = $template; + return TRUE; } // -------------------------------------------------------------------- @@ -68,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; } @@ -102,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); @@ -122,6 +201,7 @@ class CI_Table { $new[] = $temp; } + while (count($array) > 0); return $new; } @@ -133,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; } // -------------------------------------------------------------------- @@ -149,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; } // -------------------------------------------------------------------- @@ -166,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; @@ -212,13 +271,13 @@ 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; + return $this; } // -------------------------------------------------------------------- @@ -226,29 +285,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'; } @@ -256,29 +313,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) { @@ -286,28 +340,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) @@ -318,10 +366,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) { @@ -329,40 +376,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']; @@ -378,14 +420,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; } // -------------------------------------------------------------------- @@ -393,36 +435,20 @@ class CI_Table { /** * Set table data from a database result object * - * @access public - * @param object + * @param CI_DB_result $object 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); } } @@ -431,31 +457,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); } } @@ -464,12 +478,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; @@ -490,42 +503,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/system/libraries/Trackback.php b/system/libraries/Trackback.php index b0a76782..55e9a0ee 100644 --- a/system/libraries/Trackback.php +++ b/system/libraries/Trackback.php @@ -1,19 +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'); } // -------------------------------------------------------------------- @@ -50,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)) { @@ -73,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; @@ -112,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; } @@ -132,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']); } @@ -170,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"); } // -------------------------------------------------------------------- @@ -192,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"); } // -------------------------------------------------------------------- @@ -206,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] : ''; } // -------------------------------------------------------------------- @@ -221,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); @@ -240,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; } @@ -293,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; } @@ -329,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 (stripos($url, 'http') !== 0) { - $url = "http://".$url; + $url = 'http://'.$url; } } @@ -348,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) { @@ -378,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; } // -------------------------------------------------------------------- @@ -397,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); } // -------------------------------------------------------------------- @@ -425,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; } } } @@ -464,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 = ''; @@ -484,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; @@ -510,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; @@ -525,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/system/libraries/Typography.php b/system/libraries/Typography.php index 734cec10..b25d8fda 100644 --- a/system/libraries/Typography.php +++ b/system/libraries/Typography.php @@ -1,48 +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 @@ -55,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 ''; } @@ -82,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, '", - "'", - "<", - ">", - '"', - '&', - '$', - '=', - ';', - '?', - '/', - "%20", - "%22", - "%3c", // < - "%253c", // < - "%3e", // > - "%0e", // > - "%28", // ( - "%29", // ) - "%2528", // ( - "%26", // & - "%24", // $ - "%3f", // ? - "%3b", // ; - "%3d" // = - ); - - $filename = str_replace($bad, '', $filename); - - return stripslashes($filename); + $ext = ($this->file_ext_tolower) ? strtolower(end($x)) : end($x); + return '.'.$ext; } // -------------------------------------------------------------------- @@ -788,7 +1041,8 @@ class CI_Upload { /** * Limit the File Name Length * - * @param string + * @param string $filename + * @param int $length * @return string */ public function limit_filename_length($filename, $length) @@ -818,7 +1072,7 @@ class CI_Upload { * I'm not sure that it won't negatively affect certain files in unexpected ways, * but so far I haven't found that it causes trouble. * - * @return void + * @return string */ public function do_xss_clean() { @@ -829,23 +1083,34 @@ class CI_Upload { return FALSE; } - if (function_exists('memory_get_usage') && memory_get_usage() && ini_get('memory_limit') != '') + if (memory_get_usage() && ($memory_limit = ini_get('memory_limit')) > 0) { - $current = ini_get('memory_limit') * 1024 * 1024; + $memory_limit = str_split($memory_limit, strspn($memory_limit, '1234567890')); + if ( ! empty($memory_limit[1])) + { + switch ($memory_limit[1][0]) + { + case 'g': + case 'G': + $memory_limit[0] *= 1024 * 1024 * 1024; + break; + case 'm': + case 'M': + $memory_limit[0] *= 1024 * 1024; + break; + default: + break; + } + } - // There was a bug/behavioural change in PHP 5.2, where numbers over one million get output - // into scientific notation. number_format() ensures this number is an integer - // http://bugs.php.net/bug.php?id=43053 - - $new_memory = number_format(ceil(filesize($file) + $current), 0, '.', ''); - - ini_set('memory_limit', $new_memory); // When an integer is used, the value is measured in bytes. - PHP.net + $memory_limit = (int) ceil(filesize($file) + $memory_limit[0]); + ini_set('memory_limit', $memory_limit); // When an integer is used, the value is measured in bytes. - PHP.net } // If the file being uploaded is an image, then we should have no problem with XSS attacks (in theory), but // IE can be fooled into mime-type detecting a malformed image as an html file, thus executing an XSS attack on anyone - // using IE who looks at the image. It does this by inspecting the first 255 bytes of an image. To get around this - // CI will itself look at the first 255 bytes of an image to determine its relative safety. This can save a lot of + // using IE who looks at the image. It does this by inspecting the first 255 bytes of an image. To get around this + // CI will itself look at the first 255 bytes of an image to determine its relative safety. This can save a lot of // processor power and time if it is actually a clean image, as it will be in nearly all instances _except_ an // attempted XSS attack. @@ -863,10 +1128,8 @@ class CI_Upload { // ]/i', $opening_bytes)) - { - return TRUE; // its an image, no "triggers" detected in the first 256 bytes, we're good - } + // if it's an image or no "triggers" detected in the first 256 bytes - we're good + return ! preg_match('/<(a|body|head|html|img|plaintext|pre|script|table|title)[\s>]/i', $opening_bytes); } if (($data = @file_get_contents($file)) === FALSE) @@ -874,14 +1137,7 @@ class CI_Upload { return FALSE; } - $CI =& get_instance(); - - if ( ! isset($CI->security)) - { - $CI->load->library('security'); - } - - return $CI->security->xss_clean($data, TRUE); + return $this->_CI->security->xss_clean($data, TRUE); } // -------------------------------------------------------------------- @@ -889,29 +1145,22 @@ class CI_Upload { /** * Set an error message * - * @param string - * @return void + * @param string $msg + * @return CI_Upload */ - public function set_error($msg) + public function set_error($msg, $log_level = 'error') { - $CI =& get_instance(); - $CI->lang->load('upload'); + $this->_CI->lang->load('upload'); - if (is_array($msg)) + is_array($msg) OR $msg = array($msg); + foreach ($msg as $val) { - foreach ($msg as $val) - { - $msg = ($CI->lang->line($val) == FALSE) ? $val : $CI->lang->line($val); - $this->error_msg[] = $msg; - log_message('error', $msg); - } - } - else - { - $msg = ($CI->lang->line($msg) == FALSE) ? $msg : $CI->lang->line($msg); + $msg = ($this->_CI->lang->line($val) === FALSE) ? $val : $this->_CI->lang->line($val); $this->error_msg[] = $msg; - log_message('error', $msg); + log_message($log_level, $msg); } + + return $this; } // -------------------------------------------------------------------- @@ -919,46 +1168,13 @@ class CI_Upload { /** * Display the error message * - * @param string - * @param string + * @param string $open + * @param string $close * @return string */ public function display_errors($open = '

    ', $close = '

    ') { - $str = ''; - foreach ($this->error_msg as $val) - { - $str .= $open.$val.$close; - } - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * List of Mime Types - * - * This is a list of mime types. We use it to validate - * the "allowed types" set by the developer - * - * @param string - * @return string - */ - public function mimes_types($mime) - { - global $mimes; - - if (count($this->mimes) == 0) - { - if (@require(APPPATH.'config/mimes'.EXT)) - { - $this->mimes = $mimes; - unset($mimes); - } - } - - return ( ! isset($this->mimes[$mime])) ? FALSE : $this->mimes[$mime]; + return (count($this->error_msg) > 0) ? $open.implode($close.$open, $this->error_msg).$close : ''; } // -------------------------------------------------------------------- @@ -966,44 +1182,147 @@ class CI_Upload { /** * Prep Filename * - * Prevents possible script execution from Apache's handling of files multiple extensions - * http://httpd.apache.org/docs/1.3/mod/mod_mime.html#multipleext + * Prevents possible script execution from Apache's handling + * of files' multiple extensions. * - * @param string + * @link http://httpd.apache.org/docs/1.3/mod/mod_mime.html#multipleext + * + * @param string $filename * @return string */ protected function _prep_filename($filename) { - if (strpos($filename, '.') === FALSE OR $this->allowed_types == '*') + if ($this->mod_mime_fix === FALSE OR $this->allowed_types === '*' OR ($ext_pos = strrpos($filename, '.')) === FALSE) { return $filename; } - $parts = explode('.', $filename); - $ext = array_pop($parts); - $filename = array_shift($parts); - - foreach ($parts as $part) - { - if ( ! in_array(strtolower($part), $this->allowed_types) OR $this->mimes_types(strtolower($part)) === FALSE) - { - $filename .= '.'.$part.'_'; - } - else - { - $filename .= '.'.$part; - } - } - - $filename .= '.'.$ext; - - return $filename; + $ext = substr($filename, $ext_pos); + $filename = substr($filename, 0, $ext_pos); + return str_replace('.', '_', $filename).$ext; } // -------------------------------------------------------------------- -} -// END Upload Class + /** + * File MIME type + * + * Detects the (actual) MIME type of the uploaded file, if possible. + * The input array is expected to be $_FILES[$field] + * + * @param array $file + * @return void + */ + protected function _file_mime_type($file) + { + // We'll need this to validate the MIME info string (e.g. text/plain; charset=us-ascii) + $regexp = '/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s.+)?$/'; -/* End of file Upload.php */ -/* Location: ./system/libraries/Upload.php */ \ No newline at end of file + /** + * Fileinfo extension - most reliable method + * + * Apparently XAMPP, CentOS, cPanel and who knows what + * other PHP distribution channels EXPLICITLY DISABLE + * ext/fileinfo, which is otherwise enabled by default + * since PHP 5.3 ... + */ + if (function_exists('finfo_file')) + { + $finfo = @finfo_open(FILEINFO_MIME); + if (is_resource($finfo)) // It is possible that a FALSE value is returned, if there is no magic MIME database file found on the system + { + $mime = @finfo_file($finfo, $file['tmp_name']); + finfo_close($finfo); + + /* According to the comments section of the PHP manual page, + * it is possible that this function returns an empty string + * for some files (e.g. if they don't exist in the magic MIME database) + */ + if (is_string($mime) && preg_match($regexp, $mime, $matches)) + { + $this->file_type = $matches[1]; + return; + } + } + } + + /* This is an ugly hack, but UNIX-type systems provide a "native" way to detect the file type, + * which is still more secure than depending on the value of $_FILES[$field]['type'], and as it + * was reported in issue #750 (https://github.com/EllisLab/CodeIgniter/issues/750) - it's better + * than mime_content_type() as well, hence the attempts to try calling the command line with + * three different functions. + * + * Notes: + * - the DIRECTORY_SEPARATOR comparison ensures that we're not on a Windows system + * - many system admins would disable the exec(), shell_exec(), popen() and similar functions + * due to security concerns, hence the function_usable() checks + */ + if (DIRECTORY_SEPARATOR !== '\\') + { + $cmd = function_exists('escapeshellarg') + ? 'file --brief --mime '.escapeshellarg($file['tmp_name']).' 2>&1' + : 'file --brief --mime '.$file['tmp_name'].' 2>&1'; + + if (function_usable('exec')) + { + /* This might look confusing, as $mime is being populated with all of the output when set in the second parameter. + * However, we only need the last line, which is the actual return value of exec(), and as such - it overwrites + * anything that could already be set for $mime previously. This effectively makes the second parameter a dummy + * value, which is only put to allow us to get the return status code. + */ + $mime = @exec($cmd, $mime, $return_status); + if ($return_status === 0 && is_string($mime) && preg_match($regexp, $mime, $matches)) + { + $this->file_type = $matches[1]; + return; + } + } + + if ( ! ini_get('safe_mode') && function_usable('shell_exec')) + { + $mime = @shell_exec($cmd); + if (strlen($mime) > 0) + { + $mime = explode("\n", trim($mime)); + if (preg_match($regexp, $mime[(count($mime) - 1)], $matches)) + { + $this->file_type = $matches[1]; + return; + } + } + } + + if (function_usable('popen')) + { + $proc = @popen($cmd, 'r'); + if (is_resource($proc)) + { + $mime = @fread($proc, 512); + @pclose($proc); + if ($mime !== FALSE) + { + $mime = explode("\n", trim($mime)); + if (preg_match($regexp, $mime[(count($mime) - 1)], $matches)) + { + $this->file_type = $matches[1]; + return; + } + } + } + } + } + + // Fall back to mime_content_type(), if available (still better than $_FILES[$field]['type']) + if (function_exists('mime_content_type')) + { + $this->file_type = @mime_content_type($file['tmp_name']); + if (strlen($this->file_type) > 0) // It's possible that mime_content_type() returns FALSE or an empty string + { + return; + } + } + + $this->file_type = $file['type']; + } + +} diff --git a/system/libraries/User_agent.php b/system/libraries/User_agent.php index 3774fc28..cda3ef0a 100644 --- a/system/libraries/User_agent.php +++ b/system/libraries/User_agent.php @@ -1,77 +1,187 @@ -_load_agent_file(); + if (isset($_SERVER['HTTP_USER_AGENT'])) { $this->agent = trim($_SERVER['HTTP_USER_AGENT']); + $this->_compile_data(); } - if ( ! is_null($this->agent)) - { - if ($this->_load_agent_file()) - { - $this->_compile_data(); - } - } - - log_message('debug', "User Agent Class Initialized"); + log_message('info', 'User Agent Class Initialized'); } // -------------------------------------------------------------------- @@ -79,12 +189,22 @@ class CI_User_agent { /** * Compile the User Agent Data * - * @access private * @return bool */ - private function _load_agent_file() + protected function _load_agent_file() { - if ( ! @include(APPPATH.'config/user_agents'.EXT)) + if (($found = file_exists(APPPATH.'config/user_agents.php'))) + { + include(APPPATH.'config/user_agents.php'); + } + + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/user_agents.php')) + { + include(APPPATH.'config/'.ENVIRONMENT.'/user_agents.php'); + $found = TRUE; + } + + if ($found !== TRUE) { return FALSE; } @@ -127,14 +247,13 @@ class CI_User_agent { /** * Compile the User Agent Data * - * @access private * @return bool */ - private function _compile_data() + protected function _compile_data() { $this->_set_platform(); - foreach (array('_set_browser', '_set_robot', '_set_mobile') as $function) + foreach (array('_set_robot', '_set_browser', '_set_mobile') as $function) { if ($this->$function() === TRUE) { @@ -148,23 +267,24 @@ class CI_User_agent { /** * Set the Platform * - * @access private - * @return mixed + * @return bool */ - private function _set_platform() + protected function _set_platform() { - if (is_array($this->platforms) AND count($this->platforms) > 0) + if (is_array($this->platforms) && count($this->platforms) > 0) { foreach ($this->platforms as $key => $val) { - if (preg_match("|".preg_quote($key)."|i", $this->agent)) + if (preg_match('|'.preg_quote($key).'|i', $this->agent)) { $this->platform = $val; return TRUE; } } } + $this->platform = 'Unknown Platform'; + return FALSE; } // -------------------------------------------------------------------- @@ -172,16 +292,15 @@ class CI_User_agent { /** * Set the Browser * - * @access private * @return bool */ - private function _set_browser() + protected function _set_browser() { - if (is_array($this->browsers) AND count($this->browsers) > 0) + if (is_array($this->browsers) && count($this->browsers) > 0) { foreach ($this->browsers as $key => $val) { - if (preg_match("|".preg_quote($key).".*?([0-9\.]+)|i", $this->agent, $match)) + if (preg_match('|'.$key.'.*?([0-9\.]+)|i', $this->agent, $match)) { $this->is_browser = TRUE; $this->version = $match[1]; @@ -191,6 +310,7 @@ class CI_User_agent { } } } + return FALSE; } @@ -199,23 +319,24 @@ class CI_User_agent { /** * Set the Robot * - * @access private * @return bool */ - private function _set_robot() + protected function _set_robot() { - if (is_array($this->robots) AND count($this->robots) > 0) + if (is_array($this->robots) && count($this->robots) > 0) { foreach ($this->robots as $key => $val) { - if (preg_match("|".preg_quote($key)."|i", $this->agent)) + if (preg_match('|'.preg_quote($key).'|i', $this->agent)) { $this->is_robot = TRUE; $this->robot = $val; + $this->_set_mobile(); return TRUE; } } } + return FALSE; } @@ -224,16 +345,15 @@ class CI_User_agent { /** * Set the Mobile Device * - * @access private * @return bool */ - private function _set_mobile() + protected function _set_mobile() { - if (is_array($this->mobiles) AND count($this->mobiles) > 0) + if (is_array($this->mobiles) && count($this->mobiles) > 0) { foreach ($this->mobiles as $key => $val) { - if (FALSE !== (strpos(strtolower($this->agent), $key))) + if (FALSE !== (stripos($this->agent, $key))) { $this->is_mobile = TRUE; $this->mobile = $val; @@ -241,6 +361,7 @@ class CI_User_agent { } } } + return FALSE; } @@ -249,19 +370,16 @@ class CI_User_agent { /** * Set the accepted languages * - * @access private * @return void */ - private function _set_languages() + protected function _set_languages() { - if ((count($this->languages) == 0) AND isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) AND $_SERVER['HTTP_ACCEPT_LANGUAGE'] != '') + if ((count($this->languages) === 0) && ! empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - $languages = preg_replace('/(;q=[0-9\.]+)/i', '', strtolower(trim($_SERVER['HTTP_ACCEPT_LANGUAGE']))); - - $this->languages = explode(',', $languages); + $this->languages = explode(',', preg_replace('/(;\s?q=[0-9\.]+)|\s/i', '', strtolower(trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])))); } - if (count($this->languages) == 0) + if (count($this->languages) === 0) { $this->languages = array('Undefined'); } @@ -272,19 +390,16 @@ class CI_User_agent { /** * Set the accepted character sets * - * @access private * @return void */ - private function _set_charsets() + protected function _set_charsets() { - if ((count($this->charsets) == 0) AND isset($_SERVER['HTTP_ACCEPT_CHARSET']) AND $_SERVER['HTTP_ACCEPT_CHARSET'] != '') + if ((count($this->charsets) === 0) && ! empty($_SERVER['HTTP_ACCEPT_CHARSET'])) { - $charsets = preg_replace('/(;q=.+)/i', '', strtolower(trim($_SERVER['HTTP_ACCEPT_CHARSET']))); - - $this->charsets = explode(',', $charsets); + $this->charsets = explode(',', preg_replace('/(;\s?q=.+)|\s/i', '', strtolower(trim($_SERVER['HTTP_ACCEPT_CHARSET'])))); } - if (count($this->charsets) == 0) + if (count($this->charsets) === 0) { $this->charsets = array('Undefined'); } @@ -295,7 +410,7 @@ class CI_User_agent { /** * Is Browser * - * @access public + * @param string $key * @return bool */ public function is_browser($key = NULL) @@ -312,7 +427,7 @@ class CI_User_agent { } // Check for a specific browser - return array_key_exists($key, $this->browsers) AND $this->browser === $this->browsers[$key]; + return (isset($this->browsers[$key]) && $this->browser === $this->browsers[$key]); } // -------------------------------------------------------------------- @@ -320,7 +435,7 @@ class CI_User_agent { /** * Is Robot * - * @access public + * @param string $key * @return bool */ public function is_robot($key = NULL) @@ -337,7 +452,7 @@ class CI_User_agent { } // Check for a specific robot - return array_key_exists($key, $this->robots) AND $this->robot === $this->robots[$key]; + return (isset($this->robots[$key]) && $this->robot === $this->robots[$key]); } // -------------------------------------------------------------------- @@ -345,7 +460,7 @@ class CI_User_agent { /** * Is Mobile * - * @access public + * @param string $key * @return bool */ public function is_mobile($key = NULL) @@ -362,7 +477,7 @@ class CI_User_agent { } // Check for a specific robot - return array_key_exists($key, $this->mobiles) AND $this->mobile === $this->mobiles[$key]; + return (isset($this->mobiles[$key]) && $this->mobile === $this->mobiles[$key]); } // -------------------------------------------------------------------- @@ -370,16 +485,26 @@ class CI_User_agent { /** * Is this a referral from another site? * - * @access public * @return bool */ public function is_referral() { - if ( ! isset($_SERVER['HTTP_REFERER']) OR $_SERVER['HTTP_REFERER'] == '') + if ( ! isset($this->referer)) { - return FALSE; + if (empty($_SERVER['HTTP_REFERER'])) + { + $this->referer = FALSE; + } + else + { + $referer_host = @parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST); + $own_host = parse_url(config_item('base_url'), PHP_URL_HOST); + + $this->referer = ($referer_host && $referer_host !== $own_host); + } } - return TRUE; + + return $this->referer; } // -------------------------------------------------------------------- @@ -387,7 +512,6 @@ class CI_User_agent { /** * Agent String * - * @access public * @return string */ public function agent_string() @@ -400,7 +524,6 @@ class CI_User_agent { /** * Get Platform * - * @access public * @return string */ public function platform() @@ -413,7 +536,6 @@ class CI_User_agent { /** * Get Browser Name * - * @access public * @return string */ public function browser() @@ -426,7 +548,6 @@ class CI_User_agent { /** * Get the Browser Version * - * @access public * @return string */ public function version() @@ -439,7 +560,6 @@ class CI_User_agent { /** * Get The Robot Name * - * @access public * @return string */ public function robot() @@ -451,7 +571,6 @@ class CI_User_agent { /** * Get the Mobile Device * - * @access public * @return string */ public function mobile() @@ -464,12 +583,11 @@ class CI_User_agent { /** * Get the referrer * - * @access public * @return bool */ public function referrer() { - return ( ! isset($_SERVER['HTTP_REFERER']) OR $_SERVER['HTTP_REFERER'] == '') ? '' : trim($_SERVER['HTTP_REFERER']); + return empty($_SERVER['HTTP_REFERER']) ? '' : trim($_SERVER['HTTP_REFERER']); } // -------------------------------------------------------------------- @@ -477,12 +595,11 @@ class CI_User_agent { /** * Get the accepted languages * - * @access public * @return array */ public function languages() { - if (count($this->languages) == 0) + if (count($this->languages) === 0) { $this->_set_languages(); } @@ -495,12 +612,11 @@ class CI_User_agent { /** * Get the accepted Character Sets * - * @access public * @return array */ public function charsets() { - if (count($this->charsets) == 0) + if (count($this->charsets) === 0) { $this->_set_charsets(); } @@ -513,12 +629,12 @@ class CI_User_agent { /** * Test for a particular language * - * @access public + * @param string $lang * @return bool */ public function accept_lang($lang = 'en') { - return (in_array(strtolower($lang), $this->languages(), TRUE)); + return in_array(strtolower($lang), $this->languages(), TRUE); } // -------------------------------------------------------------------- @@ -526,16 +642,40 @@ class CI_User_agent { /** * Test for a particular character set * - * @access public + * @param string $charset * @return bool */ public function accept_charset($charset = 'utf-8') { - return (in_array(strtolower($charset), $this->charsets(), TRUE)); + return in_array(strtolower($charset), $this->charsets(), TRUE); + } + + // -------------------------------------------------------------------- + + /** + * Parse a custom user-agent string + * + * @param string $string + * @return void + */ + public function parse($string) + { + // Reset values + $this->is_browser = FALSE; + $this->is_robot = FALSE; + $this->is_mobile = FALSE; + $this->browser = ''; + $this->version = ''; + $this->mobile = ''; + $this->robot = ''; + + // Set the new user-agent string and parse it, unless empty + $this->agent = $string; + + if ( ! empty($string)) + { + $this->_compile_data(); + } } } - - -/* End of file User_agent.php */ -/* Location: ./system/libraries/User_agent.php */ \ No newline at end of file diff --git a/system/libraries/Xmlrpc.php b/system/libraries/Xmlrpc.php index a24bca9b..6fa79186 100644 --- a/system/libraries/Xmlrpc.php +++ b/system/libraries/Xmlrpc.php @@ -1,24 +1,47 @@ -xmlrpcName = $this->xmlrpcName; $this->xmlrpc_backslash = chr(92).chr(92); // Types for info sent back and forth @@ -85,54 +276,56 @@ class CI_Xmlrpc { $this->xmlrpcBase64 => '1', $this->xmlrpcArray => '2', $this->xmlrpcStruct => '3' - ); + ); // Array of Valid Parents for Various XML-RPC elements - $this->valid_parents = array('BOOLEAN' => array('VALUE'), - 'I4' => array('VALUE'), - 'INT' => array('VALUE'), - 'STRING' => array('VALUE'), - 'DOUBLE' => array('VALUE'), - 'DATETIME.ISO8601' => array('VALUE'), - 'BASE64' => array('VALUE'), - 'ARRAY' => array('VALUE'), - 'STRUCT' => array('VALUE'), - 'PARAM' => array('PARAMS'), - 'METHODNAME' => array('METHODCALL'), - 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'), - 'MEMBER' => array('STRUCT'), - 'NAME' => array('MEMBER'), - 'DATA' => array('ARRAY'), - 'FAULT' => array('METHODRESPONSE'), - 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT') - ); - + $this->valid_parents = array('BOOLEAN' => array('VALUE'), + 'I4' => array('VALUE'), + 'INT' => array('VALUE'), + 'STRING' => array('VALUE'), + 'DOUBLE' => array('VALUE'), + 'DATETIME.ISO8601' => array('VALUE'), + 'BASE64' => array('VALUE'), + 'ARRAY' => array('VALUE'), + 'STRUCT' => array('VALUE'), + 'PARAM' => array('PARAMS'), + 'METHODNAME' => array('METHODCALL'), + 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'), + 'MEMBER' => array('STRUCT'), + 'NAME' => array('MEMBER'), + 'DATA' => array('ARRAY'), + 'FAULT' => array('METHODRESPONSE'), + 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT') + ); // XML-RPC Responses $this->xmlrpcerr['unknown_method'] = '1'; $this->xmlrpcstr['unknown_method'] = 'This is not a known method for this XML-RPC Server'; $this->xmlrpcerr['invalid_return'] = '2'; - $this->xmlrpcstr['invalid_return'] = 'The XML data received was either invalid or not in the correct form for XML-RPC. Turn on debugging to examine the XML data further.'; + $this->xmlrpcstr['invalid_return'] = 'The XML data received was either invalid or not in the correct form for XML-RPC. Turn on debugging to examine the XML data further.'; $this->xmlrpcerr['incorrect_params'] = '3'; $this->xmlrpcstr['incorrect_params'] = 'Incorrect parameters were passed to method'; $this->xmlrpcerr['introspect_unknown'] = '4'; - $this->xmlrpcstr['introspect_unknown'] = "Cannot inspect signature for request: method unknown"; + $this->xmlrpcstr['introspect_unknown'] = 'Cannot inspect signature for request: method unknown'; $this->xmlrpcerr['http_error'] = '5'; $this->xmlrpcstr['http_error'] = "Did not receive a '200 OK' response from remote server."; $this->xmlrpcerr['no_data'] = '6'; - $this->xmlrpcstr['no_data'] ='No data received from server.'; + $this->xmlrpcstr['no_data'] = 'No data received from server.'; $this->initialize($config); - log_message('debug', "XML-RPC Class Initialized"); + log_message('info', 'XML-RPC Class Initialized'); } + // -------------------------------------------------------------------- - //------------------------------------- - // Initialize Prefs - //------------------------------------- - - function initialize($config = array()) + /** + * Initialize + * + * @param array $config + * @return void + */ + public function initialize($config = array()) { if (count($config) > 0) { @@ -145,64 +338,85 @@ class CI_Xmlrpc { } } } - // END - //------------------------------------- - // Take URL and parse it - //------------------------------------- + // -------------------------------------------------------------------- - function server($url, $port=80) + /** + * Parse server URL + * + * @param string $url + * @param int $port + * @param string $proxy + * @param int $proxy_port + * @return void + */ + public function server($url, $port = 80, $proxy = FALSE, $proxy_port = 8080) { - if (substr($url, 0, 4) != "http") + if (stripos($url, 'http') !== 0) { - $url = "http://".$url; + $url = 'http://'.$url; } $parts = parse_url($url); - $path = ( ! isset($parts['path'])) ? '/' : $parts['path']; + if (isset($parts['user'], $parts['pass'])) + { + $parts['host'] = $parts['user'].':'.$parts['pass'].'@'.$parts['host']; + } - if (isset($parts['query']) && $parts['query'] != '') + $path = isset($parts['path']) ? $parts['path'] : '/'; + + if ( ! empty($parts['query'])) { $path .= '?'.$parts['query']; } - $this->client = new XML_RPC_Client($path, $parts['host'], $port); + $this->client = new XML_RPC_Client($path, $parts['host'], $port, $proxy, $proxy_port); } - // END - //------------------------------------- - // Set Timeout - //------------------------------------- + // -------------------------------------------------------------------- - function timeout($seconds=5) + /** + * Set Timeout + * + * @param int $seconds + * @return void + */ + public function timeout($seconds = 5) { - if ( ! is_null($this->client) && is_int($seconds)) + if ($this->client !== NULL && is_int($seconds)) { $this->client->timeout = $seconds; } } - // END - //------------------------------------- - // Set Methods - //------------------------------------- + // -------------------------------------------------------------------- - function method($function) + /** + * Set Methods + * + * @param string $function Method name + * @return void + */ + public function method($function) { $this->method = $function; } - // END - //------------------------------------- - // Take Array of Data and Create Objects - //------------------------------------- + // -------------------------------------------------------------------- - function request($incoming) + /** + * Take Array of Data and Create Objects + * + * @param array $incoming + * @return void + */ + public function request($incoming) { if ( ! is_array($incoming)) { // Send Error + return; } $this->data = array(); @@ -212,49 +426,47 @@ class CI_Xmlrpc { $this->data[$key] = $this->values_parsing($value); } } - // END + // -------------------------------------------------------------------- - //------------------------------------- - // Set Debug - //------------------------------------- - - function set_debug($flag = TRUE) + /** + * Set Debug + * + * @param bool $flag + * @return void + */ + public function set_debug($flag = TRUE) { - $this->debug = ($flag == TRUE) ? TRUE : FALSE; + $this->debug = ($flag === TRUE); } - //------------------------------------- - // Values Parsing - //------------------------------------- + // -------------------------------------------------------------------- - function values_parsing($value, $return = FALSE) + /** + * Values Parsing + * + * @param mixed $value + * @return object + */ + public function values_parsing($value) { if (is_array($value) && array_key_exists(0, $value)) { - if ( ! isset($value['1']) OR ( ! isset($this->xmlrpcTypes[$value['1']]))) + if ( ! isset($value[1], $this->xmlrpcTypes[$value[1]])) { - if (is_array($value[0])) - { - $temp = new XML_RPC_Values($value['0'], 'array'); - } - else - { - $temp = new XML_RPC_Values($value['0'], 'string'); - } - } - elseif (is_array($value['0']) && ($value['1'] == 'struct' OR $value['1'] == 'array')) - { - while (list($k) = each($value['0'])) - { - $value['0'][$k] = $this->values_parsing($value['0'][$k], TRUE); - } - - $temp = new XML_RPC_Values($value['0'], $value['1']); + $temp = new XML_RPC_Values($value[0], (is_array($value[0]) ? 'array' : 'string')); } else { - $temp = new XML_RPC_Values($value['0'], $value['1']); + if (is_array($value[0]) && ($value[1] === 'struct' OR $value[1] === 'array')) + { + foreach (array_keys($value[0]) as $k) + { + $value[0][$k] = $this->values_parsing($value[0][$k]); + } + } + + $temp = new XML_RPC_Values($value[0], $value[1]); } } else @@ -264,132 +476,248 @@ class CI_Xmlrpc { return $temp; } - // END + // -------------------------------------------------------------------- - //------------------------------------- - // Sends XML-RPC Request - //------------------------------------- - - function send_request() + /** + * Sends XML-RPC Request + * + * @return bool + */ + public function send_request() { - $this->message = new XML_RPC_Message($this->method,$this->data); + $this->message = new XML_RPC_Message($this->method, $this->data); $this->message->debug = $this->debug; - if ( ! $this->result = $this->client->send($this->message)) - { - $this->error = $this->result->errstr; - return FALSE; - } - elseif ( ! is_object($this->result->val)) + if ( ! $this->result = $this->client->send($this->message) OR ! is_object($this->result->val)) { $this->error = $this->result->errstr; return FALSE; } $this->response = $this->result->decode(); - return TRUE; } - // END - //------------------------------------- - // Returns Error - //------------------------------------- + // -------------------------------------------------------------------- - function display_error() + /** + * Returns Error + * + * @return string + */ + public function display_error() { return $this->error; } - // END - //------------------------------------- - // Returns Remote Server Response - //------------------------------------- + // -------------------------------------------------------------------- - function display_response() + /** + * Returns Remote Server Response + * + * @return string + */ + public function display_response() { return $this->response; } - // END - //------------------------------------- - // Sends an Error Message for Server Request - //------------------------------------- + // -------------------------------------------------------------------- - function send_error_message($number, $message) + /** + * Sends an Error Message for Server Request + * + * @param int $number + * @param string $message + * @return object + */ + public function send_error_message($number, $message) { - return new XML_RPC_Response('0',$number, $message); + return new XML_RPC_Response(0, $number, $message); } - // END + // -------------------------------------------------------------------- - //------------------------------------- - // Send Response for Server Request - //------------------------------------- - - function send_response($response) + /** + * Send Response for Server Request + * + * @param array $response + * @return object + */ + public function send_response($response) { // $response should be array of values, which will be parsed // based on their data and type into a valid group of XML-RPC values - - $response = $this->values_parsing($response); - - return new XML_RPC_Response($response); + return new XML_RPC_Response($this->values_parsing($response)); } - // END } // END XML_RPC Class - - /** * XML-RPC Client class * * @category XML-RPC - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/xmlrpc.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/xmlrpc.html */ class XML_RPC_Client extends CI_Xmlrpc { - var $path = ''; - var $server = ''; - var $port = 80; - var $errno = ''; - var $errstring = ''; - var $timeout = 5; - var $no_multicall = FALSE; + /** + * Path + * + * @var string + */ + public $path = ''; - public function __construct($path, $server, $port=80) + /** + * Server hostname + * + * @var string + */ + public $server = ''; + + /** + * Server port + * + * @var int + */ + public $port = 80; + + /** + * + * Server username + * + * @var string + */ + public $username; + + /** + * Server password + * + * @var string + */ + public $password; + + /** + * Proxy hostname + * + * @var string + */ + public $proxy = FALSE; + + /** + * Proxy port + * + * @var int + */ + public $proxy_port = 8080; + + /** + * Error number + * + * @var string + */ + public $errno = ''; + + /** + * Error message + * + * @var string + */ + public $errstring = ''; + + /** + * Timeout in seconds + * + * @var int + */ + public $timeout = 5; + + /** + * No Multicall flag + * + * @var bool + */ + public $no_multicall = FALSE; + + // -------------------------------------------------------------------- + + /** + * Constructor + * + * @param string $path + * @param object $server + * @param int $port + * @param string $proxy + * @param int $proxy_port + * @return void + */ + public function __construct($path, $server, $port = 80, $proxy = FALSE, $proxy_port = 8080) { parent::__construct(); + $url = parse_url('http://'.$server); + + if (isset($url['user'], $url['pass'])) + { + $this->username = $url['user']; + $this->password = $url['pass']; + } + $this->port = $port; - $this->server = $server; + $this->server = $url['host']; $this->path = $path; + $this->proxy = $proxy; + $this->proxy_port = $proxy_port; } - function send($msg) + // -------------------------------------------------------------------- + + /** + * Send message + * + * @param mixed $msg + * @return object + */ + public function send($msg) { if (is_array($msg)) { // Multi-call disabled - $r = new XML_RPC_Response(0, $this->xmlrpcerr['multicall_recursion'],$this->xmlrpcstr['multicall_recursion']); - return $r; + return new XML_RPC_Response(0, $this->xmlrpcerr['multicall_recursion'], $this->xmlrpcstr['multicall_recursion']); } return $this->sendPayload($msg); } - function sendPayload($msg) + // -------------------------------------------------------------------- + + /** + * Send payload + * + * @param object $msg + * @return object + */ + public function sendPayload($msg) { - $fp = @fsockopen($this->server, $this->port,$this->errno, $this->errstr, $this->timeout); + if ($this->proxy === FALSE) + { + $server = $this->server; + $port = $this->port; + } + else + { + $server = $this->proxy; + $port = $this->proxy_port; + } + + $fp = @fsockopen($server, $port, $this->errno, $this->errstring, $this->timeout); if ( ! is_resource($fp)) { error_log($this->xmlrpcstr['http_error']); - $r = new XML_RPC_Response(0, $this->xmlrpcerr['http_error'],$this->xmlrpcstr['http_error']); - return $r; + return new XML_RPC_Response(0, $this->xmlrpcerr['http_error'], $this->xmlrpcstr['http_error']); } if (empty($msg->payload)) @@ -399,55 +727,123 @@ class XML_RPC_Client extends CI_Xmlrpc } $r = "\r\n"; - $op = "POST {$this->path} HTTP/1.0$r"; - $op .= "Host: {$this->server}$r"; - $op .= "Content-Type: text/xml$r"; - $op .= "User-Agent: {$this->xmlrpcName}$r"; - $op .= "Content-Length: ".strlen($msg->payload). "$r$r"; - $op .= $msg->payload; + $op = 'POST '.$this->path.' HTTP/1.0'.$r + .'Host: '.$this->server.$r + .'Content-Type: text/xml'.$r + .(isset($this->username, $this->password) ? 'Authorization: Basic '.base64_encode($this->username.':'.$this->password).$r : '') + .'User-Agent: '.$this->xmlrpcName.$r + .'Content-Length: '.strlen($msg->payload).$r.$r + .$msg->payload; + stream_set_timeout($fp, $this->timeout); // set timeout for subsequent operations - if ( ! fputs($fp, $op, strlen($op))) + for ($written = $timestamp = 0, $length = strlen($op); $written < $length; $written += $result) + { + if (($result = fwrite($fp, substr($op, $written))) === FALSE) + { + break; + } + // See https://bugs.php.net/bug.php?id=39598 and http://php.net/manual/en/function.fwrite.php#96951 + elseif ($result === 0) + { + if ($timestamp === 0) + { + $timestamp = time(); + } + elseif ($timestamp < (time() - $this->timeout)) + { + $result = FALSE; + break; + } + } + else + { + $timestamp = 0; + } + } + + if ($result === FALSE) { error_log($this->xmlrpcstr['http_error']); - $r = new XML_RPC_Response(0, $this->xmlrpcerr['http_error'], $this->xmlrpcstr['http_error']); - return $r; + return new XML_RPC_Response(0, $this->xmlrpcerr['http_error'], $this->xmlrpcstr['http_error']); } + $resp = $msg->parseResponse($fp); fclose($fp); return $resp; } -} // end class XML_RPC_Client - +} // END XML_RPC_Client Class /** * XML-RPC Response class * * @category XML-RPC - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/xmlrpc.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/xmlrpc.html */ class XML_RPC_Response { - var $val = 0; - var $errno = 0; - var $errstr = ''; - var $headers = array(); - var $xss_clean = TRUE; + /** + * Value + * + * @var mixed + */ + public $val = 0; + + /** + * Error number + * + * @var int + */ + public $errno = 0; + + /** + * Error message + * + * @var string + */ + public $errstr = ''; + + /** + * Headers list + * + * @var array + */ + public $headers = array(); + + /** + * XSS Filter flag + * + * @var bool + */ + public $xss_clean = TRUE; + + // -------------------------------------------------------------------- + + /** + * Constructor + * + * @param mixed $val + * @param int $code + * @param string $fstr + * @return void + */ public function __construct($val, $code = 0, $fstr = '') { - if ($code != 0) + if ($code !== 0) { // error $this->errno = $code; - $this->errstr = htmlentities($fstr); + $this->errstr = htmlspecialchars($fstr, + (is_php('5.4') ? ENT_XML1 | ENT_NOQUOTES : ENT_NOQUOTES), + 'UTF-8'); } - else if ( ! is_object($val)) + elseif ( ! is_object($val)) { // programmer error, not an object - error_log("Invalid type '" . gettype($val) . "' (value: $val) passed to XML_RPC_Response. Defaulting to empty value."); + error_log("Invalid type '".gettype($val)."' (value: ".$val.') passed to XML_RPC_Response. Defaulting to empty value.'); $this->val = new XML_RPC_Values(); } else @@ -456,177 +852,234 @@ class XML_RPC_Response } } - function faultCode() + // -------------------------------------------------------------------- + + /** + * Fault code + * + * @return int + */ + public function faultCode() { return $this->errno; } - function faultString() + // -------------------------------------------------------------------- + + /** + * Fault string + * + * @return string + */ + public function faultString() { return $this->errstr; } - function value() + // -------------------------------------------------------------------- + + /** + * Value + * + * @return mixed + */ + public function value() { return $this->val; } - function prepare_response() + // -------------------------------------------------------------------- + + /** + * Prepare response + * + * @return string xml + */ + public function prepare_response() { - $result = "\n"; - if ($this->errno) - { - $result .= ' + return "\n" + .($this->errno + ? ' faultCode - ' . $this->errno . ' + '.$this->errno.' faultString - ' . $this->errstr . ' + '.$this->errstr.' -'; - } - else - { - $result .= "\n\n" . - $this->val->serialize_class() . - "\n"; - } - $result .= "\n"; - return $result; +' + : "\n\n".$this->val->serialize_class()."\n") + ."\n"; } - function decode($array=FALSE) + // -------------------------------------------------------------------- + + /** + * Decode + * + * @param mixed $array + * @return array + */ + public function decode($array = NULL) { $CI =& get_instance(); - if ($this->xss_clean && ! isset($CI->security)) + if (is_array($array)) { - $CI->load->library('security'); - } - - if ($array !== FALSE && is_array($array)) - { - while (list($key) = each($array)) + foreach ($array as $key => &$value) { - if (is_array($array[$key])) + if (is_array($value)) { - $array[$key] = $this->decode($array[$key]); + $array[$key] = $this->decode($value); } - else + elseif ($this->xss_clean) { - $array[$key] = ($this->xss_clean) ? $CI->security->xss_clean($array[$key]) : $array[$key]; + $array[$key] = $CI->security->xss_clean($value); } } - $result = $array; + return $array; } - else - { - $result = $this->xmlrpc_decoder($this->val); - if (is_array($result)) - { - $result = $this->decode($result); - } - else - { - $result = ($this->xss_clean) ? $CI->security->xss_clean($result) : $result; - } + $result = $this->xmlrpc_decoder($this->val); + + if (is_array($result)) + { + $result = $this->decode($result); + } + elseif ($this->xss_clean) + { + $result = $CI->security->xss_clean($result); } return $result; } + // -------------------------------------------------------------------- - - //------------------------------------- - // XML-RPC Object to PHP Types - //------------------------------------- - - function xmlrpc_decoder($xmlrpc_val) + /** + * XML-RPC Object to PHP Types + * + * @param object + * @return array + */ + public function xmlrpc_decoder($xmlrpc_val) { $kind = $xmlrpc_val->kindOf(); - if ($kind == 'scalar') + if ($kind === 'scalar') { return $xmlrpc_val->scalarval(); } - elseif ($kind == 'array') + elseif ($kind === 'array') { reset($xmlrpc_val->me); - list($a,$b) = each($xmlrpc_val->me); - $size = count($b); - + $b = current($xmlrpc_val->me); $arr = array(); - for ($i = 0; $i < $size; $i++) + for ($i = 0, $size = count($b); $i < $size; $i++) { $arr[] = $this->xmlrpc_decoder($xmlrpc_val->me['array'][$i]); } return $arr; } - elseif ($kind == 'struct') + elseif ($kind === 'struct') { reset($xmlrpc_val->me['struct']); $arr = array(); - while (list($key,$value) = each($xmlrpc_val->me['struct'])) + foreach ($xmlrpc_val->me['struct'] as $key => &$value) { $arr[$key] = $this->xmlrpc_decoder($value); } + return $arr; } } + // -------------------------------------------------------------------- - //------------------------------------- - // ISO-8601 time to server or UTC time - //------------------------------------- - - function iso8601_decode($time, $utc=0) + /** + * ISO-8601 time to server or UTC time + * + * @param string + * @param bool + * @return int unix timestamp + */ + public function iso8601_decode($time, $utc = FALSE) { - // return a timet in the localtime, or UTC + // Return a time in the localtime, or UTC $t = 0; if (preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $time, $regs)) { - $fnc = ($utc == 1) ? 'gmmktime' : 'mktime'; + $fnc = ($utc === TRUE) ? 'gmmktime' : 'mktime'; $t = $fnc($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); } return $t; } -} // End Response Class - - +} // END XML_RPC_Response Class /** * XML-RPC Message class * * @category XML-RPC - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/xmlrpc.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/xmlrpc.html */ class XML_RPC_Message extends CI_Xmlrpc { - var $payload; - var $method_name; - var $params = array(); - var $xh = array(); - public function __construct($method, $pars=0) + /** + * Payload + * + * @var string + */ + public $payload; + + /** + * Method name + * + * @var string + */ + public $method_name; + + /** + * Parameter list + * + * @var array + */ + public $params = array(); + + /** + * XH? + * + * @var array + */ + public $xh = array(); + + // -------------------------------------------------------------------- + + /** + * Constructor + * + * @param string $method + * @param array $pars + * @return void + */ + public function __construct($method, $pars = FALSE) { parent::__construct(); $this->method_name = $method; if (is_array($pars) && count($pars) > 0) { - for ($i=0; $iparams[] = $pars[$i]; @@ -634,17 +1087,20 @@ class XML_RPC_Message extends CI_Xmlrpc } } - //------------------------------------- - // Create Payload to Send - //------------------------------------- + // -------------------------------------------------------------------- - function createPayload() + /** + * Create Payload to Send + * + * @return void + */ + public function createPayload() { - $this->payload = "\r\n\r\n"; - $this->payload .= '' . $this->method_name . "\r\n"; - $this->payload .= "\r\n"; + $this->payload = '\r\n\r\n" + .''.$this->method_name."\r\n" + ."\r\n"; - for ($i=0; $iparams); $i++) + for ($i = 0, $c = count($this->params); $i < $c; $i++) { // $p = XML_RPC_Values $p = $this->params[$i]; @@ -654,11 +1110,15 @@ class XML_RPC_Message extends CI_Xmlrpc $this->payload .= "\r\n\r\n"; } - //------------------------------------- - // Parse External XML-RPC Server's Response - //------------------------------------- + // -------------------------------------------------------------------- - function parseResponse($fp) + /** + * Parse External XML-RPC Server's Response + * + * @param resource + * @return object + */ + public function parseResponse($fp) { $data = ''; @@ -667,65 +1127,48 @@ class XML_RPC_Message extends CI_Xmlrpc $data .= $datum; } - //------------------------------------- - // DISPLAY HTTP CONTENT for DEBUGGING - //------------------------------------- - + // Display HTTP content for debugging if ($this->debug === TRUE) { - echo "
    ";
    -			echo "---DATA---\n" . htmlspecialchars($data) . "\n---END DATA---\n\n";
    -			echo "
    "; + echo "
    ---DATA---\n".htmlspecialchars($data)."\n---END DATA---\n\n
    "; } - //------------------------------------- - // Check for data - //------------------------------------- - - if ($data == "") + // Check for data + if ($data === '') { error_log($this->xmlrpcstr['no_data']); - $r = new XML_RPC_Response(0, $this->xmlrpcerr['no_data'], $this->xmlrpcstr['no_data']); - return $r; + return new XML_RPC_Response(0, $this->xmlrpcerr['no_data'], $this->xmlrpcstr['no_data']); } - - //------------------------------------- - // Check for HTTP 200 Response - //------------------------------------- - - if (strncmp($data, 'HTTP', 4) == 0 && ! preg_match('/^HTTP\/[0-9\.]+ 200 /', $data)) + // Check for HTTP 200 Response + if (strpos($data, 'HTTP') === 0 && ! preg_match('/^HTTP\/[0-9\.]+ 200 /', $data)) { - $errstr= substr($data, 0, strpos($data, "\n")-1); - $r = new XML_RPC_Response(0, $this->xmlrpcerr['http_error'], $this->xmlrpcstr['http_error']. ' (' . $errstr . ')'); - return $r; + $errstr = substr($data, 0, strpos($data, "\n")-1); + return new XML_RPC_Response(0, $this->xmlrpcerr['http_error'], $this->xmlrpcstr['http_error'].' ('.$errstr.')'); } //------------------------------------- - // Create and Set Up XML Parser + // Create and Set Up XML Parser //------------------------------------- $parser = xml_parser_create($this->xmlrpc_defencoding); - - $this->xh[$parser] = array(); - $this->xh[$parser]['isf'] = 0; - $this->xh[$parser]['ac'] = ''; - $this->xh[$parser]['headers'] = array(); - $this->xh[$parser]['stack'] = array(); - $this->xh[$parser]['valuestack'] = array(); - $this->xh[$parser]['isf_reason'] = 0; + $pname = (string) $parser; + $this->xh[$pname] = array( + 'isf' => 0, + 'ac' => '', + 'headers' => array(), + 'stack' => array(), + 'valuestack' => array(), + 'isf_reason' => 0 + ); xml_set_object($parser, $this); - xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, TRUE); xml_set_element_handler($parser, 'open_tag', 'closing_tag'); xml_set_character_data_handler($parser, 'character_data'); //xml_set_default_handler($parser, 'default_handler'); - - //------------------------------------- - // GET HEADERS - //------------------------------------- - + // Get headers $lines = explode("\r\n", $data); while (($line = array_shift($lines))) { @@ -733,87 +1176,67 @@ class XML_RPC_Message extends CI_Xmlrpc { break; } - $this->xh[$parser]['headers'][] = $line; + $this->xh[$pname]['headers'][] = $line; } $data = implode("\r\n", $lines); - - //------------------------------------- - // PARSE XML DATA - //------------------------------------- - + // Parse XML data if ( ! xml_parse($parser, $data, count($data))) { $errstr = sprintf('XML error: %s at line %d', - xml_error_string(xml_get_error_code($parser)), - xml_get_current_line_number($parser)); - //error_log($errstr); + xml_error_string(xml_get_error_code($parser)), + xml_get_current_line_number($parser)); + $r = new XML_RPC_Response(0, $this->xmlrpcerr['invalid_return'], $this->xmlrpcstr['invalid_return']); xml_parser_free($parser); return $r; } xml_parser_free($parser); - // --------------------------------------- - // Got Ourselves Some Badness, It Seems - // --------------------------------------- - - if ($this->xh[$parser]['isf'] > 1) + // Got ourselves some badness, it seems + if ($this->xh[$pname]['isf'] > 1) { if ($this->debug === TRUE) { - echo "---Invalid Return---\n"; - echo $this->xh[$parser]['isf_reason']; - echo "---Invalid Return---\n\n"; + echo "---Invalid Return---\n".$this->xh[$pname]['isf_reason']."---Invalid Return---\n\n"; } - $r = new XML_RPC_Response(0, $this->xmlrpcerr['invalid_return'],$this->xmlrpcstr['invalid_return'].' '.$this->xh[$parser]['isf_reason']); - return $r; + return new XML_RPC_Response(0, $this->xmlrpcerr['invalid_return'], $this->xmlrpcstr['invalid_return'].' '.$this->xh[$pname]['isf_reason']); } - elseif ( ! is_object($this->xh[$parser]['value'])) + elseif ( ! is_object($this->xh[$pname]['value'])) { - $r = new XML_RPC_Response(0, $this->xmlrpcerr['invalid_return'],$this->xmlrpcstr['invalid_return'].' '.$this->xh[$parser]['isf_reason']); - return $r; + return new XML_RPC_Response(0, $this->xmlrpcerr['invalid_return'], $this->xmlrpcstr['invalid_return'].' '.$this->xh[$pname]['isf_reason']); } - //------------------------------------- - // DISPLAY XML CONTENT for DEBUGGING - //------------------------------------- - + // Display XML content for debugging if ($this->debug === TRUE) { - echo "
    ";
    +			echo '
    ';
     
    -			if (count($this->xh[$parser]['headers'] > 0))
    +			if (count($this->xh[$pname]['headers'] > 0))
     			{
     				echo "---HEADERS---\n";
    -				foreach ($this->xh[$parser]['headers'] as $header)
    +				foreach ($this->xh[$pname]['headers'] as $header)
     				{
    -					echo "$header\n";
    +					echo $header."\n";
     				}
     				echo "---END HEADERS---\n\n";
     			}
     
    -			echo "---DATA---\n" . htmlspecialchars($data) . "\n---END DATA---\n\n";
    -
    -			echo "---PARSED---\n" ;
    -			var_dump($this->xh[$parser]['value']);
    +			echo "---DATA---\n".htmlspecialchars($data)."\n---END DATA---\n\n---PARSED---\n";
    +			var_dump($this->xh[$pname]['value']);
     			echo "\n---END PARSED---
    "; } - //------------------------------------- - // SEND RESPONSE - //------------------------------------- - - $v = $this->xh[$parser]['value']; - - if ($this->xh[$parser]['isf']) + // Send response + $v = $this->xh[$pname]['value']; + if ($this->xh[$pname]['isf']) { $errno_v = $v->me['struct']['faultCode']; $errstr_v = $v->me['struct']['faultString']; $errno = $errno_v->scalarval(); - if ($errno == 0) + if ($errno === 0) { // FAULT returned, errno needs to reflect that $errno = -1; @@ -826,10 +1249,12 @@ class XML_RPC_Message extends CI_Xmlrpc $r = new XML_RPC_Response($v); } - $r->headers = $this->xh[$parser]['headers']; + $r->headers = $this->xh[$pname]['headers']; return $r; } + // -------------------------------------------------------------------- + // ------------------------------------ // Begin Return Message Parsing section // ------------------------------------ @@ -844,63 +1269,63 @@ class XML_RPC_Message extends CI_Xmlrpc // stack - array with parent tree of the xml element, // used to validate the nesting of elements - //------------------------------------- - // Start Element Handler - //------------------------------------- + // -------------------------------------------------------------------- - function open_tag($the_parser, $name, $attrs) + /** + * Start Element Handler + * + * @param string + * @param string + * @return void + */ + public function open_tag($the_parser, $name) { + $the_parser = (string) $the_parser; + // If invalid nesting, then return if ($this->xh[$the_parser]['isf'] > 1) return; // Evaluate and check for correct nesting of XML elements - - if (count($this->xh[$the_parser]['stack']) == 0) + if (count($this->xh[$the_parser]['stack']) === 0) { - if ($name != 'METHODRESPONSE' && $name != 'METHODCALL') + if ($name !== 'METHODRESPONSE' && $name !== 'METHODCALL') { $this->xh[$the_parser]['isf'] = 2; $this->xh[$the_parser]['isf_reason'] = 'Top level XML-RPC element is missing'; return; } } - else + // not top level element: see if parent is OK + elseif ( ! in_array($this->xh[$the_parser]['stack'][0], $this->valid_parents[$name], TRUE)) { - // not top level element: see if parent is OK - if ( ! in_array($this->xh[$the_parser]['stack'][0], $this->valid_parents[$name], TRUE)) - { - $this->xh[$the_parser]['isf'] = 2; - $this->xh[$the_parser]['isf_reason'] = "XML-RPC element $name cannot be child of ".$this->xh[$the_parser]['stack'][0]; - return; - } + $this->xh[$the_parser]['isf'] = 2; + $this->xh[$the_parser]['isf_reason'] = 'XML-RPC element '.$name.' cannot be child of '.$this->xh[$the_parser]['stack'][0]; + return; } - switch($name) + switch ($name) { case 'STRUCT': case 'ARRAY': // Creates array for child elements - - $cur_val = array('value' => array(), - 'type' => $name); - + $cur_val = array('value' => array(), 'type' => $name); array_unshift($this->xh[$the_parser]['valuestack'], $cur_val); - break; + break; case 'METHODNAME': case 'NAME': $this->xh[$the_parser]['ac'] = ''; - break; + break; case 'FAULT': $this->xh[$the_parser]['isf'] = 1; - break; + break; case 'PARAM': $this->xh[$the_parser]['value'] = NULL; - break; + break; case 'VALUE': $this->xh[$the_parser]['vt'] = 'value'; $this->xh[$the_parser]['ac'] = ''; $this->xh[$the_parser]['lv'] = 1; - break; + break; case 'I4': case 'INT': case 'STRING': @@ -908,70 +1333,76 @@ class XML_RPC_Message extends CI_Xmlrpc case 'DOUBLE': case 'DATETIME.ISO8601': case 'BASE64': - if ($this->xh[$the_parser]['vt'] != 'value') + if ($this->xh[$the_parser]['vt'] !== 'value') { //two data elements inside a value: an error occurred! $this->xh[$the_parser]['isf'] = 2; - $this->xh[$the_parser]['isf_reason'] = "'Twas a $name element following a ".$this->xh[$the_parser]['vt']." element inside a single value"; + $this->xh[$the_parser]['isf_reason'] = 'There is a '.$name.' element following a ' + .$this->xh[$the_parser]['vt'].' element inside a single value'; return; } $this->xh[$the_parser]['ac'] = ''; - break; + break; case 'MEMBER': // Set name of to nothing to prevent errors later if no is found $this->xh[$the_parser]['valuestack'][0]['name'] = ''; // Set NULL value to check to see if value passed for this param/member $this->xh[$the_parser]['value'] = NULL; - break; + break; case 'DATA': case 'METHODCALL': case 'METHODRESPONSE': case 'PARAMS': // valid elements that add little to processing - break; + break; default: /// An Invalid Element is Found, so we have trouble $this->xh[$the_parser]['isf'] = 2; - $this->xh[$the_parser]['isf_reason'] = "Invalid XML-RPC element found: $name"; - break; + $this->xh[$the_parser]['isf_reason'] = 'Invalid XML-RPC element found: '.$name; + break; } // Add current element name to stack, to allow validation of nesting array_unshift($this->xh[$the_parser]['stack'], $name); - if ($name != 'VALUE') $this->xh[$the_parser]['lv'] = 0; + $name === 'VALUE' OR $this->xh[$the_parser]['lv'] = 0; } - // END + // -------------------------------------------------------------------- - //------------------------------------- - // End Element Handler - //------------------------------------- - - function closing_tag($the_parser, $name) + /** + * End Element Handler + * + * @param string + * @param string + * @return void + */ + public function closing_tag($the_parser, $name) { + $the_parser = (string) $the_parser; + if ($this->xh[$the_parser]['isf'] > 1) return; // Remove current element from stack and set variable // NOTE: If the XML validates, then we do not have to worry about - // the opening and closing of elements. Nesting is checked on the opening + // the opening and closing of elements. Nesting is checked on the opening // tag so we be safe there as well. $curr_elem = array_shift($this->xh[$the_parser]['stack']); - switch($name) + switch ($name) { case 'STRUCT': case 'ARRAY': $cur_val = array_shift($this->xh[$the_parser]['valuestack']); - $this->xh[$the_parser]['value'] = ( ! isset($cur_val['values'])) ? array() : $cur_val['values']; + $this->xh[$the_parser]['value'] = isset($cur_val['values']) ? $cur_val['values'] : array(); $this->xh[$the_parser]['vt'] = strtolower($name); - break; + break; case 'NAME': $this->xh[$the_parser]['valuestack'][0]['name'] = $this->xh[$the_parser]['ac']; - break; + break; case 'BOOLEAN': case 'I4': case 'INT': @@ -981,63 +1412,46 @@ class XML_RPC_Message extends CI_Xmlrpc case 'BASE64': $this->xh[$the_parser]['vt'] = strtolower($name); - if ($name == 'STRING') + if ($name === 'STRING') { $this->xh[$the_parser]['value'] = $this->xh[$the_parser]['ac']; } - elseif ($name=='DATETIME.ISO8601') + elseif ($name === 'DATETIME.ISO8601') { $this->xh[$the_parser]['vt'] = $this->xmlrpcDateTime; $this->xh[$the_parser]['value'] = $this->xh[$the_parser]['ac']; } - elseif ($name=='BASE64') + elseif ($name === 'BASE64') { $this->xh[$the_parser]['value'] = base64_decode($this->xh[$the_parser]['ac']); } - elseif ($name=='BOOLEAN') + elseif ($name === 'BOOLEAN') { // Translated BOOLEAN values to TRUE AND FALSE - if ($this->xh[$the_parser]['ac'] == '1') - { - $this->xh[$the_parser]['value'] = TRUE; - } - else - { - $this->xh[$the_parser]['value'] = FALSE; - } + $this->xh[$the_parser]['value'] = (bool) $this->xh[$the_parser]['ac']; } elseif ($name=='DOUBLE') { // we have a DOUBLE // we must check that only 0123456789-. are characters here - if ( ! preg_match('/^[+-]?[eE0-9\t \.]+$/', $this->xh[$the_parser]['ac'])) - { - $this->xh[$the_parser]['value'] = 'ERROR_NON_NUMERIC_FOUND'; - } - else - { - $this->xh[$the_parser]['value'] = (double)$this->xh[$the_parser]['ac']; - } + $this->xh[$the_parser]['value'] = preg_match('/^[+-]?[eE0-9\t \.]+$/', $this->xh[$the_parser]['ac']) + ? (float) $this->xh[$the_parser]['ac'] + : 'ERROR_NON_NUMERIC_FOUND'; } else { // we have an I4/INT // we must check that only 0123456789- are characters here - if ( ! preg_match('/^[+-]?[0-9\t ]+$/', $this->xh[$the_parser]['ac'])) - { - $this->xh[$the_parser]['value'] = 'ERROR_NON_NUMERIC_FOUND'; - } - else - { - $this->xh[$the_parser]['value'] = (int)$this->xh[$the_parser]['ac']; - } + $this->xh[$the_parser]['value'] = preg_match('/^[+-]?[0-9\t ]+$/', $this->xh[$the_parser]['ac']) + ? (int) $this->xh[$the_parser]['ac'] + : 'ERROR_NON_NUMERIC_FOUND'; } $this->xh[$the_parser]['ac'] = ''; $this->xh[$the_parser]['lv'] = 3; // indicate we've found a value - break; + break; case 'VALUE': // This if() detects if no scalar was inside - if ($this->xh[$the_parser]['vt']=='value') + if ($this->xh[$the_parser]['vt'] == 'value') { $this->xh[$the_parser]['value'] = $this->xh[$the_parser]['ac']; $this->xh[$the_parser]['vt'] = $this->xmlrpcString; @@ -1046,7 +1460,7 @@ class XML_RPC_Message extends CI_Xmlrpc // build the XML-RPC value out of the data received, and substitute it $temp = new XML_RPC_Values($this->xh[$the_parser]['value'], $this->xh[$the_parser]['vt']); - if (count($this->xh[$the_parser]['valuestack']) && $this->xh[$the_parser]['valuestack'][0]['type'] == 'ARRAY') + if (count($this->xh[$the_parser]['valuestack']) && $this->xh[$the_parser]['valuestack'][0]['type'] === 'ARRAY') { // Array $this->xh[$the_parser]['valuestack'][0]['values'][] = $temp; @@ -1056,57 +1470,64 @@ class XML_RPC_Message extends CI_Xmlrpc // Struct $this->xh[$the_parser]['value'] = $temp; } - break; + break; case 'MEMBER': - $this->xh[$the_parser]['ac']=''; + $this->xh[$the_parser]['ac'] = ''; // If value add to array in the stack for the last element built if ($this->xh[$the_parser]['value']) { $this->xh[$the_parser]['valuestack'][0]['values'][$this->xh[$the_parser]['valuestack'][0]['name']] = $this->xh[$the_parser]['value']; } - break; + break; case 'DATA': - $this->xh[$the_parser]['ac']=''; - break; + $this->xh[$the_parser]['ac'] = ''; + break; case 'PARAM': if ($this->xh[$the_parser]['value']) { $this->xh[$the_parser]['params'][] = $this->xh[$the_parser]['value']; } - break; + break; case 'METHODNAME': $this->xh[$the_parser]['method'] = ltrim($this->xh[$the_parser]['ac']); - break; + break; case 'PARAMS': case 'FAULT': case 'METHODCALL': case 'METHORESPONSE': // We're all good kids with nuthin' to do - break; + break; default: - // End of an Invalid Element. Taken care of during the opening tag though - break; + // End of an Invalid Element. Taken care of during the opening tag though + break; } } - //------------------------------------- - // Parses Character Data - //------------------------------------- + // -------------------------------------------------------------------- - function character_data($the_parser, $data) + /** + * Parse character data + * + * @param string + * @param string + * @return void + */ + public function character_data($the_parser, $data) { + $the_parser = (string) $the_parser; + if ($this->xh[$the_parser]['isf'] > 1) return; // XML Fault found already // If a value has not been found - if ($this->xh[$the_parser]['lv'] != 3) + if ($this->xh[$the_parser]['lv'] !== 3) { - if ($this->xh[$the_parser]['lv'] == 1) + if ($this->xh[$the_parser]['lv'] === 1) { $this->xh[$the_parser]['lv'] = 2; // Found a value } - if ( ! @isset($this->xh[$the_parser]['ac'])) + if ( ! isset($this->xh[$the_parser]['ac'])) { $this->xh[$the_parser]['ac'] = ''; } @@ -1115,88 +1536,104 @@ class XML_RPC_Message extends CI_Xmlrpc } } + // -------------------------------------------------------------------- - function addParam($par) { $this->params[]=$par; } + /** + * Add parameter + * + * @param mixed + * @return void + */ + public function addParam($par) + { + $this->params[] = $par; + } - function output_parameters($array=FALSE) + // -------------------------------------------------------------------- + + /** + * Output parameters + * + * @param array $array + * @return array + */ + public function output_parameters(array $array = array()) { $CI =& get_instance(); - if ($this->xss_clean && ! isset($CI->security)) + if ( ! empty($array)) { - $CI->load->library('security'); - } - - if ($array !== FALSE && is_array($array)) - { - while (list($key) = each($array)) + foreach ($array as $key => &$value) { - if (is_array($array[$key])) + if (is_array($value)) { - $array[$key] = $this->output_parameters($array[$key]); + $array[$key] = $this->output_parameters($value); } - else + elseif ($key !== 'bits' && $this->xss_clean) { // 'bits' is for the MetaWeblog API image bits // @todo - this needs to be made more general purpose - $array[$key] = ($key == 'bits' OR $this->xss_clean == FALSE) ? $array[$key] : $CI->security->xss_clean($array[$key]); + $array[$key] = $CI->security->xss_clean($value); } } - $parameters = $array; + return $array; } - else + + $parameters = array(); + + for ($i = 0, $c = count($this->params); $i < $c; $i++) { - $parameters = array(); + $a_param = $this->decode_message($this->params[$i]); - for ($i = 0; $i < count($this->params); $i++) + if (is_array($a_param)) { - $a_param = $this->decode_message($this->params[$i]); - - if (is_array($a_param)) - { - $parameters[] = $this->output_parameters($a_param); - } - else - { - $parameters[] = ($this->xss_clean) ? $CI->security->xss_clean($a_param) : $a_param; - } + $parameters[] = $this->output_parameters($a_param); + } + else + { + $parameters[] = ($this->xss_clean) ? $CI->security->xss_clean($a_param) : $a_param; } } return $parameters; } + // -------------------------------------------------------------------- - function decode_message($param) + /** + * Decode message + * + * @param object + * @return mixed + */ + public function decode_message($param) { $kind = $param->kindOf(); - if ($kind == 'scalar') + if ($kind === 'scalar') { return $param->scalarval(); } - elseif ($kind == 'array') + elseif ($kind === 'array') { reset($param->me); - list($a,$b) = each($param->me); - + $b = current($param->me); $arr = array(); - for($i = 0; $i < count($b); $i++) + for ($i = 0, $c = count($b); $i < $c; $i++) { $arr[] = $this->decode_message($param->me['array'][$i]); } return $arr; } - elseif ($kind == 'struct') + elseif ($kind === 'struct') { reset($param->me['struct']); - $arr = array(); - while (list($key,$value) = each($param->me['struct'])) + foreach ($param->me['struct'] as $key => &$value) { $arr[$key] = $this->decode_message($value); } @@ -1205,33 +1642,51 @@ class XML_RPC_Message extends CI_Xmlrpc } } -} // End XML_RPC_Messages class - - +} // END XML_RPC_Message Class /** * XML-RPC Values class * * @category XML-RPC - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/xmlrpc.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/user_guide/libraries/xmlrpc.html */ class XML_RPC_Values extends CI_Xmlrpc { - var $me = array(); - var $mytype = 0; + /** + * Value data + * + * @var array + */ + public $me = array(); - public function __construct($val=-1, $type='') + /** + * Value type + * + * @var int + */ + public $mytype = 0; + + // -------------------------------------------------------------------- + + /** + * Constructor + * + * @param mixed $val + * @param string $type + * @return void + */ + public function __construct($val = -1, $type = '') { parent::__construct(); - if ($val != -1 OR $type != '') + if ($val !== -1 OR $type !== '') { - $type = $type == '' ? 'string' : $type; + $type = $type === '' ? 'string' : $type; if ($this->xmlrpcTypes[$type] == 1) { - $this->addScalar($val,$type); + $this->addScalar($val, $type); } elseif ($this->xmlrpcTypes[$type] == 2) { @@ -1244,11 +1699,20 @@ class XML_RPC_Values extends CI_Xmlrpc } } - function addScalar($val, $type='string') + // -------------------------------------------------------------------- + + /** + * Add scalar value + * + * @param scalar + * @param string + * @return int + */ + public function addScalar($val, $type = 'string') { $typeof = $this->xmlrpcTypes[$type]; - if ($this->mytype==1) + if ($this->mytype === 1) { echo 'XML_RPC_Values: scalar can have only one value
    '; return 0; @@ -1260,19 +1724,12 @@ class XML_RPC_Values extends CI_Xmlrpc return 0; } - if ($type == $this->xmlrpcBoolean) + if ($type === $this->xmlrpcBoolean) { - if (strcasecmp($val,'true')==0 OR $val==1 OR ($val==true && strcasecmp($val,'false'))) - { - $val = 1; - } - else - { - $val=0; - } + $val = (int) (strcasecmp($val, 'true') === 0 OR $val === 1 OR ($val === TRUE && strcasecmp($val, 'false'))); } - if ($this->mytype == 2) + if ($this->mytype === 2) { // adding to an array here $ar = $this->me['array']; @@ -1285,14 +1742,23 @@ class XML_RPC_Values extends CI_Xmlrpc $this->me[$type] = $val; $this->mytype = $typeof; } + return 1; } - function addArray($vals) + // -------------------------------------------------------------------- + + /** + * Add array value + * + * @param array + * @return int + */ + public function addArray($vals) { - if ($this->mytype != 0) + if ($this->mytype !== 0) { - echo 'XML_RPC_Values: already initialized as a [' . $this->kindOf() . ']
    '; + echo 'XML_RPC_Values: already initialized as a ['.$this->kindOf().']
    '; return 0; } @@ -1301,11 +1767,19 @@ class XML_RPC_Values extends CI_Xmlrpc return 1; } - function addStruct($vals) + // -------------------------------------------------------------------- + + /** + * Add struct value + * + * @param object + * @return int + */ + public function addStruct($vals) { - if ($this->mytype != 0) + if ($this->mytype !== 0) { - echo 'XML_RPC_Values: already initialized as a [' . $this->kindOf() . ']
    '; + echo 'XML_RPC_Values: already initialized as a ['.$this->kindOf().']
    '; return 0; } $this->mytype = $this->xmlrpcTypes['struct']; @@ -1313,121 +1787,134 @@ class XML_RPC_Values extends CI_Xmlrpc return 1; } - function kindOf() + // -------------------------------------------------------------------- + + /** + * Get value type + * + * @return string + */ + public function kindOf() { - switch($this->mytype) + switch ($this->mytype) { - case 3: - return 'struct'; - break; - case 2: - return 'array'; - break; - case 1: - return 'scalar'; - break; - default: - return 'undef'; + case 3: return 'struct'; + case 2: return 'array'; + case 1: return 'scalar'; + default: return 'undef'; } } - function serializedata($typ, $val) + // -------------------------------------------------------------------- + + /** + * Serialize data + * + * @param string + * @param mixed + * @return string + */ + public function serializedata($typ, $val) { $rs = ''; - switch($this->xmlrpcTypes[$typ]) + switch ($this->xmlrpcTypes[$typ]) { case 3: // struct $rs .= "\n"; reset($val); - while (list($key2, $val2) = each($val)) + foreach ($val as $key2 => &$val2) { - $rs .= "\n{$key2}\n"; - $rs .= $this->serializeval($val2); - $rs .= "\n"; + $rs .= "\n{$key2}\n".$this->serializeval($val2)."\n"; } $rs .= ''; - break; + break; case 2: // array $rs .= "\n\n"; - for($i=0; $i < count($val); $i++) + for ($i = 0, $c = count($val); $i < $c; $i++) { $rs .= $this->serializeval($val[$i]); } - $rs.="\n\n"; + $rs .= "\n\n"; break; case 1: // others switch ($typ) { case $this->xmlrpcBase64: - $rs .= "<{$typ}>" . base64_encode((string)$val) . "\n"; - break; + $rs .= '<'.$typ.'>'.base64_encode( (string) $val).'\n"; + break; case $this->xmlrpcBoolean: - $rs .= "<{$typ}>" . ((bool)$val ? '1' : '0') . "\n"; - break; + $rs .= '<'.$typ.'>'.( (bool) $val ? '1' : '0').'\n"; + break; case $this->xmlrpcString: - $rs .= "<{$typ}>" . htmlspecialchars((string)$val). "\n"; - break; + $rs .= '<'.$typ.'>'.htmlspecialchars( (string) $val).'\n"; + break; default: - $rs .= "<{$typ}>{$val}\n"; - break; + $rs .= '<'.$typ.'>'.$val.'\n"; + break; } default: - break; + break; } + return $rs; } - function serialize_class() + // -------------------------------------------------------------------- + + /** + * Serialize class + * + * @return string + */ + public function serialize_class() { return $this->serializeval($this); } - function serializeval($o) - { - $ar = $o->me; - reset($ar); + // -------------------------------------------------------------------- - list($typ, $val) = each($ar); - $rs = "\n".$this->serializedata($typ, $val)."\n"; - return $rs; + /** + * Serialize value + * + * @param object + * @return string + */ + public function serializeval($o) + { + $array = $o->me; + list($value, $type) = array(reset($array), key($array)); + return "\n".$this->serializedata($type, $value)."\n"; } - function scalarval() + // -------------------------------------------------------------------- + + /** + * Scalar value + * + * @return mixed + */ + public function scalarval() { - reset($this->me); - list($a,$b) = each($this->me); - return $b; + return reset($this->me); } + // -------------------------------------------------------------------- - //------------------------------------- - // Encode time in ISO-8601 form. - //------------------------------------- - - // Useful for sending time in XML-RPC - - function iso8601_encode($time, $utc=0) + /** + * Encode time in ISO-8601 form. + * Useful for sending time in XML-RPC + * + * @param int unix timestamp + * @param bool + * @return string + */ + public function iso8601_encode($time, $utc = FALSE) { - if ($utc == 1) - { - $t = strftime("%Y%m%dT%H:%M:%S", $time); - } - else - { - if (function_exists('gmstrftime')) - $t = gmstrftime("%Y%m%dT%H:%M:%S", $time); - else - $t = strftime("%Y%m%dT%H:%M:%S", $time - date('Z')); - } - return $t; + return ($utc) ? strftime('%Y%m%dT%H:%i:%s', $time) : gmstrftime('%Y%m%dT%H:%i:%s', $time); } -} -// END XML_RPC_Values Class - -/* End of file Xmlrpc.php */ -/* Location: ./system/libraries/Xmlrpc.php */ \ No newline at end of file +} // END XML_RPC_Values Class diff --git a/system/libraries/Xmlrpcs.php b/system/libraries/Xmlrpcs.php index 9cd33214..0274f13b 100644 --- a/system/libraries/Xmlrpcs.php +++ b/system/libraries/Xmlrpcs.php @@ -1,24 +1,48 @@ -set_system_methods(); @@ -56,7 +104,7 @@ class CI_Xmlrpcs extends CI_Xmlrpc $this->methods = array_merge($this->methods, $config['functions']); } - log_message('debug', "XML-RPC Server Class Initialized"); + log_message('info', 'XML-RPC Server Class Initialized'); } // -------------------------------------------------------------------- @@ -64,11 +112,10 @@ class CI_Xmlrpcs extends CI_Xmlrpc /** * Initialize Prefs and Serve * - * @access public * @param mixed * @return void */ - function initialize($config=array()) + public function initialize($config = array()) { if (isset($config['functions']) && is_array($config['functions'])) { @@ -96,29 +143,28 @@ class CI_Xmlrpcs extends CI_Xmlrpc /** * Setting of System Methods * - * @access public * @return void */ - function set_system_methods() + public function set_system_methods() { $this->methods = array( 'system.listMethods' => array( - 'function' => 'this.listMethods', - 'signature' => array(array($this->xmlrpcArray, $this->xmlrpcString), array($this->xmlrpcArray)), - 'docstring' => 'Returns an array of available methods on this server'), - 'system.methodHelp' => array( - 'function' => 'this.methodHelp', - 'signature' => array(array($this->xmlrpcString, $this->xmlrpcString)), - 'docstring' => 'Returns a documentation string for the specified method'), + 'function' => 'this.listMethods', + 'signature' => array(array($this->xmlrpcArray, $this->xmlrpcString), array($this->xmlrpcArray)), + 'docstring' => 'Returns an array of available methods on this server'), + 'system.methodHelp' => array( + 'function' => 'this.methodHelp', + 'signature' => array(array($this->xmlrpcString, $this->xmlrpcString)), + 'docstring' => 'Returns a documentation string for the specified method'), 'system.methodSignature' => array( - 'function' => 'this.methodSignature', - 'signature' => array(array($this->xmlrpcArray, $this->xmlrpcString)), - 'docstring' => 'Returns an array describing the return type and required parameters of a method'), - 'system.multicall' => array( - 'function' => 'this.multicall', - 'signature' => array(array($this->xmlrpcArray, $this->xmlrpcArray)), - 'docstring' => 'Combine multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details') - ); + 'function' => 'this.methodSignature', + 'signature' => array(array($this->xmlrpcArray, $this->xmlrpcString)), + 'docstring' => 'Returns an array describing the return type and required parameters of a method'), + 'system.multicall' => array( + 'function' => 'this.multicall', + 'signature' => array(array($this->xmlrpcArray, $this->xmlrpcArray)), + 'docstring' => 'Combine multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details') + ); } // -------------------------------------------------------------------- @@ -126,18 +172,15 @@ class CI_Xmlrpcs extends CI_Xmlrpc /** * Main Server Function * - * @access public * @return void */ - function serve() + public function serve() { $r = $this->parseRequest(); - $payload = 'xmlrpc_defencoding.'"?'.'>'."\n"; - $payload .= $this->debug_msg; - $payload .= $r->prepare_response(); + $payload = 'xmlrpc_defencoding.'"?'.'>'."\n".$this->debug_msg.$r->prepare_response(); - header("Content-Type: text/xml"); - header("Content-Length: ".strlen($payload)); + header('Content-Type: text/xml'); + header('Content-Length: '.strlen($payload)); exit($payload); } @@ -146,19 +189,18 @@ class CI_Xmlrpcs extends CI_Xmlrpc /** * Add Method to Class * - * @access public * @param string method name * @param string function * @param string signature * @param string docstring * @return void */ - function add_to_map($methodname, $function, $sig, $doc) + public function add_to_map($methodname, $function, $sig, $doc) { $this->methods[$methodname] = array( - 'function' => $function, - 'signature' => $sig, - 'docstring' => $doc + 'function' => $function, + 'signature' => $sig, + 'docstring' => $doc ); } @@ -167,21 +209,22 @@ class CI_Xmlrpcs extends CI_Xmlrpc /** * Parse Server Request * - * @access public * @param string data * @return object xmlrpc response */ - function parseRequest($data='') + public function parseRequest($data = '') { - global $HTTP_RAW_POST_DATA; - //------------------------------------- // Get Data //------------------------------------- - if ($data == '') + if ($data === '') { - $data = $HTTP_RAW_POST_DATA; + $CI =& get_instance(); + if ($CI->input->method() === 'post') + { + $data = $CI->input->raw_input_stream; + } } //------------------------------------- @@ -189,38 +232,39 @@ class CI_Xmlrpcs extends CI_Xmlrpc //------------------------------------- $parser = xml_parser_create($this->xmlrpc_defencoding); - $parser_object = new XML_RPC_Message("filler"); + $parser_object = new XML_RPC_Message('filler'); + $pname = (string) $parser; - $parser_object->xh[$parser] = array(); - $parser_object->xh[$parser]['isf'] = 0; - $parser_object->xh[$parser]['isf_reason'] = ''; - $parser_object->xh[$parser]['params'] = array(); - $parser_object->xh[$parser]['stack'] = array(); - $parser_object->xh[$parser]['valuestack'] = array(); - $parser_object->xh[$parser]['method'] = ''; + $parser_object->xh[$pname] = array( + 'isf' => 0, + 'isf_reason' => '', + 'params' => array(), + 'stack' => array(), + 'valuestack' => array(), + 'method' => '' + ); xml_set_object($parser, $parser_object); - xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, TRUE); xml_set_element_handler($parser, 'open_tag', 'closing_tag'); xml_set_character_data_handler($parser, 'character_data'); //xml_set_default_handler($parser, 'default_handler'); - //------------------------------------- - // PARSE + PROCESS XML DATA + // PARSE + PROCESS XML DATA //------------------------------------- if ( ! xml_parse($parser, $data, 1)) { - // return XML error as a faultCode + // Return XML error as a faultCode $r = new XML_RPC_Response(0, - $this->xmlrpcerrxml + xml_get_error_code($parser), - sprintf('XML error: %s at line %d', + $this->xmlrpcerrxml + xml_get_error_code($parser), + sprintf('XML error: %s at line %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser))); xml_parser_free($parser); } - elseif ($parser_object->xh[$parser]['isf']) + elseif ($parser_object->xh[$pname]['isf']) { return new XML_RPC_Response(0, $this->xmlrpcerr['invalid_return'], $this->xmlrpcstr['invalid_return']); } @@ -228,31 +272,29 @@ class CI_Xmlrpcs extends CI_Xmlrpc { xml_parser_free($parser); - $m = new XML_RPC_Message($parser_object->xh[$parser]['method']); - $plist=''; + $m = new XML_RPC_Message($parser_object->xh[$pname]['method']); + $plist = ''; - for ($i=0; $i < count($parser_object->xh[$parser]['params']); $i++) + for ($i = 0, $c = count($parser_object->xh[$pname]['params']); $i < $c; $i++) { if ($this->debug === TRUE) { - $plist .= "$i - " . print_r(get_object_vars($parser_object->xh[$parser]['params'][$i]), TRUE). ";\n"; + $plist .= $i.' - '.print_r(get_object_vars($parser_object->xh[$pname]['params'][$i]), TRUE).";\n"; } - $m->addParam($parser_object->xh[$parser]['params'][$i]); + $m->addParam($parser_object->xh[$pname]['params'][$i]); } if ($this->debug === TRUE) { - echo "
    ";
    -				echo "---PLIST---\n" . $plist . "\n---PLIST END---\n\n";
    -				echo "
    "; + echo "
    ---PLIST---\n".$plist."\n---PLIST END---\n\n
    "; } $r = $this->_execute($m); } //------------------------------------- - // SET DEBUGGING MESSAGE + // SET DEBUGGING MESSAGE //------------------------------------- if ($this->debug === TRUE) @@ -268,24 +310,23 @@ class CI_Xmlrpcs extends CI_Xmlrpc /** * Executes the Method * - * @access protected * @param object * @return mixed */ - function _execute($m) + protected function _execute($m) { $methName = $m->method_name; // Check to see if it is a system call - $system_call = (strncmp($methName, 'system', 5) == 0) ? TRUE : FALSE; + $system_call = (strpos($methName, 'system') === 0); - if ($this->xss_clean == FALSE) + if ($this->xss_clean === FALSE) { $m->xss_clean = FALSE; } //------------------------------------- - // Valid Method + // Valid Method //------------------------------------- if ( ! isset($this->methods[$methName]['function'])) @@ -294,50 +335,45 @@ class CI_Xmlrpcs extends CI_Xmlrpc } //------------------------------------- - // Check for Method (and Object) + // Check for Method (and Object) //------------------------------------- - $method_parts = explode(".", $this->methods[$methName]['function']); - $objectCall = (isset($method_parts['1']) && $method_parts['1'] != "") ? TRUE : FALSE; + $method_parts = explode('.', $this->methods[$methName]['function']); + $objectCall = ! empty($method_parts[1]); if ($system_call === TRUE) { - if ( ! is_callable(array($this,$method_parts['1']))) + if ( ! is_callable(array($this, $method_parts[1]))) { return new XML_RPC_Response(0, $this->xmlrpcerr['unknown_method'], $this->xmlrpcstr['unknown_method']); } } - else + elseif (($objectCall && ! is_callable(array($method_parts[0], $method_parts[1]))) + OR ( ! $objectCall && ! is_callable($this->methods[$methName]['function'])) + ) { - if ($objectCall && ! is_callable(array($method_parts['0'],$method_parts['1']))) - { - return new XML_RPC_Response(0, $this->xmlrpcerr['unknown_method'], $this->xmlrpcstr['unknown_method']); - } - elseif ( ! $objectCall && ! is_callable($this->methods[$methName]['function'])) - { - return new XML_RPC_Response(0, $this->xmlrpcerr['unknown_method'], $this->xmlrpcstr['unknown_method']); - } + return new XML_RPC_Response(0, $this->xmlrpcerr['unknown_method'], $this->xmlrpcstr['unknown_method']); } //------------------------------------- - // Checking Methods Signature + // Checking Methods Signature //------------------------------------- if (isset($this->methods[$methName]['signature'])) { $sig = $this->methods[$methName]['signature']; - for ($i=0; $iparams)+1) + if (count($current_sig) === count($m->params)+1) { - for ($n=0; $n < count($m->params); $n++) + for ($n = 0, $mc = count($m->params); $n < $mc; $n++) { $p = $m->params[$n]; - $pt = ($p->kindOf() == 'scalar') ? $p->scalarval() : $p->kindOf(); + $pt = ($p->kindOf() === 'scalar') ? $p->scalarval() : $p->kindOf(); - if ($pt != $current_sig[$n+1]) + if ($pt !== $current_sig[$n+1]) { $pno = $n+1; $wanted = $current_sig[$n+1]; @@ -345,7 +381,7 @@ class CI_Xmlrpcs extends CI_Xmlrpc return new XML_RPC_Response(0, $this->xmlrpcerr['incorrect_params'], $this->xmlrpcstr['incorrect_params'] . - ": Wanted {$wanted}, got {$pt} at param {$pno})"); + ': Wanted '.$wanted.', got '.$pt.' at param '.$pno.')'); } } } @@ -353,27 +389,22 @@ class CI_Xmlrpcs extends CI_Xmlrpc } //------------------------------------- - // Calls the Function + // Calls the Function //------------------------------------- if ($objectCall === TRUE) { - if ($method_parts[0] == "this" && $system_call == TRUE) + if ($method_parts[0] === 'this' && $system_call === TRUE) { return call_user_func(array($this, $method_parts[1]), $m); } + elseif ($this->object === FALSE) + { + return get_instance()->{$method_parts[1]}($m); + } else { - if ($this->object === FALSE) - { - $CI =& get_instance(); - return $CI->$method_parts['1']($m); - } - else - { - return $this->object->$method_parts['1']($m); - //return call_user_func(array(&$method_parts['0'],$method_parts['1']), $m); - } + return $this->object->{$method_parts[1]}($m); } } else @@ -381,17 +412,16 @@ class CI_Xmlrpcs extends CI_Xmlrpc return call_user_func($this->methods[$methName]['function'], $m); } } - + // -------------------------------------------------------------------- /** - * Server Function: List Methods + * Server Function: List Methods * - * @access public * @param mixed * @return object */ - function listMethods($m) + public function listMethods($m) { $v = new XML_RPC_Values(); $output = array(); @@ -403,23 +433,22 @@ class CI_Xmlrpcs extends CI_Xmlrpc foreach ($this->system_methods as $key => $value) { - $output[]= new XML_RPC_Values($key, 'string'); + $output[] = new XML_RPC_Values($key, 'string'); } $v->addArray($output); return new XML_RPC_Response($v); } - + // -------------------------------------------------------------------- /** - * Server Function: Return Signature for Method + * Server Function: Return Signature for Method * - * @access public * @param mixed * @return object */ - function methodSignature($m) + public function methodSignature($m) { $parameters = $m->output_parameters(); $method_name = $parameters[0]; @@ -431,40 +460,35 @@ class CI_Xmlrpcs extends CI_Xmlrpc $sigs = array(); $signature = $this->methods[$method_name]['signature']; - for ($i=0; $i < count($signature); $i++) + for ($i = 0, $c = count($signature); $i < $c; $i++) { $cursig = array(); $inSig = $signature[$i]; - for ($j=0; $jxmlrpcerr['introspect_unknown'], $this->xmlrpcstr['introspect_unknown']); - } - return $r; + + return new XML_RPC_Response(0, $this->xmlrpcerr['introspect_unknown'], $this->xmlrpcstr['introspect_unknown']); } // -------------------------------------------------------------------- /** - * Server Function: Doc String for Method + * Server Function: Doc String for Method * - * @access public * @param mixed * @return object */ - function methodHelp($m) + public function methodHelp($m) { $parameters = $m->output_parameters(); $method_name = $parameters[0]; @@ -480,17 +504,16 @@ class CI_Xmlrpcs extends CI_Xmlrpc return new XML_RPC_Response(0, $this->xmlrpcerr['introspect_unknown'], $this->xmlrpcstr['introspect_unknown']); } } - + // -------------------------------------------------------------------- /** - * Server Function: Multi-call + * Server Function: Multi-call * - * @access public * @param mixed * @return object */ - function multicall($m) + public function multicall($m) { // Disabled return new XML_RPC_Response(0, $this->xmlrpcerr['unknown_method'], $this->xmlrpcstr['unknown_method']); @@ -502,19 +525,17 @@ class CI_Xmlrpcs extends CI_Xmlrpc foreach ($calls as $value) { - //$attempt = $this->_execute(new XML_RPC_Message($value[0], $value[1])); - $m = new XML_RPC_Message($value[0]); - $plist=''; + $plist = ''; - for ($i=0; $i < count($value[1]); $i++) + for ($i = 0, $c = count($value[1]); $i < $c; $i++) { $m->addParam(new XML_RPC_Values($value[1][$i], 'string')); } $attempt = $this->_execute($m); - if ($attempt->faultCode() != 0) + if ($attempt->faultCode() !== 0) { return $attempt; } @@ -528,15 +549,14 @@ class CI_Xmlrpcs extends CI_Xmlrpc // -------------------------------------------------------------------- /** - * Multi-call Function: Error Handling + * Multi-call Function: Error Handling * - * @access public * @param mixed * @return object */ - function multicall_error($err) + public function multicall_error($err) { - $str = is_string($err) ? $this->xmlrpcstr["multicall_${err}"] : $err->faultString(); + $str = is_string($err) ? $this->xmlrpcstr["multicall_${err}"] : $err->faultString(); $code = is_string($err) ? $this->xmlrpcerr["multicall_${err}"] : $err->faultCode(); $struct['faultCode'] = new XML_RPC_Values($code, 'int'); @@ -548,15 +568,14 @@ class CI_Xmlrpcs extends CI_Xmlrpc // -------------------------------------------------------------------- /** - * Multi-call Function: Processes method + * Multi-call Function: Processes method * - * @access public * @param mixed * @return object */ - function do_multicall($call) + public function do_multicall($call) { - if ($call->kindOf() != 'struct') + if ($call->kindOf() !== 'struct') { return $this->multicall_error('notstruct'); } @@ -565,14 +584,14 @@ class CI_Xmlrpcs extends CI_Xmlrpc return $this->multicall_error('nomethod'); } - list($scalar_type,$scalar_value)=each($methName->me); - $scalar_type = $scalar_type == $this->xmlrpcI4 ? $this->xmlrpcInt : $scalar_type; + list($scalar_value, $scalar_type) = array(reset($methName->me), key($methName->me)); + $scalar_type = $scalar_type === $this->xmlrpcI4 ? $this->xmlrpcInt : $scalar_type; - if ($methName->kindOf() != 'scalar' OR $scalar_type != 'string') + if ($methName->kindOf() !== 'scalar' OR $scalar_type !== 'string') { return $this->multicall_error('notstring'); } - elseif ($scalar_value == 'system.multicall') + elseif ($scalar_value === 'system.multicall') { return $this->multicall_error('recursion'); } @@ -580,23 +599,22 @@ class CI_Xmlrpcs extends CI_Xmlrpc { return $this->multicall_error('noparams'); } - elseif ($params->kindOf() != 'array') + elseif ($params->kindOf() !== 'array') { return $this->multicall_error('notarray'); } - list($a,$b)=each($params->me); - $numParams = count($b); + list($b, $a) = array(reset($params->me), key($params->me)); $msg = new XML_RPC_Message($scalar_value); - for ($i = 0; $i < $numParams; $i++) + for ($i = 0, $numParams = count($b); $i < $numParams; $i++) { $msg->params[] = $params->me['array'][$i]; } $result = $this->_execute($msg); - if ($result->faultCode() != 0) + if ($result->faultCode() !== 0) { return $this->multicall_error($result); } @@ -605,8 +623,3 @@ class CI_Xmlrpcs extends CI_Xmlrpc } } -// END XML_RPC_Server class - - -/* End of file Xmlrpcs.php */ -/* Location: ./system/libraries/Xmlrpcs.php */ \ No newline at end of file diff --git a/system/libraries/Zip.php b/system/libraries/Zip.php index 666327d5..2c71e1fb 100644 --- a/system/libraries/Zip.php +++ b/system/libraries/Zip.php @@ -1,19 +1,41 @@ -now = time(); + log_message('info', 'Zip Compression Class Initialized'); } // -------------------------------------------------------------------- @@ -56,21 +132,19 @@ class CI_Zip { * * Lets you add a virtual directory into which you can place files. * - * @access public - * @param mixed the directory name. Can be string or array + * @param mixed $directory the directory name. Can be string or array * @return void */ - function add_dir($directory) + public function add_dir($directory) { - foreach ((array)$directory as $dir) + foreach ((array) $directory as $dir) { - if ( ! preg_match("|.+/$|", $dir)) + if ( ! preg_match('|.+/$|', $dir)) { $dir .= '/'; } $dir_time = $this->_get_mod_time($dir); - $this->_add_dir($dir, $dir_time['file_mtime'], $dir_time['file_mdate']); } } @@ -78,22 +152,22 @@ class CI_Zip { // -------------------------------------------------------------------- /** - * Get file/directory modification time + * Get file/directory modification time * - * If this is a newly created file/dir, we will set the time to 'now' + * If this is a newly created file/dir, we will set the time to 'now' * - * @param string path to file - * @return array filemtime/filemdate + * @param string $dir path to file + * @return array filemtime/filemdate */ - function _get_mod_time($dir) + protected function _get_mod_time($dir) { - // filemtime() will return false, but it does raise an error. - $date = (@filemtime($dir)) ? filemtime($dir) : getdate($this->now); + // filemtime() may return false, but raises an error for non-existing files + $date = file_exists($dir) ? getdate(filemtime($dir)) : getdate($this->now); - $time['file_mtime'] = ($date['hours'] << 11) + ($date['minutes'] << 5) + $date['seconds'] / 2; - $time['file_mdate'] = (($date['year'] - 1980) << 9) + ($date['mon'] << 5) + $date['mday']; - - return $time; + return array( + 'file_mtime' => ($date['hours'] << 11) + ($date['minutes'] << 5) + $date['seconds'] / 2, + 'file_mdate' => (($date['year'] - 1980) << 9) + ($date['mon'] << 5) + $date['mday'] + ); } // -------------------------------------------------------------------- @@ -101,13 +175,14 @@ class CI_Zip { /** * Add Directory * - * @access private - * @param string the directory name + * @param string $dir the directory name + * @param int $file_mtime + * @param int $file_mdate * @return void */ - function _add_dir($dir, $file_mtime, $file_mdate) + protected function _add_dir($dir, $file_mtime, $file_mdate) { - $dir = str_replace("\\", "/", $dir); + $dir = str_replace('\\', '/', $dir); $this->zipdata .= "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00" @@ -116,7 +191,7 @@ class CI_Zip { .pack('V', 0) // crc32 .pack('V', 0) // compressed filesize .pack('V', 0) // uncompressed filesize - .pack('v', strlen($dir)) // length of pathname + .pack('v', self::strlen($dir)) // length of pathname .pack('v', 0) // extra field length .$dir // below is "data descriptor" segment @@ -131,7 +206,7 @@ class CI_Zip { .pack('V',0) // crc32 .pack('V',0) // compressed filesize .pack('V',0) // uncompressed filesize - .pack('v', strlen($dir)) // length of pathname + .pack('v', self::strlen($dir)) // length of pathname .pack('v', 0) // extra field length .pack('v', 0) // file comment length .pack('v', 0) // disk number start @@ -140,7 +215,7 @@ class CI_Zip { .pack('V', $this->offset) // relative offset of local header .$dir; - $this->offset = strlen($this->zipdata); + $this->offset = self::strlen($this->zipdata); $this->entries++; } @@ -150,29 +225,26 @@ class CI_Zip { * Add Data to Zip * * Lets you add files to the archive. If the path is included - * in the filename it will be placed within a directory. Make + * in the filename it will be placed within a directory. Make * sure you use add_dir() first to create the folder. * - * @access public - * @param mixed - * @param string + * @param mixed $filepath A single filepath or an array of file => data pairs + * @param string $data Single file contents * @return void */ - function add_data($filepath, $data = NULL) + public function add_data($filepath, $data = NULL) { if (is_array($filepath)) { foreach ($filepath as $path => $data) { $file_data = $this->_get_mod_time($path); - $this->_add_data($path, $data, $file_data['file_mtime'], $file_data['file_mdate']); } } else { $file_data = $this->_get_mod_time($filepath); - $this->_add_data($filepath, $data, $file_data['file_mtime'], $file_data['file_mdate']); } } @@ -182,21 +254,20 @@ class CI_Zip { /** * Add Data to Zip * - * @access private - * @param string the file name/path - * @param string the data to be encoded + * @param string $filepath the file name/path + * @param string $data the data to be encoded + * @param int $file_mtime + * @param int $file_mdate * @return void */ - function _add_data($filepath, $data, $file_mtime, $file_mdate) + protected function _add_data($filepath, $data, $file_mtime, $file_mdate) { - $filepath = str_replace("\\", "/", $filepath); + $filepath = str_replace('\\', '/', $filepath); - $uncompressed_size = strlen($data); + $uncompressed_size = self::strlen($data); $crc32 = crc32($data); - - $gzdata = gzcompress($data); - $gzdata = substr($gzdata, 2, -4); - $compressed_size = strlen($gzdata); + $gzdata = self::substr(gzcompress($data, $this->compression_level), 2, -4); + $compressed_size = self::strlen($gzdata); $this->zipdata .= "\x50\x4b\x03\x04\x14\x00\x00\x00\x08\x00" @@ -205,7 +276,7 @@ class CI_Zip { .pack('V', $crc32) .pack('V', $compressed_size) .pack('V', $uncompressed_size) - .pack('v', strlen($filepath)) // length of filename + .pack('v', self::strlen($filepath)) // length of filename .pack('v', 0) // extra field length .$filepath .$gzdata; // "file data" segment @@ -217,7 +288,7 @@ class CI_Zip { .pack('V', $crc32) .pack('V', $compressed_size) .pack('V', $uncompressed_size) - .pack('v', strlen($filepath)) // length of filename + .pack('v', self::strlen($filepath)) // length of filename .pack('v', 0) // extra field length .pack('v', 0) // file comment length .pack('v', 0) // disk number start @@ -226,7 +297,7 @@ class CI_Zip { .pack('V', $this->offset) // relative offset of local header .$filepath; - $this->offset = strlen($this->zipdata); + $this->offset = self::strlen($this->zipdata); $this->entries++; $this->file_num++; } @@ -236,28 +307,32 @@ class CI_Zip { /** * Read the contents of a file and add it to the zip * - * @access public + * @param string $path + * @param bool $archive_filepath * @return bool */ - function read_file($path, $preserve_filepath = FALSE) + public function read_file($path, $archive_filepath = FALSE) { - if ( ! file_exists($path)) + if (file_exists($path) && FALSE !== ($data = file_get_contents($path))) { - return FALSE; - } - - if (FALSE !== ($data = file_get_contents($path))) - { - $name = str_replace("\\", "/", $path); - - if ($preserve_filepath === FALSE) + if (is_string($archive_filepath)) { - $name = preg_replace("|.*/(.+)|", "\\1", $name); + $name = str_replace('\\', '/', $archive_filepath); + } + else + { + $name = str_replace('\\', '/', $path); + + if ($archive_filepath === FALSE) + { + $name = preg_replace('|.*/(.+)|', '\\1', $name); + } } $this->add_data($name, $data); return TRUE; } + return FALSE; } @@ -267,15 +342,17 @@ class CI_Zip { * Read a directory and add it to the zip. * * This function recursively reads a folder and everything it contains (including - * sub-folders) and creates a zip based on it. Whatever directory structure + * sub-folders) and creates a zip based on it. Whatever directory structure * is in the original file path will be recreated in the zip file. * - * @access public - * @param string path to source + * @param string $path path to source directory + * @param bool $preserve_filepath + * @param string $root_path * @return bool */ - function read_dir($path, $preserve_filepath = TRUE, $root_path = NULL) + public function read_dir($path, $preserve_filepath = TRUE, $root_path = NULL) { + $path = rtrim($path, '/\\').DIRECTORY_SEPARATOR; if ( ! $fp = @opendir($path)) { return FALSE; @@ -284,36 +361,33 @@ class CI_Zip { // Set the original directory root for child dir's to use as relative if ($root_path === NULL) { - $root_path = dirname($path).'/'; + $root_path = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, dirname($path)).DIRECTORY_SEPARATOR; } while (FALSE !== ($file = readdir($fp))) { - if (substr($file, 0, 1) == '.') + if ($file[0] === '.') { continue; } - if (@is_dir($path.$file)) + if (is_dir($path.$file)) { - $this->read_dir($path.$file."/", $preserve_filepath, $root_path); + $this->read_dir($path.$file.DIRECTORY_SEPARATOR, $preserve_filepath, $root_path); } - else + elseif (FALSE !== ($data = file_get_contents($path.$file))) { - if (FALSE !== ($data = file_get_contents($path.$file))) + $name = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $path); + if ($preserve_filepath === FALSE) { - $name = str_replace("\\", "/", $path); - - if ($preserve_filepath === FALSE) - { - $name = str_replace($root_path, '', $name); - } - - $this->add_data($name.$file, $data); + $name = str_replace($root_path, '', $name); } + + $this->add_data($name.$file, $data); } } + closedir($fp); return TRUE; } @@ -322,26 +396,23 @@ class CI_Zip { /** * Get the Zip file * - * @access public - * @return binary string + * @return string (binary encoded) */ - function get_zip() + public function get_zip() { // Is there any data to return? - if ($this->entries == 0) + if ($this->entries === 0) { return FALSE; } - $zip_data = $this->zipdata; - $zip_data .= $this->directory."\x50\x4b\x05\x06\x00\x00\x00\x00"; - $zip_data .= pack('v', $this->entries); // total # of entries "on this disk" - $zip_data .= pack('v', $this->entries); // total # of entries overall - $zip_data .= pack('V', strlen($this->directory)); // size of central dir - $zip_data .= pack('V', strlen($this->zipdata)); // offset to start of central dir - $zip_data .= "\x00\x00"; // .zip file comment length - - return $zip_data; + return $this->zipdata + .$this->directory."\x50\x4b\x05\x06\x00\x00\x00\x00" + .pack('v', $this->entries) // total # of entries "on this disk" + .pack('v', $this->entries) // total # of entries overall + .pack('V', self::strlen($this->directory)) // size of central dir + .pack('V', self::strlen($this->zipdata)) // offset to start of central dir + ."\x00\x00"; // .zip file comment length } // -------------------------------------------------------------------- @@ -351,23 +422,30 @@ class CI_Zip { * * Lets you write a file * - * @access public - * @param string the file name + * @param string $filepath the file name * @return bool */ - function archive($filepath) + public function archive($filepath) { - if ( ! ($fp = @fopen($filepath, FOPEN_WRITE_CREATE_DESTRUCTIVE))) + if ( ! ($fp = @fopen($filepath, 'w+b'))) { return FALSE; } flock($fp, LOCK_EX); - fwrite($fp, $this->get_zip()); + + for ($result = $written = 0, $data = $this->get_zip(), $length = self::strlen($data); $written < $length; $written += $result) + { + if (($result = fwrite($fp, self::substr($data, $written))) === FALSE) + { + break; + } + } + flock($fp, LOCK_UN); fclose($fp); - return TRUE; + return is_int($result); } // -------------------------------------------------------------------- @@ -375,23 +453,18 @@ class CI_Zip { /** * Download * - * @access public - * @param string the file name - * @param string the data to be encoded - * @return bool + * @param string $filename the file name + * @return void */ - function download($filename = 'backup.zip') + public function download($filename = 'backup.zip') { - if ( ! preg_match("|.+?\.zip$|", $filename)) + if ( ! preg_match('|.+?\.zip$|', $filename)) { $filename .= '.zip'; } - $CI =& get_instance(); - $CI->load->helper('download'); - + get_instance()->load->helper('download'); $get_zip = $this->get_zip(); - $zip_content =& $get_zip; force_download($filename, $zip_content); @@ -402,22 +475,58 @@ class CI_Zip { /** * Initialize Data * - * Lets you clear current zip data. Useful if you need to create + * Lets you clear current zip data. Useful if you need to create * multiple zips with different data. * - * @access public - * @return void + * @return CI_Zip */ - function clear_data() + public function clear_data() { - $this->zipdata = ''; - $this->directory = ''; - $this->entries = 0; - $this->file_num = 0; - $this->offset = 0; + $this->zipdata = ''; + $this->directory = ''; + $this->entries = 0; + $this->file_num = 0; + $this->offset = 0; + return $this; } -} + // -------------------------------------------------------------------- -/* End of file Zip.php */ -/* Location: ./system/libraries/Zip.php */ \ No newline at end of file + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return (self::$func_overload) + ? mb_strlen($str, '8bit') + : strlen($str); + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe substr() + * + * @param string $str + * @param int $start + * @param int $length + * @return string + */ + protected static function substr($str, $start, $length = NULL) + { + if (self::$func_overload) + { + // mb_substr($str, $start, null, '8bit') returns an empty + // string on PHP 5.3 + isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start); + return mb_substr($str, $start, $length, '8bit'); + } + + return isset($length) + ? substr($str, $start, $length) + : substr($str, $start); + } +} diff --git a/system/libraries/index.html b/system/libraries/index.html index c942a79c..b702fbc3 100644 --- a/system/libraries/index.html +++ b/system/libraries/index.html @@ -1,3 +1,4 @@ + 403 Forbidden @@ -7,4 +8,4 @@

    Directory access is forbidden.

    - \ No newline at end of file + From 73e0f1d7d13d4d3c63d1a29e5475007fab9f8918 Mon Sep 17 00:00:00 2001 From: Warren Volz Date: Thu, 30 Nov 2017 19:01:11 -0700 Subject: [PATCH 29/36] fix missing () from num_rows calls --- application/controllers/Api.php | 48 +++++++++++++++---------------- application/models/User_model.php | 6 ++-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/application/controllers/Api.php b/application/controllers/Api.php index 6109b9b2..2acfedbb 100644 --- a/application/controllers/Api.php +++ b/application/controllers/Api.php @@ -14,7 +14,7 @@ class API extends CI_Controller { //$this->db->select("DATE_FORMAT(COL_TIME_ON, '%H:%i') AS time_on", FALSE ); //$this->db->select("DATE_FORMAT(COL_TIME_ON, '%d/%c/%Y') AS date_on", FALSE ); $this->db->like('COL_CALL', $callsign); - $this->db->or_like('COL_GRIDSQUARE', $callsign); + $this->db->or_like('COL_GRIDSQUARE', $callsign); $query = $this->db->get($this->config->item('table_name')); @@ -22,10 +22,10 @@ class API extends CI_Controller { foreach ($query->result() as $result) { - $results[] = $result; + $results[] = $result; } - header('Content-type: application/json'); + header('Content-type: application/json'); //$arr = array ('a'=>1,'b'=>2,'c'=>3,'d'=>4,'e'=>5); echo $_GET['jsoncallback'].'('.json_encode($results).')'; //assign resulting code to $_GET['jsoncallback]. @@ -82,7 +82,7 @@ class API extends CI_Controller { // Example of authing function auth($key) { $this->load->model('api_model'); - header("Content-type: text/xml"); + header("Content-type: text/xml"); if($this->api_model->access($key) == "No Key Found" || $this->api_model->access($key) == "Key Disabled") { echo ""; echo "Key Invalid - either not found or disabled"; @@ -160,7 +160,7 @@ class API extends CI_Controller { // Retrieve the arguments from the query string $data['data']['format'] = $arguments['format']; - + // Call the parser within the API model to build the query $query = $this->api_model->select_parse($arguments); @@ -185,7 +185,7 @@ class API extends CI_Controller { // Cycle through the results, and translate between MySQL column names // and more friendly, descriptive names - if($results->num_rows != 0) + if($results->num_rows() != 0) { foreach ($results->result() as $row) { $record = (array)$row; @@ -234,7 +234,7 @@ class API extends CI_Controller { // Retrieve the arguments from the query string $data['data']['format'] = $arguments['format']; - + // Call the parser within the API model to build the query $query = $this->api_model->select_parse($arguments); @@ -263,7 +263,7 @@ class API extends CI_Controller { $this->load->view('api/index', $data); } - + function add() { // Load the API and Logbook models @@ -274,7 +274,7 @@ class API extends CI_Controller { // Retrieve the arguments from the query string $arguments = $this->_retrieve(); - + // Call the parser within the API model to build the query $query = $this->api_model->insert_parse($arguments); @@ -345,45 +345,45 @@ class API extends CI_Controller { // Return the arguments return $arguments; } - + /* ENDPOINT for Rig Control */ - + function radio() { header('Content-type: application/json'); - + //$json = '{"radio":"FT-950","frequency":14075,"mode":"SSB","timestamp":"2012/04/07 16:47"}'; - + $this->load->model('cat'); - + //var_dump(file_get_contents("php://input"), true); - + // Decode JSON and store $obj = json_decode(file_get_contents("php://input"), true); - // Store Result to Database + // Store Result to Database $this->cat->update($obj); // Return Message - + $arr = array('status' => 'success'); echo json_encode($arr); - + } - + function cat_status () { header('Content-type: application/json'); - - $this->load->model('cat'); - + + $this->load->model('cat'); + // Decode JSON and store $obj = json_decode(file_get_contents("php://input"), true); - // Store Result to Database + // Store Result to Database $this->cat->cat_status($obj); // Return Message - + $arr = array('status' => 'success'); echo json_encode($arr); diff --git a/application/models/User_model.php b/application/models/User_model.php index b1ac6d1f..a0444ba3 100644 --- a/application/models/User_model.php +++ b/application/models/User_model.php @@ -46,7 +46,7 @@ class User_Model extends CI_Model { // FUNCTION: bool exists($username) // Check if a user exists (by username) function exists($username) { - if($this->get($username)->num_rows == 0) { + if($this->get($username)->num_rows() == 0) { return 0; } else { return 1; @@ -56,7 +56,7 @@ class User_Model extends CI_Model { // FUNCTION: bool exists_by_id($id) // Check if a user exists (by user ID) function exists_by_id($id) { - if($this->get_by_id($id)->num_rows == 0) { + if($this->get_by_id($id)->num_rows() == 0) { return 0; } else { return 1; @@ -66,7 +66,7 @@ class User_Model extends CI_Model { // FUNCTION: bool exists_by_email($email) // Check if a user exists (by email address) function exists_by_email($email) { - if($this->get_by_email($email)->num_rows == 0) { + if($this->get_by_email($email)->num_rows() == 0) { return 0; } else { return 1; From 713d2825ad98cb18489a2e3665fe9d6aeb463728 Mon Sep 17 00:00:00 2001 From: Warren Volz Date: Mon, 11 Dec 2017 13:23:16 -0700 Subject: [PATCH 30/36] sync sample config file with codeignitor 3.1.6 changes --- application/config/config.sample.php | 326 ++++++++++++++++++++------- 1 file changed, 244 insertions(+), 82 deletions(-) diff --git a/application/config/config.sample.php b/application/config/config.sample.php index 1c501c2e..9b426c80 100644 --- a/application/config/config.sample.php +++ b/application/config/config.sample.php @@ -1,4 +1,5 @@ -]+$/i +| | DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!! | */ -$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-=[]()*,'; +$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-'; /* |-------------------------------------------------------------------------- @@ -214,9 +246,6 @@ $config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-=[]()*,'; | By default CodeIgniter uses search-engine friendly segment based URLs: | example.com/who/what/where/ | -| By default CodeIgniter enables access to the $_GET array. If for some -| reason you would like to disable it, set 'allow_get_array' to FALSE. -| | You can optionally enable standard query string based URLs: | example.com?who=me&what=something&where=here | @@ -231,27 +260,42 @@ $config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-=[]()*,'; | use segment based URLs. | */ -$config['allow_get_array'] = TRUE; $config['enable_query_strings'] = FALSE; -$config['controller_trigger'] = 'c'; -$config['function_trigger'] = 'm'; -$config['directory_trigger'] = 'd'; // experimental not currently in use +$config['controller_trigger'] = 'c'; +$config['function_trigger'] = 'm'; +$config['directory_trigger'] = 'd'; + +/* +|-------------------------------------------------------------------------- +| Allow $_GET array +|-------------------------------------------------------------------------- +| +| By default CodeIgniter enables access to the $_GET array. If for some +| reason you would like to disable it, set 'allow_get_array' to FALSE. +| +| WARNING: This feature is DEPRECATED and currently available only +| for backwards compatibility purposes! +| +*/ +$config['allow_get_array'] = TRUE; /* |-------------------------------------------------------------------------- | Error Logging Threshold |-------------------------------------------------------------------------- | -| If you have enabled error logging, you can set an error threshold to -| determine what gets logged. Threshold options are: | You can enable error logging by setting a threshold over zero. The | threshold determines what gets logged. Threshold options are: | -| 0 = Disables logging, Error logging TURNED OFF -| 1 = Error Messages (including PHP errors) -| 2 = Debug Messages -| 3 = Informational Messages -| 4 = All Messages +| 0 = Disables logging, Error logging TURNED OFF +| 1 = Error Messages (including PHP errors) +| 2 = Debug Messages +| 3 = Informational Messages +| 4 = All Messages +| +| You can also pass an array with threshold levels to show individual error types +| +| array(2) = Debug Messages, without Error Messages | | For a live site you'll usually only enable Errors (1) to be logged otherwise | your log files will fill up very fast. @@ -265,11 +309,37 @@ $config['log_threshold'] = 0; |-------------------------------------------------------------------------- | | Leave this BLANK unless you would like to set something other than the default -| application/logs/ folder. Use a full server path with trailing slash. +| application/logs/ directory. Use a full server path with trailing slash. | */ $config['log_path'] = ''; +/* +|-------------------------------------------------------------------------- +| Log File Extension +|-------------------------------------------------------------------------- +| +| The default filename extension for log files. The default 'php' allows for +| protecting the log files via basic scripting, when they are to be stored +| under a publicly accessible directory. +| +| Note: Leaving it blank will default to 'php'. +| +*/ +$config['log_file_extension'] = ''; + +/* +|-------------------------------------------------------------------------- +| Log File Permissions +|-------------------------------------------------------------------------- +| +| The file system permissions to be applied on newly created log files. +| +| IMPORTANT: This MUST be an integer (no quotes) and you MUST use octal +| integer notation (i.e. 0700, 0644, etc.) +*/ +$config['log_file_permissions'] = 0644; + /* |-------------------------------------------------------------------------- | Date Format for Logs @@ -281,24 +351,55 @@ $config['log_path'] = ''; */ $config['log_date_format'] = 'Y-m-d H:i:s'; +/* +|-------------------------------------------------------------------------- +| Error Views Directory Path +|-------------------------------------------------------------------------- +| +| Leave this BLANK unless you would like to set something other than the default +| application/views/errors/ directory. Use a full server path with trailing slash. +| +*/ +$config['error_views_path'] = ''; + /* |-------------------------------------------------------------------------- | Cache Directory Path |-------------------------------------------------------------------------- | | Leave this BLANK unless you would like to set something other than the default -| system/cache/ folder. Use a full server path with trailing slash. +| application/cache/ directory. Use a full server path with trailing slash. | */ $config['cache_path'] = ''; +/* +|-------------------------------------------------------------------------- +| Cache Include Query String +|-------------------------------------------------------------------------- +| +| Whether to take the URL query string into consideration when generating +| output cache files. Valid options are: +| +| FALSE = Disabled +| TRUE = Enabled, take all query parameters into account. +| Please be aware that this may result in numerous cache +| files generated for the same page over and over again. +| array('q') = Enabled, but only take into account the specified list +| of query parameters. +| +*/ +$config['cache_query_string'] = FALSE; + /* |-------------------------------------------------------------------------- | Encryption Key |-------------------------------------------------------------------------- | -| If you use the Encryption class or the Session class you -| MUST set an encryption key. See the user guide for info. +| If you use the Encryption class, you must set an encryption key. +| See the user guide for more info. +| +| https://codeigniter.com/user_guide/libraries/encryption.html | */ $config['encryption_key'] = 'flossie1234555541'; @@ -308,44 +409,94 @@ $config['encryption_key'] = 'flossie1234555541'; | Session Variables |-------------------------------------------------------------------------- | -| 'sess_cookie_name' = the name you want for the cookie -| 'sess_expiration' = the number of SECONDS you want the session to last. -| by default sessions last 7200 seconds (two hours). Set to zero for no expiration. -| 'sess_expire_on_close' = Whether to cause the session to expire automatically -| when the browser window is closed -| 'sess_encrypt_cookie' = Whether to encrypt the cookie -| 'sess_use_database' = Whether to save the session data to a database -| 'sess_table_name' = The name of the session database table -| 'sess_match_ip' = Whether to match the user's IP address when reading the session data -| 'sess_match_useragent' = Whether to match the User Agent when reading the session data -| 'sess_time_to_update' = how many seconds between CI refreshing Session Information +| 'sess_driver' +| +| The storage driver to use: files, database, redis, memcached +| +| 'sess_cookie_name' +| +| The session cookie name, must contain only [0-9a-z_-] characters +| +| 'sess_expiration' +| +| The number of SECONDS you want the session to last. +| Setting to 0 (zero) means expire when the browser is closed. +| +| 'sess_save_path' +| +| The location to save sessions to, driver dependent. +| +| For the 'files' driver, it's a path to a writable directory. +| WARNING: Only absolute paths are supported! +| +| For the 'database' driver, it's a table name. +| Please read up the manual for the format with other session drivers. +| +| IMPORTANT: You are REQUIRED to set a valid save path! +| +| 'sess_match_ip' +| +| Whether to match the user's IP address when reading the session data. +| +| WARNING: If you're using the database driver, don't forget to update +| your session table's PRIMARY KEY when changing this setting. +| +| 'sess_time_to_update' +| +| How many seconds between CI regenerating the session ID. +| +| 'sess_regenerate_destroy' +| +| Whether to destroy session data associated with the old session ID +| when auto-regenerating the session ID. When set to FALSE, the data +| will be later deleted by the garbage collector. +| +| Other session cookie settings are shared with the rest of the application, +| except for 'cookie_prefix' and 'cookie_httponly', which are ignored here. | */ -$config['sess_cookie_name'] = 'ci_cloudlog'; -$config['sess_expiration'] = 0; -$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_cloudlog'; +$config['sess_expiration'] = 0; +$config['sess_save_path'] = '/tmp'; +$config['sess_match_ip'] = FALSE; +$config['sess_time_to_update'] = 300; +$config['sess_regenerate_destroy'] = FALSE; /* |-------------------------------------------------------------------------- | Cookie Related Variables |-------------------------------------------------------------------------- | -| 'cookie_prefix' = Set a prefix if you need to avoid collisions -| 'cookie_domain' = Set to .your-domain.com for site-wide cookies -| 'cookie_path' = Typically will be a forward slash -| 'cookie_secure' = Cookies will only be set if a secure HTTPS connection exists. +| 'cookie_prefix' = Set a cookie name prefix if you need to avoid collisions +| 'cookie_domain' = Set to .your-domain.com for site-wide cookies +| 'cookie_path' = Typically will be a forward slash +| 'cookie_secure' = Cookie will only be set if a secure HTTPS connection exists. +| 'cookie_httponly' = Cookie will only be accessible via HTTP(S) (no javascript) +| +| Note: These settings (with the exception of 'cookie_prefix' and +| 'cookie_httponly') will also affect sessions. | */ -$config['cookie_prefix'] = ""; -$config['cookie_domain'] = ""; -$config['cookie_path'] = "/"; -$config['cookie_secure'] = FALSE; +$config['cookie_prefix'] = ''; +$config['cookie_domain'] = ''; +$config['cookie_path'] = '/'; +$config['cookie_secure'] = FALSE; +$config['cookie_httponly'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Standardize newlines +|-------------------------------------------------------------------------- +| +| Determines whether to standardize newline characters in input data, +| meaning to replace \r\n, \r, \n occurrences with the PHP_EOL value. +| +| WARNING: This feature is DEPRECATED and currently available only +| for backwards compatibility purposes! +| +*/ +$config['standardize_newlines'] = FALSE; /* |-------------------------------------------------------------------------- @@ -355,6 +506,9 @@ $config['cookie_secure'] = FALSE; | Determines whether the XSS filter is always active when GET, POST or | COOKIE data is encountered | +| WARNING: This feature is DEPRECATED and currently available only +| for backwards compatibility purposes! +| */ $config['global_xss_filtering'] = FALSE; @@ -369,11 +523,15 @@ $config['global_xss_filtering'] = FALSE; | 'csrf_token_name' = The token name | 'csrf_cookie_name' = The cookie name | 'csrf_expire' = The number in seconds the token should expire. +| 'csrf_regenerate' = Regenerate token on every submission +| 'csrf_exclude_uris' = Array of URIs which ignore CSRF checks */ $config['csrf_protection'] = FALSE; $config['csrf_token_name'] = 'csrf_test_name'; $config['csrf_cookie_name'] = 'csrf_cookie_name'; $config['csrf_expire'] = 7200; +$config['csrf_regenerate'] = TRUE; +$config['csrf_exclude_uris'] = array(); /* |-------------------------------------------------------------------------- @@ -385,6 +543,9 @@ $config['csrf_expire'] = 7200; | Even if it does, however, not all browsers support compression | so enable only if you are reasonably sure your visitors can handle it. | +| Only used if zlib.output_compression is turned off in your php.ini. +| Please do not use it together with httpd-level output compression. +| | VERY IMPORTANT: If you are getting a blank page when compression is enabled it | means you are prematurely outputting something to your browser. It could | even be a line of whitespace at the end of one of your scripts. For @@ -399,15 +560,14 @@ $config['compress_output'] = FALSE; | Master Time Reference |-------------------------------------------------------------------------- | -| Options are 'local' or 'gmt'. This pref tells the system whether to use -| your server's local time as the master 'now' reference, or convert it to -| GMT. See the 'date helper' page of the user guide for information -| regarding date handling. +| Options are 'local' or any PHP supported timezone. This preference tells +| the system whether to use your server's local time as the master 'now' +| reference, or convert it to the configured one timezone. See the 'date +| helper' page of the user guide for information regarding date handling. | */ $config['time_reference'] = 'local'; - /* |-------------------------------------------------------------------------- | Rewrite PHP Short Tags @@ -417,23 +577,25 @@ $config['time_reference'] = 'local'; | can rewrite the tags on-the-fly, enabling you to utilize that syntax | in your view files. Options are TRUE or FALSE (boolean) | +| Note: You need to have eval() enabled for this to work. +| */ $config['rewrite_short_tags'] = FALSE; - /* |-------------------------------------------------------------------------- | Reverse Proxy IPs |-------------------------------------------------------------------------- | -| If your server is behind a reverse proxy, you must whitelist the proxy IP -| addresses from which CodeIgniter should trust the HTTP_X_FORWARDED_FOR -| header in order to properly identify the visitor's IP address. -| Comma-delimited, e.g. '10.0.1.200,10.0.1.201' +| If your server is behind a reverse proxy, you must whitelist the proxy +| IP addresses from which CodeIgniter should trust headers such as +| HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify +| the visitor's IP address. | +| You can use both an array or a comma-separated list of proxy addresses, +| as well as specifying whole subnets. Here are a few examples: +| +| Comma-separated: '10.0.1.200,192.168.5.0/24' +| Array: array('10.0.1.200', '192.168.5.0/24') */ $config['proxy_ips'] = ''; - - -/* End of file config.php */ -/* Location: ./application/config/config.php */ From 2c2e2c97e574b6f0843dd8c1a7570a94254ffaf4 Mon Sep 17 00:00:00 2001 From: Warren Volz Date: Tue, 12 Dec 2017 16:57:18 -0700 Subject: [PATCH 31/36] sync installer config template files, fix dbconf sample - syncs config.php and database.php for installer with CI 3.1.6 versions - syncs sample database config file with CI 3.1.6 (missed before) --- application/config/config.sample.php | 24 +- application/config/database.sample.php | 99 +++++--- install/config/config.php | 312 +++++++++++++++++++------ install/config/database.php | 99 +++++--- 4 files changed, 390 insertions(+), 144 deletions(-) diff --git a/application/config/config.sample.php b/application/config/config.sample.php index 9b426c80..9e713a9f 100644 --- a/application/config/config.sample.php +++ b/application/config/config.sample.php @@ -7,10 +7,10 @@ defined('BASEPATH') OR exit('No direct script access allowed'); | Install Options |-------------------------------------------------------------------------- | -| 'app_name' Name of the App 'Cloudlog' -| 'app_version' Set by the dev team. -| 'directory' directory where cloudlog is installed eg "logger" -| 'callbook' Selects which Callbook lookup to use defaults "hamli" but supports "qrz" +| 'app_name' Name of the App 'Cloudlog' +| 'app_version' Set by the dev team. +| 'directory' directory where cloudlog is installed eg "logger" +| 'callbook' Selects which Callbook lookup to use defaults "hamqth" but supports "qrz" */ $config['app_name'] = "Cloudlog"; @@ -24,9 +24,9 @@ $config['callbook'] = "hamqth"; // Options are hamqth or qrz | Logbook Options |-------------------------------------------------------------------------- | -| 'table_name' SQL table where log can be found -| 'locator' Default locator used to calculate bearings/distance -| 'display_freq' Show or Hide frequnecy info +| 'table_name' SQL table where log can be found +| 'locator' Default locator used to calculate bearings/distance +| 'display_freq' Show or Hide frequnecy info */ $config['table_name'] = "TABLE_HRD_CONTACTS_V01"; @@ -38,11 +38,11 @@ $config['display_freq'] = true; | Authentication |-------------------------------------------------------------------------- | -| 'use_auth' False turns all authentication off, best used when setting up -| 'auth_table' MySQL Database Table defaults "users" -| 'auth_mode' Minimum user level required 0 = anonymous, 1 = viewer, -| 2 = editor, 3 = api user, 99 = owner -| 'auth_level[]' Defines level titles +| 'use_auth' False turns all authentication off, best used when setting up +| 'auth_table' MySQL Database Table defaults "users" +| 'auth_mode' Minimum user level required 0 = anonymous, 1 = viewer, +| 2 = editor, 3 = api user, 99 = owner +| 'auth_level[]' Defines level titles */ $config['use_auth'] = true; diff --git a/application/config/database.sample.php b/application/config/database.sample.php index a30cee4f..fd33eca3 100644 --- a/application/config/database.sample.php +++ b/application/config/database.sample.php @@ -1,57 +1,98 @@ -db->last_query() and profiling of DB queries. +| When you run a query, with this setting set to TRUE (default), +| CodeIgniter will store the SQL statement for debugging purposes. +| However, this may cause high memory usage, especially if you run +| a lot of SQL queries ... disable this to avoid that problem. | | The $active_group variable lets you choose which connection group to -| make active. By default there is only one group (the "default" group). +| make active. By default there is only one group (the 'default' group). | -| The $active_record variables lets you determine whether or not to load -| the active record class +| The $query_builder variables lets you determine whether or not to load +| the query builder class. */ +$active_group = 'default'; +$query_builder = TRUE; -// The following values will probably need to be changed. -$db['default']['username'] = "root"; -$db['default']['password'] = ""; -$db['default']['database'] = "log"; - -// The following values can probably stay the same. -$db['default']['hostname'] = "localhost"; -$db['default']['dbdriver'] = "mysql"; -$db['default']['dbprefix'] = ""; -$db['default']['pconnect'] = TRUE; -$db['default']['db_debug'] = TRUE; -$db['default']['cache_on'] = FALSE; -$db['default']['cachedir'] = ""; -$db['default']['char_set'] = "utf8"; -$db['default']['dbcollat'] = "utf8_general_ci"; - -$active_group = "default"; -$active_record = TRUE; - -/* End of file database.php */ -/* Location: ./system/application/config/database.php */ +$db['default'] = array( + // The following values will probably need to be changed. + 'dsn' => '', + 'hostname' => 'localhost', + 'username' => 'root', + 'password' => '', + 'database' => 'log', + // The following values can probably stay the same. + 'dbdriver' => 'mysqli', + 'dbprefix' => '', + 'pconnect' => TRUE, + 'db_debug' => (ENVIRONMENT !== 'production'), + 'cache_on' => FALSE, + 'cachedir' => '', + 'char_set' => 'utf8', + 'dbcollat' => 'utf8_general_ci', + 'swap_pre' => '', + 'encrypt' => FALSE, + 'compress' => FALSE, + 'stricton' => FALSE, + 'failover' => array(), + 'save_queries' => TRUE +); diff --git a/install/config/config.php b/install/config/config.php index 379f3db6..13ae46ab 100644 --- a/install/config/config.php +++ b/install/config/config.php @@ -1,4 +1,5 @@ -]+$/i +| | DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!! | */ -$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-=[]()*,'; +$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-'; /* |-------------------------------------------------------------------------- @@ -187,9 +221,6 @@ $config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-=[]()*,'; | By default CodeIgniter uses search-engine friendly segment based URLs: | example.com/who/what/where/ | -| By default CodeIgniter enables access to the $_GET array. If for some -| reason you would like to disable it, set 'allow_get_array' to FALSE. -| | You can optionally enable standard query string based URLs: | example.com?who=me&what=something&where=here | @@ -204,19 +235,30 @@ $config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-=[]()*,'; | use segment based URLs. | */ -$config['allow_get_array'] = TRUE; $config['enable_query_strings'] = FALSE; -$config['controller_trigger'] = 'c'; -$config['function_trigger'] = 'm'; -$config['directory_trigger'] = 'd'; // experimental not currently in use +$config['controller_trigger'] = 'c'; +$config['function_trigger'] = 'm'; +$config['directory_trigger'] = 'd'; + +/* +|-------------------------------------------------------------------------- +| Allow $_GET array +|-------------------------------------------------------------------------- +| +| By default CodeIgniter enables access to the $_GET array. If for some +| reason you would like to disable it, set 'allow_get_array' to FALSE. +| +| WARNING: This feature is DEPRECATED and currently available only +| for backwards compatibility purposes! +| +*/ +$config['allow_get_array'] = TRUE; /* |-------------------------------------------------------------------------- | Error Logging Threshold |-------------------------------------------------------------------------- | -| If you have enabled error logging, you can set an error threshold to -| determine what gets logged. Threshold options are: | You can enable error logging by setting a threshold over zero. The | threshold determines what gets logged. Threshold options are: | @@ -226,6 +268,10 @@ $config['directory_trigger'] = 'd'; // experimental not currently in use | 3 = Informational Messages | 4 = All Messages | +| You can also pass an array with threshold levels to show individual error types +| +| array(2) = Debug Messages, without Error Messages +| | For a live site you'll usually only enable Errors (1) to be logged otherwise | your log files will fill up very fast. | @@ -238,11 +284,37 @@ $config['log_threshold'] = 0; |-------------------------------------------------------------------------- | | Leave this BLANK unless you would like to set something other than the default -| application/logs/ folder. Use a full server path with trailing slash. +| application/logs/ directory. Use a full server path with trailing slash. | */ $config['log_path'] = ''; +/* +|-------------------------------------------------------------------------- +| Log File Extension +|-------------------------------------------------------------------------- +| +| The default filename extension for log files. The default 'php' allows for +| protecting the log files via basic scripting, when they are to be stored +| under a publicly accessible directory. +| +| Note: Leaving it blank will default to 'php'. +| +*/ +$config['log_file_extension'] = ''; + +/* +|-------------------------------------------------------------------------- +| Log File Permissions +|-------------------------------------------------------------------------- +| +| The file system permissions to be applied on newly created log files. +| +| IMPORTANT: This MUST be an integer (no quotes) and you MUST use octal +| integer notation (i.e. 0700, 0644, etc.) +*/ +$config['log_file_permissions'] = 0644; + /* |-------------------------------------------------------------------------- | Date Format for Logs @@ -254,24 +326,55 @@ $config['log_path'] = ''; */ $config['log_date_format'] = 'Y-m-d H:i:s'; +/* +|-------------------------------------------------------------------------- +| Error Views Directory Path +|-------------------------------------------------------------------------- +| +| Leave this BLANK unless you would like to set something other than the default +| application/views/errors/ directory. Use a full server path with trailing slash. +| +*/ +$config['error_views_path'] = ''; + /* |-------------------------------------------------------------------------- | Cache Directory Path |-------------------------------------------------------------------------- | | Leave this BLANK unless you would like to set something other than the default -| system/cache/ folder. Use a full server path with trailing slash. +| application/cache/ directory. Use a full server path with trailing slash. | */ $config['cache_path'] = ''; +/* +|-------------------------------------------------------------------------- +| Cache Include Query String +|-------------------------------------------------------------------------- +| +| Whether to take the URL query string into consideration when generating +| output cache files. Valid options are: +| +| FALSE = Disabled +| TRUE = Enabled, take all query parameters into account. +| Please be aware that this may result in numerous cache +| files generated for the same page over and over again. +| array('q') = Enabled, but only take into account the specified list +| of query parameters. +| +*/ +$config['cache_query_string'] = FALSE; + /* |-------------------------------------------------------------------------- | Encryption Key |-------------------------------------------------------------------------- | -| If you use the Encryption class or the Session class you -| MUST set an encryption key. See the user guide for info. +| If you use the Encryption class, you must set an encryption key. +| See the user guide for more info. +| +| https://codeigniter.com/user_guide/libraries/encryption.html | */ $config['encryption_key'] = 'flossie1234555541'; @@ -281,44 +384,94 @@ $config['encryption_key'] = 'flossie1234555541'; | Session Variables |-------------------------------------------------------------------------- | -| 'sess_cookie_name' = the name you want for the cookie -| 'sess_expiration' = the number of SECONDS you want the session to last. -| by default sessions last 7200 seconds (two hours). Set to zero for no expiration. -| 'sess_expire_on_close' = Whether to cause the session to expire automatically -| when the browser window is closed -| 'sess_encrypt_cookie' = Whether to encrypt the cookie -| 'sess_use_database' = Whether to save the session data to a database -| 'sess_table_name' = The name of the session database table -| 'sess_match_ip' = Whether to match the user's IP address when reading the session data -| 'sess_match_useragent' = Whether to match the User Agent when reading the session data -| 'sess_time_to_update' = how many seconds between CI refreshing Session Information +| 'sess_driver' +| +| The storage driver to use: files, database, redis, memcached +| +| 'sess_cookie_name' +| +| The session cookie name, must contain only [0-9a-z_-] characters +| +| 'sess_expiration' +| +| The number of SECONDS you want the session to last. +| Setting to 0 (zero) means expire when the browser is closed. +| +| 'sess_save_path' +| +| The location to save sessions to, driver dependent. +| +| For the 'files' driver, it's a path to a writable directory. +| WARNING: Only absolute paths are supported! +| +| For the 'database' driver, it's a table name. +| Please read up the manual for the format with other session drivers. +| +| IMPORTANT: You are REQUIRED to set a valid save path! +| +| 'sess_match_ip' +| +| Whether to match the user's IP address when reading the session data. +| +| WARNING: If you're using the database driver, don't forget to update +| your session table's PRIMARY KEY when changing this setting. +| +| 'sess_time_to_update' +| +| How many seconds between CI regenerating the session ID. +| +| 'sess_regenerate_destroy' +| +| Whether to destroy session data associated with the old session ID +| when auto-regenerating the session ID. When set to FALSE, the data +| will be later deleted by the garbage collector. +| +| Other session cookie settings are shared with the rest of the application, +| except for 'cookie_prefix' and 'cookie_httponly', which are ignored here. | */ -$config['sess_cookie_name'] = 'ci_cloudlog'; -$config['sess_expiration'] = 0; -$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_cloudlog'; +$config['sess_expiration'] = 0; +$config['sess_save_path'] = '/tmp'; +$config['sess_match_ip'] = FALSE; +$config['sess_time_to_update'] = 300; +$config['sess_regenerate_destroy'] = FALSE; /* |-------------------------------------------------------------------------- | Cookie Related Variables |-------------------------------------------------------------------------- | -| 'cookie_prefix' = Set a prefix if you need to avoid collisions -| 'cookie_domain' = Set to .your-domain.com for site-wide cookies -| 'cookie_path' = Typically will be a forward slash -| 'cookie_secure' = Cookies will only be set if a secure HTTPS connection exists. +| 'cookie_prefix' = Set a cookie name prefix if you need to avoid collisions +| 'cookie_domain' = Set to .your-domain.com for site-wide cookies +| 'cookie_path' = Typically will be a forward slash +| 'cookie_secure' = Cookie will only be set if a secure HTTPS connection exists. +| 'cookie_httponly' = Cookie will only be accessible via HTTP(S) (no javascript) +| +| Note: These settings (with the exception of 'cookie_prefix' and +| 'cookie_httponly') will also affect sessions. | */ -$config['cookie_prefix'] = ""; -$config['cookie_domain'] = ""; -$config['cookie_path'] = "/"; +$config['cookie_prefix'] = ''; +$config['cookie_domain'] = ''; +$config['cookie_path'] = '/'; $config['cookie_secure'] = FALSE; +$config['cookie_httponly'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| Standardize newlines +|-------------------------------------------------------------------------- +| +| Determines whether to standardize newline characters in input data, +| meaning to replace \r\n, \r, \n occurrences with the PHP_EOL value. +| +| WARNING: This feature is DEPRECATED and currently available only +| for backwards compatibility purposes! +| +*/ +$config['standardize_newlines'] = FALSE; /* |-------------------------------------------------------------------------- @@ -328,6 +481,9 @@ $config['cookie_secure'] = FALSE; | Determines whether the XSS filter is always active when GET, POST or | COOKIE data is encountered | +| WARNING: This feature is DEPRECATED and currently available only +| for backwards compatibility purposes! +| */ $config['global_xss_filtering'] = FALSE; @@ -342,11 +498,15 @@ $config['global_xss_filtering'] = FALSE; | 'csrf_token_name' = The token name | 'csrf_cookie_name' = The cookie name | 'csrf_expire' = The number in seconds the token should expire. +| 'csrf_regenerate' = Regenerate token on every submission +| 'csrf_exclude_uris' = Array of URIs which ignore CSRF checks */ $config['csrf_protection'] = FALSE; $config['csrf_token_name'] = 'csrf_test_name'; $config['csrf_cookie_name'] = 'csrf_cookie_name'; $config['csrf_expire'] = 7200; +$config['csrf_regenerate'] = TRUE; +$config['csrf_exclude_uris'] = array(); /* |-------------------------------------------------------------------------- @@ -358,6 +518,9 @@ $config['csrf_expire'] = 7200; | Even if it does, however, not all browsers support compression | so enable only if you are reasonably sure your visitors can handle it. | +| Only used if zlib.output_compression is turned off in your php.ini. +| Please do not use it together with httpd-level output compression. +| | VERY IMPORTANT: If you are getting a blank page when compression is enabled it | means you are prematurely outputting something to your browser. It could | even be a line of whitespace at the end of one of your scripts. For @@ -372,15 +535,14 @@ $config['compress_output'] = FALSE; | Master Time Reference |-------------------------------------------------------------------------- | -| Options are 'local' or 'gmt'. This pref tells the system whether to use -| your server's local time as the master 'now' reference, or convert it to -| GMT. See the 'date helper' page of the user guide for information -| regarding date handling. +| Options are 'local' or any PHP supported timezone. This preference tells +| the system whether to use your server's local time as the master 'now' +| reference, or convert it to the configured one timezone. See the 'date +| helper' page of the user guide for information regarding date handling. | */ $config['time_reference'] = 'local'; - /* |-------------------------------------------------------------------------- | Rewrite PHP Short Tags @@ -390,23 +552,25 @@ $config['time_reference'] = 'local'; | can rewrite the tags on-the-fly, enabling you to utilize that syntax | in your view files. Options are TRUE or FALSE (boolean) | +| Note: You need to have eval() enabled for this to work. +| */ $config['rewrite_short_tags'] = FALSE; - /* |-------------------------------------------------------------------------- | Reverse Proxy IPs |-------------------------------------------------------------------------- | -| If your server is behind a reverse proxy, you must whitelist the proxy IP -| addresses from which CodeIgniter should trust the HTTP_X_FORWARDED_FOR -| header in order to properly identify the visitor's IP address. -| Comma-delimited, e.g. '10.0.1.200,10.0.1.201' +| If your server is behind a reverse proxy, you must whitelist the proxy +| IP addresses from which CodeIgniter should trust headers such as +| HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify +| the visitor's IP address. | +| You can use both an array or a comma-separated list of proxy addresses, +| as well as specifying whole subnets. Here are a few examples: +| +| Comma-separated: '10.0.1.200,192.168.5.0/24' +| Array: array('10.0.1.200', '192.168.5.0/24') */ $config['proxy_ips'] = ''; - - -/* End of file config.php */ -/* Location: ./application/config/config.php */ diff --git a/install/config/database.php b/install/config/database.php index a687a226..e222bab2 100644 --- a/install/config/database.php +++ b/install/config/database.php @@ -1,57 +1,98 @@ -db->last_query() and profiling of DB queries. +| When you run a query, with this setting set to TRUE (default), +| CodeIgniter will store the SQL statement for debugging purposes. +| However, this may cause high memory usage, especially if you run +| a lot of SQL queries ... disable this to avoid that problem. | | The $active_group variable lets you choose which connection group to -| make active. By default there is only one group (the "default" group). +| make active. By default there is only one group (the 'default' group). | -| The $active_record variables lets you determine whether or not to load -| the active record class +| The $query_builder variables lets you determine whether or not to load +| the query builder class. */ +$active_group = 'default'; +$query_builder = TRUE; -// The following values will probably need to be changed. -$db['default']['username'] = "%USERNAME%"; -$db['default']['password'] = "%PASSWORD%"; -$db['default']['database'] = "%DATABASE%"; - -// The following values can probably stay the same. -$db['default']['hostname'] = "%HOSTNAME%"; -$db['default']['dbdriver'] = "mysql"; -$db['default']['dbprefix'] = ""; -$db['default']['pconnect'] = TRUE; -$db['default']['db_debug'] = TRUE; -$db['default']['cache_on'] = FALSE; -$db['default']['cachedir'] = ""; -$db['default']['char_set'] = "utf8"; -$db['default']['dbcollat'] = "utf8_general_ci"; - -$active_group = "default"; -$active_record = TRUE; - -/* End of file database.php */ -/* Location: ./system/application/config/database.php */ +$db['default'] = array( + // The following values will probably need to be changed. + 'dsn' => '', + 'hostname' => '%HOSTNAME%', + 'username' => '%USERNAME%', + 'password' => '%PASSWORD%', + 'database' => '%DATABASE%', + // The following values can probably stay the same. + 'dbdriver' => 'mysqli', + 'dbprefix' => '', + 'pconnect' => TRUE, + 'db_debug' => (ENVIRONMENT !== 'production'), + 'cache_on' => FALSE, + 'cachedir' => '', + 'char_set' => 'utf8', + 'dbcollat' => 'utf8_general_ci', + 'swap_pre' => '', + 'encrypt' => FALSE, + 'compress' => FALSE, + 'stricton' => FALSE, + 'failover' => array(), + 'save_queries' => TRUE +); From 17dff29c20e4d9ec09a085eceeda537e4b4e327e Mon Sep 17 00:00:00 2001 From: Warren Volz Date: Wed, 13 Dec 2017 00:13:01 +0000 Subject: [PATCH 32/36] update lotw url - p1k changed to lotw --- application/views/lotw/import.php | 2 +- install/assets/install.sql | 2 +- sql/tables/config.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/views/lotw/import.php b/application/views/lotw/import.php index 2e346124..226ad861 100644 --- a/application/views/lotw/import.php +++ b/application/views/lotw/import.php @@ -7,7 +7,7 @@ Upload a file -

    Upload the Exported ADIF file from LoTW from the Download Report Area, to mark QSOs as confirmed on LOTW.

    +

    Upload the Exported ADIF file from LoTW from the Download Report Area, to mark QSOs as confirmed on LOTW.

    Important Log files must have the file type .adi

    diff --git a/install/assets/install.sql b/install/assets/install.sql index f827cdea..a749012f 100755 --- a/install/assets/install.sql +++ b/install/assets/install.sql @@ -3805,6 +3805,6 @@ CREATE TABLE IF NOT EXISTS `config` ( -- INSERT INTO `config` (`id`, `lotw_download_url`, `lotw_upload_url`, `lotw_rcvd_mark`, `lotw_login_url`, `eqsl_download_url`, `eqsl_rcvd_mark`) VALUES -(1, 'https://p1k.arrl.org/lotwuser/lotwreport.adi', 'https://p1k.arrl.org/lotwuser/upload', 'Y', 'https://p1k.arrl.org/lotwuser/default', 'http://www.eqsl.cc/qslcard/DownloadInBox.cfm', 'Y'); +(1, 'https://lotw.arrl.org/lotwuser/lotwreport.adi', 'https://lotw.arrl.org/lotwuser/upload', 'Y', 'https://lotw.arrl.org/lotwuser/default', 'http://www.eqsl.cc/qslcard/DownloadInBox.cfm', 'Y'); ALTER TABLE `cat` ADD `downlink_freq` INT(11) NOT NULL AFTER `mode`, ADD `uplink_freq` INT(11) NOT NULL AFTER `downlink_freq`, ADD `downlink_mode` VARCHAR(255) NOT NULL AFTER `uplink_freq`, ADD `uplink_mode` VARCHAR(255) NOT NULL AFTER `downlink_mode`, ADD `sat_name` VARCHAR(255) NOT NULL AFTER `uplink_mode`; diff --git a/sql/tables/config.sql b/sql/tables/config.sql index db6e105f..63150a8a 100644 --- a/sql/tables/config.sql +++ b/sql/tables/config.sql @@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS `config` ( -- INSERT INTO `config` (`id`, `lotw_download_url`, `lotw_upload_url`, `lotw_rcvd_mark`, `lotw_login_url`, `eqsl_download_url`, `eqsl_rcvd_mark`) VALUES -(1, 'https://p1k.arrl.org/lotwuser/lotwreport.adi', 'https://p1k.arrl.org/lotwuser/upload', 'Y', 'https://p1k.arrl.org/lotwuser/default', 'http://www.eqsl.cc/qslcard/DownloadInBox.cfm', 'Y'); +(1, 'https://lotw.arrl.org/lotwuser/lotwreport.adi', 'https://lotw.arrl.org/lotwuser/upload', 'Y', 'https://lotw.arrl.org/lotwuser/default', 'http://www.eqsl.cc/qslcard/DownloadInBox.cfm', 'Y'); /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; From 2d4b015a440b6244ac64edde0b7cb3c586f1d5fb Mon Sep 17 00:00:00 2001 From: Warren Volz Date: Wed, 13 Dec 2017 00:55:49 +0000 Subject: [PATCH 33/36] urlencode lotw password and last qsl date in requests to lotw - fixes 404 errors due to un-encoded password or dates --- application/controllers/lotw.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/controllers/lotw.php b/application/controllers/lotw.php index 775e2c09..f98584da 100644 --- a/application/controllers/lotw.php +++ b/application/controllers/lotw.php @@ -126,14 +126,14 @@ class Lotw extends CI_Controller { // Build URL for LoTW report file $lotw_url .= "?"; $lotw_url .= "login=" . $data['user_lotw_name']; - $lotw_url .= "&password=" . $data['user_lotw_password']; + $lotw_url .= "&password=" . urlencode($data['user_lotw_password']); $lotw_url .= "&qso_query=1&qso_qsl='yes'"; //TODO: Option to specifiy whether we download location data from LoTW or not //$lotw_url .= "&qso_qsldetail=\"yes\"; $lotw_url .= "&qso_qslsince="; - $lotw_url .= "$lotw_last_qsl_date"; + $lotw_url .= urlencode("$lotw_last_qsl_date"); // Only pull back entries that belong to this callsign $lotw_call = $this->session->userdata('user_callsign'); From 08686b535a22c08844334e756dbf0cca42a7e3a2 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Wed, 13 Dec 2017 13:34:08 +0000 Subject: [PATCH 34/36] Revert "Lotw url fix" --- application/controllers/lotw.php | 4 ++-- application/views/lotw/import.php | 2 +- install/assets/install.sql | 2 +- sql/tables/config.sql | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/controllers/lotw.php b/application/controllers/lotw.php index f98584da..775e2c09 100644 --- a/application/controllers/lotw.php +++ b/application/controllers/lotw.php @@ -126,14 +126,14 @@ class Lotw extends CI_Controller { // Build URL for LoTW report file $lotw_url .= "?"; $lotw_url .= "login=" . $data['user_lotw_name']; - $lotw_url .= "&password=" . urlencode($data['user_lotw_password']); + $lotw_url .= "&password=" . $data['user_lotw_password']; $lotw_url .= "&qso_query=1&qso_qsl='yes'"; //TODO: Option to specifiy whether we download location data from LoTW or not //$lotw_url .= "&qso_qsldetail=\"yes\"; $lotw_url .= "&qso_qslsince="; - $lotw_url .= urlencode("$lotw_last_qsl_date"); + $lotw_url .= "$lotw_last_qsl_date"; // Only pull back entries that belong to this callsign $lotw_call = $this->session->userdata('user_callsign'); diff --git a/application/views/lotw/import.php b/application/views/lotw/import.php index 226ad861..2e346124 100644 --- a/application/views/lotw/import.php +++ b/application/views/lotw/import.php @@ -7,7 +7,7 @@ Upload a file -

    Upload the Exported ADIF file from LoTW from the Download Report Area, to mark QSOs as confirmed on LOTW.

    +

    Upload the Exported ADIF file from LoTW from the Download Report Area, to mark QSOs as confirmed on LOTW.

    Important Log files must have the file type .adi

    diff --git a/install/assets/install.sql b/install/assets/install.sql index a749012f..f827cdea 100755 --- a/install/assets/install.sql +++ b/install/assets/install.sql @@ -3805,6 +3805,6 @@ CREATE TABLE IF NOT EXISTS `config` ( -- INSERT INTO `config` (`id`, `lotw_download_url`, `lotw_upload_url`, `lotw_rcvd_mark`, `lotw_login_url`, `eqsl_download_url`, `eqsl_rcvd_mark`) VALUES -(1, 'https://lotw.arrl.org/lotwuser/lotwreport.adi', 'https://lotw.arrl.org/lotwuser/upload', 'Y', 'https://lotw.arrl.org/lotwuser/default', 'http://www.eqsl.cc/qslcard/DownloadInBox.cfm', 'Y'); +(1, 'https://p1k.arrl.org/lotwuser/lotwreport.adi', 'https://p1k.arrl.org/lotwuser/upload', 'Y', 'https://p1k.arrl.org/lotwuser/default', 'http://www.eqsl.cc/qslcard/DownloadInBox.cfm', 'Y'); ALTER TABLE `cat` ADD `downlink_freq` INT(11) NOT NULL AFTER `mode`, ADD `uplink_freq` INT(11) NOT NULL AFTER `downlink_freq`, ADD `downlink_mode` VARCHAR(255) NOT NULL AFTER `uplink_freq`, ADD `uplink_mode` VARCHAR(255) NOT NULL AFTER `downlink_mode`, ADD `sat_name` VARCHAR(255) NOT NULL AFTER `uplink_mode`; diff --git a/sql/tables/config.sql b/sql/tables/config.sql index 63150a8a..db6e105f 100644 --- a/sql/tables/config.sql +++ b/sql/tables/config.sql @@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS `config` ( -- INSERT INTO `config` (`id`, `lotw_download_url`, `lotw_upload_url`, `lotw_rcvd_mark`, `lotw_login_url`, `eqsl_download_url`, `eqsl_rcvd_mark`) VALUES -(1, 'https://lotw.arrl.org/lotwuser/lotwreport.adi', 'https://lotw.arrl.org/lotwuser/upload', 'Y', 'https://lotw.arrl.org/lotwuser/default', 'http://www.eqsl.cc/qslcard/DownloadInBox.cfm', 'Y'); +(1, 'https://p1k.arrl.org/lotwuser/lotwreport.adi', 'https://p1k.arrl.org/lotwuser/upload', 'Y', 'https://p1k.arrl.org/lotwuser/default', 'http://www.eqsl.cc/qslcard/DownloadInBox.cfm', 'Y'); /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; From 90826b589fdb63a01679a9b5411b7d96c1101be6 Mon Sep 17 00:00:00 2001 From: Peter Goodhall Date: Sat, 16 Dec 2017 15:37:27 +0000 Subject: [PATCH 35/36] Removed PHP version Cloudlog now works on PHP7. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 198b3fb1..d122c8c3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Website: [http://www.cloudlog.co.uk](http://www.cloudlog.co.uk) ## Requirements -* PHP 5 & MySQL +* PHP & MySQL ## Versions @@ -78,4 +78,4 @@ We use the [Clublog](http://www.clublog.org/) cty.xml and APIs within the DXCC e ![HamQTH Logo](https://www.hamqth.com/images/hamqth_125x125.png "HamQTH Logo") -Cloudlog supports using the callsign lookup service from [HamQTH](https://www.hamqth.com) \ No newline at end of file +Cloudlog supports using the callsign lookup service from [HamQTH](https://www.hamqth.com) From 85a167eda93001d59cb6e842069d3e151ad19bac Mon Sep 17 00:00:00 2001 From: Warren Volz Date: Mon, 18 Dec 2017 00:41:17 -0700 Subject: [PATCH 36/36] Re-add adif + tq8 mime types, upload path - set ADIF file upload path to ./upload/ - for ADIF my browser uses text/plain, so add this to the adi/adif types --- application/config/mimes.php | 8 +++++++- application/controllers/Adif.php | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/application/config/mimes.php b/application/config/mimes.php index 01765333..b0fcbdf3 100644 --- a/application/config/mimes.php +++ b/application/config/mimes.php @@ -179,5 +179,11 @@ return array( 'odt' => 'application/vnd.oasis.opendocument.text', 'odm' => 'application/vnd.oasis.opendocument.text-master', 'ott' => 'application/vnd.oasis.opendocument.text-template', - 'oth' => 'application/vnd.oasis.opendocument.text-web' + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'adi' => array('application/octet-stream','text/plain'), + 'adif' => array('application/octet-stream','text/plain'), + 'ADI' => array('application/octet-stream','text/plain'), + 'ADIF' => array('application/octet-stream','text/plain'), + 'tq8' => 'application/octet-stream', + 'TQ8' => 'application/octet-stream' ); diff --git a/application/controllers/Adif.php b/application/controllers/Adif.php index e0a72ed5..ae91b83c 100644 --- a/application/controllers/Adif.php +++ b/application/controllers/Adif.php @@ -70,7 +70,7 @@ class adif extends CI_Controller { $data['page_title'] = "ADIF Import"; - $config['upload_path'] = 'uploads/'; + $config['upload_path'] = './uploads/'; $config['allowed_types'] = 'adi|ADI|adif|ADIF'; $this->load->library('upload', $config);