diff options
Diffstat (limited to 'system/libraries')
50 files changed, 13714 insertions, 9725 deletions
diff --git a/system/libraries/Cache/Cache.php b/system/libraries/Cache/Cache.php index 673e63de3..f3dfe25e4 100644 --- a/system/libraries/Cache/Cache.php +++ b/system/libraries/Cache/Cache.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2006 - 2014 EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 2.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 2.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * CodeIgniter Caching Class @@ -21,31 +44,83 @@ * @package CodeIgniter * @subpackage Libraries * @category Core - * @author ExpressionEngine Dev Team + * @author EllisLab Dev Team * @link */ class CI_Cache extends CI_Driver_Library { - protected $valid_drivers = array( - 'cache_apc', 'cache_file', 'cache_memcached', 'cache_dummy' + /** + * Valid cache drivers + * + * @var array + */ + protected $valid_drivers = array( + 'apc', + 'apcu', + 'dummy', + 'file', + 'memcached', + 'redis', + 'wincache' ); - protected $_cache_path = NULL; // Path of cache files (if file-based cache) - protected $_adapter = 'dummy'; - protected $_backup_driver; + /** + * Path of cache files (if file-based cache) + * + * @var string + */ + protected $_cache_path = NULL; - // ------------------------------------------------------------------------ + /** + * Reference to the driver + * + * @var mixed + */ + protected $_adapter = 'dummy'; + + /** + * Fallback driver + * + * @var string + */ + protected $_backup_driver = 'dummy'; + + /** + * Cache key prefix + * + * @var string + */ + public $key_prefix = ''; /** * Constructor * - * @param array + * Initialize class properties based on the configuration array. + * + * @param array $config = array() + * @return void */ public function __construct($config = array()) { - if ( ! empty($config)) + isset($config['adapter']) && $this->_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; + } } } @@ -54,15 +129,15 @@ class CI_Cache extends CI_Driver_Library { /** * Get * - * Look for a value in the cache. If it exists, return the data + * Look for a value in the cache. If it exists, return the data * if not, return FALSE * - * @param string - * @return mixed value that is stored/FALSE on failure + * @param string $id + * @return mixed value matching $id or FALSE on failure */ public function get($id) { - return $this->{$this->_adapter}->get($id); + return $this->{$this->_adapter}->get($this->key_prefix.$id); } // ------------------------------------------------------------------------ @@ -70,15 +145,15 @@ class CI_Cache extends CI_Driver_Library { /** * Cache Save * - * @param string Unique Key - * @param mixed Data to store - * @param int Length of time (in seconds) to cache the data - * - * @return boolean true on success/false on failure + * @param string $id Cache ID + * @param mixed $data Data to store + * @param int $ttl Cache TTL (in seconds) + * @param bool $raw Whether to store the raw value + * @return bool TRUE on success, FALSE on failure */ - public function save($id, $data, $ttl = 60) + public function save($id, $data, $ttl = 60, $raw = FALSE) { - return $this->{$this->_adapter}->save($id, $data, $ttl); + return $this->{$this->_adapter}->save($this->key_prefix.$id, $data, $ttl, $raw); } // ------------------------------------------------------------------------ @@ -86,86 +161,78 @@ class CI_Cache extends CI_Driver_Library { /** * Delete from Cache * - * @param mixed unique identifier of the item in the cache - * @return boolean true on success/false on failure + * @param string $id Cache ID + * @return bool TRUE on success, FALSE on failure */ public function delete($id) { - return $this->{$this->_adapter}->delete($id); + return $this->{$this->_adapter}->delete($this->key_prefix.$id); } // ------------------------------------------------------------------------ /** - * Clean the cache + * Increment a raw value * - * @return boolean false on failure/true on success + * @param string $id Cache ID + * @param int $offset Step/value to add + * @return mixed New value on success or FALSE on failure */ - public function clean() + public function increment($id, $offset = 1) { - return $this->{$this->_adapter}->clean(); + return $this->{$this->_adapter}->increment($this->key_prefix.$id, $offset); } // ------------------------------------------------------------------------ /** - * Cache Info + * Decrement a raw value * - * @param string user/filehits - * @return mixed array on success, false on failure + * @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 cache_info($type = 'user') + public function decrement($id, $offset = 1) { - return $this->{$this->_adapter}->cache_info($type); + return $this->{$this->_adapter}->decrement($this->key_prefix.$id, $offset); } // ------------------------------------------------------------------------ /** - * Get Cache Metadata + * Clean the cache * - * @param mixed key to get cache metadata on - * @return mixed return value from child method + * @return bool TRUE on success, FALSE on failure */ - public function get_metadata($id) + public function clean() { - return $this->{$this->_adapter}->get_metadata($id); + return $this->{$this->_adapter}->clean(); } // ------------------------------------------------------------------------ /** - * Initialize - * - * Initialize class properties based on the configuration array. + * Cache Info * - * @param array - * @return void + * @param string $type = 'user' user/filehits + * @return mixed array containing cache info on success OR FALSE on failure */ - private function _initialize($config) + public function cache_info($type = 'user') { - $default_config = array( - 'adapter', - 'memcached' - ); - - foreach ($default_config as $key) - { - if (isset($config[$key])) - { - $param = '_'.$key; + return $this->{$this->_adapter}->cache_info($type); + } - $this->{$param} = $config[$key]; - } - } + // ------------------------------------------------------------------------ - if (isset($config['backup'])) - { - if (in_array('cache_'.$config['backup'], $this->valid_drivers)) - { - $this->_backup_driver = $config['backup']; - } - } + /** + * Get Cache Metadata + * + * @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($this->key_prefix.$id); } // ------------------------------------------------------------------------ @@ -173,14 +240,14 @@ class CI_Cache extends CI_Driver_Library { /** * Is the requested driver supported in this environment? * - * @param string The driver to test. - * @return array + * @param string $driver The driver to test + * @return array */ public function is_supported($driver) { - static $support = array(); + static $support; - if ( ! isset($support[$driver])) + if ( ! isset($support, $support[$driver])) { $support[$driver] = $this->{$driver}->is_supported(); } @@ -191,24 +258,12 @@ class CI_Cache extends CI_Driver_Library { // ------------------------------------------------------------------------ /** - * __get() + * Get currently loaded driver * - * @param child - * @return object + * @return string */ - public function __get($child) + public function get_loaded_driver() { - $obj = parent::__get($child); - - if ( ! $this->is_supported($child)) - { - $this->_adapter = $this->_backup_driver; - } - - return $obj; + return $this->_adapter; } - } - -/* 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 fdc740138..229920449 100644 --- a/system/libraries/Cache/drivers/Cache_apc.php +++ b/system/libraries/Cache/drivers/Cache_apc.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2006 - 2014 EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 2.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 2.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * CodeIgniter APC Caching Class @@ -21,26 +44,44 @@ * @package CodeIgniter * @subpackage Libraries * @category Core - * @author ExpressionEngine Dev Team + * @author EllisLab Dev Team * @link */ - class CI_Cache_apc extends CI_Driver { /** + * Class constructor + * + * Only present so that an error message is logged + * if APC is not available. + * + * @return void + */ + public function __construct() + { + if ( ! $this->is_supported()) + { + log_message('error', 'Cache: Failed to initialize APC; extension not loaded/enabled?'); + } + } + + // ------------------------------------------------------------------------ + + /** * Get * - * Look for a value in the cache. If it exists, return the data + * Look for a value in the cache. If it exists, return the data * if not, return FALSE * - * @param string - * @return mixed value that is stored/FALSE on failure + * @param string + * @return mixed value that is stored/FALSE on failure */ public function get($id) { - $data = apc_fetch($id); + $success = FALSE; + $data = apc_fetch($id, $success); - return (is_array($data)) ? $data[0] : FALSE; + return ($success === TRUE) ? $data : FALSE; } // ------------------------------------------------------------------------ @@ -48,15 +89,15 @@ class CI_Cache_apc extends CI_Driver { /** * Cache Save * - * @param string Unique Key - * @param mixed Data to store - * @param int Length of time (in seconds) to cache the data - * - * @return boolean true on success/false on failure + * @param string $id Cache ID + * @param mixed $data Data to store + * @param int $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); } // ------------------------------------------------------------------------ @@ -64,8 +105,8 @@ class CI_Cache_apc extends CI_Driver { /** * Delete from Cache * - * @param mixed unique identifier of the item in the cache - * @param boolean true on success/false on failure + * @param mixed unique identifier of the item in the cache + * @return bool true on success/false on failure */ public function delete($id) { @@ -75,9 +116,37 @@ class CI_Cache_apc extends CI_Driver { // ------------------------------------------------------------------------ /** + * Increment a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to add + * @return mixed New value on success or FALSE on failure + */ + public function increment($id, $offset = 1) + { + return apc_inc($id, $offset); + } + + // ------------------------------------------------------------------------ + + /** + * Decrement a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to reduce by + * @return mixed New value on success or FALSE on failure + */ + public function decrement($id, $offset = 1) + { + return apc_dec($id, $offset); + } + + // ------------------------------------------------------------------------ + + /** * Clean the cache * - * @return boolean false on failure/true on success + * @return bool false on failure/true on success */ public function clean() { @@ -89,8 +158,8 @@ class CI_Cache_apc extends CI_Driver { /** * Cache Info * - * @param string user/filehits - * @return mixed array on success, false on failure + * @param string user/filehits + * @return mixed array on success, false on failure */ public function cache_info($type = NULL) { @@ -102,25 +171,35 @@ class CI_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; + } + + $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 array( - 'expire' => $time + $ttl, - 'mtime' => $time, - 'data' => $data - ); + return FALSE; } // ------------------------------------------------------------------------ @@ -129,19 +208,11 @@ class CI_Cache_apc extends CI_Driver { * is_supported() * * Check to see if APC is available on this system, bail if it isn't. + * + * @return bool */ public function is_supported() { - if ( ! extension_loaded('apc') OR ini_get('apc.enabled') != "1") - { - log_message('error', 'The APC PHP extension must be loaded to use APC Cache.'); - return FALSE; - } - - return TRUE; + return (extension_loaded('apc') && ini_get('apc.enabled')); } - } - -/* End of file Cache_apc.php */ -/* Location: ./system/libraries/Cache/drivers/Cache_apc.php */
\ No newline at end of file diff --git a/system/libraries/Cache/drivers/Cache_apcu.php b/system/libraries/Cache/drivers/Cache_apcu.php new file mode 100644 index 000000000..01f80e79b --- /dev/null +++ b/system/libraries/Cache/drivers/Cache_apcu.php @@ -0,0 +1,219 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2014 - 2019, British Columbia Institute of Technology + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.2.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CodeIgniter APCu Caching Class + * + * @package CodeIgniter + * @subpackage Libraries + * @category Core + * @author CodeIgniter Dev team + */ +class CI_Cache_apcu extends CI_Driver { + + /** + * Class constructor + * + * Only present so that an error message is logged + * if APCu is not available. + * + * @return void + */ + public function __construct() + { + if ( ! $this->is_supported()) + { + log_message('error', 'Cache: Failed to initialize APCu; 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 + */ + public function get($id) + { + $success = FALSE; + $data = apcu_fetch($id, $success); + + if ($success === TRUE) + { + return is_array($data) + ? $data[0] + : $data; + } + + return FALSE; + } + + // ------------------------------------------------------------------------ + + /** + * Cache Save + * + * @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 + * @return bool TRUE on success, FALSE on failure + */ + public function save($id, $data, $ttl = 60, $raw = FALSE) + { + $ttl = (int) $ttl; + + return apcu_store( + $id, + ($raw === TRUE ? $data : array($data, time(), $ttl)), + $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 apcu_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) + { + return apcu_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 apcu_dec($id, $offset); + } + + // ------------------------------------------------------------------------ + + /** + * Clean the cache + * + * @return bool false on failure/true on success + */ + public function clean() + { + return apcu_clear_cache(); + } + + // ------------------------------------------------------------------------ + + /** + * Cache Info + * + * @return mixed array on success, false on failure + */ + public function cache_info() + { + return apcu_cache_info(); + } + + // ------------------------------------------------------------------------ + + /** + * Get Cache Metadata + * + * @param mixed key to get cache metadata on + * @return mixed array on success/false on failure + */ + public function get_metadata($id) + { + $success = FALSE; + $stored = apcu_fetch($id, $success); + + if ($success === FALSE OR count($stored) !== 3) + { + return FALSE; + } + + list($data, $time, $ttl) = $stored; + + return array( + 'expire' => $time + $ttl, + 'mtime' => $time, + 'data' => $data + ); + } + + // ------------------------------------------------------------------------ + + /** + * is_supported() + * + * Check to see if APCu is available on this system, bail if it isn't. + * + * @return bool + */ + public function is_supported() + { + return (extension_loaded('apcu') && ini_get('apc.enabled')); + } +}
\ 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 6c38e91ad..f3ca220f6 100644 --- a/system/libraries/Cache/drivers/Cache_dummy.php +++ b/system/libraries/Cache/drivers/Cache_dummy.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2006 - 2014 EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 2.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 2.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * CodeIgniter Dummy Caching Class @@ -21,10 +44,9 @@ * @package CodeIgniter * @subpackage Libraries * @category Core - * @author ExpressionEngine Dev Team + * @author EllisLab Dev Team * @link */ - class CI_Cache_dummy extends CI_Driver { /** @@ -32,8 +54,8 @@ class CI_Cache_dummy extends CI_Driver { * * Since this is the dummy class, it's always going to return FALSE. * - * @param string - * @return Boolean FALSE + * @param string + * @return bool FALSE */ public function get($id) { @@ -45,13 +67,13 @@ class CI_Cache_dummy extends CI_Driver { /** * Cache Save * - * @param string Unique Key - * @param mixed Data to store - * @param int Length of time (in seconds) to cache the data - * - * @return boolean TRUE, Simulating success + * @param string Unique Key + * @param mixed Data to store + * @param int Length of time (in seconds) to cache the data + * @param bool Whether to store the raw value + * @return bool TRUE, Simulating success */ - public function save($id, $data, $ttl = 60) + public function save($id, $data, $ttl = 60, $raw = FALSE) { return TRUE; } @@ -61,8 +83,8 @@ class CI_Cache_dummy extends CI_Driver { /** * Delete from Cache * - * @param mixed unique identifier of the item in the cache - * @param boolean TRUE, simulating success + * @param mixed unique identifier of the item in the cache + * @return bool TRUE, simulating success */ public function delete($id) { @@ -72,9 +94,37 @@ class CI_Cache_dummy 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 TRUE; + } + + // ------------------------------------------------------------------------ + + /** + * 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 TRUE; + } + + // ------------------------------------------------------------------------ + + /** * Clean the cache * - * @return boolean TRUE, simulating success + * @return bool TRUE, simulating success */ public function clean() { @@ -86,21 +136,21 @@ class CI_Cache_dummy extends CI_Driver { /** * Cache Info * - * @param string user/filehits - * @return boolean FALSE + * @param string user/filehits + * @return bool FALSE */ - public function cache_info($type = NULL) - { - return FALSE; - } + public function cache_info($type = NULL) + { + return FALSE; + } // ------------------------------------------------------------------------ /** * Get Cache Metadata * - * @param mixed key to get cache metadata on - * @return boolean FALSE + * @param mixed key to get cache metadata on + * @return bool FALSE */ public function get_metadata($id) { @@ -113,7 +163,7 @@ class CI_Cache_dummy extends CI_Driver { * Is this caching driver supported on the system? * Of course this one is. * - * @return TRUE; + * @return bool TRUE */ public function is_supported() { @@ -121,6 +171,3 @@ class CI_Cache_dummy extends CI_Driver { } } - -/* End of file Cache_dummy.php */ -/* Location: ./system/libraries/Cache/drivers/Cache_dummy.php */
\ No newline at end of file diff --git a/system/libraries/Cache/drivers/Cache_file.php b/system/libraries/Cache/drivers/Cache_file.php index 2f250e764..3a4be98a9 100644 --- a/system/libraries/Cache/drivers/Cache_file.php +++ b/system/libraries/Cache/drivers/Cache_file.php @@ -1,45 +1,72 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2006 - 2014 EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 2.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 2.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** - * CodeIgniter Memcached Caching Class + * CodeIgniter File Caching Class * * @package CodeIgniter * @subpackage Libraries * @category Core - * @author ExpressionEngine Dev Team + * @author EllisLab Dev Team * @link */ - class CI_Cache_file extends CI_Driver { + /** + * Directory in which to save cache files + * + * @var string + */ protected $_cache_path; /** - * Constructor + * Initialize file-based cache + * + * @return void */ public function __construct() { $CI =& get_instance(); $CI->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 +74,13 @@ class CI_Cache_file extends CI_Driver { /** * Fetch from cache * - * @param mixed unique key id - * @return mixed data on success/false on failure + * @param string $id Cache ID + * @return mixed Data on success, FALSE on failure */ public function get($id) { - if ( ! file_exists($this->_cache_path.$id)) - { - return FALSE; - } - - $data = read_file($this->_cache_path.$id); - $data = unserialize($data); - - if (time() > $data['time'] + $data['ttl']) - { - $this->delete($id); - return FALSE; - } - - return $data['data']; + $data = $this->_get($id); + return is_array($data) ? $data['data'] : FALSE; } // ------------------------------------------------------------------------ @@ -74,23 +88,23 @@ class CI_Cache_file extends CI_Driver { /** * Save into cache * - * @param string unique key - * @param mixed data to store - * @param int length of time (in seconds) the cache is valid - * - Default is 60 seconds - * @return boolean true on success/false on failure + * @param string $id Cache ID + * @param mixed $data Data to store + * @param int $ttl Time to live in seconds + * @param bool $raw Whether to store the raw value (unused) + * @return bool TRUE on success, FALSE on failure */ - public function save($id, $data, $ttl = 60) + public function save($id, $data, $ttl = 60, $raw = FALSE) { $contents = array( - 'time' => time(), - 'ttl' => $ttl, - 'data' => $data - ); + 'time' => time(), + 'ttl' => $ttl, + 'data' => $data + ); if (write_file($this->_cache_path.$id, serialize($contents))) { - @chmod($this->_cache_path.$id, 0777); + chmod($this->_cache_path.$id, 0640); return TRUE; } @@ -102,16 +116,68 @@ class CI_Cache_file extends CI_Driver { /** * Delete from Cache * - * @param mixed unique identifier of item in cache - * @return boolean true on success/false on failure + * @param mixed unique identifier of item in cache + * @return bool true on success/false on failure */ public function delete($id) { - try { - return unlink($this->_cache_path.$id); - } catch (\ErrorException $e) { - return false; + 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; } // ------------------------------------------------------------------------ @@ -119,11 +185,11 @@ class CI_Cache_file extends CI_Driver { /** * Clean the Cache * - * @return boolean false on failure/true on success + * @return bool false on failure/true on success */ public function clean() { - return delete_files($this->_cache_path); + return delete_files($this->_cache_path, FALSE, TRUE); } // ------------------------------------------------------------------------ @@ -133,8 +199,8 @@ class CI_Cache_file extends CI_Driver { * * Not supported by file-based caching * - * @param string user/filehits - * @return mixed FALSE + * @param string user/filehits + * @return mixed FALSE */ public function cache_info($type = NULL) { @@ -146,31 +212,30 @@ class CI_Cache_file extends CI_Driver { /** * Get Cache Metadata * - * @param mixed key to get cache metadata on - * @return mixed FALSE on failure, array on success. + * @param mixed key to get cache metadata on + * @return mixed FALSE on failure, array on success. */ public function get_metadata($id) { - 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)) { $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 ); } @@ -184,14 +249,39 @@ class CI_Cache_file extends CI_Driver { * * In the file driver, check to see that the cache directory is indeed writable * - * @return boolean + * @return bool */ public function is_supported() { return is_really_writable($this->_cache_path); } -} + // ------------------------------------------------------------------------ + + /** + * 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)); -/* End of file Cache_file.php */ -/* Location: ./system/libraries/Cache/drivers/Cache_file.php */
\ No newline at end of file + if ($data['ttl'] > 0 && time() > $data['time'] + $data['ttl']) + { + file_exists($this->_cache_path.$id) && 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 f9d578b93..55963bb82 100644 --- a/system/libraries/Cache/drivers/Cache_memcached.php +++ b/system/libraries/Cache/drivers/Cache_memcached.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2006 - 2014 EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 2.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 2.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * CodeIgniter Memcached Caching Class @@ -21,35 +44,117 @@ * @package CodeIgniter * @subpackage Libraries * @category Core - * @author ExpressionEngine Dev Team + * @author EllisLab Dev Team * @link */ - class CI_Cache_memcached extends CI_Driver { - private $_memcached; // Holds the memcached object + /** + * Holds the memcached object + * + * @var object + */ + protected $_memcached; + + /** + * Memcached configuration + * + * @var array + */ + protected $_config = array( + 'default' => array( + 'host' => '127.0.0.1', + 'port' => 11211, + 'weight' => 1 + ) + ); + + // ------------------------------------------------------------------------ + + /** + * Class constructor + * + * Setup Memcache(d) + * + * @return void + */ + public function __construct() + { + // Try to load memcached server info from the config file. + $CI =& get_instance(); + $defaults = $this->_config['default']; + + if ($CI->config->load('memcached', TRUE, TRUE)) + { + $this->_config = $CI->config->config['memcached']; + } - protected $_memcache_conf = array( - 'default' => array( - 'default_host' => '127.0.0.1', - 'default_port' => 11211, - 'default_weight' => 1 - ) + 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_name => $cache_server) + { + if ( ! isset($cache_server['hostname'])) + { + log_message('debug', 'Cache: Memcache(d) configuration "'.$cache_name.'" doesn\'t include a hostname; ignoring.'); + continue; + } + elseif ($cache_server['hostname'][0] === '/') + { + $cache_server['port'] = 0; + } + elseif (empty($cache_server['port'])) + { + $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,20 +162,26 @@ class CI_Cache_memcached extends CI_Driver { /** * Save * - * @param string unique identifier - * @param mixed data being cached - * @param int time to live - * @return boolean true on success, false on failure + * @param string $id Cache ID + * @param mixed $data Data being cached + * @param int $ttl Time to live + * @param bool $raw Whether to store the raw value + * @return bool TRUE on success, FALSE on failure */ - public function save($id, $data, $ttl = 60) + public function save($id, $data, $ttl = 60, $raw = FALSE) { - if (get_class($this->_memcached) == 'Memcached') + if ($raw !== TRUE) { - return $this->_memcached->set($id, array($data, time(), $ttl), $ttl); + $data = array($data, time(), $ttl); } - else if (get_class($this->_memcached) == 'Memcache') + + if ($this->_memcached instanceof Memcached) { - return $this->_memcached->set($id, array($data, time(), $ttl), 0, $ttl); + return $this->_memcached->set($id, $data, $ttl); + } + elseif ($this->_memcached instanceof Memcache) + { + return $this->_memcached->set($id, $data, 0, $ttl); } return FALSE; @@ -81,8 +192,8 @@ class CI_Cache_memcached extends CI_Driver { /** * Delete from Cache * - * @param mixed key to be deleted. - * @return boolean true on success, false on failure + * @param mixed $id key to be deleted. + * @return bool true on success, false on failure */ public function delete($id) { @@ -92,9 +203,47 @@ class CI_Cache_memcached extends CI_Driver { // ------------------------------------------------------------------------ /** + * Increment a raw value + * + * @param string $id Cache ID + * @param int $offset Step/value to add + * @return mixed New value on success or FALSE on failure + */ + public function increment($id, $offset = 1) + { + if (($result = $this->_memcached->increment($id, $offset)) === FALSE) + { + return $this->_memcached->add($id, $offset) ? $offset : FALSE; + } + + return $result; + } + + // ------------------------------------------------------------------------ + + /** + * 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) + { + if (($result = $this->_memcached->decrement($id, $offset)) === FALSE) + { + return $this->_memcached->add($id, 0) ? 0 : FALSE; + } + + return $result; + } + + // ------------------------------------------------------------------------ + + /** * Clean the Cache * - * @return boolean false on failure/true on success + * @return bool false on failure/true on success */ public function clean() { @@ -106,10 +255,9 @@ class CI_Cache_memcached extends CI_Driver { /** * Cache Info * - * @param null type not supported in memcached - * @return mixed array on success, false on failure + * @return mixed array on success, false on failure */ - public function cache_info($type = NULL) + public function cache_info() { return $this->_memcached->getStats(); } @@ -119,8 +267,8 @@ class CI_Cache_memcached extends CI_Driver { /** * Get Cache Metadata * - * @param mixed key to get cache metadata on - * @return mixed FALSE on failure, array on success. + * @param mixed $id key to get cache metadata on + * @return mixed FALSE on failure, array on success. */ public function get_metadata($id) { @@ -143,72 +291,36 @@ class CI_Cache_memcached extends CI_Driver { // ------------------------------------------------------------------------ /** - * Setup memcached. + * Is supported + * + * Returns FALSE if memcached is not supported on the system. + * If it is, we setup the memcached object & return TRUE + * + * @return bool */ - private function _setup_memcached() + public function is_supported() { - // 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'] - ); - } + return (extension_loaded('memcached') OR extension_loaded('memcache')); } // ------------------------------------------------------------------------ - /** - * Is supported + * Class destructor * - * Returns FALSE if memcached is not supported on the system. - * If it is, we setup the memcached object & return TRUE + * Closes the connection to Memcache(d) if present. + * + * @return void */ - public function is_supported() + public function __destruct() { - if ( ! extension_loaded('memcached')) + if ($this->_memcached instanceof Memcache) { - log_message('error', 'The Memcached Extension must be loaded to use Memcached Cache.'); - return FALSE; + $this->_memcached->close(); + } + elseif ($this->_memcached instanceof Memcached && method_exists($this->_memcached, 'quit')) + { + $this->_memcached->quit(); } - - $this->_setup_memcached(); - return TRUE; } - } - -/* 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 000000000..22af592e7 --- /dev/null +++ b/system/libraries/Cache/drivers/Cache_redis.php @@ -0,0 +1,360 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CodeIgniter Redis Caching Class + * + * @package CodeIgniter + * @subpackage Libraries + * @category Core + * @author Anton Lindqvist <anton@qvister.se> + * @link + */ +class CI_Cache_redis extends CI_Driver +{ + /** + * Default config + * + * @static + * @var array + */ + protected static $_default_config = array( + 'host' => '127.0.0.1', + 'password' => NULL, + 'port' => 6379, + 'timeout' => 0, + 'database' => 0 + ); + + /** + * Redis connection + * + * @var Redis + */ + protected $_redis; + + /** + * del()/delete() method name depending on phpRedis version + * + * @var string + */ + protected static $_delete_name; + + /** + * sRem()/sRemove() method name depending on phpRedis version + * + * @var string + */ + protected static $_sRemove_name; + + // ------------------------------------------------------------------------ + + /** + * Class constructor + * + * Setup Redis + * + * Loads Redis config file if present. Will halt execution + * if a Redis connection can't be established. + * + * @return void + * @throws RedisException + * @see Redis::connect() + */ + public function __construct() + { + if ( ! $this->is_supported()) + { + log_message('error', 'Cache: Failed to create Redis object; extension not loaded?'); + return; + } + + if ( ! isset(static::$_delete_name, static::$_sRemove_name)) + { + if (version_compare(phpversion('redis'), '5', '>=')) + { + static::$_delete_name = 'del'; + static::$_sRemove_name = 'sRem'; + } + else + { + static::$_delete_name = 'delete'; + static::$_sRemove_name = 'sRemove'; + } + } + + $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(); + + // The following calls used to be wrapped in a try ... catch + // and just log an error, but that only causes more errors later. + if ( ! $this->_redis->connect($config['host'], ($config['host'][0] === '/' ? 0 : $config['port']), $config['timeout'])) + { + 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.'); + } + + if (isset($config['database']) && $config['database'] > 0 && ! $this->_redis->select($config['database'])) + { + log_message('error', 'Cache: Redis select database failed.'); + } + } + + // ------------------------------------------------------------------------ + + /** + * Get cache + * + * @param string $key Cache ID + * @return mixed + */ + public function get($key) + { + $data = $this->_redis->hMGet($key, array('__ci_type', '__ci_value')); + + if ($value !== FALSE && $this->_redis->sIsMember('_ci_redis_serialized', $key)) + { + return FALSE; + } + + switch ($data['__ci_type']) + { + case 'array': + case 'object': + return unserialize($data['__ci_value']); + case 'boolean': + case 'integer': + case 'double': // Yes, 'double' is returned and NOT 'float' + case 'string': + case 'NULL': + return settype($data['__ci_value'], $data['__ci_type']) + ? $data['__ci_value'] + : FALSE; + case 'resource': + default: + return FALSE; + } + } + + // ------------------------------------------------------------------------ + + /** + * 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) + { + switch ($data_type = gettype($data)) + { + case 'array': + case 'object': + $data = serialize($data); + break; + case 'boolean': + case 'integer': + case 'double': // Yes, 'double' is returned and NOT 'float' + case 'string': + case 'NULL': + break; + case 'resource': + default: + return FALSE; + } + + if ( ! $this->_redis->hMSet($id, array('__ci_type' => $data_type, '__ci_value' => $data))) + { + return FALSE; + } + else + { + $this->_redis->{static::$_sRemove_name}('_ci_redis_serialized', $id); + } + + return TRUE; + } + + // ------------------------------------------------------------------------ + + /** + * Delete from cache + * + * @param string $key Cache key + * @return bool + */ + public function delete($key) + { + if ($this->_redis->{static::$_delete_name}($key) !== 1) + { + return FALSE; + } + + $this->_redis->{static::$_sRemove_name}('_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->incrBy($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->decrBy($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 000000000..bd18148f1 --- /dev/null +++ b/system/libraries/Cache/drivers/Cache_wincache.php @@ -0,0 +1,218 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CodeIgniter Wincache Caching Class + * + * Read more about Wincache functions here: + * https://www.php.net/manual/en/ref.wincache.php + * + * @package CodeIgniter + * @subpackage Libraries + * @category Core + * @author Mike Murkovic + * @link + */ +class CI_Cache_wincache extends CI_Driver { + + /** + * Class constructor + * + * Only present so that an error message is logged + * if APC is not available. + * + * @return void + */ + public function __construct() + { + if ( ! $this->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 index c942a79ce..bcb7cae34 100644 --- a/system/libraries/Cache/drivers/index.html +++ b/system/libraries/Cache/drivers/index.html @@ -1,4 +1,5 @@ -<html> +<!DOCTYPE html> +<html lang="en"> <head> <title>403 Forbidden</title> </head> @@ -7,4 +8,4 @@ <p>Directory access is forbidden.</p> </body> -</html>
\ No newline at end of file +</html> diff --git a/system/libraries/Cache/index.html b/system/libraries/Cache/index.html index c942a79ce..bcb7cae34 100644 --- a/system/libraries/Cache/index.html +++ b/system/libraries/Cache/index.html @@ -1,4 +1,5 @@ -<html> +<!DOCTYPE html> +<html lang="en"> <head> <title>403 Forbidden</title> </head> @@ -7,4 +8,4 @@ <p>Directory access is forbidden.</p> </body> -</html>
\ No newline at end of file +</html> diff --git a/system/libraries/Calendar.php b/system/libraries/Calendar.php index 626097a9b..8eefc82ed 100644 --- a/system/libraries/Calendar.php +++ b/system/libraries/Calendar.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * CodeIgniter Calendar Class @@ -23,43 +46,96 @@ * @package CodeIgniter * @subpackage Libraries * @category Libraries - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/calendar.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/calendar.html */ class CI_Calendar { - var $CI; - var $lang; - var $local_time; - var $template = ''; - var $start_day = 'sunday'; - var $month_type = 'long'; - var $day_type = 'abr'; - var $show_next_prev = FALSE; - var $next_prev_url = ''; + /** + * Calendar layout template + * + * @var mixed + */ + public $template = ''; + + /** + * Replacements array for template + * + * @var array + */ + public $replacements = array(); + + /** + * Day of the week to start the calendar on + * + * @var string + */ + public $start_day = 'sunday'; + + /** + * How to display months + * + * @var string + */ + public $month_type = 'long'; + + /** + * How to display names of days + * + * @var string + */ + public $day_type = 'abr'; + + /** + * Whether to show next/prev month links + * + * @var bool + */ + public $show_next_prev = FALSE; /** - * Constructor + * Url base to use for next/prev month links * - * Loads the calendar language file and sets the default time reference + * @var bool + */ + public $next_prev_url = ''; + + /** + * Show days of other months + * + * @var bool + */ + public $show_other_days = FALSE; + + // -------------------------------------------------------------------- + + /** + * CI Singleton + * + * @var object + */ + protected $CI; + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * Loads the calendar language file and sets the default time reference. + * + * @uses CI_Lang::$is_loaded + * + * @param array $config Calendar options + * @return void */ public function __construct($config = array()) { $this->CI =& get_instance(); + $this->CI->lang->load('calendar'); - if ( ! in_array('calendar_lang.php', $this->CI->lang->is_loaded, TRUE)) - { - $this->CI->lang->load('calendar'); - } - - $this->local_time = time(); + empty($config) OR $this->initialize($config); - if (count($config) > 0) - { - $this->initialize($config); - } - - log_message('debug', "Calendar Class Initialized"); + log_message('info', 'Calendar Class Initialized'); } // -------------------------------------------------------------------- @@ -69,11 +145,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 +157,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 +172,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()) { - // Set and validate the supplied month/year - if ($year == '') - $year = date("Y", $this->local_time); + $local_time = time(); - if ($month == '') - $month = date("m", $this->local_time); - - if (strlen($year) == 1) + // Set and validate the supplied month/year + 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 +214,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 +228,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 +348,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 +362,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 +373,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 +399,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 +416,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 +439,7 @@ class CI_Calendar { $date['year']--; } - if (strlen($date['month']) == 1) + if (strlen($date['month']) === 1) { $date['month'] = '0'.$date['month']; } @@ -366,30 +452,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 +469,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' => '<table border="0" cellpadding="4" cellspacing="0">', - 'heading_row_start' => '<tr>', - 'heading_previous_cell' => '<th><a href="{previous_url}"><<</a></th>', - 'heading_title_cell' => '<th colspan="{colspan}">{heading}</th>', - 'heading_next_cell' => '<th><a href="{next_url}">>></a></th>', - 'heading_row_end' => '</tr>', - 'week_row_start' => '<tr>', - 'week_day_cell' => '<td>{week_day}</td>', - 'week_row_end' => '</tr>', - 'cal_row_start' => '<tr>', - 'cal_cell_start' => '<td>', - 'cal_cell_start_today' => '<td>', - 'cal_cell_content' => '<a href="{content}">{day}</a>', - 'cal_cell_content_today' => '<a href="{content}"><strong>{day}</strong></a>', - 'cal_cell_no_content' => '{day}', - 'cal_cell_no_content_today' => '<strong>{day}</strong>', - 'cal_cell_blank' => ' ', - 'cal_cell_end' => '</td>', - 'cal_cell_end_today' => '</td>', - 'cal_row_end' => '</tr>', - 'table_close' => '</table>' - ); + return array( + 'table_open' => '<table border="0" cellpadding="4" cellspacing="0">', + 'heading_row_start' => '<tr>', + 'heading_previous_cell' => '<th><a href="{previous_url}"><<</a></th>', + 'heading_title_cell' => '<th colspan="{colspan}">{heading}</th>', + 'heading_next_cell' => '<th><a href="{next_url}">>></a></th>', + 'heading_row_end' => '</tr>', + 'week_row_start' => '<tr>', + 'week_day_cell' => '<td>{week_day}</td>', + 'week_row_end' => '</tr>', + 'cal_row_start' => '<tr>', + 'cal_cell_start' => '<td>', + 'cal_cell_start_today' => '<td>', + 'cal_cell_start_other' => '<td style="color: #666;">', + 'cal_cell_content' => '<a href="{content}">{day}</a>', + 'cal_cell_content_today' => '<a href="{content}"><strong>{day}</strong></a>', + 'cal_cell_no_content' => '{day}', + 'cal_cell_no_content_today' => '<strong>{day}</strong>', + 'cal_cell_blank' => ' ', + 'cal_cell_other' => '{day}', + 'cal_cell_end' => '</td>', + 'cal_cell_end_today' => '</td>', + 'cal_cell_end_other' => '</td>', + 'cal_row_end' => '</tr>', + 'table_close' => '</table>' + ); } // -------------------------------------------------------------------- @@ -437,39 +509,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)) - { - $this->temp[$val] = $match['1']; - } - else + $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) { - 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 deleted file mode 100644 index 86a01f796..000000000 --- a/system/libraries/Cart.php +++ /dev/null @@ -1,551 +0,0 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); -/** - * CodeIgniter - * - * An open source application development framework for PHP 5.1.6 or newer - * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2006 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 - * @filesource - */ - -// ------------------------------------------------------------------------ - -/** - * Shopping Cart Class - * - * @package CodeIgniter - * @subpackage Libraries - * @category Shopping Cart - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/cart.html - */ -class CI_Cart { - - // These are the regular expression rules that we use to validate the product ID and product name - var $product_id_rules = '\.a-z0-9_-'; // alpha-numeric, dashes, underscores, or periods - var $product_name_rules = '\.\:\-_ a-z0-9'; // alpha-numeric, dashes, underscores, colons or periods - - // Private variables. Do not change! - var $CI; - var $_cart_contents = array(); - - - /** - * Shopping Class Constructor - * - * The constructor loads the Session class, used to store the shopping cart contents. - */ - public function __construct($params = array()) - { - // Set the super object to a local variable for use later - $this->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; - } - } - - // Load the Sessions class - $this->CI->load->library('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 - { - // No cart exists so we'll set some base values - $this->_cart_contents['cart_total'] = 0; - $this->_cart_contents['total_items'] = 0; - } - - log_message('debug', "Cart Class Initialized"); - } - - // -------------------------------------------------------------------- - - /** - * Insert items into the cart and save it to the session table - * - * @access public - * @param array - * @return bool - */ - function insert($items = array()) - { - // Was any cart data passed? No? Bah... - if ( ! is_array($items) OR count($items) == 0) - { - log_message('error', 'The insert method must be passed an array containing data.'); - return FALSE; - } - - // You can either insert 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" - // at the top level. If it's not found, we will assume it's a multi-dimensional array. - - $save_cart = FALSE; - if (isset($items['id'])) - { - if (($rowid = $this->_insert($items))) - { - $save_cart = TRUE; - } - } - else - { - foreach ($items as $val) - { - if (is_array($val) AND isset($val['id'])) - { - if ($this->_insert($val)) - { - $save_cart = TRUE; - } - } - } - } - - // Save the cart data if the insert was successful - if ($save_cart == TRUE) - { - $this->_save_cart(); - return isset($rowid) ? $rowid : TRUE; - } - - return FALSE; - } - - // -------------------------------------------------------------------- - - /** - * Insert - * - * @access private - * @param array - * @return bool - */ - function _insert($items = array()) - { - // Was any cart data passed? No? Bah... - if ( ! is_array($items) OR count($items) == 0) - { - log_message('error', 'The insert method must be passed an array containing data.'); - return FALSE; - } - - // -------------------------------------------------------------------- - - // 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'])) - { - log_message('error', 'The cart array must contain a product ID, quantity, price, and name.'); - return FALSE; - } - - // -------------------------------------------------------------------- - - // 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'])); - - // If the quantity is zero or blank there's nothing for us to do - if ( ! is_numeric($items['qty']) OR $items['qty'] == 0) - { - return FALSE; - } - - // -------------------------------------------------------------------- - - // 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'])) - { - log_message('error', 'Invalid product ID. The product ID can only contain alpha-numeric characters, dashes, and underscores'); - return FALSE; - } - - // -------------------------------------------------------------------- - - // 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'])) - { - 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; - } - - // -------------------------------------------------------------------- - - // 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; - } - - // -------------------------------------------------------------------- - - // 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. - // Each row in the cart array, however, must have a unique index that identifies not only - // a particular product, but makes it possible to store identical products with different options. - // For example, what if someone buys two identical t-shirts (same product ID), but in - // different sizes? The product ID (and other attributes, like the name) will be identical for - // both sizes because it's the same shirt. The only difference will be the size. - // 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) - { - $rowid = md5($items['id'].implode('', $items['options'])); - } - else - { - // No options were submitted so we simply MD5 the product ID. - // Technically, we don't need to MD5 the ID in this case, but it makes - // sense to standardize the format of array indexes for both conditions - $rowid = md5($items['id']); - } - - // -------------------------------------------------------------------- - - // Now that we have our unique "row ID", we'll add our cart items to the master array - - // let's unset this first, just to make sure our index contains only the data from this submission - unset($this->_cart_contents[$rowid]); - - // Create a new index with our new row ID - $this->_cart_contents[$rowid]['rowid'] = $rowid; - - // And add the new items to the cart array - foreach ($items as $key => $val) - { - $this->_cart_contents[$rowid][$key] = $val; - } - - // Woot! - return $rowid; - } - - // -------------------------------------------------------------------- - - /** - * Update the cart - * - * This function permits the quantity of a given item to be changed. - * 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. - * - * @access public - * @param array - * @param string - * @return bool - */ - function update($items = array()) - { - // Was any cart data passed? - 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". - // 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 ($this->_update($items) == TRUE) - { - $save_cart = TRUE; - } - } - else - { - foreach ($items as $val) - { - if (is_array($val) AND isset($val['rowid']) AND isset($val['qty'])) - { - if ($this->_update($val) == TRUE) - { - $save_cart = TRUE; - } - } - } - } - - // Save the cart data if the insert was successful - if ($save_cart == TRUE) - { - $this->_save_cart(); - return TRUE; - } - - return FALSE; - } - - // -------------------------------------------------------------------- - - /** - * Update the cart - * - * This function permits the quantity of a given item to be changed. - * 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. - * - * @access private - * @param array - * @return bool - */ - 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']])) - { - return FALSE; - } - - // Prep the quantity - $items['qty'] = preg_replace('/([^0-9])/i', '', $items['qty']); - - // Is the quantity a number? - if ( ! is_numeric($items['qty'])) - { - return FALSE; - } - - // 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']) - { - return FALSE; - } - - // 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']]); - } - else - { - $this->_cart_contents[$items['rowid']]['qty'] = $items['qty']; - } - - return TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Save the cart array to the session DB - * - * @access private - * @return bool - */ - function _save_cart() - { - // Unset these so our total can be calculated correctly below - unset($this->_cart_contents['total_items']); - unset($this->_cart_contents['cart_total']); - - // Lets add up the individual prices and set the cart sub-total - $total = 0; - $items = 0; - 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'])) - { - continue; - } - - $total += ($val['price'] * $val['qty']); - $items += $val['qty']; - - // Set the subtotal - $this->_cart_contents[$key]['subtotal'] = ($this->_cart_contents[$key]['price'] * $this->_cart_contents[$key]['qty']); - } - - // Set the cart total and total items. - $this->_cart_contents['total_items'] = $items; - $this->_cart_contents['cart_total'] = $total; - - // Is our cart empty? If so we delete it from the session - if (count($this->_cart_contents) <= 2) - { - $this->CI->session->unset_userdata('cart_contents'); - - // Nothing more to do... coffee time! - return FALSE; - } - - // If we made it this far it means that our cart has data. - // Let's pass it to the Session class so it can be stored - $this->CI->session->set_userdata(array('cart_contents' => $this->_cart_contents)); - - // Woot! - return TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Cart Total - * - * @access public - * @return integer - */ - function total() - { - return $this->_cart_contents['cart_total']; - } - - // -------------------------------------------------------------------- - - /** - * Total Items - * - * Returns the total item count - * - * @access public - * @return integer - */ - function total_items() - { - return $this->_cart_contents['total_items']; - } - - // -------------------------------------------------------------------- - - /** - * Cart Contents - * - * Returns the entire cart array - * - * @access public - * @return array - */ - function contents() - { - $cart = $this->_cart_contents; - - // Remove these so they don't create a problem when showing the cart table - unset($cart['total_items']); - unset($cart['cart_total']); - - return $cart; - } - - // -------------------------------------------------------------------- - - /** - * 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 - */ - function has_options($rowid = '') - { - if ( ! isset($this->_cart_contents[$rowid]['options']) OR count($this->_cart_contents[$rowid]['options']) === 0) - { - return FALSE; - } - - return TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Product options - * - * Returns the an array of options, for a particular product row ID - * - * @access public - * @return array - */ - function product_options($rowid = '') - { - if ( ! isset($this->_cart_contents[$rowid]['options'])) - { - return array(); - } - - return $this->_cart_contents[$rowid]['options']; - } - - // -------------------------------------------------------------------- - - /** - * Format Number - * - * Returns the supplied number with commas and a decimal point. - * - * @access public - * @return integer - */ - 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, '.', ','); - } - - // -------------------------------------------------------------------- - - /** - * Destroy the cart - * - * Empties the cart and kills the session - * - * @access public - * @return null - */ - function destroy() - { - unset($this->_cart_contents); - - $this->_cart_contents['cart_total'] = 0; - $this->_cart_contents['total_items'] = 0; - - $this->CI->session->unset_userdata('cart_contents'); - } - - -} - -/* End of file Cart.php */ -/* Location: ./system/libraries/Cart.php */
\ No newline at end of file diff --git a/system/libraries/Driver.php b/system/libraries/Driver.php index 9ae7b0c7c..7059be645 100644 --- a/system/libraries/Driver.php +++ b/system/libraries/Driver.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author EllisLab Dev Team - * @copyright Copyright (c) 2006 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * CodeIgniter Driver Library Class @@ -27,72 +50,150 @@ * @author EllisLab Dev Team * @link */ +#[\AllowDynamicProperties] class CI_Driver_Library { - protected $valid_drivers = array(); + /** + * Array of drivers that are available to use with the driver class + * + * @var array + */ + protected $valid_drivers = array(); + + /** + * Name of the current class - usually the driver class + * + * @var string + */ protected $lib_name; - // The first time a child is used it won't exist, so we instantiate it - // subsequents calls will go straight to the proper child. - function __get($child) + /** + * Get magic method + * + * The first time a child is used it won't exist, so we instantiate it + * subsequents calls will go straight to the proper child. + * + * @param string Child class name + * @return object Child class + */ + public function __get($child) { + // 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)) { - $this->lib_name = get_class($this); + // Get library name without any prefix + $this->lib_name = str_replace(array('CI_', $prefix), '', get_class($this)); } - // The class will be prefixed with the parent lib - $child_class = $this->lib_name.'_'.$child; + // The child will be prefixed with the parent lib + $child_name = $this->lib_name.'_'.$child; - // Remove the CI_ prefix and lowercase - $lib_name = ucfirst(strtolower(str_replace('CI_', '', $this->lib_name))); - $driver_name = strtolower(str_replace('CI_', '', $child_class)); + // 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); - if (in_array($driver_name, array_map('strtolower', $this->valid_drivers))) + // Is there an extension? + $class_name = $prefix.$child_name; + $found = class_exists($class_name, FALSE); + if ( ! $found) { - // check and see if the driver is in a separate file - if ( ! class_exists($child_class)) + // Check for subclass file + foreach ($paths as $path) { - // check application path first - foreach (get_instance()->load->get_package_paths(TRUE) as $path) + // Does the file exist? + $file = $path.'libraries/'.$this->lib_name.'/drivers/'.$prefix.$child_name.'.php'; + if (file_exists($file)) { - // loves me some nesting! - foreach (array(ucfirst($driver_name), $driver_name) as $class) + // Yes - require base class from BASEPATH + $basepath = BASEPATH.'libraries/'.$this->lib_name.'/drivers/'.$child_name.'.php'; + if ( ! file_exists($basepath)) { - $filepath = $path.'libraries/'.$lib_name.'/drivers/'.$class.'.php'; - - if (file_exists($filepath)) - { - include_once $filepath; - break; - } + $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; } + } + } - // it's a valid driver, but the file simply can't be found - if ( ! class_exists($child_class)) + // 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) { - log_message('error', "Unable to load the requested driver: ".$child_class); - show_error("Unable to load the requested driver: ".$child_class); + // Does the file exist? + $file = $path.'libraries/'.$this->lib_name.'/drivers/'.$child_name.'.php'; + if (file_exists($file)) + { + // Include source + include_once($file); + break; + } } } + } - $obj = new $child_class; - $obj->decorate($this); - $this->$child = $obj; - return $this->$child; + // 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); + } } - // The requested driver isn't valid! - log_message('error', "Invalid driver requested: ".$child_class); - show_error("Invalid driver requested: ".$child_class); + // 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 +208,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 +248,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 +263,7 @@ class CI_Driver { { if ($method->isPublic()) { - $this->methods[] = $method->getName(); + $this->_methods[] = $method->getName(); } } @@ -147,15 +271,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 +290,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 +316,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,14 +335,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 10cbc346d..1b94b8358 100644 --- a/system/libraries/Email.php +++ b/system/libraries/Email.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * CodeIgniter Email Class @@ -23,79 +46,353 @@ * @package CodeIgniter * @subpackage Libraries * @category Libraries - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/email.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/email.html */ class CI_Email { - var $useragent = "CodeIgniter"; - var $mailpath = "/usr/sbin/sendmail"; // Sendmail path - var $protocol = "mail"; // mail/sendmail/smtp - var $smtp_host = ""; // SMTP Server. Example: mail.earthlink.net - var $smtp_user = ""; // SMTP Username - var $smtp_pass = ""; // SMTP Password - var $smtp_port = "25"; // SMTP Port - var $smtp_timeout = 5; // SMTP Timeout in seconds - var $smtp_crypto = ""; // SMTP Encryption. Can be null, tls or ssl. - var $wordwrap = TRUE; // TRUE/FALSE Turns word-wrap on/off - var $wrapchars = "76"; // Number of characters to wrap at. - var $mailtype = "text"; // text/html Defines email formatting - var $charset = "utf-8"; // Default char set: iso-8859-1 or us-ascii - var $multipart = "mixed"; // "mixed" (in the body) or "related" (separate) - var $alt_message = ''; // Alternative message for HTML emails - var $validate = FALSE; // TRUE/FALSE. Enables email validation - var $priority = "3"; // Default priority (1 - 5) - var $newline = "\n"; // Default newline. "\r\n" or "\n" (Use "\r\n" to comply with RFC 822) - var $crlf = "\n"; // The RFC 2045 compliant CRLF for quoted-printable is "\r\n". Apparently some servers, - // even on the receiving end think they need to muck with CRLFs, so using "\n", while - // distasteful, is the only thing that seems to work for all environments. - var $send_multipart = TRUE; // TRUE/FALSE - Yahoo does not like multipart alternative, so this is an override. Set to FALSE for Yahoo. - var $bcc_batch_mode = FALSE; // TRUE/FALSE Turns on/off Bcc batch feature - var $bcc_batch_size = 200; // If bcc_batch_mode = TRUE, sets max number of Bccs in each batch - var $_safe_mode = FALSE; - var $_subject = ""; - var $_body = ""; - var $_finalbody = ""; - var $_alt_boundary = ""; - var $_atc_boundary = ""; - var $_header_str = ""; - var $_smtp_connect = ""; - var $_encoding = "8bit"; - var $_IP = FALSE; - var $_smtp_auth = FALSE; - var $_replyto_flag = FALSE; - var $_debug_msg = array(); - var $_recipients = array(); - var $_cc_array = array(); - var $_bcc_array = array(); - var $_headers = array(); - var $_attach_name = array(); - var $_attach_type = array(); - var $_attach_disp = array(); - var $_protocols = array('mail', 'sendmail', 'smtp'); - var $_base_charsets = array('us-ascii', 'iso-2022-'); // 7-bit charsets (excluding language suffix) - var $_bit_depths = array('7bit', '8bit'); - var $_priorities = array('1 (Highest)', '2 (High)', '3 (Normal)', '4 (Low)', '5 (Lowest)'); + /** + * Used as the User-Agent and X-Mailer headers' value. + * + * @var string + */ + public $useragent = 'CodeIgniter'; + + /** + * Path to the Sendmail binary. + * + * @var string + */ + public $mailpath = '/usr/sbin/sendmail'; // Sendmail path + + /** + * Which method to use for sending e-mails. + * + * @var string 'mail', 'sendmail' or 'smtp' + */ + public $protocol = 'mail'; // mail/sendmail/smtp + + /** + * STMP Server host + * + * @var string + */ + public $smtp_host = ''; + + /** + * SMTP Username + * + * @var string + */ + public $smtp_user = ''; + + /** + * SMTP Password + * + * @var string + */ + public $smtp_pass = ''; + + /** + * SMTP Server port + * + * @var int + */ + public $smtp_port = 25; + + /** + * SMTP connection timeout in seconds + * + * @var int + */ + public $smtp_timeout = 5; + + /** + * SMTP persistent connection + * + * @var bool + */ + public $smtp_keepalive = FALSE; + + /** + * SMTP Encryption + * + * @var string empty, 'tls' or 'ssl' + */ + public $smtp_crypto = ''; + + /** + * Whether to apply word-wrapping to the message body. + * + * @var bool + */ + public $wordwrap = TRUE; + + /** + * Number of characters to wrap at. + * + * @see CI_Email::$wordwrap + * @var int + */ + public $wrapchars = 76; + + /** + * Message format. + * + * @var string 'text' or 'html' + */ + public $mailtype = 'text'; + + /** + * Character set (default: utf-8) + * + * @var string + */ + public $charset = 'utf-8'; + + /** + * Alternative message (for HTML messages only) + * + * @var string + */ + public $alt_message = ''; + + /** + * Whether to validate e-mail addresses. + * + * @var bool + */ + public $validate = TRUE; + + /** + * X-Priority header value. + * + * @var int 1-5 + */ + public $priority = 3; // Default priority (1 - 5) + + /** + * Newline character sequence. + * Use "\r\n" to comply with RFC 822. + * + * @link https://www.ietf.org/rfc/rfc822.txt + * @var string "\r\n" or "\n" + */ + public $newline = "\n"; // Default newline. "\r\n" or "\n" (Use "\r\n" to comply with RFC 822) + + /** + * CRLF character sequence + * + * RFC 2045 specifies that for 'quoted-printable' encoding, + * "\r\n" must be used. However, it appears that some servers + * (even on the receiving end) don't handle it properly and + * switching to "\n", while improper, is the only solution + * that seems to work for all environments. + * + * @link https://www.ietf.org/rfc/rfc822.txt + * @var string + */ + public $crlf = "\n"; + + /** + * Whether to use Delivery Status Notification. + * + * @var bool + */ + public $dsn = FALSE; + + /** + * Whether to send multipart alternatives. + * Yahoo! doesn't seem to like these. + * + * @var bool + */ + public $send_multipart = TRUE; + + /** + * Whether to send messages to BCC recipients in batches. + * + * @var bool + */ + public $bcc_batch_mode = FALSE; + + /** + * BCC Batch max number size. + * + * @see CI_Email::$bcc_batch_mode + * @var int + */ + public $bcc_batch_size = 200; + + // -------------------------------------------------------------------- + + /** + * Subject header + * + * @var string + */ + protected $_subject = ''; + + /** + * Message body + * + * @var string + */ + protected $_body = ''; + + /** + * Final message body to be sent. + * + * @var string + */ + protected $_finalbody = ''; + + /** + * Final headers to send + * + * @var string + */ + protected $_header_str = ''; + + /** + * SMTP Connection socket placeholder + * + * @var resource + */ + protected $_smtp_connect = ''; + + /** + * Mail encoding + * + * @var string '8bit' or '7bit' + */ + protected $_encoding = '8bit'; + + /** + * Whether to perform SMTP authentication + * + * @var bool + */ + protected $_smtp_auth = FALSE; + + /** + * Whether to send a Reply-To header + * + * @var bool + */ + protected $_replyto_flag = FALSE; + + /** + * Debug messages + * + * @see CI_Email::print_debugger() + * @var string + */ + protected $_debug_msg = array(); + + /** + * Recipients + * + * @var string[] + */ + protected $_recipients = array(); + + /** + * CC Recipients + * + * @var string[] + */ + protected $_cc_array = array(); + + /** + * BCC Recipients + * + * @var string[] + */ + protected $_bcc_array = array(); + + /** + * Message headers + * + * @var string[] + */ + protected $_headers = array(); + + /** + * Attachment data + * + * @var array + */ + protected $_attachments = array(); + + /** + * Valid $protocol values + * + * @see CI_Email::$protocol + * @var string[] + */ + protected $_protocols = array('mail', 'sendmail', 'smtp'); + + /** + * Base charsets + * + * Character sets valid for 7-bit encoding, + * excluding language suffix. + * + * @var string[] + */ + protected $_base_charsets = array('us-ascii', 'iso-2022-'); + + /** + * Bit depths + * + * Valid mail encodings + * + * @see CI_Email::$_encoding + * @var string[] + */ + protected $_bit_depths = array('7bit', '8bit'); + /** + * $priority translations + * + * Actual values to send with the X-Priority header + * + * @var string[] + */ + protected $_priorities = array( + 1 => '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); + + isset(self::$func_overload) OR self::$func_overload = ( ! is_php('8.0') && extension_loaded('mbstring') && @ini_get('mbstring.func_overload')); - log_message('debug', "Email Class Initialized"); + log_message('info', 'Email Class Initialized'); } // -------------------------------------------------------------------- @@ -103,12 +400,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)) @@ -125,10 +423,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; } @@ -138,30 +435,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; @@ -172,25 +466,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)) @@ -200,12 +498,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; } @@ -215,16 +515,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) @@ -232,17 +531,21 @@ class CI_Email { $this->validate_email($this->_str_to_array($replyto)); } - if ($name == '') - { - $name = $replyto; - } - - if (strncmp($name, '"', 1) != 0) + if ($name !== '') { - $name = '"'.$name.'"'; + // 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); + } } - $this->_set_header('Reply-To', $name.' <'.$replyto.'>'); + $this->set_header('Reply-To', $name.' <'.$replyto.'>'); $this->_replyto_flag = TRUE; return $this; @@ -253,9 +556,8 @@ class CI_Email { /** * Set Recipients * - * @access public * @param string - * @return void + * @return CI_Email */ public function to($to) { @@ -267,21 +569,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; } @@ -291,23 +584,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; } @@ -320,34 +611,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; @@ -358,14 +647,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; } @@ -374,43 +662,85 @@ class CI_Email { /** * Set Body * - * @access public * @param string - * @return void + * @return CI_Email */ public function message($body) { - $this->_body = rtrim(str_replace("\r", "", $body)); + $this->_body = rtrim(str_replace("\r", '', $body)); + return $this; + } - /* 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()) + /** + * Assign file attachments + * + * @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($file, $disposition = '', $newname = NULL, $mime = '') + { + if ($mime === '') { - $this->_body = stripslashes($this->_body); + 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; } // -------------------------------------------------------------------- /** - * Assign file attachments + * Set and return attachment Content-ID * - * @access public - * @param string - * @return void + * Useful for attached inline pictures + * + * @param string $filename + * @return string */ - public function attach($filename, $disposition = 'attachment') + public function attachment_cid($filename) { - $this->_attach_name[] = $filename; - $this->_attach_type[] = $this->_mime_types(pathinfo($filename, PATHINFO_EXTENSION)); - $this->_attach_disp[] = $disposition; // Can also be 'inline' Not sure if it matters - return $this; + 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; } // -------------------------------------------------------------------- @@ -418,14 +748,14 @@ class CI_Email { /** * Add a Header Item * - * @access protected * @param string * @param string - * @return void + * @return CI_Email */ - protected function _set_header($header, $value) + public function set_header($header, $value) { - $this->_headers[$header] = $value; + $this->_headers[$header] = str_replace(array("\n", "\r"), '', $value); + return $this; } // -------------------------------------------------------------------- @@ -433,7 +763,6 @@ class CI_Email { /** * Convert a String to an Array * - * @access protected * @param string * @return array */ @@ -441,16 +770,11 @@ class CI_Email { { if ( ! is_array($email)) { - if (strpos($email, ',') !== FALSE) - { - $email = preg_split('/[\s,]/', $email, -1, PREG_SPLIT_NO_EMPTY); - } - else - { - $email = trim($email); - settype($email, "array"); - } + return (strpos($email, ',') !== FALSE) + ? preg_split('/[\s,]/', $email, -1, PREG_SPLIT_NO_EMPTY) + : (array) trim($email); } + return $email; } @@ -459,13 +783,12 @@ class CI_Email { /** * Set Multipart Value * - * @access public * @param string - * @return void + * @return CI_Email */ - public function set_alt_message($str = '') + public function set_alt_message($str) { - $this->alt_message = $str; + $this->alt_message = (string) $str; return $this; } @@ -474,13 +797,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; } @@ -489,13 +811,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; } @@ -504,13 +825,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; } @@ -519,25 +839,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; } @@ -546,20 +853,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; } @@ -568,52 +867,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 protected - * @return void - */ - protected function _set_boundaries() - { - $this->_alt_boundary = "B_ALT_".uniqid(''); // multipart/alternative - $this->_atc_boundary = "B_ATC_".uniqid(''); // attachment boundary - } - - // -------------------------------------------------------------------- - - /** * Get the Message ID * - * @access protected * @return string */ protected function _get_message_id() { - $from = $this->_headers['Return-Path']; - $from = str_replace(">", "", $from); - $from = str_replace("<", "", $from); - - return "<".uniqid('').strstr($from, '@').">"; + $from = str_replace(array('>', '<'), '', $this->_headers['Return-Path']); + return '<'.uniqid('').strstr($from, '@').'>'; } // -------------------------------------------------------------------- @@ -621,19 +894,13 @@ class CI_Email { /** * Get Mail Protocol * - * @access protected - * @param bool - * @return string + * @return mixed */ - protected 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; } // -------------------------------------------------------------------- @@ -641,26 +908,21 @@ class CI_Email { /** * Get Mail Encoding * - * @access protected - * @param bool * @return string */ - protected 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; } // -------------------------------------------------------------------- @@ -668,27 +930,20 @@ class CI_Email { /** * Get content type (text/html/attachment) * - * @access protected * @return string */ protected function _get_content_type() { - if ($this->mailtype == 'html' && count($this->_attach_name) == 0) + if ($this->mailtype === 'html') { - return 'html'; + return 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'; } - else - { - return 'plain'; - } + + return 'plain'; } // -------------------------------------------------------------------- @@ -696,17 +951,16 @@ class CI_Email { /** * Set RFC 822 Date * - * @access protected * @return string */ protected function _set_date() { - $timezone = date("Z"); - $operator = (strncmp($timezone, '-', 1) == 0) ? '-' : '+'; + $timezone = date('Z'); + $operator = ($timezone[0] === '-') ? '-' : '+'; $timezone = abs($timezone); - $timezone = floor($timezone/3600) * 100 + ($timezone % 3600 ) / 60; + $timezone = floor($timezone/3600) * 100 + ($timezone % 3600) / 60; - return sprintf("%s %s%04d", date("D, j M Y H:i:s"), $operator, $timezone); + return sprintf('%s %s%04d', date('D, j M Y H:i:s'), $operator, $timezone); } // -------------------------------------------------------------------- @@ -714,12 +968,11 @@ class CI_Email { /** * Mime message * - * @access protected * @return string */ protected function _get_mime_message() { - return "This is a multi-part message in MIME format.".$this->newline."Your email application may not support this format."; + return 'This is a multi-part message in MIME format.'.$this->newline.'Your email application may not support this format.'; } // -------------------------------------------------------------------- @@ -727,7 +980,6 @@ class CI_Email { /** * Validate Email Address * - * @access public * @param string * @return bool */ @@ -756,13 +1008,24 @@ 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') && preg_match('#\A([^@]+)@(.+)\z#', $email, $matches)) + { + $domain = defined('INTL_IDNA_VARIANT_UTS46') + ? idn_to_ascii($matches[2], 0, INTL_IDNA_VARIANT_UTS46) + : idn_to_ascii($matches[2]); + + if ($domain !== FALSE) + { + $email = $matches[1].'@'.$domain; + } + } + + return (bool) filter_var($email, FILTER_VALIDATE_EMAIL); } // -------------------------------------------------------------------- @@ -770,7 +1033,6 @@ class CI_Email { /** * Clean Extended Email Address: Joe Smith <joe@smith.com> * - * @access public * @param string * @return string */ @@ -778,28 +1040,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; @@ -810,47 +1058,36 @@ class CI_Email { /** * Build alternative plain text message * - * This public function provides the raw message for use - * in plain-text headers of HTML-formatted emails. + * Provides the raw message for use in plain-text headers of + * HTML-formatted emails. * If the user hasn't specified his own alternative message * it creates one by stripping the HTML * - * @access protected * @return string */ protected function _get_alt_message() { - if ($this->alt_message != "") - { - return $this->word_wrap($this->alt_message, '76'); - } - - if (preg_match('/\<body.*?\>(.*)\<\/body\>/si', $this->_body, $match)) + if ( ! empty($this->alt_message)) { - $body = $match['1']; - } - else - { - $body = $this->_body; + return ($this->wordwrap) + ? $this->word_wrap($this->alt_message, 76) + : $this->alt_message; } - $body = trim(strip_tags($body)); - $body = preg_replace( '#<!--(.*)--\>#', "", $body); - $body = str_replace("\t", "", $body); + $body = preg_match('/\<body.*?\>(.*)\<\/body\>/si', $this->_body, $match) ? $match[1] : $this->_body; + $body = str_replace("\t", '', preg_replace('#<!--(.*)--\>#', '', trim(strip_tags($body)))); for ($i = 20; $i >= 3; $i--) { - $n = ""; - - for ($x = 1; $x <= $i; $x ++) - { - $n .= "\n"; - } - - $body = str_replace($n, "\n\n", $body); + $body = str_replace(str_repeat("\n", $i), "\n\n", $body); } - return $this->word_wrap($body, '76'); + // Reduce multiple spaces + $body = preg_replace('| +|', ' ', $body); + + return ($this->wordwrap) + ? $this->word_wrap($body, 76) + : $body; } // -------------------------------------------------------------------- @@ -858,83 +1095,79 @@ class CI_Email { /** * Word Wrap * - * @access public * @param string - * @param integer + * @param int line-length limit * @return string */ - public function word_wrap($str, $charlim = '') + public function word_wrap($str, $charlim = NULL) { - // Se the character limit - if ($charlim == '') + // Set the character limit, if not already present + if (empty($charlim)) { - $charlim = ($this->wrapchars == "") ? "76" : $this->wrapchars; + $charlim = empty($this->wrapchars) ? 76 : $this->wrapchars; } - // Reduce multiple spaces - $str = preg_replace("| +|", " ", $str); - // Standardize newlines if (strpos($str, "\r") !== FALSE) { $str = str_replace(array("\r\n", "\r"), "\n", $str); } + // Reduce multiple spaces at end of line + $str = preg_replace('| +\n|', "\n", $str); + // 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 public function to do the initial wordwrap. + // 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 (self::strlen($line) <= $charlim) { $output .= $line.$this->newline; continue; } $temp = ''; - while ((strlen($line)) > $charlim) + do { // 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 .= self::substr($line, 0, $charlim - 1); + $line = self::substr($line, $charlim - 1); } + while (self::strlen($line) > $charlim); // 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 != '') - { - $output .= $temp.$this->newline.$line; - } - else + if ($temp !== '') { - $output .= $line; + $output .= $temp.$this->newline; } - $output .= $this->newline; + $output .= $line.$this->newline; } // Put our markers back @@ -942,7 +1175,7 @@ class CI_Email { { foreach ($unwrap as $key => $val) { - $output = str_replace("{{unwrapped".$key."}}", $val, $output); + $output = str_replace('{{unwrapped'.$key.'}}', $val, $output); } } @@ -954,17 +1187,16 @@ class CI_Email { /** * Build final headers * - * @access protected - * @param string - * @return string + * @return void */ protected function _build_headers() { - $this->_set_header('X-Sender', $this->clean_email($this->_headers['From'])); - $this->_set_header('X-Mailer', $this->useragent); - $this->_set_header('X-Priority', $this->_priorities[$this->priority - 1]); - $this->_set_header('Message-ID', $this->_get_message_id()); - $this->_set_header('Mime-Version', '1.0'); + $this->set_header('User-Agent', $this->useragent); + $this->set_header('X-Sender', $this->clean_email($this->_headers['From'])); + $this->set_header('X-Mailer', $this->useragent); + $this->set_header('X-Priority', $this->_priorities[$this->priority]); + $this->set_header('Message-ID', $this->_get_message_id()); + $this->set_header('Mime-Version', '1.0'); } // -------------------------------------------------------------------- @@ -972,31 +1204,33 @@ class CI_Email { /** * Write Headers as a string * - * @access protected * @return void */ protected function _write_headers() { - if ($this->protocol == 'mail') + if ($this->protocol === 'mail') { - $this->_subject = $this->_headers['Subject']; - unset($this->_headers['Subject']); + if (isset($this->_headers['Subject'])) + { + $this->_subject = $this->_headers['Subject']; + unset($this->_headers['Subject']); + } } reset($this->_headers); - $this->_header_str = ""; + $this->_header_str = ''; foreach ($this->_headers as $key => $val) { $val = trim($val); - if ($val != "") + if ($val !== '') { - $this->_header_str .= $key.": ".$val.$this->newline; + $this->_header_str .= $key.': '.$val.$this->newline; } } - if ($this->_get_protocol() == 'mail') + if ($this->_get_protocol() === 'mail') { $this->_header_str = rtrim($this->_header_str); } @@ -1007,179 +1241,225 @@ class CI_Email { /** * Build Final Body and attachments * - * @access protected * @return void */ protected function _build_message() { - if ($this->wordwrap === TRUE AND $this->mailtype != 'html') + if ($this->wordwrap === TRUE && $this->mailtype !== 'html') { $this->_body = $this->word_wrap($this->_body); } - $this->_set_boundaries(); $this->_write_headers(); - $hdr = ($this->_get_protocol() == 'mail') ? $this->newline : ''; + $hdr = ($this->_get_protocol() === 'mail') ? $this->newline : ''; $body = ''; switch ($this->_get_content_type()) { - case 'plain' : + case 'plain': - $hdr .= "Content-Type: text/plain; charset=" . $this->charset . $this->newline; - $hdr .= "Content-Transfer-Encoding: " . $this->_get_encoding(); + $hdr .= 'Content-Type: text/plain; charset='.$this->charset.$this->newline + .'Content-Transfer-Encoding: '.$this->_get_encoding(); - if ($this->_get_protocol() == 'mail') + if ($this->_get_protocol() === 'mail') { - $this->_header_str .= rtrim($hdr); + $this->_header_str .= $hdr; $this->_finalbody = $this->_body; } else { - $this->_finalbody = $hdr . $this->newline . $this->newline . $this->_body; + $this->_finalbody = $hdr.$this->newline.$this->newline.$this->_body; } return; - break; - case 'html' : + case 'html': if ($this->send_multipart === FALSE) { - $hdr .= "Content-Type: text/html; charset=" . $this->charset . $this->newline; - $hdr .= "Content-Transfer-Encoding: quoted-printable"; + $hdr .= 'Content-Type: text/html; charset='.$this->charset.$this->newline + .'Content-Transfer-Encoding: quoted-printable'; } else { - $hdr .= "Content-Type: multipart/alternative; boundary=\"" . $this->_alt_boundary . "\"" . $this->newline . $this->newline; + $boundary = uniqid('B_ALT_'); + $hdr .= 'Content-Type: multipart/alternative; boundary="'.$boundary.'"'; - $body .= $this->_get_mime_message() . $this->newline . $this->newline; - $body .= "--" . $this->_alt_boundary . $this->newline; + $body .= $this->_get_mime_message().$this->newline.$this->newline + .'--'.$boundary.$this->newline - $body .= "Content-Type: text/plain; charset=" . $this->charset . $this->newline; - $body .= "Content-Transfer-Encoding: " . $this->_get_encoding() . $this->newline . $this->newline; - $body .= $this->_get_alt_message() . $this->newline . $this->newline . "--" . $this->_alt_boundary . $this->newline; + .'Content-Type: text/plain; charset='.$this->charset.$this->newline + .'Content-Transfer-Encoding: '.$this->_get_encoding().$this->newline.$this->newline + .$this->_get_alt_message().$this->newline.$this->newline + .'--'.$boundary.$this->newline - $body .= "Content-Type: text/html; charset=" . $this->charset . $this->newline; - $body .= "Content-Transfer-Encoding: quoted-printable" . $this->newline . $this->newline; + .'Content-Type: text/html; charset='.$this->charset.$this->newline + .'Content-Transfer-Encoding: quoted-printable'.$this->newline.$this->newline; } - $this->_finalbody = $body . $this->_prep_quoted_printable($this->_body) . $this->newline . $this->newline; + $this->_finalbody = $body.$this->_prep_quoted_printable($this->_body).$this->newline.$this->newline; - - if ($this->_get_protocol() == 'mail') + if ($this->_get_protocol() === 'mail') { - $this->_header_str .= rtrim($hdr); + $this->_header_str .= $hdr; } else { - $this->_finalbody = $hdr . $this->_finalbody; + $this->_finalbody = $hdr.$this->newline.$this->newline.$this->_finalbody; } - if ($this->send_multipart !== FALSE) { - $this->_finalbody .= "--" . $this->_alt_boundary . "--"; + $this->_finalbody .= '--'.$boundary.'--'; } return; - break; - case 'plain-attach' : + case 'plain-attach': - $hdr .= "Content-Type: multipart/".$this->multipart."; boundary=\"" . $this->_atc_boundary."\"" . $this->newline . $this->newline; + $boundary = uniqid('B_ATC_'); + $hdr .= 'Content-Type: multipart/mixed; boundary="'.$boundary.'"'; - if ($this->_get_protocol() == 'mail') + if ($this->_get_protocol() === 'mail') { - $this->_header_str .= rtrim($hdr); + $this->_header_str .= $hdr; } - $body .= $this->_get_mime_message() . $this->newline . $this->newline; - $body .= "--" . $this->_atc_boundary . $this->newline; + $body .= $this->_get_mime_message().$this->newline + .$this->newline + .'--'.$boundary.$this->newline + .'Content-Type: text/plain; charset='.$this->charset.$this->newline + .'Content-Transfer-Encoding: '.$this->_get_encoding().$this->newline + .$this->newline + .$this->_body.$this->newline.$this->newline; + + $this->_append_attachments($body, $boundary); + + break; + case 'html-attach': + + $alt_boundary = uniqid('B_ALT_'); + $last_boundary = NULL; - $body .= "Content-Type: text/plain; charset=" . $this->charset . $this->newline; - $body .= "Content-Transfer-Encoding: " . $this->_get_encoding() . $this->newline . $this->newline; + if ($this->_attachments_have_multipart('mixed')) + { + $atc_boundary = uniqid('B_ATC_'); + $hdr .= 'Content-Type: multipart/mixed; boundary="'.$atc_boundary.'"'; + $last_boundary = $atc_boundary; + } - $body .= $this->_body . $this->newline . $this->newline; + if ($this->_attachments_have_multipart('related')) + { + $rel_boundary = uniqid('B_REL_'); + $rel_boundary_header = 'Content-Type: multipart/related; boundary="'.$rel_boundary.'"'; - break; - case 'html-attach' : + if (isset($last_boundary)) + { + $body .= '--'.$last_boundary.$this->newline.$rel_boundary_header; + } + else + { + $hdr .= $rel_boundary_header; + } - $hdr .= "Content-Type: multipart/".$this->multipart."; boundary=\"" . $this->_atc_boundary."\"" . $this->newline . $this->newline; + $last_boundary = $rel_boundary; + } - if ($this->_get_protocol() == 'mail') + if ($this->_get_protocol() === 'mail') { - $this->_header_str .= rtrim($hdr); + $this->_header_str .= $hdr; } - $body .= $this->_get_mime_message() . $this->newline . $this->newline; - $body .= "--" . $this->_atc_boundary . $this->newline; + self::strlen($body) && $body .= $this->newline.$this->newline; + $body .= $this->_get_mime_message().$this->newline.$this->newline + .'--'.$last_boundary.$this->newline - $body .= "Content-Type: multipart/alternative; boundary=\"" . $this->_alt_boundary . "\"" . $this->newline .$this->newline; - $body .= "--" . $this->_alt_boundary . $this->newline; + .'Content-Type: multipart/alternative; boundary="'.$alt_boundary.'"'.$this->newline.$this->newline + .'--'.$alt_boundary.$this->newline - $body .= "Content-Type: text/plain; charset=" . $this->charset . $this->newline; - $body .= "Content-Transfer-Encoding: " . $this->_get_encoding() . $this->newline . $this->newline; - $body .= $this->_get_alt_message() . $this->newline . $this->newline . "--" . $this->_alt_boundary . $this->newline; + .'Content-Type: text/plain; charset='.$this->charset.$this->newline + .'Content-Transfer-Encoding: '.$this->_get_encoding().$this->newline.$this->newline + .$this->_get_alt_message().$this->newline.$this->newline + .'--'.$alt_boundary.$this->newline - $body .= "Content-Type: text/html; charset=" . $this->charset . $this->newline; - $body .= "Content-Transfer-Encoding: quoted-printable" . $this->newline . $this->newline; + .'Content-Type: text/html; charset='.$this->charset.$this->newline + .'Content-Transfer-Encoding: quoted-printable'.$this->newline.$this->newline - $body .= $this->_prep_quoted_printable($this->_body) . $this->newline . $this->newline; - $body .= "--" . $this->_alt_boundary . "--" . $this->newline . $this->newline; + .$this->_prep_quoted_printable($this->_body).$this->newline.$this->newline + .'--'.$alt_boundary.'--'.$this->newline.$this->newline; - break; + if ( ! empty($rel_boundary)) + { + $body .= $this->newline.$this->newline; + $this->_append_attachments($body, $rel_boundary, 'related'); + } + + // multipart/mixed attachments + if ( ! empty($atc_boundary)) + { + $body .= $this->newline.$this->newline; + $this->_append_attachments($body, $atc_boundary, 'mixed'); + } + + break; } - $attachment = array(); + $this->_finalbody = ($this->_get_protocol() === 'mail') + ? $body + : $hdr.$this->newline.$this->newline.$body; + } - $z = 0; + // -------------------------------------------------------------------- - for ($i=0; $i < count($this->_attach_name); $i++) + protected function _attachments_have_multipart($type) + { + foreach ($this->_attachments as &$attachment) { - $filename = $this->_attach_name[$i]; - $basename = basename($filename); - $ctype = $this->_attach_type[$i]; - - if ( ! file_exists($filename)) + if ($attachment['multipart'] === $type) { - $this->_set_error_message('lang:email_attachment_missing', $filename); - return FALSE; + return TRUE; } + } - $h = "--".$this->_atc_boundary.$this->newline; - $h .= "Content-type: ".$ctype."; "; - $h .= "name=\"".$basename."\"".$this->newline; - $h .= "Content-Disposition: ".$this->_attach_disp[$i].";".$this->newline; - $h .= "Content-Transfer-Encoding: base64".$this->newline; + return FALSE; + } - $attachment[$z++] = $h; - $file = filesize($filename) +1; + // -------------------------------------------------------------------- - if ( ! $fp = fopen($filename, FOPEN_READ)) + /** + * Prepares attachment string + * + * @param string $body Message body to append to + * @param string $boundary Multipart boundary + * @param string $multipart When provided, only attachments of this type will be processed + * @return string + */ + protected function _append_attachments(&$body, $boundary, $multipart = null) + { + for ($i = 0, $c = count($this->_attachments); $i < $c; $i++) + { + if (isset($multipart) && $this->_attachments[$i]['multipart'] !== $multipart) { - $this->_set_error_message('lang:email_attachment_unreadable', $filename); - return FALSE; + continue; } - $attachment[$z++] = chunk_split(base64_encode(fread($fp, $file))); - fclose($fp); - } - - $body .= implode($this->newline, $attachment).$this->newline."--".$this->_atc_boundary."--"; + $name = isset($this->_attachments[$i]['name'][1]) + ? $this->_attachments[$i]['name'][1] + : basename($this->_attachments[$i]['name'][0]); - - if ($this->_get_protocol() == 'mail') - { - $this->_finalbody = $body; - } - else - { - $this->_finalbody = $hdr . $body; + $body .= '--'.$boundary.$this->newline + .'Content-Type: '.$this->_attachments[$i]['type'].'; name="'.$name.'"'.$this->newline + .'Content-Disposition: '.$this->_attachments[$i]['disposition'].';'.$this->newline + .'Content-Transfer-Encoding: base64'.$this->newline + .(empty($this->_attachments[$i]['cid']) ? '' : 'Content-ID: <'.$this->_attachments[$i]['cid'].'>'.$this->newline) + .$this->newline + .$this->_attachments[$i]['content'].$this->newline; } - return; + // $name won't be set if no attachments were appended, + // and therefore a boundary wouldn't be necessary + empty($name) OR $body .= '--'.$boundary.'--'; } // -------------------------------------------------------------------- @@ -1188,28 +1468,42 @@ class CI_Email { * Prep Quoted Printable * * Prepares string for Quoted-Printable Content-Transfer-Encoding - * Refer to RFC 2045 http://www.ietf.org/rfc/rfc2045.txt + * Refer to RFC 2045 https://www.ietf.org/rfc/rfc2045.txt * - * @access protected * @param string - * @param integer * @return string */ - protected function _prep_quoted_printable($str, $charlim = '') + protected function _prep_quoted_printable($str) { - // Set the character limit - // Don't allow over 76, as that will make servers and MUAs barf - // all over quoted-printable data - if ($charlim == '' OR $charlim > '76') + // ASCII code numbers for "safe" characters that can always be + // used literally, without encoding, as described in RFC 2049. + // https://www.ietf.org/rfc/rfc2049.txt + static $ascii_safe_chars = array( + // ' ( ) + , - . / : = ? + 39, 40, 41, 43, 44, 45, 46, 47, 58, 61, 63, + // numbers + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + // upper-case letters + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + // lower-case letters + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 + ); + + // We are intentionally wrapping so mail servers will encode characters + // properly and MUAs will behave, so {unwrap} must go! + $str = str_replace(array('{unwrap}', '{/unwrap}'), '', $str); + + // RFC 2045 specifies CRLF as "\r\n". + // However, many developers choose to override that and violate + // the RFC rules due to (apparently) a bug in MS Exchange, + // which only works with "\n". + if ($this->crlf === "\r\n") { - $charlim = '76'; + return quoted_printable_encode($str); } - // Reduce multiple spaces - $str = preg_replace("| +|", " ", $str); - - // kill nulls - $str = preg_replace('/\x00+/', '', $str); + // Reduce multiple spaces & remove nulls + $str = preg_replace(array('| +|', '/\x00+/'), array(' ', ''), $str); // Standardize newlines if (strpos($str, "\r") !== FALSE) @@ -1217,19 +1511,12 @@ class CI_Email { $str = str_replace(array("\r\n", "\r"), "\n", $str); } - // We are intentionally wrapping so mail servers will encode characters - // properly and MUAs will behave, so {unwrap} must go! - $str = str_replace(array('{unwrap}', '{/unwrap}'), '', $str); - - // Break into an array of lines - $lines = explode("\n", $str); - $escape = '='; $output = ''; - foreach ($lines as $line) + foreach (explode("\n", $str) as $line) { - $length = strlen($line); + $length = self::strlen($line); $temp = ''; // Loop through each character in the line to add soft-wrap @@ -1238,24 +1525,33 @@ class CI_Email { for ($i = 0; $i < $length; $i++) { // Grab the next character - $char = substr($line, $i, 1); + $char = $line[$i]; $ascii = ord($char); // Convert spaces and tabs but only if it's the end of the line - if ($i == ($length - 1)) + if ($ascii === 32 OR $ascii === 9) { - $char = ($ascii == '32' OR $ascii == '9') ? $escape.sprintf('%02s', dechex($ascii)) : $char; + if ($i === ($length - 1)) + { + $char = $escape.sprintf('%02s', dechex($ascii)); + } } - - // encode = signs - if ($ascii == '61') + // DO NOT move this below the $ascii_safe_chars line! + // + // = (equals) signs are allowed by RFC2049, but must be encoded + // as they are the encoding delimiter! + elseif ($ascii === 61) { $char = $escape.strtoupper(sprintf('%02s', dechex($ascii))); // =3D } + elseif ( ! in_array($ascii, $ascii_safe_chars, TRUE)) + { + $char = $escape.strtoupper(sprintf('%02s', dechex($ascii))); + } // If we're at the character limit, add the line to the output, // reset our temp variable, and keep on chuggin' - if ((strlen($temp) + strlen($char)) >= $charlim) + if ((self::strlen($temp) + self::strlen($char)) >= 76) { $output .= $temp.$escape.$this->crlf; $temp = ''; @@ -1270,9 +1566,7 @@ class CI_Email { } // get rid of extra CRLF tacked onto the end - $output = substr($output, 0, strlen($this->crlf) * -1); - - return $output; + return self::substr($output, 0, self::strlen($this->crlf) * -1); } // -------------------------------------------------------------------- @@ -1280,71 +1574,78 @@ class CI_Email { /** * Prep Q Encoding * - * Performs "Q Encoding" on a string for use in email headers. It's related - * but not identical to quoted-printable, so it has its own method + * Performs "Q Encoding" on a string for use in email headers. + * It's related but not identical to quoted-printable, so it has its + * own method. * - * @access public - * @param str - * @param bool // set to TRUE for processing From: headers - * @return str + * @param string + * @return string */ - protected function _prep_q_encoding($str, $from = FALSE) + protected function _prep_q_encoding($str) { - $str = str_replace(array("\r", "\n"), array('', ''), $str); - - // Line length must not exceed 76 characters, so we adjust for - // a space, 7 extra characters =??Q??=, and the charset that we will add to each line - $limit = 75 - 7 - strlen($this->charset); + $str = str_replace(array("\r", "\n"), '', $str); - // these special characters must be converted too - $convert = array('_', '=', '?'); - - if ($from === TRUE) + if ($this->charset === 'UTF-8') { - $convert[] = ','; - $convert[] = ';'; + // Note: We used to have mb_encode_mimeheader() as the first choice + // here, but it turned out to be buggy and unreliable. DO NOT + // re-add it! -- Narf + if (ICONV_ENABLED === TRUE) + { + $output = @iconv_mime_encode('', $str, + array( + 'scheme' => 'Q', + 'line-length' => 76, + 'input-charset' => $this->charset, + 'output-charset' => $this->charset, + 'line-break-chars' => $this->crlf + ) + ); + + // There are reports that iconv_mime_encode() might fail and return FALSE + if ($output !== FALSE) + { + // iconv_mime_encode() will always put a header field name. + // We've passed it an empty one, but it still prepends our + // encoded string with ': ', so we need to strip it. + return self::substr($output, 2); + } + + $chars = iconv_strlen($str, 'UTF-8'); + } + elseif (MB_ENABLED === TRUE) + { + $chars = mb_strlen($str, 'UTF-8'); + } } - $output = ''; - $temp = ''; + // We might already have this set for UTF-8 + isset($chars) OR $chars = self::strlen($str); - for ($i = 0, $length = strlen($str); $i < $length; $i++) + $output = '=?'.$this->charset.'?Q?'; + for ($i = 0, $length = self::strlen($output); $i < $chars; $i++) { - // Grab the next character - $char = substr($str, $i, 1); - $ascii = ord($char); + $chr = ($this->charset === 'UTF-8' && ICONV_ENABLED === TRUE) + ? '='.implode('=', str_split(strtoupper(bin2hex(iconv_substr($str, $i, 1, $this->charset))), 2)) + : '='.strtoupper(bin2hex($str[$i])); - // convert ALL non-printable ASCII characters and our specials - if ($ascii < 32 OR $ascii > 126 OR in_array($char, $convert)) + // RFC 2045 sets a limit of 76 characters per line. + // We'll append ?= to the end of each line though. + if ($length + ($l = self::strlen($chr)) > 74) { - $char = '='.dechex($ascii); + $output .= '?='.$this->crlf // EOL + .' =?'.$this->charset.'?Q?'.$chr; // New line + $length = 6 + self::strlen($this->charset) + $l; // Reset the length for the new line } - - // handle regular spaces a bit more compactly than =20 - if ($ascii == 32) - { - $char = '_'; - } - - // If we're at the character limit, add the line to the output, - // reset our temp variable, and keep on chuggin' - if ((strlen($temp) + strlen($char)) >= $limit) + else { - $output .= $temp.$this->crlf; - $temp = ''; + $output .= $chr; + $length += $l; } - - // Add the character to our temporary line - $temp .= $char; } - $str = $output.$temp; - - // wrap each line with the shebang, charset, and transfer encoding - // the preceding space on successive lines is required for header "folding" - $str = trim(preg_replace('/^(.*)$/m', ' =?'.$this->charset.'?Q?$1?=', $str)); - - return $str; + // End the header + return $output.'?='; } // -------------------------------------------------------------------- @@ -1352,19 +1653,25 @@ class CI_Email { /** * Send Email * - * @access public + * @param bool $auto_clear = TRUE * @return bool */ - public function send() + public function send($auto_clear = TRUE) { - if ($this->_replyto_flag == FALSE) + if ( ! isset($this->_headers['From'])) + { + $this->_set_error_message('lang:email_no_from'); + return FALSE; + } + + if ($this->_replyto_flag === FALSE) { $this->reply_to($this->_headers['From']); } - if (( ! isset($this->_recipients) AND ! isset($this->_headers['To'])) AND - ( ! isset($this->_bcc_array) AND ! isset($this->_headers['Bcc'])) AND - ( ! isset($this->_headers['Cc']))) + if (empty($this->_recipients) && ! isset($this->_headers['To']) + && empty($this->_bcc_array) && ! isset($this->_headers['Bcc']) + && ! isset($this->_headers['Cc'])) { $this->_set_error_message('lang:email_no_recipients'); return FALSE; @@ -1372,71 +1679,71 @@ class CI_Email { $this->_build_headers(); - if ($this->bcc_batch_mode AND count($this->_bcc_array) > 0) + if ($this->bcc_batch_mode && count($this->_bcc_array) > $this->bcc_batch_size) { - if (count($this->_bcc_array) > $this->bcc_batch_size) - return $this->batch_bcc_send(); + $this->batch_bcc_send(); + + if ($auto_clear) + { + $this->clear(); + } + + return TRUE; } $this->_build_message(); + $result = $this->_spool_email(); - if ( ! $this->_spool_email()) + if ($result && $auto_clear) { - return FALSE; - } - else - { - return TRUE; + $this->clear(); } + + return $result; } // -------------------------------------------------------------------- /** - * Batch Bcc Send. Sends groups of BCCs in batches + * Batch Bcc Send. Sends groups of BCCs in batches * - * @access public - * @return bool + * @return void */ public function batch_bcc_send() { - $float = $this->bcc_batch_size -1; - - $set = ""; - + $float = $this->bcc_batch_size - 1; + $set = ''; $chunk = array(); - for ($i = 0; $i < count($this->_bcc_array); $i++) + for ($i = 0, $c = count($this->_bcc_array); $i < $c; $i++) { if (isset($this->_bcc_array[$i])) { - $set .= ", ".$this->_bcc_array[$i]; + $set .= ', '.$this->_bcc_array[$i]; } - if ($i == $float) + if ($i === $float) { - $chunk[] = substr($set, 1); - $float = $float + $this->bcc_batch_size; - $set = ""; + $chunk[] = self::substr($set, 1); + $float += $this->bcc_batch_size; + $set = ''; } - if ($i == count($this->_bcc_array)-1) + if ($i === $c-1) { - $chunk[] = substr($set, 1); + $chunk[] = self::substr($set, 1); } } - for ($i = 0; $i < count($chunk); $i++) + for ($i = 0, $c = count($chunk); $i < $c; $i++) { unset($this->_headers['Bcc']); - unset($bcc); - $bcc = $this->_str_to_array($chunk[$i]); - $bcc = $this->clean_email($bcc); + $bcc = $this->clean_email($this->_str_to_array($chunk[$i])); - if ($this->protocol != 'smtp') + if ($this->protocol !== 'smtp') { - $this->_set_header('Bcc', implode(", ", $bcc)); + $this->set_header('Bcc', implode(', ', $bcc)); } else { @@ -1453,12 +1760,11 @@ class CI_Email { /** * Unwrap special elements * - * @access protected * @return void */ protected function _unwrap_specials() { - $this->_finalbody = preg_replace_callback("/\{unwrap\}(.*?)\{\/unwrap\}/si", array($this, '_remove_nl_callback'), $this->_finalbody); + $this->_finalbody = preg_replace_callback('/\{unwrap\}(.*?)\{\/unwrap\}/si', array($this, '_remove_nl_callback'), $this->_finalbody); } // -------------------------------------------------------------------- @@ -1466,7 +1772,7 @@ class CI_Email { /** * Strip line-breaks via callback * - * @access protected + * @param string $matches * @return string */ protected function _remove_nl_callback($matches) @@ -1484,44 +1790,57 @@ class CI_Email { /** * Spool mail to the mail server * - * @access protected * @return bool */ protected function _spool_email() { $this->_unwrap_specials(); - switch ($this->_get_protocol()) + $protocol = $this->_get_protocol(); + $method = '_send_with_'.$protocol; + if ( ! $this->$method()) { - case 'mail' : + $this->_set_error_message('lang:email_send_failure_'.($protocol === 'mail' ? 'phpmail' : $protocol)); + return FALSE; + } - if ( ! $this->_send_with_mail()) - { - $this->_set_error_message('lang:email_send_failure_phpmail'); - return FALSE; - } - break; - case 'sendmail' : + $this->_set_error_message('lang:email_sent', $protocol); + return TRUE; + } - if ( ! $this->_send_with_sendmail()) - { - $this->_set_error_message('lang:email_send_failure_sendmail'); - return FALSE; - } - break; - case 'smtp' : + // -------------------------------------------------------------------- - if ( ! $this->_send_with_smtp()) - { - $this->_set_error_message('lang:email_send_failure_smtp'); - return FALSE; - } - break; + /** + * Validate email for shell + * + * Applies stricter, shell-safe validation to email addresses. + * Introduced to prevent RCE via sendmail's -f option. + * + * @see https://github.com/bcit-ci/CodeIgniter/issues/4963 + * @see https://gist.github.com/Zenexer/40d02da5e07f151adeaeeaa11af9ab36 + * @license https://creativecommons.org/publicdomain/zero/1.0/ CC0 1.0, Public Domain + * + * Credits for the base concept go to Paul Buonopane <paul@namepros.com> + * + * @param string $email + * @return bool + */ + protected function _validate_email_for_shell(&$email) + { + if (function_exists('idn_to_ascii') && $atpos = strpos($email, '@')) + { + list($account, $domain) = explode('@', $email, 2); + $domain = defined('INTL_IDNA_VARIANT_UTS46') + ? idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) + : idn_to_ascii($domain); + if ($domain !== FALSE) + { + $email = $account.'@'.$domain; + } } - $this->_set_error_message('lang:email_sent', $this->_get_protocol()); - return TRUE; + return (filter_var($email, FILTER_VALIDATE_EMAIL) === $email && preg_match('#\A[a-z0-9._+-]+@[a-z0-9.-]{1,253}\z#i', $email)); } // -------------------------------------------------------------------- @@ -1529,36 +1848,27 @@ class CI_Email { /** * Send using mail() * - * @access protected * @return bool */ protected function _send_with_mail() { - if ($this->_safe_mode == TRUE) + if (is_array($this->_recipients)) { - if ( ! mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str)) - { - return FALSE; - } - else - { - return TRUE; - } + $this->_recipients = implode(', ', $this->_recipients); } - else - { - // most documentation of sendmail using the "-f" flag lacks a space after it, however - // we've encountered servers that seem to require it to be in place. - if ( ! mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str, "-f ".$this->clean_email($this->_headers['From']))) - { - return FALSE; - } - else - { - return TRUE; - } + // _validate_email_for_shell() below accepts by reference, + // so this needs to be assigned to a variable + $from = $this->clean_email($this->_headers['Return-Path']); + + if ( ! $this->_validate_email_for_shell($from)) + { + return mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str); } + + // most documentation of sendmail using the "-f" flag lacks a space after it, however + // we've encountered servers that seem to require it to be in place. + return mail($this->_recipients, $this->_subject, $this->_finalbody, $this->_header_str, '-f '.$from); } // -------------------------------------------------------------------- @@ -1566,14 +1876,24 @@ class CI_Email { /** * Send using Sendmail * - * @access protected * @return bool */ protected function _send_with_sendmail() { - $fp = @popen($this->mailpath . " -oi -f ".$this->clean_email($this->_headers['From'])." -t", 'w'); + // _validate_email_for_shell() below accepts by reference, + // so this needs to be assigned to a variable + $from = $this->clean_email($this->_headers['From']); + if ($this->_validate_email_for_shell($from)) + { + $from = '-f '.$from; + } + else + { + $from = ''; + } - if ($fp === FALSE OR $fp === NULL) + // is popen() enabled? + if ( ! function_usable('popen') OR FALSE === ($fp = @popen($this->mailpath.' -oi '.$from.' -t', 'w'))) { // server probably has popen disabled, so nothing we can do to get a verbose error. return FALSE; @@ -1584,12 +1904,7 @@ class CI_Email { $status = pclose($fp); - if (version_compare(PHP_VERSION, '4.2.3') == -1) - { - $status = $status >> 8 & 0xFF; - } - - if ($status != 0) + if ($status !== 0) { $this->_set_error_message('lang:email_exit_status', $status); $this->_set_error_message('lang:email_no_socket'); @@ -1604,103 +1919,149 @@ class CI_Email { /** * Send using SMTP * - * @access protected * @return bool */ protected function _send_with_smtp() { - if ($this->smtp_host == '') + if ($this->smtp_host === '') { $this->_set_error_message('lang:email_no_hostname'); return FALSE; } - $this->_smtp_connect(); - $this->_smtp_authenticate(); + if ( ! $this->_smtp_connect() OR ! $this->_smtp_authenticate()) + { + return FALSE; + } - $this->_send_command('from', $this->clean_email($this->_headers['From'])); + if ( ! $this->_send_command('from', $this->clean_email($this->_headers['From']))) + { + $this->_smtp_end(); + return FALSE; + } foreach ($this->_recipients as $val) { - $this->_send_command('to', $val); + if ( ! $this->_send_command('to', $val)) + { + $this->_smtp_end(); + return FALSE; + } } - if (count($this->_cc_array) > 0) + foreach ($this->_cc_array as $val) { - foreach ($this->_cc_array as $val) + if ($val !== '' && ! $this->_send_command('to', $val)) { - if ($val != "") - { - $this->_send_command('to', $val); - } + $this->_smtp_end(); + return FALSE; } } - if (count($this->_bcc_array) > 0) + foreach ($this->_bcc_array as $val) { - foreach ($this->_bcc_array as $val) + if ($val !== '' && ! $this->_send_command('to', $val)) { - if ($val != "") - { - $this->_send_command('to', $val); - } + $this->_smtp_end(); + return FALSE; } } - $this->_send_command('data'); + if ( ! $this->_send_command('data')) + { + $this->_smtp_end(); + return FALSE; + } // perform dot transformation on any lines that begin with a dot - $this->_send_data($this->_header_str . preg_replace('/^\./m', '..$1', $this->_finalbody)); + $this->_send_data($this->_header_str.preg_replace('/^\./m', '..$1', $this->_finalbody)); $this->_send_data('.'); - $reply = $this->_get_smtp_data(); - $this->_set_error_message($reply); - if (strncmp($reply, '250', 3) != 0) + $this->_smtp_end(); + + if (strpos($reply, '250') !== 0) { $this->_set_error_message('lang:email_smtp_error', $reply); return FALSE; } - $this->_send_command('quit'); return TRUE; } // -------------------------------------------------------------------- /** + * SMTP End + * + * Shortcut to send RSET or QUIT depending on keep-alive + * + * @return void + */ + protected function _smtp_end() + { + $this->_send_command($this->smtp_keepalive ? 'reset' : 'quit'); + } + + // -------------------------------------------------------------------- + + /** * SMTP Connect * - * @access protected - * @param string * @return string */ protected function _smtp_connect() { - $ssl = NULL; - if ($this->smtp_crypto == 'ssl') - $ssl = 'ssl://'; - $this->_smtp_connect = fsockopen($ssl.$this->smtp_host, - $this->smtp_port, - $errno, - $errstr, - $this->smtp_timeout); + if (is_resource($this->_smtp_connect)) + { + return TRUE; + } + + $ssl = ($this->smtp_crypto === 'ssl') ? 'ssl://' : ''; + + $this->_smtp_connect = fsockopen( + $ssl.$this->smtp_host, + $this->smtp_port, + $errno, + $errstr, + $this->smtp_timeout + ); if ( ! is_resource($this->_smtp_connect)) { - $this->_set_error_message('lang:email_smtp_error', $errno." ".$errstr); + $this->_set_error_message('lang:email_smtp_error', $errno.' '.$errstr); return FALSE; } + stream_set_timeout($this->_smtp_connect, $this->smtp_timeout); $this->_set_error_message($this->_get_smtp_data()); - if ($this->smtp_crypto == 'tls') + if ($this->smtp_crypto === 'tls') { $this->_send_command('hello'); $this->_send_command('starttls'); - stream_socket_enable_crypto($this->_smtp_connect, TRUE, STREAM_CRYPTO_METHOD_TLS_CLIENT); + + /** + * STREAM_CRYPTO_METHOD_TLS_CLIENT is quite the mess ... + * + * - On PHP <5.6 it doesn't even mean TLS, but SSL 2.0, and there's no option to use actual TLS + * - On PHP 5.6.0-5.6.6, >=7.2 it means negotiation with any of TLS 1.0, 1.1, 1.2 + * - On PHP 5.6.7-7.1.* it means only TLS 1.0 + * + * We want the negotiation, so we'll force it below ... + */ + $method = is_php('5.6') + ? STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT + : STREAM_CRYPTO_METHOD_TLS_CLIENT; + $crypto = stream_socket_enable_crypto($this->_smtp_connect, TRUE, $method); + + if ($crypto !== TRUE) + { + $this->_set_error_message('lang:email_smtp_error', $this->_get_smtp_data()); + return FALSE; + } } return $this->_send_command('hello'); @@ -1711,67 +2072,70 @@ class CI_Email { /** * Send SMTP command * - * @access protected * @param string * @param string - * @return string + * @return bool */ protected function _send_command($cmd, $data = '') { switch ($cmd) { - case 'hello' : - - if ($this->_smtp_auth OR $this->_get_encoding() == '8bit') - $this->_send_data('EHLO '.$this->_get_hostname()); - else - $this->_send_data('HELO '.$this->_get_hostname()); - - $resp = 250; - break; - case 'starttls' : - - $this->_send_data('STARTTLS'); - - $resp = 220; - break; - case 'from' : - - $this->_send_data('MAIL FROM:<'.$data.'>'); - - $resp = 250; - break; - case 'to' : - - $this->_send_data('RCPT TO:<'.$data.'>'); - - $resp = 250; - break; - case 'data' : - - $this->_send_data('DATA'); - - $resp = 354; - break; - case 'quit' : - - $this->_send_data('QUIT'); + case 'hello': + if ($this->_smtp_auth OR $this->_get_encoding() === '8bit') + { + $this->_send_data('EHLO '.$this->_get_hostname()); + } + else + { + $this->_send_data('HELO '.$this->_get_hostname()); + } - $resp = 221; - break; + $resp = 250; + break; + case 'starttls': + $this->_send_data('STARTTLS'); + $resp = 220; + break; + case 'from': + $this->_send_data('MAIL FROM:<'.$data.'>'); + $resp = 250; + break; + case 'to': + if ($this->dsn) + { + $this->_send_data('RCPT TO:<'.$data.'> NOTIFY=SUCCESS,DELAY,FAILURE ORCPT=rfc822;'.$data); + } + else + { + $this->_send_data('RCPT TO:<'.$data.'>'); + } + $resp = 250; + break; + case 'data': + $this->_send_data('DATA'); + $resp = 354; + break; + case 'reset': + $this->_send_data('RSET'); + $resp = 250; + break; + case 'quit': + $this->_send_data('QUIT'); + $resp = 221; + break; } $reply = $this->_get_smtp_data(); - $this->_debug_msg[] = "<pre>".$cmd.": ".$reply."</pre>"; + $this->_debug_msg[] = '<pre>'.$cmd.': '.$reply.'</pre>'; - if (substr($reply, 0, 3) != $resp) + if ((int) self::substr($reply, 0, 3) !== $resp) { $this->_set_error_message('lang:email_smtp_error', $reply); return FALSE; } - if ($cmd == 'quit') + if ($cmd === 'quit') { fclose($this->_smtp_connect); } @@ -1782,9 +2146,8 @@ class CI_Email { // -------------------------------------------------------------------- /** - * SMTP Authenticate + * SMTP Authenticate * - * @access protected * @return bool */ protected function _smtp_authenticate() @@ -1794,42 +2157,48 @@ class CI_Email { return TRUE; } - if ($this->smtp_user == "" AND $this->smtp_pass == "") + if ($this->smtp_user === '' && $this->smtp_pass === '') { $this->_set_error_message('lang:email_no_smtp_unpw'); return FALSE; } $this->_send_data('AUTH LOGIN'); - $reply = $this->_get_smtp_data(); - if (strncmp($reply, '334', 3) != 0) + if (strpos($reply, '503') === 0) // Already authenticated + { + return TRUE; + } + elseif (strpos($reply, '334') !== 0) { $this->_set_error_message('lang:email_failed_smtp_login', $reply); return FALSE; } $this->_send_data(base64_encode($this->smtp_user)); - $reply = $this->_get_smtp_data(); - if (strncmp($reply, '334', 3) != 0) + if (strpos($reply, '334') !== 0) { $this->_set_error_message('lang:email_smtp_auth_un', $reply); return FALSE; } $this->_send_data(base64_encode($this->smtp_pass)); - $reply = $this->_get_smtp_data(); - if (strncmp($reply, '235', 3) != 0) + if (strpos($reply, '235') !== 0) { $this->_set_error_message('lang:email_smtp_auth_pw', $reply); return FALSE; } + if ($this->smtp_keepalive) + { + $this->_smtp_auth = FALSE; + } + return TRUE; } @@ -1838,20 +2207,45 @@ class CI_Email { /** * Send SMTP data * - * @access protected + * @param string $data * @return bool */ protected function _send_data($data) { - if ( ! fwrite($this->_smtp_connect, $data . $this->newline)) + $data .= $this->newline; + for ($written = $timestamp = 0, $length = self::strlen($data); $written < $length; $written += $result) { - $this->_set_error_message('lang:email_smtp_data_failure', $data); - return FALSE; + if (($result = fwrite($this->_smtp_connect, self::substr($data, $written))) === FALSE) + { + break; + } + // See https://bugs.php.net/bug.php?id=39598 and https://secure.php.net/manual/en/function.fwrite.php#96951 + elseif ($result === 0) + { + if ($timestamp === 0) + { + $timestamp = time(); + } + elseif ($timestamp < (time() - $this->smtp_timeout)) + { + $result = FALSE; + break; + } + + usleep(250000); + continue; + } + + $timestamp = 0; } - else + + if ($result === FALSE) { - return TRUE; + $this->_set_error_message('lang:email_smtp_data_failure', $data); + return FALSE; } + + return TRUE; } // -------------------------------------------------------------------- @@ -1859,18 +2253,17 @@ class CI_Email { /** * Get SMTP data * - * @access protected * @return string */ protected function _get_smtp_data() { - $data = ""; + $data = ''; while ($str = fgets($this->_smtp_connect, 512)) { $data .= $str; - if (substr($str, 3, 1) == " ") + if ($str[3] === ' ') { break; } @@ -1884,209 +2277,142 @@ class CI_Email { /** * Get Hostname * - * @access protected + * There are only two legal types of hostname - either a fully + * qualified domain name (eg: "mail.example.com") or an IP literal + * (eg: "[1.2.3.4]"). + * + * @link https://tools.ietf.org/html/rfc5321#section-2.3.5 + * @link https://cbl.abuseat.org/namingproblems.html * @return string */ protected function _get_hostname() { - return (isset($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : 'localhost.localdomain'; + if (isset($_SERVER['SERVER_NAME'])) + { + return $_SERVER['SERVER_NAME']; + } + + return isset($_SERVER['SERVER_ADDR']) ? '['.$_SERVER['SERVER_ADDR'].']' : '[127.0.0.1]'; } // -------------------------------------------------------------------- /** - * Get IP + * Get Debug Message * - * @access protected + * @param array $include List of raw data chunks to include in the output + * Valid options are: 'headers', 'subject', 'body' * @return string */ - protected function _get_ip() + public function print_debugger($include = array('headers', 'subject', 'body')) { - if ($this->_IP !== FALSE) - { - return $this->_IP; - } + $msg = implode('', $this->_debug_msg); + + // Determine which parts of our raw data needs to be printed + $raw_data = ''; + is_array($include) OR $include = array($include); - $cip = (isset($_SERVER['HTTP_CLIENT_IP']) AND $_SERVER['HTTP_CLIENT_IP'] != "") ? $_SERVER['HTTP_CLIENT_IP'] : FALSE; - $rip = (isset($_SERVER['REMOTE_ADDR']) AND $_SERVER['REMOTE_ADDR'] != "") ? $_SERVER['REMOTE_ADDR'] : FALSE; - $fip = (isset($_SERVER['HTTP_X_FORWARDED_FOR']) AND $_SERVER['HTTP_X_FORWARDED_FOR'] != "") ? $_SERVER['HTTP_X_FORWARDED_FOR'] : FALSE; + in_array('headers', $include, TRUE) && $raw_data = htmlspecialchars($this->_header_str)."\n"; + in_array('subject', $include, TRUE) && $raw_data .= htmlspecialchars($this->_subject)."\n"; + in_array('body', $include, TRUE) && $raw_data .= htmlspecialchars($this->_finalbody); - if ($cip && $rip) $this->_IP = $cip; - elseif ($rip) $this->_IP = $rip; - elseif ($cip) $this->_IP = $cip; - elseif ($fip) $this->_IP = $fip; + return $msg.($raw_data === '' ? '' : '<pre>'.$raw_data.'</pre>'); + } + + // -------------------------------------------------------------------- - if (strpos($this->_IP, ',') !== FALSE) + /** + * Set Message + * + * @param string $msg + * @param string $val = '' + * @return void + */ + protected function _set_error_message($msg, $val = '') + { + $CI =& get_instance(); + $CI->lang->load('email'); + + if (sscanf($msg, 'lang:%s', $line) !== 1 OR FALSE === ($line = $CI->lang->line($line))) { - $x = explode(',', $this->_IP); - $this->_IP = end($x); + $this->_debug_msg[] = str_replace('%s', $val, $msg).'<br />'; } - - if ( ! preg_match( "/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/", $this->_IP)) + else { - $this->_IP = '0.0.0.0'; + $this->_debug_msg[] = str_replace('%s', $val, $line).'<br />'; } - - unset($cip); - unset($rip); - unset($fip); - - return $this->_IP; } // -------------------------------------------------------------------- /** - * Get Debug Message + * Mime Types * - * @access public + * @param string * @return string */ - public function print_debugger() + protected function _mime_types($ext = '') { - $msg = ''; + $ext = strtolower($ext); + + $mimes =& get_mimes(); - if (count($this->_debug_msg) > 0) + if (isset($mimes[$ext])) { - foreach ($this->_debug_msg as $val) - { - $msg .= $val; - } + return is_array($mimes[$ext]) + ? current($mimes[$ext]) + : $mimes[$ext]; } - $msg .= "<pre>".htmlspecialchars($this->_header_str)."\n".htmlspecialchars($this->_subject)."\n".htmlspecialchars($this->_finalbody).'</pre>'; - return $msg; + return 'application/x-unknown-content-type'; } // -------------------------------------------------------------------- /** - * Set Message + * Destructor * - * @access protected - * @param string - * @return string + * @return void */ - protected function _set_error_message($msg, $val = '') + public function __destruct() { - $CI =& get_instance(); - $CI->lang->load('email'); + is_resource($this->_smtp_connect) && $this->_send_command('quit'); + } - if (substr($msg, 0, 5) != 'lang:' || FALSE === ($line = $CI->lang->line(substr($msg, 5)))) - { - $this->_debug_msg[] = str_replace('%s', $val, $msg)."<br />"; - } - else - { - $this->_debug_msg[] = str_replace('%s', $val, $line)."<br />"; - } + // -------------------------------------------------------------------- + + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return (self::$func_overload) + ? mb_strlen($str, '8bit') + : strlen($str); } // -------------------------------------------------------------------- /** - * Mime Types + * Byte-safe substr() * - * @access protected - * @param string + * @param string $str + * @param int $start + * @param int $length * @return string */ - protected function _mime_types($ext = "") + protected static function substr($str, $start, $length = NULL) { - $mimes = array( 'hqx' => 'application/mac-binhex40', - 'cpt' => 'application/mac-compactpro', - 'doc' => 'application/msword', - 'bin' => 'application/macbinary', - 'dms' => 'application/octet-stream', - 'lha' => 'application/octet-stream', - 'lzh' => 'application/octet-stream', - 'exe' => 'application/octet-stream', - 'class' => 'application/octet-stream', - 'psd' => 'application/octet-stream', - 'so' => 'application/octet-stream', - 'sea' => 'application/octet-stream', - 'dll' => 'application/octet-stream', - 'oda' => 'application/oda', - 'pdf' => 'application/pdf', - 'ai' => 'application/postscript', - 'eps' => 'application/postscript', - 'ps' => 'application/postscript', - 'smi' => 'application/smil', - 'smil' => 'application/smil', - 'mif' => 'application/vnd.mif', - 'xls' => 'application/vnd.ms-excel', - 'ppt' => 'application/vnd.ms-powerpoint', - 'wbxml' => 'application/vnd.wap.wbxml', - 'wmlc' => 'application/vnd.wap.wmlc', - 'dcr' => 'application/x-director', - 'dir' => 'application/x-director', - 'dxr' => 'application/x-director', - 'dvi' => 'application/x-dvi', - 'gtar' => 'application/x-gtar', - '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' => 'application/x-tar', - 'xhtml' => 'application/xhtml+xml', - 'xht' => 'application/xhtml+xml', - 'zip' => 'application/zip', - 'mid' => 'audio/midi', - 'midi' => 'audio/midi', - 'mpga' => 'audio/mpeg', - 'mp2' => 'audio/mpeg', - 'mp3' => 'audio/mpeg', - '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' => 'image/jpeg', - 'jpg' => 'image/jpeg', - 'jpe' => 'image/jpeg', - 'png' => 'image/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' => 'text/plain', - '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', - 'word' => 'application/msword', - 'xl' => 'application/excel', - 'eml' => 'message/rfc822' - ); - - return ( ! isset($mimes[strtolower($ext)])) ? "application/x-unknown-content-type" : $mimes[strtolower($ext)]; - } + if (self::$func_overload) + { + return mb_substr($str, $start, $length, '8bit'); + } + return isset($length) + ? substr($str, $start, $length) + : substr($str, $start); + } } -// END CI_Email class - -/* End of file Email.php */ -/* Location: ./system/libraries/Email.php */ diff --git a/system/libraries/Encrypt.php b/system/libraries/Encrypt.php deleted file mode 100644 index 8e5c1fe53..000000000 --- a/system/libraries/Encrypt.php +++ /dev/null @@ -1,500 +0,0 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); -/** - * CodeIgniter - * - * An open source application development framework for PHP 5.1.6 or newer - * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 - * @filesource - */ - -// ------------------------------------------------------------------------ - -/** - * CodeIgniter Encryption Class - * - * Provides two-way keyed encoding using Mcrypt - * - * @package CodeIgniter - * @subpackage Libraries - * @category Libraries - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/encryption.html - */ -class CI_Encrypt { - - var $CI; - var $encryption_key = ''; - var $_hash_type = 'sha1'; - var $_mcrypt_exists = FALSE; - var $_mcrypt_cipher; - var $_mcrypt_mode; - - /** - * Constructor - * - * Simply determines whether the mcrypt library exists. - * - */ - public function __construct() - { - $this->CI =& get_instance(); - $this->_mcrypt_exists = ( ! function_exists('mcrypt_encrypt')) ? FALSE : TRUE; - - if ($this->_mcrypt_exists === FALSE) - { - show_error('The Encrypt library requires the Mcrypt extension.'); - } - - log_message('debug', "Encrypt Class Initialized"); - } - - // -------------------------------------------------------------------- - - /** - * Fetch the encryption key - * - * Returns it as MD5 in order to have an exact-length 128 bit key. - * Mcrypt is sensitive to keys that are not the correct length - * - * @access public - * @param string - * @return string - */ - function get_key($key = '') - { - if ($key == '') - { - if ($this->encryption_key != '') - { - return $this->encryption_key; - } - - $CI =& get_instance(); - $key = $CI->config->item('encryption_key'); - - if ($key == FALSE) - { - show_error('In order to use the encryption class requires that you set an encryption key in your config file.'); - } - } - - return md5($key); - } - - // -------------------------------------------------------------------- - - /** - * Set the encryption key - * - * @access public - * @param string - * @return void - */ - function set_key($key = '') - { - $this->encryption_key = $key; - } - - // -------------------------------------------------------------------- - - /** - * Encode - * - * Encodes the message string using bitwise XOR encoding. - * The key is combined with a random hash, and then it - * too gets converted using XOR. The whole thing is then run - * through mcrypt using the randomized key. The end result - * is a double-encrypted message string that is randomized - * with each call to this function, even if the supplied - * message and key are the same. - * - * @access public - * @param string the string to encode - * @param string the key - * @return string - */ - function encode($string, $key = '') - { - $key = $this->get_key($key); - $enc = $this->mcrypt_encode($string, $key); - - return base64_encode($enc); - } - - // -------------------------------------------------------------------- - - /** - * Decode - * - * Reverses the above process - * - * @access public - * @param string - * @param string - * @return string - */ - function decode($string, $key = '') - { - $key = $this->get_key($key); - - if (preg_match('/[^a-zA-Z0-9\/\+=]/', $string)) - { - return FALSE; - } - - $dec = base64_decode($string); - - if (($dec = $this->mcrypt_decode($dec, $key)) === FALSE) - { - return FALSE; - } - - return $dec; - } - - // -------------------------------------------------------------------- - - /** - * Encode from Legacy - * - * Takes an encoded string from the original Encryption class algorithms and - * returns a newly encoded string using the improved method added in 2.0.0 - * This allows for backwards compatibility and a method to transition to the - * new encryption algorithms. - * - * For more details, see http://codeigniter.com/user_guide/installation/upgrade_200.html#encryption - * - * @access public - * @param string - * @param int (mcrypt mode constant) - * @param string - * @return string - */ - function encode_from_legacy($string, $legacy_mode = MCRYPT_MODE_ECB, $key = '') - { - // decode it first - // set mode temporarily to what it was when string was encoded with the legacy - // algorithm - typically MCRYPT_MODE_ECB - $current_mode = $this->_get_mode(); - $this->set_mode($legacy_mode); - - $key = $this->get_key($key); - - if (preg_match('/[^a-zA-Z0-9\/\+=]/', $string)) - { - return FALSE; - } - - $dec = base64_decode($string); - - if (($dec = $this->mcrypt_decode($dec, $key)) === FALSE) - { - return FALSE; - } - - $dec = $this->_xor_decode($dec, $key); - - // set the mcrypt mode back to what it should be, typically MCRYPT_MODE_CBC - $this->set_mode($current_mode); - - // and re-encode - return base64_encode($this->mcrypt_encode($dec, $key)); - } - - // -------------------------------------------------------------------- - - /** - * XOR Decode - * - * Takes an encoded string and key as input and generates the - * plain-text original message - * - * @access private - * @param string - * @param string - * @return string - */ - function _xor_decode($string, $key) - { - $string = $this->_xor_merge($string, $key); - - $dec = ''; - for ($i = 0; $i < strlen($string); $i++) - { - $dec .= (substr($string, $i++, 1) ^ substr($string, $i, 1)); - } - - return $dec; - } - - // -------------------------------------------------------------------- - - /** - * XOR key + string Combiner - * - * Takes a string and key as input and computes the difference using XOR - * - * @access private - * @param string - * @param string - * @return string - */ - function _xor_merge($string, $key) - { - $hash = $this->hash($key); - $str = ''; - for ($i = 0; $i < strlen($string); $i++) - { - $str .= substr($string, $i, 1) ^ substr($hash, ($i % strlen($hash)), 1); - } - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Encrypt using Mcrypt - * - * @access public - * @param string - * @param string - * @return string - */ - function mcrypt_encode($data, $key) - { - $init_size = mcrypt_get_iv_size($this->_get_cipher(), $this->_get_mode()); - $init_vect = mcrypt_create_iv($init_size, MCRYPT_RAND); - return $this->_add_cipher_noise($init_vect.mcrypt_encrypt($this->_get_cipher(), $key, $data, $this->_get_mode(), $init_vect), $key); - } - - // -------------------------------------------------------------------- - - /** - * Decrypt using Mcrypt - * - * @access public - * @param string - * @param string - * @return string - */ - function mcrypt_decode($data, $key) - { - $data = $this->_remove_cipher_noise($data, $key); - $init_size = mcrypt_get_iv_size($this->_get_cipher(), $this->_get_mode()); - - if ($init_size > strlen($data)) - { - return FALSE; - } - - $init_vect = substr($data, 0, $init_size); - $data = substr($data, $init_size); - return rtrim(mcrypt_decrypt($this->_get_cipher(), $key, $data, $this->_get_mode(), $init_vect), "\0"); - } - - // -------------------------------------------------------------------- - - /** - * Adds permuted noise to the IV + encrypted data to protect - * against Man-in-the-middle attacks on CBC mode ciphers - * http://www.ciphersbyritter.com/GLOSSARY.HTM#IV - * - * Function description - * - * @access private - * @param string - * @param string - * @return string - */ - function _add_cipher_noise($data, $key) - { - $keyhash = $this->hash($key); - $keylen = strlen($keyhash); - $str = ''; - - for ($i = 0, $j = 0, $len = strlen($data); $i < $len; ++$i, ++$j) - { - if ($j >= $keylen) - { - $j = 0; - } - - $str .= chr((ord($data[$i]) + ord($keyhash[$j])) % 256); - } - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Removes permuted noise from the IV + encrypted data, reversing - * _add_cipher_noise() - * - * Function description - * - * @access public - * @param type - * @return type - */ - function _remove_cipher_noise($data, $key) - { - $keyhash = $this->hash($key); - $keylen = strlen($keyhash); - $str = ''; - - for ($i = 0, $j = 0, $len = strlen($data); $i < $len; ++$i, ++$j) - { - if ($j >= $keylen) - { - $j = 0; - } - - $temp = ord($data[$i]) - ord($keyhash[$j]); - - if ($temp < 0) - { - $temp = $temp + 256; - } - - $str .= chr($temp); - } - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Set the Mcrypt Cipher - * - * @access public - * @param constant - * @return string - */ - function set_cipher($cipher) - { - $this->_mcrypt_cipher = $cipher; - } - - // -------------------------------------------------------------------- - - /** - * Set the Mcrypt Mode - * - * @access public - * @param constant - * @return string - */ - function set_mode($mode) - { - $this->_mcrypt_mode = $mode; - } - - // -------------------------------------------------------------------- - - /** - * Get Mcrypt cipher Value - * - * @access private - * @return string - */ - function _get_cipher() - { - if ($this->_mcrypt_cipher == '') - { - $this->_mcrypt_cipher = MCRYPT_RIJNDAEL_256; - } - - return $this->_mcrypt_cipher; - } - - // -------------------------------------------------------------------- - - /** - * Get Mcrypt Mode Value - * - * @access private - * @return string - */ - function _get_mode() - { - if ($this->_mcrypt_mode == '') - { - $this->_mcrypt_mode = MCRYPT_MODE_CBC; - } - - return $this->_mcrypt_mode; - } - - // -------------------------------------------------------------------- - - /** - * Set the Hash type - * - * @access public - * @param string - * @return string - */ - function set_hash($type = 'sha1') - { - $this->_hash_type = ($type != 'sha1' AND $type != 'md5') ? 'sha1' : $type; - } - - // -------------------------------------------------------------------- - - /** - * Hash encode a string - * - * @access public - * @param string - * @return string - */ - function hash($str) - { - return ($this->_hash_type == 'sha1') ? $this->sha1($str) : md5($str); - } - - // -------------------------------------------------------------------- - - /** - * Generate an SHA1 Hash - * - * @access public - * @param string - * @return string - */ - function sha1($str) - { - if ( ! function_exists('sha1')) - { - if ( ! function_exists('mhash')) - { - require_once(BASEPATH.'libraries/Sha1.php'); - $SH = new CI_SHA; - return $SH->generate($str); - } - else - { - return bin2hex(mhash(MHASH_SHA1, $str)); - } - } - else - { - return sha1($str); - } - } - -} - -// END CI_Encrypt class - -/* End of file Encrypt.php */ -/* Location: ./system/libraries/Encrypt.php */ diff --git a/system/libraries/Encryption.php b/system/libraries/Encryption.php new file mode 100644 index 000000000..572cab3fc --- /dev/null +++ b/system/libraries/Encryption.php @@ -0,0 +1,939 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CodeIgniter Encryption Class + * + * Provides two-way keyed encryption via PHP's MCrypt and/or OpenSSL extensions. + * + * @package CodeIgniter + * @subpackage Libraries + * @category Libraries + * @author Andrey Andreev + * @link https://codeigniter.com/userguide3/libraries/encryption.html + */ +class CI_Encryption { + + /** + * Encryption cipher + * + * @var string + */ + protected $_cipher = 'aes-128'; + + /** + * Cipher mode + * + * @var string + */ + protected $_mode = 'cbc'; + + /** + * Cipher handle + * + * @var mixed + */ + protected $_handle; + + /** + * Encryption key + * + * @var string + */ + protected $_key; + + /** + * PHP extension to be used + * + * @var string + */ + protected $_driver; + + /** + * List of usable drivers (PHP extensions) + * + * @var array + */ + protected $_drivers = array(); + + /** + * List of available modes + * + * @var array + */ + protected $_modes = array( + 'mcrypt' => array( + 'cbc' => 'cbc', + 'ecb' => 'ecb', + 'ofb' => 'nofb', + 'ofb8' => 'ofb', + 'cfb' => 'ncfb', + 'cfb8' => 'cfb', + 'ctr' => 'ctr', + 'stream' => 'stream' + ), + 'openssl' => array( + 'cbc' => 'cbc', + 'ecb' => 'ecb', + 'ofb' => 'ofb', + 'cfb' => 'cfb', + 'cfb8' => 'cfb8', + 'ctr' => 'ctr', + 'stream' => '', + 'xts' => 'xts' + ) + ); + + /** + * List of supported HMAC algorithms + * + * name => digest size pairs + * + * @var array + */ + protected $_digests = array( + 'sha224' => 28, + 'sha256' => 32, + 'sha384' => 48, + 'sha512' => 64 + ); + + /** + * mbstring.func_overload flag + * + * @var bool + */ + protected static $func_overload; + + // -------------------------------------------------------------------- + + /** + * Class constructor + * + * @param array $params Configuration parameters + * @return void + */ + public function __construct(array $params = array()) + { + $this->_drivers = array( + 'mcrypt' => defined('MCRYPT_DEV_URANDOM'), + 'openssl' => extension_loaded('openssl') + ); + + if ( ! $this->_drivers['mcrypt'] && ! $this->_drivers['openssl']) + { + show_error('Encryption: Unable to find an available encryption driver.'); + } + + isset(self::$func_overload) OR self::$func_overload = ( ! is_php('8.0') && extension_loaded('mbstring') && @ini_get('mbstring.func_overload')); + $this->initialize($params); + + if ( ! isset($this->_key) && self::strlen($key = config_item('encryption_key')) > 0) + { + $this->_key = $key; + } + + log_message('info', 'Encryption Class Initialized'); + } + + // -------------------------------------------------------------------- + + /** + * Initialize + * + * @param array $params Configuration parameters + * @return CI_Encryption + */ + public function initialize(array $params) + { + if ( ! empty($params['driver'])) + { + if (isset($this->_drivers[$params['driver']])) + { + if ($this->_drivers[$params['driver']]) + { + $this->_driver = $params['driver']; + } + else + { + log_message('error', "Encryption: Driver '".$params['driver']."' is not available."); + } + } + else + { + log_message('error', "Encryption: Unknown driver '".$params['driver']."' cannot be configured."); + } + } + + if (empty($this->_driver)) + { + $this->_driver = ($this->_drivers['openssl'] === TRUE) + ? 'openssl' + : 'mcrypt'; + + log_message('debug', "Encryption: Auto-configured driver '".$this->_driver."'."); + } + + empty($params['cipher']) && $params['cipher'] = $this->_cipher; + empty($params['key']) OR $this->_key = $params['key']; + $this->{'_'.$this->_driver.'_initialize'}($params); + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Initialize MCrypt + * + * @param array $params Configuration parameters + * @return void + */ + protected function _mcrypt_initialize($params) + { + if ( ! empty($params['cipher'])) + { + $params['cipher'] = strtolower($params['cipher']); + $this->_cipher_alias($params['cipher']); + + if ( ! in_array($params['cipher'], mcrypt_list_algorithms(), TRUE)) + { + log_message('error', 'Encryption: MCrypt cipher '.strtoupper($params['cipher']).' is not available.'); + } + else + { + $this->_cipher = $params['cipher']; + } + } + + if ( ! empty($params['mode'])) + { + $params['mode'] = strtolower($params['mode']); + if ( ! isset($this->_modes['mcrypt'][$params['mode']])) + { + log_message('error', 'Encryption: MCrypt mode '.strtoupper($params['mode']).' is not available.'); + } + else + { + $this->_mode = $this->_modes['mcrypt'][$params['mode']]; + } + } + + if (isset($this->_cipher, $this->_mode)) + { + if (is_resource($this->_handle) + && (strtolower(mcrypt_enc_get_algorithms_name($this->_handle)) !== $this->_cipher + OR strtolower(mcrypt_enc_get_modes_name($this->_handle)) !== $this->_mode) + ) + { + mcrypt_module_close($this->_handle); + } + + if ($this->_handle = mcrypt_module_open($this->_cipher, '', $this->_mode, '')) + { + log_message('info', 'Encryption: MCrypt cipher '.strtoupper($this->_cipher).' initialized in '.strtoupper($this->_mode).' mode.'); + } + else + { + log_message('error', 'Encryption: Unable to initialize MCrypt with cipher '.strtoupper($this->_cipher).' in '.strtoupper($this->_mode).' mode.'); + } + } + } + + // -------------------------------------------------------------------- + + /** + * Initialize OpenSSL + * + * @param array $params Configuration parameters + * @return void + */ + protected function _openssl_initialize($params) + { + if ( ! empty($params['cipher'])) + { + $params['cipher'] = strtolower($params['cipher']); + $this->_cipher_alias($params['cipher']); + $this->_cipher = $params['cipher']; + } + + if ( ! empty($params['mode'])) + { + $params['mode'] = strtolower($params['mode']); + if ( ! isset($this->_modes['openssl'][$params['mode']])) + { + log_message('error', 'Encryption: OpenSSL mode '.strtoupper($params['mode']).' is not available.'); + } + else + { + $this->_mode = $this->_modes['openssl'][$params['mode']]; + } + } + + if (isset($this->_cipher, $this->_mode)) + { + // This is mostly for the stream mode, which doesn't get suffixed in OpenSSL + $handle = empty($this->_mode) + ? $this->_cipher + : $this->_cipher.'-'.$this->_mode; + + if ( ! in_array($handle, openssl_get_cipher_methods(), TRUE)) + { + $this->_handle = NULL; + log_message('error', 'Encryption: Unable to initialize OpenSSL with method '.strtoupper($handle).'.'); + } + else + { + $this->_handle = $handle; + log_message('info', 'Encryption: OpenSSL initialized with method '.strtoupper($handle).'.'); + } + } + } + + // -------------------------------------------------------------------- + + /** + * Create a random key + * + * @param int $length Output length + * @return string + */ + public function create_key($length) + { + if (function_exists('random_bytes')) + { + try + { + return random_bytes((int) $length); + } + catch (Exception $e) + { + log_message('error', $e->getMessage()); + return FALSE; + } + } + elseif (defined('MCRYPT_DEV_URANDOM')) + { + return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + } + + $is_secure = NULL; + $key = openssl_random_pseudo_bytes($length, $is_secure); + return ($is_secure === TRUE) + ? $key + : FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Encrypt + * + * @param string $data Input data + * @param array $params Input parameters + * @return string + */ + public function encrypt($data, array $params = NULL) + { + if (($params = $this->_get_params($params)) === FALSE) + { + return FALSE; + } + + isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption'); + + if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE) + { + return FALSE; + } + + $params['base64'] && $data = base64_encode($data); + + if (isset($params['hmac_digest'])) + { + isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication'); + return hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']).$data; + } + + return $data; + } + + // -------------------------------------------------------------------- + + /** + * Encrypt via MCrypt + * + * @param string $data Input data + * @param array $params Input parameters + * @return string + */ + protected function _mcrypt_encrypt($data, $params) + { + if ( ! is_resource($params['handle'])) + { + return FALSE; + } + + // The greater-than-1 comparison is mostly a work-around for a bug, + // where 1 is returned for ARCFour instead of 0. + $iv = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1) + ? $this->create_key($iv_size) + : NULL; + + if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0) + { + if ($params['handle'] !== $this->_handle) + { + mcrypt_module_close($params['handle']); + } + + return FALSE; + } + + // Use PKCS#7 padding in order to ensure compatibility with OpenSSL + // and other implementations outside of PHP. + if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE)) + { + $block_size = mcrypt_enc_get_block_size($params['handle']); + $pad = $block_size - (self::strlen($data) % $block_size); + $data .= str_repeat(chr($pad), $pad); + } + + // Work-around for yet another strange behavior in MCrypt. + // + // When encrypting in ECB mode, the IV is ignored. Yet + // mcrypt_enc_get_iv_size() returns a value larger than 0 + // even if ECB is used AND mcrypt_generic_init() complains + // if you don't pass an IV with length equal to the said + // return value. + // + // This probably would've been fine (even though still wasteful), + // but OpenSSL isn't that dumb and we need to make the process + // portable, so ... + $data = (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB') + ? $iv.mcrypt_generic($params['handle'], $data) + : mcrypt_generic($params['handle'], $data); + + mcrypt_generic_deinit($params['handle']); + if ($params['handle'] !== $this->_handle) + { + mcrypt_module_close($params['handle']); + } + + return $data; + } + + // -------------------------------------------------------------------- + + /** + * Encrypt via OpenSSL + * + * @param string $data Input data + * @param array $params Input parameters + * @return string + */ + protected function _openssl_encrypt($data, $params) + { + if (empty($params['handle'])) + { + return FALSE; + } + + $iv = ($iv_size = openssl_cipher_iv_length($params['handle'])) + ? $this->create_key($iv_size) + : ''; + + $data = openssl_encrypt( + $data, + $params['handle'], + $params['key'], + OPENSSL_RAW_DATA, + $iv + ); + + if ($data === FALSE) + { + return FALSE; + } + + return $iv.$data; + } + + // -------------------------------------------------------------------- + + /** + * Decrypt + * + * @param string $data Encrypted data + * @param array $params Input parameters + * @return string + */ + public function decrypt($data, array $params = NULL) + { + if (($params = $this->_get_params($params)) === FALSE) + { + return FALSE; + } + + if (isset($params['hmac_digest'])) + { + // This might look illogical, but it is done during encryption as well ... + // The 'base64' value is effectively an inverted "raw data" parameter + $digest_size = ($params['base64']) + ? $this->_digests[$params['hmac_digest']] * 2 + : $this->_digests[$params['hmac_digest']]; + + if (self::strlen($data) <= $digest_size) + { + return FALSE; + } + + $hmac_input = self::substr($data, 0, $digest_size); + $data = self::substr($data, $digest_size); + + isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication'); + $hmac_check = hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']); + + // Time-attack-safe comparison + $diff = 0; + for ($i = 0; $i < $digest_size; $i++) + { + $diff |= ord($hmac_input[$i]) ^ ord($hmac_check[$i]); + } + + if ($diff !== 0) + { + return FALSE; + } + } + + if ($params['base64']) + { + $data = base64_decode($data); + } + + isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption'); + + return $this->{'_'.$this->_driver.'_decrypt'}($data, $params); + } + + // -------------------------------------------------------------------- + + /** + * Decrypt via MCrypt + * + * @param string $data Encrypted data + * @param array $params Input parameters + * @return string + */ + protected function _mcrypt_decrypt($data, $params) + { + if ( ! is_resource($params['handle'])) + { + return FALSE; + } + + // The greater-than-1 comparison is mostly a work-around for a bug, + // where 1 is returned for ARCFour instead of 0. + if (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1) + { + if (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB') + { + $iv = self::substr($data, 0, $iv_size); + $data = self::substr($data, $iv_size); + } + else + { + // MCrypt is dumb and this is ignored, only size matters + $iv = str_repeat("\x0", $iv_size); + } + } + else + { + $iv = ''; + } + + if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0) + { + if ($params['handle'] !== $this->_handle) + { + mcrypt_module_close($params['handle']); + } + + return FALSE; + } + + $data = mdecrypt_generic($params['handle'], $data); + // Remove PKCS#7 padding, if necessary + if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE)) + { + $data = self::substr($data, 0, -ord($data[self::strlen($data)-1])); + } + + mcrypt_generic_deinit($params['handle']); + if ($params['handle'] !== $this->_handle) + { + mcrypt_module_close($params['handle']); + } + + return $data; + } + + // -------------------------------------------------------------------- + + /** + * Decrypt via OpenSSL + * + * @param string $data Encrypted data + * @param array $params Input parameters + * @return string + */ + protected function _openssl_decrypt($data, $params) + { + if ($iv_size = openssl_cipher_iv_length($params['handle'])) + { + $iv = self::substr($data, 0, $iv_size); + $data = self::substr($data, $iv_size); + } + else + { + $iv = ''; + } + + return empty($params['handle']) + ? FALSE + : openssl_decrypt( + $data, + $params['handle'], + $params['key'], + OPENSSL_RAW_DATA, + $iv + ); + } + + // -------------------------------------------------------------------- + + /** + * Get params + * + * @param array $params Input parameters + * @return array + */ + protected function _get_params($params) + { + if (empty($params)) + { + return isset($this->_cipher, $this->_mode, $this->_key, $this->_handle) + ? array( + 'handle' => $this->_handle, + 'cipher' => $this->_cipher, + 'mode' => $this->_mode, + 'key' => NULL, + 'base64' => TRUE, + 'hmac_digest' => 'sha512', + 'hmac_key' => NULL + ) + : FALSE; + } + elseif ( ! isset($params['cipher'], $params['mode'], $params['key'])) + { + return FALSE; + } + + if (isset($params['mode'])) + { + $params['mode'] = strtolower($params['mode']); + if ( ! isset($this->_modes[$this->_driver][$params['mode']])) + { + return FALSE; + } + + $params['mode'] = $this->_modes[$this->_driver][$params['mode']]; + } + + if (isset($params['hmac']) && $params['hmac'] === FALSE) + { + $params['hmac_digest'] = $params['hmac_key'] = NULL; + } + else + { + if ( ! isset($params['hmac_key'])) + { + return FALSE; + } + elseif (isset($params['hmac_digest'])) + { + $params['hmac_digest'] = strtolower($params['hmac_digest']); + if ( ! isset($this->_digests[$params['hmac_digest']])) + { + return FALSE; + } + } + else + { + $params['hmac_digest'] = 'sha512'; + } + } + + $params = array( + 'handle' => NULL, + 'cipher' => $params['cipher'], + 'mode' => $params['mode'], + 'key' => $params['key'], + 'base64' => isset($params['raw_data']) ? ! $params['raw_data'] : FALSE, + 'hmac_digest' => $params['hmac_digest'], + 'hmac_key' => $params['hmac_key'] + ); + + $this->_cipher_alias($params['cipher']); + $params['handle'] = ($params['cipher'] !== $this->_cipher OR $params['mode'] !== $this->_mode) + ? $this->{'_'.$this->_driver.'_get_handle'}($params['cipher'], $params['mode']) + : $this->_handle; + + return $params; + } + + // -------------------------------------------------------------------- + + /** + * Get MCrypt handle + * + * @param string $cipher Cipher name + * @param string $mode Encryption mode + * @return resource + */ + protected function _mcrypt_get_handle($cipher, $mode) + { + return mcrypt_module_open($cipher, '', $mode, ''); + } + + // -------------------------------------------------------------------- + + /** + * Get OpenSSL handle + * + * @param string $cipher Cipher name + * @param string $mode Encryption mode + * @return string + */ + protected function _openssl_get_handle($cipher, $mode) + { + // OpenSSL methods aren't suffixed with '-stream' for this mode + return ($mode === 'stream') + ? $cipher + : $cipher.'-'.$mode; + } + + // -------------------------------------------------------------------- + + /** + * Cipher alias + * + * Tries to translate cipher names between MCrypt and OpenSSL's "dialects". + * + * @param string $cipher Cipher name + * @return void + */ + protected function _cipher_alias(&$cipher) + { + static $dictionary; + + if (empty($dictionary)) + { + $dictionary = array( + 'mcrypt' => array( + 'aes-128' => 'rijndael-128', + 'aes-192' => 'rijndael-128', + 'aes-256' => 'rijndael-128', + 'des3-ede3' => 'tripledes', + 'bf' => 'blowfish', + 'cast5' => 'cast-128', + 'rc4' => 'arcfour', + 'rc4-40' => 'arcfour' + ), + 'openssl' => array( + 'rijndael-128' => 'aes-128', + 'tripledes' => 'des-ede3', + 'blowfish' => 'bf', + 'cast-128' => 'cast5', + 'arcfour' => 'rc4-40', + 'rc4' => 'rc4-40' + ) + ); + + // Notes: + // + // - Rijndael-128 is, at the same time all three of AES-128, + // AES-192 and AES-256. The only difference between them is + // the key size. Rijndael-192, Rijndael-256 on the other hand + // also have different block sizes and are NOT AES-compatible. + // + // - Blowfish is said to be supporting key sizes between + // 4 and 56 bytes, but it appears that between MCrypt and + // OpenSSL, only those of 16 and more bytes are compatible. + // Also, don't know what MCrypt's 'blowfish-compat' is. + // + // - CAST-128/CAST5 produces a longer cipher when encrypted via + // OpenSSL, but (strangely enough) can be decrypted by either + // extension anyway. + // Also, it appears that OpenSSL uses 16 rounds regardless of + // the key size, while RFC2144 says that for key sizes lower + // than 11 bytes, only 12 rounds should be used. This makes + // it portable only with keys of between 11 and 16 bytes. + // + // - RC4 (ARCFour) has a strange implementation under OpenSSL. + // Its 'rc4-40' cipher method seems to work flawlessly, yet + // there's another one, 'rc4' that only works with a 16-byte key. + // + // - DES is compatible, but doesn't need an alias. + // + // Other seemingly matching ciphers between MCrypt, OpenSSL: + // + // - RC2 is NOT compatible and only an obscure forum post + // confirms that it is MCrypt's fault. + } + + if (isset($dictionary[$this->_driver][$cipher])) + { + $cipher = $dictionary[$this->_driver][$cipher]; + } + } + + // -------------------------------------------------------------------- + + /** + * HKDF + * + * @link https://tools.ietf.org/rfc/rfc5869.txt + * @param $key Input key + * @param $digest A SHA-2 hashing algorithm + * @param $salt Optional salt + * @param $length Output length (defaults to the selected digest size) + * @param $info Optional context/application-specific info + * @return string A pseudo-random key + */ + public function hkdf($key, $digest = 'sha512', $salt = NULL, $length = NULL, $info = '') + { + if ( ! isset($this->_digests[$digest])) + { + return FALSE; + } + + if (empty($length) OR ! is_int($length)) + { + $length = $this->_digests[$digest]; + } + elseif ($length > (255 * $this->_digests[$digest])) + { + return FALSE; + } + + self::strlen($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]); + + $prk = hash_hmac($digest, $key, $salt, TRUE); + $key = ''; + for ($key_block = '', $block_index = 1; self::strlen($key) < $length; $block_index++) + { + $key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE); + $key .= $key_block; + } + + return self::substr($key, 0, $length); + } + + // -------------------------------------------------------------------- + + /** + * __get() magic + * + * @param string $key Property name + * @return mixed + */ + public function __get($key) + { + // Because aliases + if ($key === 'mode') + { + return array_search($this->_mode, $this->_modes[$this->_driver], TRUE); + } + elseif (in_array($key, array('cipher', 'driver', 'drivers', 'digests'), TRUE)) + { + return $this->{'_'.$key}; + } + + return NULL; + } + + // -------------------------------------------------------------------- + + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return (self::$func_overload) + ? mb_strlen((string) $str, '8bit') + : strlen((string) $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) + { + return mb_substr($str, $start, $length, '8bit'); + } + + return isset($length) + ? substr($str, $start, $length) + : substr($str, $start); + } +} diff --git a/system/libraries/Form_validation.php b/system/libraries/Form_validation.php index 3839fe42b..dd1685db1 100644 --- a/system/libraries/Form_validation.php +++ b/system/libraries/Form_validation.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Form Validation Class @@ -21,41 +44,103 @@ * @package CodeIgniter * @subpackage Libraries * @category Validation - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/form_validation.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/form_validation.html */ class CI_Form_validation { + /** + * Reference to the CodeIgniter instance + * + * @var object + */ protected $CI; - protected $_field_data = array(); - protected $_config_rules = array(); - protected $_error_array = array(); - protected $_error_messages = array(); - protected $_error_prefix = '<p>'; - protected $_error_suffix = '</p>'; - protected $error_string = ''; - protected $_safe_form_data = FALSE; /** - * Constructor + * Validation data for the current form submission + * + * @var array + */ + protected $_field_data = array(); + + /** + * Validation rules for the current form + * + * @var array + */ + protected $_config_rules = array(); + + /** + * Array of validation errors + * + * @var array + */ + protected $_error_array = array(); + + /** + * Array of custom error messages + * + * @var array + */ + protected $_error_messages = array(); + + /** + * Start tag for error wrapping + * + * @var string + */ + protected $_error_prefix = '<p>'; + + /** + * End tag for error wrapping + * + * @var string + */ + protected $_error_suffix = '</p>'; + + /** + * Custom error message + * + * @var string + */ + protected $error_string = ''; + + /** + * Custom data to validate + * + * @var array + */ + public $validation_data = array(); + + /** + * Initialize Form_Validation class + * + * @param array $rules + * @return void */ public function __construct($rules = array()) { $this->CI =& get_instance(); + // applies delimiters set in config file. + if (isset($rules['error_prefix'])) + { + $this->_error_prefix = $rules['error_prefix']; + unset($rules['error_prefix']); + } + if (isset($rules['error_suffix'])) + { + $this->_error_suffix = $rules['error_suffix']; + unset($rules['error_suffix']); + } + // Validation rules can be stored in a config file. $this->_config_rules = $rules; // Automatically load the form helper $this->CI->load->helper('form'); - // Set the character encoding in MB. - if (function_exists('mb_internal_encoding')) - { - mb_internal_encoding($this->CI->config->item('charset')); - } - - log_message('debug', "Form Validation Class Initialized"); + log_message('info', 'Form Validation Class Initialized'); } // -------------------------------------------------------------------- @@ -64,86 +149,99 @@ class CI_Form_validation { * Set Rules * * This function takes an array of field names and validation - * rules as input, validates the info, and stores it + * rules as input, any custom error messages, validates the info, + * and stores it * - * @access public - * @param mixed - * @param string - * @return void + * @param mixed $field + * @param string $label + * @param mixed $rules + * @param array $errors + * @return CI_Form_validation */ - public function set_rules($field, $label = '', $rules = '') + public function set_rules($field, $label = null, $rules = null, $errors = array()) { // No reason to set rules if we have no POST data - if (count($_POST) == 0) + // or a validation array has not been specified + if ($this->CI->input->method() !== 'post' && empty($this->validation_data)) { return $this; } - // If an array was passed via the first parameter instead of indidual string + // If an array was passed via the first parameter instead of individual string // values we cycle through it and recursively call this function. if (is_array($field)) { foreach ($field as $row) { // Houston, we have a problem... - if ( ! isset($row['field']) OR ! isset($row['rules'])) + if ( ! isset($row['field'], $row['rules'])) { continue; } // If the field label wasn't passed we use the field name - $label = ( ! isset($row['label'])) ? $row['field'] : $row['label']; + $label = isset($row['label']) ? $row['label'] : $row['field']; + + // Add the custom error message array + $errors = (isset($row['errors']) && is_array($row['errors'])) ? $row['errors'] : array(); // Here we go! - $this->set_rules($row['field'], $label, $row['rules']); + $this->set_rules($row['field'], $label, $row['rules'], $errors); } + return $this; } + elseif ( ! isset($rules)) + { + throw new BadMethodCallException('Form_validation: set_rules() called without a $rules parameter'); + } - // No fields? Nothing to do... - if ( ! is_string($field) OR ! is_string($rules) OR $field == '') + // No fields or no rules? Nothing to do... + if ( ! is_string($field) OR $field === '' OR empty($rules)) { - return $this; + throw new RuntimeException('Form_validation: set_rules() called with an empty $rules parameter'); + } + elseif ( ! is_array($rules)) + { + // BC: Convert pipe-separated rules string to an array + if ( ! is_string($rules)) + { + throw new InvalidArgumentException('Form_validation: set_rules() expect $rules to be string or array; '.gettype($rules).' given'); + } + + $rules = preg_split('/\|(?![^\[]*\])/', $rules); } // If the field label wasn't passed we use the field name - $label = ($label == '') ? $field : $label; + $label = ($label === '') ? $field : $label; + + $indexes = array(); - // Is the field name an array? We test for the existence of a bracket "[" in - // the field name to determine this. If it is an array, we break it apart + // Is the field name an array? If it is an array, we break it apart // into its components so that we can fetch the corresponding POST data later - if (strpos($field, '[') !== FALSE AND preg_match_all('/\[(.*?)\]/', $field, $matches)) + if (($is_array = (bool) preg_match_all('/\[(.*?)\]/', $field, $matches)) === TRUE) { - // Note: Due to a bug in current() that affects some versions - // of PHP we can not pass function call directly into it - $x = explode('[', $field); - $indexes[] = current($x); + sscanf($field, '%[^[][', $indexes[0]); - for ($i = 0; $i < count($matches['0']); $i++) + for ($i = 0, $c = count($matches[0]); $i < $c; $i++) { - if ($matches['1'][$i] != '') + if ($matches[1][$i] !== '') { - $indexes[] = $matches['1'][$i]; + $indexes[] = $matches[1][$i]; } } - - $is_array = TRUE; - } - else - { - $indexes = array(); - $is_array = FALSE; } // Build our master array $this->_field_data[$field] = array( - 'field' => $field, - 'label' => $label, - 'rules' => $rules, - 'is_array' => $is_array, - 'keys' => $indexes, - 'postdata' => NULL, - 'error' => '' + 'field' => $field, + 'label' => $label, + 'rules' => $rules, + 'errors' => $errors, + 'is_array' => $is_array, + 'keys' => $indexes, + 'postdata' => NULL, + 'error' => '' ); return $this; @@ -152,15 +250,39 @@ class CI_Form_validation { // -------------------------------------------------------------------- /** + * By default, form validation uses the $_POST array to validate + * + * If an array is set through this method, then this array will + * be used instead of the $_POST array + * + * Note that if you are validating multiple arrays, then the + * reset_validation() function should be called after validating + * each array due to the limitations of CI's singleton + * + * @param array $data + * @return CI_Form_validation + */ + public function set_data(array $data) + { + if ( ! empty($data)) + { + $this->validation_data = $data; + } + + return $this; + } + + // -------------------------------------------------------------------- + + /** * Set Error Message * - * Lets users set their own error messages on the fly. Note: The key - * name has to match the function name that it corresponds to. + * Lets users set their own error messages on the fly. Note: + * The key name has to match the function name that it corresponds to. * - * @access public - * @param string + * @param array * @param string - * @return string + * @return CI_Form_validation */ public function set_message($lang, $val = '') { @@ -170,7 +292,6 @@ class CI_Form_validation { } $this->_error_messages = array_merge($this->_error_messages, $lang); - return $this; } @@ -181,16 +302,14 @@ class CI_Form_validation { * * Permits a prefix/suffix to be added to each error message * - * @access public * @param string * @param string - * @return void + * @return CI_Form_validation */ public function set_error_delimiters($prefix = '<p>', $suffix = '</p>') { $this->_error_prefix = $prefix; $this->_error_suffix = $suffix; - return $this; } @@ -201,23 +320,24 @@ class CI_Form_validation { * * Gets the error message associated with a particular field * - * @access public - * @param string the field name - * @return void + * @param string $field Field name + * @param string $prefix HTML start tag + * @param string $suffix HTML end tag + * @return string */ - public function error($field = '', $prefix = '', $suffix = '') + public function error($field, $prefix = '', $suffix = '') { - if ( ! isset($this->_field_data[$field]['error']) OR $this->_field_data[$field]['error'] == '') + if (empty($this->_field_data[$field]['error'])) { return ''; } - if ($prefix == '') + if ($prefix === '') { $prefix = $this->_error_prefix; } - if ($suffix == '') + if ($suffix === '') { $suffix = $this->_error_suffix; } @@ -228,29 +348,42 @@ class CI_Form_validation { // -------------------------------------------------------------------- /** + * Get Array of Error Messages + * + * Returns the error messages as an array + * + * @return array + */ + public function error_array() + { + return $this->_error_array; + } + + // -------------------------------------------------------------------- + + /** * Error String * * Returns the error messages as a string, wrapped in the error delimiters * - * @access public * @param string * @param string - * @return str + * @return string */ public function error_string($prefix = '', $suffix = '') { - // No errrors, validation passes! + // No errors, validation passes! if (count($this->_error_array) === 0) { return ''; } - if ($prefix == '') + if ($prefix === '') { $prefix = $this->_error_prefix; } - if ($suffix == '') + if ($suffix === '') { $suffix = $this->_error_suffix; } @@ -259,7 +392,7 @@ class CI_Form_validation { $str = ''; foreach ($this->_error_array as $val) { - if ($val != '') + if ($val !== '') { $str .= $prefix.$val.$suffix."\n"; } @@ -275,43 +408,39 @@ class CI_Form_validation { * * This function does all the work. * - * @access public + * @param string $config + * @param array $data * @return bool */ - public function run($group = '') + public function run($config = NULL, &$data = NULL) { - // Do we even have any data to process? Mm? - if (count($_POST) == 0) - { - return FALSE; - } + $validation_array = empty($this->validation_data) + ? $_POST + : $this->validation_data; // Does the _field_data array containing the validation rules exist? // If not, we look to see if they were assigned via a config file - if (count($this->_field_data) == 0) + if (count($this->_field_data) === 0) { // No validation rules? We're done... - if (count($this->_config_rules) == 0) + if (empty($this->_config_rules)) { return FALSE; } - // Is there a validation rule for the particular URI being accessed? - $uri = ($group == '') ? trim($this->CI->uri->ruri_string(), '/') : $group; - - if ($uri != '' AND isset($this->_config_rules[$uri])) - { - $this->set_rules($this->_config_rules[$uri]); - } - else + if (empty($config)) { - $this->set_rules($this->_config_rules); + // Is there a validation rule for the particular URI being accessed? + $config = trim($this->CI->uri->ruri_string(), '/'); + isset($this->_config_rules[$config]) OR $config = $this->CI->router->class.'/'.$this->CI->router->method; } - // We're we able to set the rules correctly? - if (count($this->_field_data) == 0) + $this->set_rules(isset($this->_config_rules[$config]) ? $this->_config_rules[$config] : $this->_config_rules); + + // Were we able to set the rules correctly? + if (count($this->_field_data) === 0) { - log_message('debug', "Unable to find validation rules"); + log_message('debug', 'Unable to find validation rules'); return FALSE; } } @@ -319,47 +448,108 @@ class CI_Form_validation { // Load the language file containing error messages $this->CI->lang->load('form_validation'); - // Cycle through the rules for each field, match the - // corresponding $_POST item and test for errors - foreach ($this->_field_data as $field => $row) + // Cycle through the rules for each field and match the corresponding $validation_data item + foreach ($this->_field_data as $field => &$row) { - // Fetch the data from the corresponding $_POST array and cache it in the _field_data array. + // Fetch the data from the validation_data array item and cache it in the _field_data array. // Depending on whether the field name is an array or a string will determine where we get it from. - - if ($row['is_array'] == TRUE) + if ($row['is_array'] === TRUE) { - $this->_field_data[$field]['postdata'] = $this->_reduce_array($_POST, $row['keys']); + $this->_field_data[$field]['postdata'] = $this->_reduce_array($validation_array, $row['keys']); } - else + elseif (isset($validation_array[$field])) { - if (isset($_POST[$field]) AND $_POST[$field] != "") - { - $this->_field_data[$field]['postdata'] = $_POST[$field]; - } + $this->_field_data[$field]['postdata'] = $validation_array[$field]; } + } - $this->_execute($row, explode('|', $row['rules']), $this->_field_data[$field]['postdata']); + // Execute validation rules + // Note: A second foreach (for now) is required in order to avoid false-positives + // for rules like 'matches', which correlate to other validation fields. + foreach ($this->_field_data as $field => &$row) + { + // Don't try to validate if we have no rules set + if (empty($row['rules'])) + { + continue; + } + + $this->_execute($row, $row['rules'], $row['postdata']); } - // Did we end up with any errors? - $total_errors = count($this->_error_array); + if ( ! empty($this->_error_array)) + { + return FALSE; + } - if ($total_errors > 0) + // Fill $data if requested, otherwise modify $_POST, as long as + // set_data() wasn't used (yea, I know it sounds confusing) + if (func_num_args() >= 2) { - $this->_safe_form_data = TRUE; + $data = empty($this->validation_data) ? $_POST : $this->validation_data; + $this->_reset_data_array($data); + return TRUE; } - // Now we need to re-set the POST data with the new, processed data - $this->_reset_post_array(); + empty($this->validation_data) && $this->_reset_data_array($_POST); + return TRUE; + } - // No errors, validation passes! - if ($total_errors == 0) + // -------------------------------------------------------------------- + + /** + * Prepare rules + * + * Re-orders the provided rules in order of importance, so that + * they can easily be executed later without weird checks ... + * + * "Callbacks" are given the highest priority (always called), + * followed by 'required' (called if callbacks didn't fail), + * and then every next rule depends on the previous one passing. + * + * @param array $rules + * @return array + */ + protected function _prepare_rules($rules) + { + $new_rules = array(); + $callbacks = array(); + + foreach ($rules as &$rule) { - return TRUE; + // Let 'required' always be the first (non-callback) rule + if ($rule === 'required') + { + array_unshift($new_rules, 'required'); + } + // 'isset' is a kind of a weird alias for 'required' ... + elseif ($rule === 'isset' && (empty($new_rules) OR $new_rules[0] !== 'required')) + { + array_unshift($new_rules, 'isset'); + } + // The old/classic 'callback_'-prefixed rules + elseif (is_string($rule) && strncmp('callback_', $rule, 9) === 0) + { + $callbacks[] = $rule; + } + // Proper callables + elseif (is_callable($rule)) + { + $callbacks[] = $rule; + } + // "Named" callables; i.e. array('name' => $callable) + elseif (is_array($rule) && isset($rule[0], $rule[1]) && is_callable($rule[1])) + { + $callbacks[] = $rule; + } + // Everything else goes at the end of the queue + else + { + $new_rules[] = $rule; + } } - // Validation fails - return FALSE; + return array_merge($callbacks, $new_rules); } // -------------------------------------------------------------------- @@ -367,34 +557,20 @@ class CI_Form_validation { /** * Traverse a multidimensional $_POST array index until the data is found * - * @access private * @param array * @param array - * @param integer + * @param int * @return mixed */ protected function _reduce_array($array, $keys, $i = 0) { - if (is_array($array)) + if (is_array($array) && isset($keys[$i])) { - if (isset($keys[$i])) - { - if (isset($array[$keys[$i]])) - { - $array = $this->_reduce_array($array[$keys[$i]], $keys, ($i+1)); - } - else - { - return NULL; - } - } - else - { - return $array; - } + return isset($array[$keys[$i]]) ? $this->_reduce_array($array[$keys[$i]], $keys, ($i+1)) : NULL; } - return $array; + // NULL must be returned for empty fields + return ($array === '') ? NULL : $array; } // -------------------------------------------------------------------- @@ -402,54 +578,36 @@ class CI_Form_validation { /** * Re-populate the _POST array with our finalized and processed data * - * @access private - * @return null + * @return void */ - protected function _reset_post_array() + protected function _reset_data_array(&$data) { foreach ($this->_field_data as $field => $row) { - if ( ! is_null($row['postdata'])) + if ($row['postdata'] !== NULL) { - if ($row['is_array'] == FALSE) + if ($row['is_array'] === FALSE) { - if (isset($_POST[$row['field']])) - { - $_POST[$row['field']] = $this->prep_for_form($row['postdata']); - } + isset($data[$field]) && $data[$field] = is_array($row['postdata']) ? NULL : $row['postdata']; } else { - // start with a reference - $post_ref =& $_POST; + $data_ref =& $data; // before we assign values, make a reference to the right POST key - if (count($row['keys']) == 1) + if (count($row['keys']) === 1) { - $post_ref =& $post_ref[current($row['keys'])]; + $data_ref =& $data[current($row['keys'])]; } else { foreach ($row['keys'] as $val) { - $post_ref =& $post_ref[$val]; + $data_ref =& $data_ref[$val]; } } - if (is_array($row['postdata'])) - { - $array = array(); - foreach ($row['postdata'] as $k => $v) - { - $array[$k] = $this->prep_for_form($v); - } - - $post_ref = $array; - } - else - { - $post_ref = $this->prep_for_form($row['postdata']); - } + $data_ref = $row['postdata']; } } } @@ -460,92 +618,38 @@ class CI_Form_validation { /** * Executes the Validation routines * - * @access private * @param array * @param array * @param mixed - * @param integer + * @param int * @return mixed */ protected function _execute($row, $rules, $postdata = NULL, $cycles = 0) { + $allow_arrays = in_array('is_array', $rules, TRUE); + // If the $_POST data is an array we will run a recursive call - if (is_array($postdata)) + // + // Note: We MUST check if the array is empty or not! + // Otherwise empty arrays will always pass validation. + if ($allow_arrays === FALSE && is_array($postdata) && ! empty($postdata)) { foreach ($postdata as $key => $val) { - $this->_execute($row, $rules, $val, $cycles); - $cycles++; - } - - return; - } - - // -------------------------------------------------------------------- - - // If the field is blank, but NOT required, no further tests are necessary - $callback = FALSE; - if ( ! in_array('required', $rules) AND is_null($postdata)) - { - // Before we bail out, does the rule contain a callback? - if (preg_match("/(callback_\w+(\[.*?\])?)/", implode(' ', $rules), $match)) - { - $callback = TRUE; - $rules = (array('1' => $match[1])); - } - else - { - return; - } - } - - // -------------------------------------------------------------------- - - // Isset Test. Typically this rule will only apply to checkboxes. - if (is_null($postdata) AND $callback == FALSE) - { - if (in_array('isset', $rules, TRUE) OR in_array('required', $rules)) - { - // Set the message type - $type = (in_array('required', $rules)) ? 'required' : 'isset'; - - if ( ! isset($this->_error_messages[$type])) - { - if (FALSE === ($line = $this->CI->lang->line($type))) - { - $line = 'The field was not set'; - } - } - else - { - $line = $this->_error_messages[$type]; - } - - // Build the error message - $message = sprintf($line, $this->_translate_fieldname($row['label'])); - - // Save the error message - $this->_field_data[$row['field']]['error'] = $message; - - if ( ! isset($this->_error_array[$row['field']])) - { - $this->_error_array[$row['field']] = $message; - } + $this->_execute($row, $rules, $val, $key); } return; } - // -------------------------------------------------------------------- - - // Cycle through each rule and run it - foreach ($rules As $rule) + $rules = $this->_prepare_rules($rules); + foreach ($rules as $rule) { $_in_array = FALSE; // We set the $postdata variable with the current data in our master array so that // each cycle of the loop is dealing with the processed data from the last cycle - if ($row['is_array'] == TRUE AND is_array($this->_field_data[$row['field']]['postdata'])) + if ($row['is_array'] === TRUE && is_array($this->_field_data[$row['field']]['postdata'])) { // We shouldn't need this safety, but just in case there isn't an array index // associated with this cycle we'll bail out @@ -557,120 +661,158 @@ class CI_Form_validation { $postdata = $this->_field_data[$row['field']]['postdata'][$cycles]; $_in_array = TRUE; } + // If we get an array field, but it's not expected - then it is most likely + // somebody messing with the form on the client side, so we'll just consider + // it an empty field + elseif ($allow_arrays === FALSE && is_array($this->_field_data[$row['field']]['postdata'])) + { + $postdata = NULL; + } else { $postdata = $this->_field_data[$row['field']]['postdata']; } - // -------------------------------------------------------------------- - // Is the rule a callback? - $callback = FALSE; - if (substr($rule, 0, 9) == 'callback_') + $callback = $callable = FALSE; + if (is_string($rule)) + { + if (strpos($rule, 'callback_') === 0) + { + $rule = substr($rule, 9); + $callback = TRUE; + } + } + elseif (is_callable($rule)) + { + $callable = TRUE; + } + elseif (is_array($rule) && isset($rule[0], $rule[1]) && is_callable($rule[1])) { - $rule = substr($rule, 9); - $callback = TRUE; + // We have a "named" callable, so save the name + $callable = $rule[0]; + $rule = $rule[1]; } // Strip the parameter (if exists) from the rule // Rules can contain a parameter: max_length[5] $param = FALSE; - if (preg_match("/(.*?)\[(.*)\]/", $rule, $match)) + if ( ! $callable && preg_match('/(.*?)\[(.*)\]/', $rule, $match)) + { + $rule = $match[1]; + $param = $match[2]; + } + + // Ignore empty, non-required inputs with a few exceptions ... + if ( + ($postdata === NULL OR ($allow_arrays === FALSE && $postdata === '')) + && $callback === FALSE + && $callable === FALSE + && ! in_array($rule, array('required', 'isset', 'matches'), TRUE) + ) { - $rule = $match[1]; - $param = $match[2]; + continue; } // Call the function that corresponds to the rule - if ($callback === TRUE) + if ($callback OR $callable !== FALSE) { - if ( ! method_exists($this->CI, $rule)) + if ($callback) { - continue; + if ( ! method_exists($this->CI, $rule)) + { + log_message('debug', 'Unable to find callback validation rule: '.$rule); + $result = FALSE; + } + else + { + // Run the function and grab the result + $result = $this->CI->$rule($postdata, $param); + } } + else + { + $result = is_array($rule) + ? $rule[0]->{$rule[1]}($postdata) + : $rule($postdata); - // Run the function and grab the result - $result = $this->CI->$rule($postdata, $param); + // Is $callable set to a rule name? + if ($callable !== FALSE) + { + $rule = $callable; + } + } // Re-assign the result to the master data array - if ($_in_array == TRUE) + if ($_in_array === TRUE) { - $this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result; + $this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result; } else { - $this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result; - } - - // If the field isn't required and we just processed a callback we'll move on... - if ( ! in_array('required', $rules, TRUE) AND $result !== FALSE) - { - continue; + $this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result; } } - else + elseif ( ! method_exists($this, $rule)) { - if ( ! method_exists($this, $rule)) + // If our own wrapper function doesn't exist we see if a native PHP function does. + // Users can use any native PHP function call that has one param. + if (function_exists($rule)) { - // If our own wrapper function doesn't exist we see if a native PHP function does. - // Users can use any native PHP function call that has one param. - if (function_exists($rule)) - { - $result = $rule($postdata); + // Native PHP functions issue warnings if you pass them more parameters than they use + $result = ($param !== FALSE) ? $rule($postdata, $param) : $rule($postdata); - if ($_in_array == TRUE) - { - $this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result; - } - else - { - $this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result; - } + if ($_in_array === TRUE) + { + $this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result; } else { - log_message('debug', "Unable to find validation rule: ".$rule); + $this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result; } - - continue; } - + else + { + log_message('debug', 'Unable to find validation rule: '.$rule); + $result = FALSE; + } + } + else + { $result = $this->$rule($postdata, $param); - if ($_in_array == TRUE) + if ($_in_array === TRUE) { - $this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result; + $this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result; } else { - $this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result; + $this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result; } } - // Did the rule test negatively? If so, grab the error. + // Did the rule test negatively? If so, grab the error. if ($result === FALSE) { - if ( ! isset($this->_error_messages[$rule])) + // Callable rules might not have named error messages + if ( ! is_string($rule)) { - if (FALSE === ($line = $this->CI->lang->line($rule))) - { - $line = 'Unable to access an error message corresponding to your field name.'; - } + $line = $this->CI->lang->line('form_validation_error_message_not_set').'(Anonymous function)'; } else { - $line = $this->_error_messages[$rule]; + $line = $this->_get_error_message($rule, $row['field']); } // Is the parameter we are inserting into the error message the name - // of another field? If so we need to grab its "field label" - if (isset($this->_field_data[$param]) AND isset($this->_field_data[$param]['label'])) + // of another field? If so we need to grab its "field label" + if (isset($this->_field_data[$param], $this->_field_data[$param]['label'])) { $param = $this->_translate_fieldname($this->_field_data[$param]['label']); } // Build the error message - $message = sprintf($line, $this->_translate_fieldname($row['label']), $param); + $message = $this->_build_error_msg($line, $this->_translate_fieldname($row['label']), $param); // Save the error message $this->_field_data[$row['field']]['error'] = $message; @@ -688,26 +830,47 @@ class CI_Form_validation { // -------------------------------------------------------------------- /** + * Get the error message for the rule + * + * @param string $rule The rule name + * @param string $field The field name + * @return string + */ + protected function _get_error_message($rule, $field) + { + // check if a custom message is defined through validation config row. + if (isset($this->_field_data[$field]['errors'][$rule])) + { + return $this->_field_data[$field]['errors'][$rule]; + } + // check if a custom message has been set using the set_message() function + elseif (isset($this->_error_messages[$rule])) + { + return $this->_error_messages[$rule]; + } + elseif (FALSE !== ($line = $this->CI->lang->line('form_validation_'.$rule))) + { + return $line; + } + + return $this->CI->lang->line('form_validation_error_message_not_set').'('.$rule.')'; + } + + // -------------------------------------------------------------------- + + /** * Translate a field name * - * @access private * @param string the field name * @return string */ protected function _translate_fieldname($fieldname) { - // Do we need to translate the field name? - // We look for the prefix lang: to determine this - if (substr($fieldname, 0, 5) == 'lang:') + // Do we need to translate the field name? We look for the prefix 'lang:' to determine this + // If we find one, but there's no translation for the string - just return it + if (sscanf($fieldname, 'lang:%s', $line) === 1 && FALSE === ($fieldname = $this->CI->lang->line($line, FALSE))) { - // Grab the variable - $line = substr($fieldname, 5); - - // Were we able to translate the field name? If not we use $line - if (FALSE === ($fieldname = $this->CI->lang->line($line))) - { - return $line; - } + return $line; } return $fieldname; @@ -716,25 +879,60 @@ class CI_Form_validation { // -------------------------------------------------------------------- /** + * Build an error message using the field and param. + * + * @param string The error message line + * @param string A field's human name + * @param mixed A rule's optional parameter + * @return string + */ + protected function _build_error_msg($line, $field = '', $param = '') + { + // Check for %s in the string for legacy support. + if (strpos($line, '%s') !== FALSE) + { + return sprintf($line, $field, $param); + } + + return str_replace(array('{field}', '{param}'), array($field, $param), $line); + } + + // -------------------------------------------------------------------- + + /** + * Checks if the rule is present within the validator + * + * Permits you to check if a rule is present within the validator + * + * @param string the field name + * @return bool + */ + public function has_rule($field) + { + return isset($this->_field_data[$field]); + } + + // -------------------------------------------------------------------- + + /** * Get the value from a form * * Permits you to repopulate a form field with the value it was submitted * with, or, if that value doesn't exist, with the default * - * @access public * @param string the field name * @param string - * @return void + * @return string */ public function set_value($field = '', $default = '') { - if ( ! isset($this->_field_data[$field])) + if ( ! isset($this->_field_data[$field], $this->_field_data[$field]['postdata'])) { return $default; } // If the data is an array output them one at a time. - // E.g: form_input('name[]', set_value('name[]'); + // E.g: form_input('name[]', set_value('name[]'); if (is_array($this->_field_data[$field]['postdata'])) { return array_shift($this->_field_data[$field]['postdata']); @@ -751,37 +949,36 @@ class CI_Form_validation { * Enables pull-down lists to be set to the value the user * selected in the event of an error * - * @access public * @param string * @param string + * @param bool * @return string */ public function set_select($field = '', $value = '', $default = FALSE) { - if ( ! isset($this->_field_data[$field]) OR ! isset($this->_field_data[$field]['postdata'])) + if ( ! isset($this->_field_data[$field], $this->_field_data[$field]['postdata'])) { - if ($default === TRUE AND count($this->_field_data) === 0) - { - return ' selected="selected"'; - } - return ''; + return ($default === TRUE && count($this->_field_data) === 0) ? ' selected="selected"' : ''; } $field = $this->_field_data[$field]['postdata']; - + $value = (string) $value; if (is_array($field)) { - if ( ! in_array($value, $field)) + // Note: in_array('', array(0)) returns TRUE, do not use it + foreach ($field as &$v) { - return ''; + if ($value === $v) + { + return ' selected="selected"'; + } } + + return ''; } - else + elseif (($field === '' OR $value === '') OR ($field !== $value)) { - if (($field == '' OR $value == '') OR ($field != $value)) - { - return ''; - } + return ''; } return ' selected="selected"'; @@ -795,37 +992,36 @@ class CI_Form_validation { * Enables radio buttons to be set to the value the user * selected in the event of an error * - * @access public * @param string * @param string + * @param bool * @return string */ public function set_radio($field = '', $value = '', $default = FALSE) { - if ( ! isset($this->_field_data[$field]) OR ! isset($this->_field_data[$field]['postdata'])) + if ( ! isset($this->_field_data[$field], $this->_field_data[$field]['postdata'])) { - if ($default === TRUE AND count($this->_field_data) === 0) - { - return ' checked="checked"'; - } - return ''; + return ($default === TRUE && count($this->_field_data) === 0) ? ' checked="checked"' : ''; } $field = $this->_field_data[$field]['postdata']; - + $value = (string) $value; if (is_array($field)) { - if ( ! in_array($value, $field)) + // Note: in_array('', array(0)) returns TRUE, do not use it + foreach ($field as &$v) { - return ''; + if ($value === $v) + { + return ' checked="checked"'; + } } + + return ''; } - else + elseif (($field === '' OR $value === '') OR ($field !== $value)) { - if (($field == '' OR $value == '') OR ($field != $value)) - { - return ''; - } + return ''; } return ' checked="checked"'; @@ -839,40 +1035,15 @@ class CI_Form_validation { * Enables checkboxes to be set to the value the user * selected in the event of an error * - * @access public * @param string * @param string + * @param bool * @return string */ public function set_checkbox($field = '', $value = '', $default = FALSE) { - if ( ! isset($this->_field_data[$field]) OR ! isset($this->_field_data[$field]['postdata'])) - { - if ($default === TRUE AND count($this->_field_data) === 0) - { - return ' checked="checked"'; - } - return ''; - } - - $field = $this->_field_data[$field]['postdata']; - - if (is_array($field)) - { - if ( ! in_array($value, $field)) - { - return ''; - } - } - else - { - if (($field == '' OR $value == '') OR ($field != $value)) - { - return ''; - } - } - - return ' checked="checked"'; + // Logic is exactly the same as for radio fields + return $this->set_radio($field, $value, $default); } // -------------------------------------------------------------------- @@ -880,20 +1051,14 @@ class CI_Form_validation { /** * Required * - * @access public * @param string * @return bool */ public function required($str) { - if ( ! is_array($str)) - { - return (trim($str) == '') ? FALSE : TRUE; - } - else - { - return ( ! empty($str)); - } + return is_array($str) + ? (empty($str) === FALSE) + : (trim((string) $str) !== ''); } // -------------------------------------------------------------------- @@ -901,19 +1066,13 @@ class CI_Form_validation { /** * Performs a Regular Expression match test. * - * @access public * @param string - * @param regex + * @param string regex * @return bool */ public function regex_match($str, $regex) { - if ( ! preg_match($regex, $str)) - { - return FALSE; - } - - return TRUE; + return (bool) preg_match($regex, $str); } // -------------------------------------------------------------------- @@ -921,64 +1080,68 @@ class CI_Form_validation { /** * Match one field to another * - * @access public - * @param string - * @param field + * @param string $str string to compare against + * @param string $field * @return bool */ public function matches($str, $field) { - if ( ! isset($_POST[$field])) - { - return FALSE; - } + return isset($this->_field_data[$field], $this->_field_data[$field]['postdata']) + ? ($str === $this->_field_data[$field]['postdata']) + : FALSE; + } - $field = $_POST[$field]; + // -------------------------------------------------------------------- - return ($str !== $field) ? FALSE : TRUE; + /** + * Differs from another field + * + * @param string + * @param string field + * @return bool + */ + public function differs($str, $field) + { + return ! (isset($this->_field_data[$field]) && $this->_field_data[$field]['postdata'] === $str); } - + // -------------------------------------------------------------------- /** - * Match one field to another + * Is Unique * - * @access public - * @param string - * @param field + * Check if the input value doesn't already exist + * in the specified database field. + * + * @param string $str + * @param string $field * @return bool */ public function is_unique($str, $field) { - list($table, $field)=explode('.', $field); - $query = $this->CI->db->limit(1)->get_where($table, array($field => $str)); - - return $query->num_rows() === 0; - } + sscanf($field, '%[^.].%[^.]', $table, $field); + return isset($this->CI->db) + ? ($this->CI->db->limit(1)->get_where($table, array($field => $str))->num_rows() === 0) + : FALSE; + } // -------------------------------------------------------------------- /** * Minimum Length * - * @access public * @param string - * @param value + * @param string * @return bool */ public function min_length($str, $val) { - if (preg_match("/[^0-9]/", $val)) + if ( ! is_numeric($val)) { return FALSE; } - if (function_exists('mb_strlen')) - { - return (mb_strlen($str) < $val) ? FALSE : TRUE; - } - - return (strlen($str) < $val) ? FALSE : TRUE; + return ($val <= mb_strlen($str)); } // -------------------------------------------------------------------- @@ -986,24 +1149,18 @@ class CI_Form_validation { /** * Max Length * - * @access public * @param string - * @param value + * @param string * @return bool */ public function max_length($str, $val) { - if (preg_match("/[^0-9]/", $val)) + if ( ! is_numeric($val)) { return FALSE; } - if (function_exists('mb_strlen')) - { - return (mb_strlen($str) > $val) ? FALSE : TRUE; - } - - return (strlen($str) > $val) ? FALSE : TRUE; + return ($val >= mb_strlen($str)); } // -------------------------------------------------------------------- @@ -1011,24 +1168,64 @@ class CI_Form_validation { /** * Exact Length * - * @access public * @param string - * @param value + * @param string * @return bool */ public function exact_length($str, $val) { - if (preg_match("/[^0-9]/", $val)) + if ( ! is_numeric($val)) + { + return FALSE; + } + + return (mb_strlen($str) === (int) $val); + } + + // -------------------------------------------------------------------- + + /** + * Valid URL + * + * @param string $str + * @return bool + */ + public function valid_url($str) + { + if (empty($str)) + { + return FALSE; + } + elseif (preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $str, $matches)) + { + if (empty($matches[2])) + { + return FALSE; + } + elseif ( ! in_array(strtolower($matches[1]), array('http', 'https'), TRUE)) + { + return FALSE; + } + + $str = $matches[2]; + } + + // Apparently, FILTER_VALIDATE_URL doesn't reject digit-only names for some reason ... + // See https://github.com/bcit-ci/CodeIgniter/issues/5755 + if (ctype_digit($str)) { return FALSE; } - if (function_exists('mb_strlen')) + // PHP 7 accepts IPv6 addresses within square brackets as hostnames, + // but it appears that the PR that came in with https://bugs.php.net/bug.php?id=68039 + // was never merged into a PHP 5 branch ... https://3v4l.org/8PsSN + if (preg_match('/^\[([^\]]+)\]/', $str, $matches) && ! is_php('7') && filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== FALSE) { - return (mb_strlen($str) != $val) ? FALSE : TRUE; + $str = 'ipv6.host'.substr($str, strlen($matches[1]) + 2); } - return (strlen($str) != $val) ? FALSE : TRUE; + return (filter_var('http://'.$str, FILTER_VALIDATE_URL) !== FALSE); } // -------------------------------------------------------------------- @@ -1036,13 +1233,24 @@ class CI_Form_validation { /** * Valid Email * - * @access public * @param string * @return bool */ public function valid_email($str) { - return ( ! preg_match("/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix", $str)) ? FALSE : TRUE; + if (function_exists('idn_to_ascii') && preg_match('#\A([^@]+)@(.+)\z#', $str, $matches)) + { + $domain = defined('INTL_IDNA_VARIANT_UTS46') + ? idn_to_ascii($matches[2], 0, INTL_IDNA_VARIANT_UTS46) + : idn_to_ascii($matches[2]); + + if ($domain !== FALSE) + { + $str = $matches[1].'@'.$domain; + } + } + + return (bool) filter_var($str, FILTER_VALIDATE_EMAIL); } // -------------------------------------------------------------------- @@ -1050,7 +1258,6 @@ class CI_Form_validation { /** * Valid Emails * - * @access public * @param string * @return bool */ @@ -1063,7 +1270,7 @@ class CI_Form_validation { foreach (explode(',', $str) as $email) { - if (trim($email) != '' && $this->valid_email(trim($email)) === FALSE) + if (trim($email) !== '' && $this->valid_email(trim($email)) === FALSE) { return FALSE; } @@ -1077,10 +1284,9 @@ class CI_Form_validation { /** * Validate IP Address * - * @access public * @param string - * @param string "ipv4" or "ipv6" to validate a specific ip format - * @return string + * @param string 'ipv4' or 'ipv6' to validate a specific IP format + * @return bool */ public function valid_ip($ip, $which = '') { @@ -1090,15 +1296,39 @@ class CI_Form_validation { // -------------------------------------------------------------------- /** + * Validate MAC address + * + * @param string $mac + * @return bool + */ + public function valid_mac($mac) + { + if ( ! is_php('5.5')) + { + // Most common format, with either dash or colon delimiters + if (preg_match('#\A[0-9a-f]{2}(?<delimiter>[:-])([0-9a-f]{2}(?P=delimiter)){4}[0-9a-f]{2}\z#i', $mac)) + { + return TRUE; + } + + // The less common format; e.g. 0123.4567.89ab + return (bool) preg_match('#((\A|\.)[0-9a-f]{4}){3}\z#i', $mac); + } + + return (bool) filter_var($mac, FILTER_VALIDATE_MAC); + } + + // -------------------------------------------------------------------- + + /** * Alpha * - * @access public * @param string * @return bool */ public function alpha($str) { - return ( ! preg_match("/^([a-z])+$/i", $str)) ? FALSE : TRUE; + return ctype_alpha($str); } // -------------------------------------------------------------------- @@ -1106,56 +1336,52 @@ class CI_Form_validation { /** * Alpha-numeric * - * @access public * @param string * @return bool */ public function alpha_numeric($str) { - return ( ! preg_match("/^([a-z0-9])+$/i", $str)) ? FALSE : TRUE; + return ctype_alnum((string) $str); } // -------------------------------------------------------------------- /** - * Alpha-numeric with underscores and dashes + * Alpha-numeric w/ spaces * - * @access public * @param string * @return bool */ - public function alpha_dash($str) + public function alpha_numeric_spaces($str) { - return ( ! preg_match("/^([-a-z0-9_-])+$/i", $str)) ? FALSE : TRUE; + return (bool) preg_match('/^[A-Z0-9 ]+$/i', $str); } // -------------------------------------------------------------------- /** - * Numeric + * Alpha-numeric with underscores and dashes * - * @access public * @param string * @return bool */ - public function numeric($str) + public function alpha_dash($str) { - return (bool)preg_match( '/^[\-+]?[0-9]*\.?[0-9]+$/', $str); - + return (bool) preg_match('/^[a-z0-9_-]+$/i', $str); } // -------------------------------------------------------------------- /** - * Is Numeric + * Numeric * - * @access public * @param string * @return bool */ - public function is_numeric($str) + public function numeric($str) { - return ( ! is_numeric($str)) ? FALSE : TRUE; + return (bool) preg_match('/^[\-+]?[0-9]*\.?[0-9]+$/', $str); + } // -------------------------------------------------------------------- @@ -1163,7 +1389,6 @@ class CI_Form_validation { /** * Integer * - * @access public * @param string * @return bool */ @@ -1177,7 +1402,6 @@ class CI_Form_validation { /** * Decimal number * - * @access public * @param string * @return bool */ @@ -1189,19 +1413,29 @@ class CI_Form_validation { // -------------------------------------------------------------------- /** - * Greather than + * Greater than * - * @access public * @param string + * @param int * @return bool */ public function greater_than($str, $min) { - if ( ! is_numeric($str)) - { - return FALSE; - } - return $str > $min; + return is_numeric($str) ? ($str > $min) : FALSE; + } + + // -------------------------------------------------------------------- + + /** + * Equal to or Greater than + * + * @param string + * @param int + * @return bool + */ + public function greater_than_equal_to($str, $min) + { + return is_numeric($str) ? ($str >= $min) : FALSE; } // -------------------------------------------------------------------- @@ -1209,104 +1443,83 @@ class CI_Form_validation { /** * Less than * - * @access public * @param string + * @param int * @return bool */ public function less_than($str, $max) { - if ( ! is_numeric($str)) - { - return FALSE; - } - return $str < $max; + return is_numeric($str) ? ($str < $max) : FALSE; } // -------------------------------------------------------------------- /** - * Is a Natural number (0,1,2,3, etc.) + * Equal to or Less than * - * @access public * @param string + * @param int * @return bool */ - public function is_natural($str) + public function less_than_equal_to($str, $max) { - return (bool) preg_match( '/^[0-9]+$/', $str); + return is_numeric($str) ? ($str <= $max) : FALSE; } // -------------------------------------------------------------------- /** - * Is a Natural number, but not a zero (1,2,3, etc.) + * Value should be within an array of values * - * @access public + * @param string * @param string * @return bool */ - public function is_natural_no_zero($str) + public function in_list($value, $list) { - if ( ! preg_match( '/^[0-9]+$/', $str)) - { - return FALSE; - } + return in_array($value, explode(',', $list), TRUE); + } - if ($str == 0) - { - return FALSE; - } + // -------------------------------------------------------------------- - return TRUE; + /** + * Is a Natural number (0,1,2,3, etc.) + * + * @param string + * @return bool + */ + public function is_natural($str) + { + return ctype_digit((string) $str); } // -------------------------------------------------------------------- /** - * Valid Base64 - * - * Tests a string for characters outside of the Base64 alphabet - * as defined by RFC 2045 http://www.faqs.org/rfcs/rfc2045 + * Is a Natural number, but not a zero (1,2,3, etc.) * - * @access public * @param string * @return bool */ - public function valid_base64($str) + public function is_natural_no_zero($str) { - return (bool) ! preg_match('/[^a-zA-Z0-9\/\+=]/', $str); + return ($str != 0 && ctype_digit((string) $str)); } // -------------------------------------------------------------------- /** - * Prep data for form + * Valid Base64 * - * This function allows HTML to be safely shown in a form. - * Special characters are converted. + * Tests a string for characters outside of the Base64 alphabet + * as defined by RFC 2045 http://www.faqs.org/rfcs/rfc2045 * - * @access public * @param string - * @return string + * @return bool */ - public function prep_for_form($data = '') + public function valid_base64($str) { - if (is_array($data)) - { - foreach ($data as $key => $val) - { - $data[$key] = $this->prep_for_form($val); - } - - return $data; - } - - if ($this->_safe_form_data == FALSE OR $data === '') - { - return $data; - } - - return str_replace(array("'", '"', '<', '>'), array("'", """, '<', '>'), stripslashes($data)); + return (base64_encode(base64_decode($str)) === $str); } // -------------------------------------------------------------------- @@ -1314,20 +1527,14 @@ class CI_Form_validation { /** * Prep URL * - * @access public * @param string * @return string */ public function prep_url($str = '') { - if ($str == 'http://' OR $str == '') - { - return ''; - } - - if (substr($str, 0, 7) != 'http://' && substr($str, 0, 8) != 'https://') + if ($str !== '' && stripos($str, 'http://') !== 0 && stripos($str, 'https://') !== 0) { - $str = 'http://'.$str; + return 'http://'.$str; } return $str; @@ -1338,45 +1545,44 @@ class CI_Form_validation { /** * Strip Image Tags * - * @access public * @param string * @return string */ public function strip_image_tags($str) { - return $this->CI->input->strip_image_tags($str); + return $this->CI->security->strip_image_tags($str); } // -------------------------------------------------------------------- /** - * XSS Clean + * Convert PHP tags to entities * - * @access public * @param string * @return string */ - public function xss_clean($str) + public function encode_php_tags($str) { - return $this->CI->security->xss_clean($str); + return str_replace(array('<?', '?>'), array('<?', '?>'), $str); } // -------------------------------------------------------------------- /** - * Convert PHP tags to entities + * Reset validation vars * - * @access public - * @param string - * @return string + * Prevents subsequent validation routines from being affected by the + * results of any previous validation routine due to the CI singleton. + * + * @return CI_Form_validation */ - public function encode_php_tags($str) + public function reset_validation() { - return str_replace(array('<?php', '<?PHP', '<?', '?>'), array('<?php', '<?PHP', '<?', '?>'), $str); + $this->_field_data = array(); + $this->_error_array = array(); + $this->_error_messages = array(); + $this->error_string = ''; + return $this; } } -// END Form Validation Class - -/* End of file Form_validation.php */ -/* Location: ./system/libraries/Form_validation.php */ diff --git a/system/libraries/Ftp.php b/system/libraries/Ftp.php index 1656dfb47..15a0887b7 100644 --- a/system/libraries/Ftp.php +++ b/system/libraries/Ftp.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * FTP Class @@ -21,33 +44,76 @@ * @package CodeIgniter * @subpackage Libraries * @category Libraries - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/ftp.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/ftp.html */ class CI_FTP { - var $hostname = ''; - var $username = ''; - var $password = ''; - var $port = 21; - var $passive = TRUE; - var $debug = FALSE; - var $conn_id = FALSE; + /** + * FTP Server hostname + * + * @var string + */ + public $hostname = ''; + + /** + * FTP Username + * + * @var string + */ + public $username = ''; + + /** + * FTP Password + * + * @var string + */ + public $password = ''; + + /** + * FTP Server port + * + * @var int + */ + public $port = 21; + + /** + * Passive mode flag + * + * @var bool + */ + public $passive = TRUE; + + /** + * Debug flag + * + * Specifies whether to display error messages. + * + * @var bool + */ + public $debug = FALSE; + // -------------------------------------------------------------------- /** - * Constructor - Sets Preferences + * Connection ID * - * The constructor can be passed an array of config values + * @var resource + */ + protected $conn_id; + + // -------------------------------------------------------------------- + + /** + * Constructor + * + * @param array $config + * @return void */ public function __construct($config = array()) { - if (count($config) > 0) - { - $this->initialize($config); - } - - log_message('debug', "FTP Class Initialized"); + empty($config) OR $this->initialize($config); + log_message('info', 'FTP Class Initialized'); } // -------------------------------------------------------------------- @@ -55,11 +121,10 @@ class CI_FTP { /** * Initialize preferences * - * @access public - * @param array + * @param array $config * @return void */ - function initialize($config = array()) + public function initialize($config = array()) { foreach ($config as $key => $val) { @@ -78,11 +143,10 @@ class CI_FTP { /** * FTP Connect * - * @access public - * @param array the connection values + * @param array $config Connection values * @return bool */ - function connect($config = array()) + public function connect($config = array()) { if (count($config) > 0) { @@ -91,24 +155,26 @@ class CI_FTP { if (FALSE === ($this->conn_id = @ftp_connect($this->hostname, $this->port))) { - if ($this->debug == TRUE) + if ($this->debug === TRUE) { $this->_error('ftp_unable_to_connect'); } + return FALSE; } if ( ! $this->_login()) { - if ($this->debug == TRUE) + if ($this->debug === TRUE) { $this->_error('ftp_unable_to_login'); } + return FALSE; } // Set passive mode if needed - if ($this->passive == TRUE) + if ($this->passive === TRUE) { ftp_pasv($this->conn_id, TRUE); } @@ -121,10 +187,9 @@ class CI_FTP { /** * FTP Login * - * @access private * @return bool */ - function _login() + protected function _login() { return @ftp_login($this->conn_id, $this->username, $this->password); } @@ -134,42 +199,41 @@ class CI_FTP { /** * Validates the connection ID * - * @access private * @return bool */ - function _is_conn() + protected function _is_conn() { - if ( ! is_resource($this->conn_id)) + if ($this->conn_id === FALSE) { - if ($this->debug == TRUE) + if ($this->debug === TRUE) { $this->_error('ftp_no_connection'); } + return FALSE; } + return TRUE; } // -------------------------------------------------------------------- - /** * Change directory * * The second parameter lets us momentarily turn off debugging so that * this function can be used to test for the existence of a folder - * without throwing an error. There's no FTP equivalent to is_dir() + * without throwing an error. There's no FTP equivalent to is_dir() * so we do it by trying to change to a particular directory. * Internally, this parameter is only used by the "mirror" function below. * - * @access public - * @param string - * @param bool + * @param string $path + * @param bool $suppress_debug * @return bool */ - function changedir($path = '', $supress_debug = FALSE) + public function changedir($path, $suppress_debug = FALSE) { - if ($path == '' OR ! $this->_is_conn()) + if ( ! $this->_is_conn()) { return FALSE; } @@ -178,10 +242,11 @@ class CI_FTP { if ($result === FALSE) { - if ($this->debug == TRUE AND $supress_debug == FALSE) + if ($this->debug === TRUE && $suppress_debug === FALSE) { $this->_error('ftp_unable_to_changedir'); } + return FALSE; } @@ -193,13 +258,13 @@ class CI_FTP { /** * Create a directory * - * @access public - * @param string + * @param string $path + * @param int $permissions * @return bool */ - function mkdir($path = '', $permissions = NULL) + public function mkdir($path, $permissions = NULL) { - if ($path == '' OR ! $this->_is_conn()) + if ($path === '' OR ! $this->_is_conn()) { return FALSE; } @@ -208,17 +273,18 @@ class CI_FTP { if ($result === FALSE) { - if ($this->debug == TRUE) + if ($this->debug === TRUE) { - $this->_error('ftp_unable_to_makdir'); + $this->_error('ftp_unable_to_mkdir'); } + return FALSE; } // Set file permissions if needed - if ( ! is_null($permissions)) + if ($permissions !== NULL) { - $this->chmod($path, (int)$permissions); + $this->chmod($path, (int) $permissions); } return TRUE; @@ -229,13 +295,13 @@ class CI_FTP { /** * Upload a file to the server * - * @access public - * @param string - * @param string - * @param string + * @param string $locpath + * @param string $rempath + * @param string $mode + * @param int $permissions * @return bool */ - function upload($locpath, $rempath, $mode = 'auto', $permissions = NULL) + public function upload($locpath, $rempath, $mode = 'auto', $permissions = NULL) { if ( ! $this->_is_conn()) { @@ -249,30 +315,31 @@ class CI_FTP { } // Set the mode if not specified - if ($mode == 'auto') + if ($mode === 'auto') { // Get the file extension so we can set the upload type $ext = $this->_getext($locpath); $mode = $this->_settype($ext); } - $mode = ($mode == 'ascii') ? FTP_ASCII : FTP_BINARY; + $mode = ($mode === 'ascii') ? FTP_ASCII : FTP_BINARY; $result = @ftp_put($this->conn_id, $rempath, $locpath, $mode); if ($result === FALSE) { - if ($this->debug == TRUE) + if ($this->debug === TRUE) { $this->_error('ftp_unable_to_upload'); } + return FALSE; } // Set file permissions if needed - if ( ! is_null($permissions)) + if ($permissions !== NULL) { - $this->chmod($rempath, (int)$permissions); + $this->chmod($rempath, (int) $permissions); } return TRUE; @@ -283,13 +350,12 @@ class CI_FTP { /** * Download a file from a remote server to the local server * - * @access public - * @param string - * @param string - * @param string + * @param string $rempath + * @param string $locpath + * @param string $mode * @return bool */ - function download($rempath, $locpath, $mode = 'auto') + public function download($rempath, $locpath, $mode = 'auto') { if ( ! $this->_is_conn()) { @@ -297,23 +363,24 @@ class CI_FTP { } // Set the mode if not specified - if ($mode == 'auto') + if ($mode === 'auto') { // Get the file extension so we can set the upload type $ext = $this->_getext($rempath); $mode = $this->_settype($ext); } - $mode = ($mode == 'ascii') ? FTP_ASCII : FTP_BINARY; + $mode = ($mode === 'ascii') ? FTP_ASCII : FTP_BINARY; $result = @ftp_get($this->conn_id, $locpath, $rempath, $mode); if ($result === FALSE) { - if ($this->debug == TRUE) + if ($this->debug === TRUE) { $this->_error('ftp_unable_to_download'); } + return FALSE; } @@ -325,13 +392,12 @@ class CI_FTP { /** * Rename (or move) a file * - * @access public - * @param string - * @param string - * @param bool + * @param string $old_file + * @param string $new_file + * @param bool $move * @return bool */ - function rename($old_file, $new_file, $move = FALSE) + public function rename($old_file, $new_file, $move = FALSE) { if ( ! $this->_is_conn()) { @@ -342,12 +408,11 @@ class CI_FTP { if ($result === FALSE) { - if ($this->debug == TRUE) + if ($this->debug === TRUE) { - $msg = ($move == FALSE) ? 'ftp_unable_to_rename' : 'ftp_unable_to_move'; - - $this->_error($msg); + $this->_error('ftp_unable_to_'.($move === FALSE ? 'rename' : 'move')); } + return FALSE; } @@ -359,12 +424,11 @@ class CI_FTP { /** * Move a file * - * @access public - * @param string - * @param string + * @param string $old_file + * @param string $new_file * @return bool */ - function move($old_file, $new_file) + public function move($old_file, $new_file) { return $this->rename($old_file, $new_file, TRUE); } @@ -374,11 +438,10 @@ class CI_FTP { /** * Rename (or move) a file * - * @access public - * @param string + * @param string $filepath * @return bool */ - function delete_file($filepath) + public function delete_file($filepath) { if ( ! $this->_is_conn()) { @@ -389,10 +452,11 @@ class CI_FTP { if ($result === FALSE) { - if ($this->debug == TRUE) + if ($this->debug === TRUE) { $this->_error('ftp_unable_to_delete'); } + return FALSE; } @@ -403,13 +467,12 @@ class CI_FTP { /** * Delete a folder and recursively delete everything (including sub-folders) - * containted within it. + * contained within it. * - * @access public - * @param string + * @param string $filepath * @return bool */ - function delete_dir($filepath) + public function delete_dir($filepath) { if ( ! $this->_is_conn()) { @@ -417,31 +480,29 @@ class CI_FTP { } // Add a trailing slash to the file path if needed - $filepath = preg_replace("/(.+?)\/*$/", "\\1/", $filepath); + $filepath = preg_replace('/(.+?)\/*$/', '\\1/', $filepath); $list = $this->list_files($filepath); - - if ($list !== FALSE AND count($list) > 0) + if ( ! empty($list)) { - foreach ($list as $item) + for ($i = 0, $c = count($list); $i < $c; $i++) { - // If we can't delete the item it's probaly a folder so - // we'll recursively call delete_dir() - if ( ! @ftp_delete($this->conn_id, $item)) + // If we can't delete the item it's probably a directory, + // so we'll recursively call delete_dir() + if ( ! preg_match('#/\.\.?$#', $list[$i]) && ! @ftp_delete($this->conn_id, $list[$i])) { - $this->delete_dir($item); + $this->delete_dir($filepath.$list[$i]); } } } - $result = @ftp_rmdir($this->conn_id, $filepath); - - if ($result === FALSE) + if (@ftp_rmdir($this->conn_id, $filepath) === FALSE) { - if ($this->debug == TRUE) + if ($this->debug === TRUE) { $this->_error('ftp_unable_to_delete'); } + return FALSE; } @@ -453,36 +514,24 @@ class CI_FTP { /** * Set file permissions * - * @access public - * @param string the file path - * @param string the permissions + * @param string $path File path + * @param int $perm Permissions * @return bool */ - function chmod($path, $perm) + public function chmod($path, $perm) { if ( ! $this->_is_conn()) { return FALSE; } - // Permissions can only be set when running PHP 5 - if ( ! function_exists('ftp_chmod')) + if (@ftp_chmod($this->conn_id, $perm, $path) === FALSE) { - if ($this->debug == TRUE) + if ($this->debug === TRUE) { $this->_error('ftp_unable_to_chmod'); } - return FALSE; - } - - $result = @ftp_chmod($this->conn_id, $perm, $path); - if ($result === FALSE) - { - if ($this->debug == TRUE) - { - $this->_error('ftp_unable_to_chmod'); - } return FALSE; } @@ -494,17 +543,14 @@ class CI_FTP { /** * FTP List files in the specified directory * - * @access public + * @param string $path * @return array */ - function list_files($path = '.') + public function list_files($path = '.') { - if ( ! $this->_is_conn()) - { - return FALSE; - } - - return ftp_nlist($this->conn_id, $path); + return $this->_is_conn() + ? ftp_nlist($this->conn_id, $path) + : FALSE; } // ------------------------------------------------------------------------ @@ -512,16 +558,16 @@ class CI_FTP { /** * Read a directory and recreate it remotely * - * This function recursively reads a folder and everything it contains (including - * sub-folders) and creates a mirror via FTP based on it. Whatever the directory structure - * of the original file path will be recreated on the server. + * This function recursively reads a folder and everything it contains + * (including sub-folders) and creates a mirror via FTP based on it. + * Whatever the directory structure of the original file path will be + * recreated on the server. * - * @access public - * @param string path to source with trailing slash - * @param string path to destination - include the base folder with trailing slash + * @param string $locpath Path to source with trailing slash + * @param string $rempath Path to destination - include the base folder with trailing slash * @return bool */ - function mirror($locpath, $rempath) + public function mirror($locpath, $rempath) { if ( ! $this->_is_conn()) { @@ -531,24 +577,20 @@ class CI_FTP { // Open the local file path if ($fp = @opendir($locpath)) { - // Attempt to open the remote file path. - if ( ! $this->changedir($rempath, TRUE)) + // Attempt to open the remote file path and try to create it, if it doesn't exist + if ( ! $this->changedir($rempath, TRUE) && ( ! $this->mkdir($rempath) OR ! $this->changedir($rempath))) { - // If it doesn't exist we'll attempt to create the direcotory - if ( ! $this->mkdir($rempath) OR ! $this->changedir($rempath)) - { - return FALSE; - } + return FALSE; } // Recursively read the local directory while (FALSE !== ($file = readdir($fp))) { - if (@is_dir($locpath.$file) && substr($file, 0, 1) != '.') + if (is_dir($locpath.$file) && $file[0] !== '.') { - $this->mirror($locpath.$file."/", $rempath.$file."/"); + $this->mirror($locpath.$file.'/', $rempath.$file.'/'); } - elseif (substr($file, 0, 1) != ".") + elseif ($file[0] !== '.') { // Get the file extension so we can se the upload type $ext = $this->_getext($file); @@ -557,63 +599,41 @@ class CI_FTP { $this->upload($locpath.$file, $rempath.$file, $mode); } } + return TRUE; } return FALSE; } - // -------------------------------------------------------------------- /** * Extract the file extension * - * @access private - * @param string + * @param string $filename * @return string */ - function _getext($filename) + protected function _getext($filename) { - if (FALSE === strpos($filename, '.')) - { - return 'txt'; - } - - $x = explode('.', $filename); - return end($x); + return (($dot = strrpos($filename, '.')) === FALSE) + ? 'txt' + : substr($filename, $dot + 1); } - // -------------------------------------------------------------------- /** * Set the upload type * - * @access private - * @param string + * @param string $ext Filename extension * @return string */ - function _settype($ext) + protected function _settype($ext) { - $text_types = array( - 'txt', - 'text', - 'php', - 'phps', - 'php4', - 'js', - 'css', - 'htm', - 'html', - 'phtml', - 'shtml', - 'log', - 'xml' - ); - - - return (in_array($ext, $text_types)) ? 'ascii' : 'binary'; + return in_array($ext, array('txt', 'text', 'php', 'phps', 'php4', 'js', 'css', 'htm', 'html', 'phtml', 'shtml', 'log', 'xml'), TRUE) + ? 'ascii' + : 'binary'; } // ------------------------------------------------------------------------ @@ -621,19 +641,13 @@ class CI_FTP { /** * Close the connection * - * @access public - * @param string path to source - * @param string path to destination * @return bool */ - function close() + public function close() { - if ( ! $this->_is_conn()) - { - return FALSE; - } - - @ftp_close($this->conn_id); + return $this->_is_conn() + ? @ftp_close($this->conn_id) + : FALSE; } // ------------------------------------------------------------------------ @@ -641,20 +655,14 @@ class CI_FTP { /** * Display error message * - * @access private - * @param string - * @return bool + * @param string $line + * @return void */ - function _error($line) + protected function _error($line) { $CI =& get_instance(); $CI->lang->load('ftp'); show_error($CI->lang->line($line)); } - } -// END FTP Class - -/* End of file Ftp.php */ -/* Location: ./system/libraries/Ftp.php */
\ No newline at end of file diff --git a/system/libraries/Image_lib.php b/system/libraries/Image_lib.php index eccfe41c7..4e5fc7be6 100644 --- a/system/libraries/Image_lib.php +++ b/system/libraries/Image_lib.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Image Manipulation class @@ -21,65 +44,346 @@ * @package CodeIgniter * @subpackage Libraries * @category Image_lib - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/image_lib.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/image_lib.html */ class CI_Image_lib { - var $image_library = 'gd2'; // Can be: imagemagick, netpbm, gd, gd2 - var $library_path = ''; - var $dynamic_output = FALSE; // Whether to send to browser or write to disk - var $source_image = ''; - var $new_image = ''; - var $width = ''; - var $height = ''; - var $quality = '90'; - var $create_thumb = FALSE; - var $thumb_marker = '_thumb'; - var $maintain_ratio = TRUE; // Whether to maintain aspect ratio when resizing or use hard values - var $master_dim = 'auto'; // auto, height, or width. Determines what to use as the master dimension - var $rotation_angle = ''; - var $x_axis = ''; - var $y_axis = ''; + /** + * PHP extension/library to use for image manipulation + * Can be: imagemagick, netpbm, gd, gd2 + * + * @var string + */ + public $image_library = 'gd2'; + + /** + * Path to the graphic library (if applicable) + * + * @var string + */ + public $library_path = ''; + + /** + * Whether to send to browser or write to disk + * + * @var bool + */ + public $dynamic_output = FALSE; + + /** + * Path to original image + * + * @var string + */ + public $source_image = ''; + + /** + * Path to the modified image + * + * @var string + */ + public $new_image = ''; + + /** + * Image width + * + * @var int + */ + public $width = ''; + + /** + * Image height + * + * @var int + */ + public $height = ''; + + /** + * Quality percentage of new image + * + * @var int + */ + public $quality = 90; + + /** + * Whether to create a thumbnail + * + * @var bool + */ + public $create_thumb = FALSE; + + /** + * String to add to thumbnail version of image + * + * @var string + */ + public $thumb_marker = '_thumb'; + + /** + * Whether to maintain aspect ratio when resizing or use hard values + * + * @var bool + */ + public $maintain_ratio = TRUE; + + /** + * auto, height, or width. Determines what to use as the master dimension + * + * @var string + */ + public $master_dim = 'auto'; + + /** + * Angle at to rotate image + * + * @var string + */ + public $rotation_angle = ''; + + /** + * X Coordinate for manipulation of the current image + * + * @var int + */ + public $x_axis = ''; + + /** + * Y Coordinate for manipulation of the current image + * + * @var int + */ + public $y_axis = ''; + // -------------------------------------------------------------------------- // Watermark Vars - var $wm_text = ''; // Watermark text if graphic is not used - var $wm_type = 'text'; // Type of watermarking. Options: text/overlay - var $wm_x_transp = 4; - var $wm_y_transp = 4; - var $wm_overlay_path = ''; // Watermark image path - var $wm_font_path = ''; // TT font - var $wm_font_size = 17; // Font size (different versions of GD will either use points or pixels) - var $wm_vrt_alignment = 'B'; // Vertical alignment: T M B - var $wm_hor_alignment = 'C'; // Horizontal alignment: L R C - var $wm_padding = 0; // Padding around text - var $wm_hor_offset = 0; // Lets you push text to the right - var $wm_vrt_offset = 0; // Lets you push text down - var $wm_font_color = '#ffffff'; // Text color - var $wm_shadow_color = ''; // Dropshadow color - var $wm_shadow_distance = 2; // Dropshadow distance - var $wm_opacity = 50; // Image opacity: 1 - 100 Only works with image + // -------------------------------------------------------------------------- + + /** + * Watermark text if graphic is not used + * + * @var string + */ + public $wm_text = ''; + + /** + * Type of watermarking. Options: text/overlay + * + * @var string + */ + public $wm_type = 'text'; + + /** + * Default transparency for watermark + * + * @var int + */ + public $wm_x_transp = 4; + + /** + * Default transparency for watermark + * + * @var int + */ + public $wm_y_transp = 4; + + /** + * Watermark image path + * + * @var string + */ + public $wm_overlay_path = ''; + + /** + * TT font + * + * @var string + */ + public $wm_font_path = ''; + + /** + * Font size (different versions of GD will either use points or pixels) + * + * @var int + */ + public $wm_font_size = 17; + + /** + * Vertical alignment: T M B + * + * @var string + */ + public $wm_vrt_alignment = 'B'; + + /** + * Horizontal alignment: L R C + * + * @var string + */ + public $wm_hor_alignment = 'C'; + + /** + * Padding around text + * + * @var int + */ + public $wm_padding = 0; + + /** + * Lets you push text to the right + * + * @var int + */ + public $wm_hor_offset = 0; + + /** + * Lets you push text down + * + * @var int + */ + public $wm_vrt_offset = 0; + + /** + * Text color + * + * @var string + */ + protected $wm_font_color = '#ffffff'; + + /** + * Dropshadow color + * + * @var string + */ + protected $wm_shadow_color = ''; + + /** + * Dropshadow distance + * + * @var int + */ + public $wm_shadow_distance = 2; + + /** + * Image opacity: 1 - 100 Only works with image + * + * @var int + */ + public $wm_opacity = 50; + // -------------------------------------------------------------------------- // Private Vars - var $source_folder = ''; - var $dest_folder = ''; - var $mime_type = ''; - var $orig_width = ''; - var $orig_height = ''; - var $image_type = ''; - var $size_str = ''; - var $full_src_path = ''; - var $full_dst_path = ''; - var $create_fnc = 'imagecreatetruecolor'; - var $copy_fnc = 'imagecopyresampled'; - var $error_msg = array(); - var $wm_use_drop_shadow = FALSE; - var $wm_use_truetype = FALSE; - - /** - * Constructor + // -------------------------------------------------------------------------- + + /** + * Source image folder * - * @param string + * @var string + */ + public $source_folder = ''; + + /** + * Destination image folder + * + * @var string + */ + public $dest_folder = ''; + + /** + * Image mime-type + * + * @var string + */ + public $mime_type = ''; + + /** + * Original image width + * + * @var int + */ + public $orig_width = ''; + + /** + * Original image height + * + * @var int + */ + public $orig_height = ''; + + /** + * Image format + * + * @var string + */ + public $image_type = ''; + + /** + * Size of current image + * + * @var string + */ + public $size_str = ''; + + /** + * Full path to source image + * + * @var string + */ + public $full_src_path = ''; + + /** + * Full path to destination image + * + * @var string + */ + public $full_dst_path = ''; + + /** + * File permissions + * + * @var int + */ + public $file_permissions = 0644; + + /** + * Name of function to create image + * + * @var string + */ + public $create_fnc = 'imagecreatetruecolor'; + + /** + * Name of function to copy image + * + * @var string + */ + public $copy_fnc = 'imagecopyresampled'; + + /** + * Error messages + * + * @var array + */ + public $error_msg = array(); + + /** + * Whether to have a drop shadow on watermark + * + * @var bool + */ + protected $wm_use_drop_shadow = FALSE; + + /** + * Whether to use truetype fonts + * + * @var bool + */ + public $wm_use_truetype = FALSE; + + /** + * Initialize Image Library + * + * @param array $props * @return void */ public function __construct($props = array()) @@ -89,7 +393,17 @@ class CI_Image_lib { $this->initialize($props); } - log_message('debug', "Image Lib Class Initialized"); + /** + * A work-around for some improperly formatted, but + * usable JPEGs; known to be produced by Samsung + * smartphones' front-facing cameras. + * + * @see https://github.com/bcit-ci/CodeIgniter/issues/4967 + * @see https://bugs.php.net/bug.php?id=72404 + */ + ini_set('gd.jpeg_ignore_warning', 1); + + log_message('info', 'Image Lib Class Initialized'); } // -------------------------------------------------------------------- @@ -99,20 +413,41 @@ class CI_Image_lib { * * Resets values in case this class is used in a loop * - * @access public * @return void */ - function clear() + public function clear() { - $props = array('source_folder', 'dest_folder', 'source_image', 'full_src_path', 'full_dst_path', 'new_image', 'image_type', 'size_str', 'quality', 'orig_width', 'orig_height', 'width', 'height', 'rotation_angle', 'x_axis', 'y_axis', 'create_fnc', 'copy_fnc', 'wm_overlay_path', 'wm_use_truetype', 'dynamic_output', 'wm_font_size', 'wm_text', 'wm_vrt_alignment', 'wm_hor_alignment', 'wm_padding', 'wm_hor_offset', 'wm_vrt_offset', 'wm_font_color', 'wm_use_drop_shadow', 'wm_shadow_color', 'wm_shadow_distance', 'wm_opacity'); + $props = array('thumb_marker', 'library_path', 'source_image', 'new_image', 'width', 'height', 'rotation_angle', 'x_axis', 'y_axis', 'wm_text', 'wm_overlay_path', 'wm_font_path', 'wm_shadow_color', 'source_folder', 'dest_folder', 'mime_type', 'orig_width', 'orig_height', 'image_type', 'size_str', 'full_src_path', 'full_dst_path'); foreach ($props as $val) { $this->$val = ''; } - // special consideration for master_dim - $this->master_dim = 'auto'; + $this->image_library = 'gd2'; + $this->dynamic_output = FALSE; + $this->quality = 90; + $this->create_thumb = FALSE; + $this->thumb_marker = '_thumb'; + $this->maintain_ratio = TRUE; + $this->master_dim = 'auto'; + $this->wm_type = 'text'; + $this->wm_x_transp = 4; + $this->wm_y_transp = 4; + $this->wm_font_size = 17; + $this->wm_vrt_alignment = 'B'; + $this->wm_hor_alignment = 'C'; + $this->wm_padding = 0; + $this->wm_hor_offset = 0; + $this->wm_vrt_offset = 0; + $this->wm_font_color = '#ffffff'; + $this->wm_shadow_distance = 2; + $this->wm_opacity = 50; + $this->create_fnc = 'imagecreatetruecolor'; + $this->copy_fnc = 'imagecopyresampled'; + $this->error_msg = array(); + $this->wm_use_drop_shadow = FALSE; + $this->wm_use_truetype = FALSE; } // -------------------------------------------------------------------- @@ -120,42 +455,62 @@ class CI_Image_lib { /** * initialize image preferences * - * @access public * @param array * @return bool */ - function initialize($props = array()) + public function initialize($props = array()) { - /* - * Convert array elements into class variables - */ + // Convert array elements into class variables if (count($props) > 0) { foreach ($props as $key => $val) { - $this->$key = $val; + if (property_exists($this, $key)) + { + if (in_array($key, array('wm_font_color', 'wm_shadow_color'), TRUE)) + { + if (preg_match('/^#?([0-9a-f]{3}|[0-9a-f]{6})$/i', $val, $matches)) + { + /* $matches[1] contains our hex color value, but it might be + * both in the full 6-length format or the shortened 3-length + * value. + * We'll later need the full version, so we keep it if it's + * already there and if not - we'll convert to it. We can + * access string characters by their index as in an array, + * so we'll do that and use concatenation to form the final + * value: + */ + $val = (strlen($matches[1]) === 6) + ? '#'.$matches[1] + : '#'.$matches[1][0].$matches[1][0].$matches[1][1].$matches[1][1].$matches[1][2].$matches[1][2]; + } + else + { + continue; + } + } + elseif (in_array($key, array('width', 'height'), TRUE) && ! ctype_digit((string) $val)) + { + continue; + } + + $this->$key = $val; + } } } - /* - * Is there a source image? - * - * If not, there's no reason to continue - * - */ - if ($this->source_image == '') + // Is there a source image? If not, there's no reason to continue + if ($this->source_image === '') { $this->set_error('imglib_source_image_required'); - return FALSE; + return FALSE; } - /* - * Is getimagesize() Available? + /* Is getimagesize() available? * * We use it to determine the image properties (width/height). - * Note: We need to figure out how to determine image + * Note: We need to figure out how to determine image * properties using ImageMagick and NetPBM - * */ if ( ! function_exists('getimagesize')) { @@ -165,17 +520,15 @@ class CI_Image_lib { $this->image_library = strtolower($this->image_library); - /* - * Set the full server path + /* Set the full server path * * The source image may or may not contain a path. * Either way, we'll try use realpath to generate the * full server path in order to more reliably read it. - * */ - if (function_exists('realpath') AND @realpath($this->source_image) !== FALSE) + if (($full_source_path = realpath($this->source_image)) !== FALSE) { - $full_source_path = str_replace("\\", "/", realpath($this->source_image)); + $full_source_path = str_replace('\\', '/', $full_source_path); } else { @@ -189,7 +542,7 @@ class CI_Image_lib { // Set the Image Properties if ( ! $this->get_image_properties($this->source_folder.$this->source_image)) { - return FALSE; + return FALSE; } /* @@ -197,64 +550,51 @@ class CI_Image_lib { * * If the user has set a "new_image" name it means * we are making a copy of the source image. If not - * it means we are altering the original. We'll + * it means we are altering the original. We'll * set the destination filename and path accordingly. - * */ - if ($this->new_image == '') + if ($this->new_image === '') + { + $this->dest_image = $this->source_image; + $this->dest_folder = $this->source_folder; + } + elseif (strpos($this->new_image, '/') === FALSE && strpos($this->new_image, '\\') === FALSE) { - $this->dest_image = $this->source_image; + $this->dest_image = $this->new_image; $this->dest_folder = $this->source_folder; } else { - if (strpos($this->new_image, '/') === FALSE AND strpos($this->new_image, '\\') === FALSE) + // Is there a file name? + if ( ! preg_match('#\.(jpg|jpeg|gif|png)$#i', $this->new_image)) { - $this->dest_folder = $this->source_folder; - $this->dest_image = $this->new_image; + $this->dest_image = $this->source_image; + $this->dest_folder = $this->new_image; } else { - if (function_exists('realpath') AND @realpath($this->new_image) !== FALSE) - { - $full_dest_path = str_replace("\\", "/", realpath($this->new_image)); - } - else - { - $full_dest_path = $this->new_image; - } - - // Is there a file name? - if ( ! preg_match("#\.(jpg|jpeg|gif|png)$#i", $full_dest_path)) - { - $this->dest_folder = $full_dest_path.'/'; - $this->dest_image = $this->source_image; - } - else - { - $x = explode('/', $full_dest_path); - $this->dest_image = end($x); - $this->dest_folder = str_replace($this->dest_image, '', $full_dest_path); - } + $x = explode('/', str_replace('\\', '/', $this->new_image)); + $this->dest_image = end($x); + $this->dest_folder = str_replace($this->dest_image, '', $this->new_image); } + + $this->dest_folder = realpath($this->dest_folder).'/'; } - /* - * Compile the finalized filenames/paths + /* Compile the finalized filenames/paths * * We'll create two master strings containing the * full server path to the source image and the * full server path to the destination image. * We'll also split the destination image name * so we can insert the thumbnail marker if needed. - * */ - if ($this->create_thumb === FALSE OR $this->thumb_marker == '') + if ($this->create_thumb === FALSE OR $this->thumb_marker === '') { $this->thumb_marker = ''; } - $xp = $this->explode_name($this->dest_image); + $xp = $this->explode_name($this->dest_image); $filename = $xp['name']; $file_ext = $xp['ext']; @@ -262,71 +602,60 @@ class CI_Image_lib { $this->full_src_path = $this->source_folder.$this->source_image; $this->full_dst_path = $this->dest_folder.$filename.$this->thumb_marker.$file_ext; - /* - * Should we maintain image proportions? + /* Should we maintain image proportions? * * When creating thumbs or copies, the target width/height * might not be in correct proportion with the source - * image's width/height. We'll recalculate it here. - * + * image's width/height. We'll recalculate it here. */ - if ($this->maintain_ratio === TRUE && ($this->width != '' AND $this->height != '')) + if ($this->maintain_ratio === TRUE && ($this->width !== 0 OR $this->height !== 0)) { $this->image_reproportion(); } - /* - * Was a width and height specified? - * - * If the destination width/height was - * not submitted we will use the values - * from the actual file + /* Was a width and height specified? * + * If the destination width/height was not submitted we + * will use the values from the actual file */ - if ($this->width == '') + if ($this->width === '') + { $this->width = $this->orig_width; + } - if ($this->height == '') + if ($this->height === '') + { $this->height = $this->orig_height; + } // Set the quality - $this->quality = trim(str_replace("%", "", $this->quality)); + $this->quality = trim(str_replace('%', '', $this->quality)); - if ($this->quality == '' OR $this->quality == 0 OR ! is_numeric($this->quality)) + if ($this->quality === '' OR $this->quality === 0 OR ! ctype_digit($this->quality)) + { $this->quality = 90; + } // Set the x/y coordinates - $this->x_axis = ($this->x_axis == '' OR ! is_numeric($this->x_axis)) ? 0 : $this->x_axis; - $this->y_axis = ($this->y_axis == '' OR ! is_numeric($this->y_axis)) ? 0 : $this->y_axis; + is_numeric($this->x_axis) OR $this->x_axis = 0; + is_numeric($this->y_axis) OR $this->y_axis = 0; // Watermark-related Stuff... - if ($this->wm_font_color != '') + if ($this->wm_overlay_path !== '') { - if (strlen($this->wm_font_color) == 6) - { - $this->wm_font_color = '#'.$this->wm_font_color; - } + $this->wm_overlay_path = str_replace('\\', '/', realpath($this->wm_overlay_path)); } - if ($this->wm_shadow_color != '') + if ($this->wm_shadow_color !== '') { - if (strlen($this->wm_shadow_color) == 6) - { - $this->wm_shadow_color = '#'.$this->wm_shadow_color; - } - } - - if ($this->wm_overlay_path != '') - { - $this->wm_overlay_path = str_replace("\\", "/", realpath($this->wm_overlay_path)); + $this->wm_use_drop_shadow = TRUE; } - - if ($this->wm_shadow_color != '') + elseif ($this->wm_use_drop_shadow === TRUE && $this->wm_shadow_color === '') { - $this->wm_use_drop_shadow = TRUE; + $this->wm_use_drop_shadow = FALSE; } - if ($this->wm_font_path != '') + if ($this->wm_font_path !== '') { $this->wm_use_truetype = TRUE; } @@ -342,18 +671,11 @@ class CI_Image_lib { * This is a wrapper function that chooses the proper * resize function based on the protocol specified * - * @access public * @return bool */ - function resize() + public function resize() { - $protocol = 'image_process_'.$this->image_library; - - if (preg_match('/gd2$/i', $protocol)) - { - $protocol = 'image_process_gd'; - } - + $protocol = ($this->image_library === 'gd2') ? 'image_process_gd' : 'image_process_'.$this->image_library; return $this->$protocol('resize'); } @@ -365,18 +687,11 @@ class CI_Image_lib { * This is a wrapper function that chooses the proper * cropping function based on the protocol specified * - * @access public * @return bool */ - function crop() + public function crop() { - $protocol = 'image_process_'.$this->image_library; - - if (preg_match('/gd2$/i', $protocol)) - { - $protocol = 'image_process_gd'; - } - + $protocol = ($this->image_library === 'gd2') ? 'image_process_gd' : 'image_process_'.$this->image_library; return $this->$protocol('crop'); } @@ -388,22 +703,21 @@ class CI_Image_lib { * This is a wrapper function that chooses the proper * rotation function based on the protocol specified * - * @access public * @return bool */ - function rotate() + public function rotate() { // Allowed rotation values $degs = array(90, 180, 270, 'vrt', 'hor'); - if ($this->rotation_angle == '' OR ! in_array($this->rotation_angle, $degs)) + if ($this->rotation_angle === '' OR ! in_array($this->rotation_angle, $degs)) { $this->set_error('imglib_rotation_angle_required'); - return FALSE; + return FALSE; } // Reassign the width and height - if ($this->rotation_angle == 90 OR $this->rotation_angle == 270) + if ($this->rotation_angle === 90 OR $this->rotation_angle === 270) { $this->width = $this->orig_height; $this->height = $this->orig_width; @@ -414,23 +728,16 @@ class CI_Image_lib { $this->height = $this->orig_height; } - // Choose resizing function - if ($this->image_library == 'imagemagick' OR $this->image_library == 'netpbm') + if ($this->image_library === 'imagemagick' OR $this->image_library === 'netpbm') { $protocol = 'image_process_'.$this->image_library; - return $this->$protocol('rotate'); } - if ($this->rotation_angle == 'hor' OR $this->rotation_angle == 'vrt') - { - return $this->image_mirror_gd(); - } - else - { - return $this->image_rotate_gd(); - } + return ($this->rotation_angle === 'hor' OR $this->rotation_angle === 'vrt') + ? $this->image_mirror_gd() + : $this->image_rotate_gd(); } // -------------------------------------------------------------------- @@ -440,36 +747,29 @@ class CI_Image_lib { * * This function will resize or crop * - * @access public * @param string * @return bool */ - function image_process_gd($action = 'resize') + public function image_process_gd($action = 'resize') { $v2_override = FALSE; // If the target width/height match the source, AND if the new file name is not equal to the old file name // we'll simply make a copy of the original with the new name... assuming dynamic rendering is off. - if ($this->dynamic_output === FALSE) + if ($this->dynamic_output === FALSE && $this->orig_width === $this->width && $this->orig_height === $this->height) { - if ($this->orig_width == $this->width AND $this->orig_height == $this->height) + if ($this->source_image !== $this->new_image && @copy($this->full_src_path, $this->full_dst_path)) { - if ($this->source_image != $this->new_image) - { - if (@copy($this->full_src_path, $this->full_dst_path)) - { - @chmod($this->full_dst_path, FILE_WRITE_MODE); - } - } - - return TRUE; + chmod($this->full_dst_path, $this->file_permissions); } + + return TRUE; } // Let's set up our values based on the action - if ($action == 'crop') + if ($action === 'crop') { - // Reassign the source width/height if cropping + // Reassign the source width/height if cropping $this->orig_width = $this->width; $this->orig_height = $this->height; @@ -477,7 +777,7 @@ class CI_Image_lib { if ($this->gd_version() !== FALSE) { $gd_version = str_replace('0', '', $this->gd_version()); - $v2_override = ($gd_version == 2) ? TRUE : FALSE; + $v2_override = ($gd_version == 2); } } else @@ -487,20 +787,21 @@ class CI_Image_lib { $this->y_axis = 0; } - // Create the image handle + // Create the image handle if ( ! ($src_img = $this->image_create_gd())) { return FALSE; } - // Create The Image - // - // old conditional which users report cause problems with shared GD libs who report themselves as "2.0 or greater" - // it appears that this is no longer the issue that it was in 2004, so we've removed it, retaining it in the comment - // below should that ever prove inaccurate. - // - // if ($this->image_library == 'gd2' AND function_exists('imagecreatetruecolor') AND $v2_override == FALSE) - if ($this->image_library == 'gd2' AND function_exists('imagecreatetruecolor')) + /* Create the image + * + * Old conditional which users report cause problems with shared GD libs who report themselves as "2.0 or greater" + * it appears that this is no longer the issue that it was in 2004, so we've removed it, retaining it in the comment + * below should that ever prove inaccurate. + * + * if ($this->image_library === 'gd2' && function_exists('imagecreatetruecolor') && $v2_override === FALSE) + */ + if ($this->image_library === 'gd2' && function_exists('imagecreatetruecolor')) { $create = 'imagecreatetruecolor'; $copy = 'imagecopyresampled'; @@ -513,7 +814,7 @@ class CI_Image_lib { $dst_img = $create($this->width, $this->height); - if ($this->image_type == 3) // png we can actually preserve transparency + if ($this->image_type === 3) // png we can actually preserve transparency { imagealphablending($dst_img, FALSE); imagesavealpha($dst_img, TRUE); @@ -521,26 +822,24 @@ class CI_Image_lib { $copy($dst_img, $src_img, 0, 0, $this->x_axis, $this->y_axis, $this->width, $this->height, $this->orig_width, $this->orig_height); - // Show the image - if ($this->dynamic_output == TRUE) + // Show the image + if ($this->dynamic_output === TRUE) { $this->image_display_gd($dst_img); } - else + elseif ( ! $this->image_save_gd($dst_img)) // Or save it { - // Or save it - if ( ! $this->image_save_gd($dst_img)) - { - return FALSE; - } + return FALSE; } - // Kill the file handles + // Kill the file handles imagedestroy($dst_img); imagedestroy($src_img); - // Set the file to 777 - @chmod($this->full_dst_path, FILE_WRITE_MODE); + if ($this->dynamic_output !== TRUE) + { + chmod($this->full_dst_path, $this->file_permissions); + } return TRUE; } @@ -552,65 +851,65 @@ class CI_Image_lib { * * This function will resize, crop or rotate * - * @access public * @param string * @return bool */ - function image_process_imagemagick($action = 'resize') + public function image_process_imagemagick($action = 'resize') { - // Do we have a vaild library path? - if ($this->library_path == '') + // Do we have a vaild library path? + if ($this->library_path === '') { $this->set_error('imglib_libpath_invalid'); return FALSE; } - if ( ! preg_match("/convert$/i", $this->library_path)) + if ( ! preg_match('/convert$/i', $this->library_path)) { - $this->library_path = rtrim($this->library_path, '/').'/'; - - $this->library_path .= 'convert'; + $this->library_path = rtrim($this->library_path, '/').'/convert'; } // Execute the command - $cmd = $this->library_path." -quality ".$this->quality; + $cmd = $this->library_path.' -quality '.$this->quality; - if ($action == 'crop') + if ($action === 'crop') { - $cmd .= " -crop ".$this->width."x".$this->height."+".$this->x_axis."+".$this->y_axis." \"$this->full_src_path\" \"$this->full_dst_path\" 2>&1"; + $cmd .= ' -crop '.$this->width.'x'.$this->height.'+'.$this->x_axis.'+'.$this->y_axis; } - elseif ($action == 'rotate') + elseif ($action === 'rotate') { - switch ($this->rotation_angle) - { - case 'hor' : $angle = '-flop'; - break; - case 'vrt' : $angle = '-flip'; - break; - default : $angle = '-rotate '.$this->rotation_angle; - break; - } - - $cmd .= " ".$angle." \"$this->full_src_path\" \"$this->full_dst_path\" 2>&1"; + $cmd .= ($this->rotation_angle === 'hor' OR $this->rotation_angle === 'vrt') + ? ' -flop' + : ' -rotate '.$this->rotation_angle; } - else // Resize + else // Resize { - $cmd .= " -resize ".$this->width."x".$this->height." \"$this->full_src_path\" \"$this->full_dst_path\" 2>&1"; + if($this->maintain_ratio === TRUE) + { + $cmd .= ' -resize '.$this->width.'x'.$this->height; + } + else + { + $cmd .= ' -resize '.$this->width.'x'.$this->height.'\!'; + } } - $retval = 1; + $cmd .= ' '.escapeshellarg($this->full_src_path).' '.escapeshellarg($this->full_dst_path).' 2>&1'; - @exec($cmd, $output, $retval); + $retval = 1; + // exec() might be disabled + if (function_usable('exec')) + { + @exec($cmd, $output, $retval); + } - // Did it work? + // Did it work? if ($retval > 0) { $this->set_error('imglib_image_process_failed'); return FALSE; } - // Set the file to 777 - @chmod($this->full_dst_path, FILE_WRITE_MODE); + chmod($this->full_dst_path, $this->file_permissions); return TRUE; } @@ -622,52 +921,55 @@ class CI_Image_lib { * * This function will resize, crop or rotate * - * @access public * @param string * @return bool */ - function image_process_netpbm($action = 'resize') + public function image_process_netpbm($action = 'resize') { - if ($this->library_path == '') + if ($this->library_path === '') { $this->set_error('imglib_libpath_invalid'); return FALSE; } - // Build the resizing command + // Build the resizing command switch ($this->image_type) { case 1 : - $cmd_in = 'giftopnm'; - $cmd_out = 'ppmtogif'; + $cmd_in = 'giftopnm'; + $cmd_out = 'ppmtogif'; break; case 2 : - $cmd_in = 'jpegtopnm'; - $cmd_out = 'ppmtojpeg'; + $cmd_in = 'jpegtopnm'; + $cmd_out = 'ppmtojpeg'; break; case 3 : - $cmd_in = 'pngtopnm'; - $cmd_out = 'ppmtopng'; + $cmd_in = 'pngtopnm'; + $cmd_out = 'ppmtopng'; + break; + case 18 : + $cmd_in = 'webptopnm'; + $cmd_out = 'ppmtowebp'; break; } - if ($action == 'crop') + if ($action === 'crop') { $cmd_inner = 'pnmcut -left '.$this->x_axis.' -top '.$this->y_axis.' -width '.$this->width.' -height '.$this->height; } - elseif ($action == 'rotate') + elseif ($action === 'rotate') { switch ($this->rotation_angle) { - case 90 : $angle = 'r270'; + case 90: $angle = 'r270'; break; - case 180 : $angle = 'r180'; + case 180: $angle = 'r180'; break; - case 270 : $angle = 'r90'; + case 270: $angle = 'r90'; break; - case 'vrt' : $angle = 'tb'; + case 'vrt': $angle = 'tb'; break; - case 'hor' : $angle = 'lr'; + case 'hor': $angle = 'lr'; break; } @@ -678,13 +980,16 @@ class CI_Image_lib { $cmd_inner = 'pnmscale -xysize '.$this->width.' '.$this->height; } - $cmd = $this->library_path.$cmd_in.' '.$this->full_src_path.' | '.$cmd_inner.' | '.$cmd_out.' > '.$this->dest_folder.'netpbm.tmp'; + $cmd = $this->library_path.$cmd_in.' '.escapeshellarg($this->full_src_path).' | '.$cmd_inner.' | '.$cmd_out.' > '.$this->dest_folder.'netpbm.tmp'; $retval = 1; + // exec() might be disabled + if (function_usable('exec')) + { + @exec($cmd, $output, $retval); + } - @exec($cmd, $output, $retval); - - // Did it work? + // Did it work? if ($retval > 0) { $this->set_error('imglib_image_process_failed'); @@ -694,9 +999,9 @@ class CI_Image_lib { // With NetPBM we have to create a temporary image. // If you try manipulating the original it fails so // we have to rename the temp file. - copy ($this->dest_folder.'netpbm.tmp', $this->full_dst_path); - unlink ($this->dest_folder.'netpbm.tmp'); - @chmod($this->full_dst_path, FILE_WRITE_MODE); + copy($this->dest_folder.'netpbm.tmp', $this->full_dst_path); + unlink($this->dest_folder.'netpbm.tmp'); + chmod($this->full_dst_path, $this->file_permissions); return TRUE; } @@ -706,12 +1011,11 @@ class CI_Image_lib { /** * Image Rotate Using GD * - * @access public * @return bool */ - function image_rotate_gd() + public function image_rotate_gd() { - // Create the image handle + // Create the image handle if ( ! ($src_img = $this->image_create_gd())) { return FALSE; @@ -722,32 +1026,26 @@ class CI_Image_lib { // going to have to figure out how to determine the color // of the alpha channel in a future release. - $white = imagecolorallocate($src_img, 255, 255, 255); + $white = imagecolorallocate($src_img, 255, 255, 255); - // Rotate it! + // Rotate it! $dst_img = imagerotate($src_img, $this->rotation_angle, $white); - // Save the Image - if ($this->dynamic_output == TRUE) + // Show the image + if ($this->dynamic_output === TRUE) { $this->image_display_gd($dst_img); } - else + elseif ( ! $this->image_save_gd($dst_img)) // ... or save it { - // Or save it - if ( ! $this->image_save_gd($dst_img)) - { - return FALSE; - } + return FALSE; } - // Kill the file handles + // Kill the file handles imagedestroy($dst_img); imagedestroy($src_img); - // Set the file to 777 - - @chmod($this->full_dst_path, FILE_WRITE_MODE); + chmod($this->full_dst_path, $this->file_permissions); return TRUE; } @@ -759,10 +1057,9 @@ class CI_Image_lib { * * This function will flip horizontal or vertical * - * @access public * @return bool */ - function image_mirror_gd() + public function image_mirror_gd() { if ( ! $src_img = $this->image_create_gd()) { @@ -772,12 +1069,12 @@ class CI_Image_lib { $width = $this->orig_width; $height = $this->orig_height; - if ($this->rotation_angle == 'hor') + if ($this->rotation_angle === 'hor') { for ($i = 0; $i < $height; $i++) { - $left = 0; - $right = $width-1; + $left = 0; + $right = $width - 1; while ($left < $right) { @@ -797,41 +1094,36 @@ class CI_Image_lib { for ($i = 0; $i < $width; $i++) { $top = 0; - $bot = $height-1; + $bottom = $height - 1; - while ($top < $bot) + while ($top < $bottom) { $ct = imagecolorat($src_img, $i, $top); - $cb = imagecolorat($src_img, $i, $bot); + $cb = imagecolorat($src_img, $i, $bottom); imagesetpixel($src_img, $i, $top, $cb); - imagesetpixel($src_img, $i, $bot, $ct); + imagesetpixel($src_img, $i, $bottom, $ct); $top++; - $bot--; + $bottom--; } } } - // Show the image - if ($this->dynamic_output == TRUE) + // Show the image + if ($this->dynamic_output === TRUE) { $this->image_display_gd($src_img); } - else + elseif ( ! $this->image_save_gd($src_img)) // ... or save it { - // Or save it - if ( ! $this->image_save_gd($src_img)) - { - return FALSE; - } + return FALSE; } - // Kill the file handles + // Kill the file handles imagedestroy($src_img); - // Set the file to 777 - @chmod($this->full_dst_path, FILE_WRITE_MODE); + chmod($this->full_dst_path, $this->file_permissions); return TRUE; } @@ -844,20 +1136,11 @@ class CI_Image_lib { * This is a wrapper function that chooses the type * of watermarking based on the specified preference. * - * @access public - * @param string * @return bool */ - function watermark() + public function watermark() { - if ($this->wm_type == 'overlay') - { - return $this->overlay_watermark(); - } - else - { - return $this->text_watermark(); - } + return ($this->wm_type === 'overlay') ? $this->overlay_watermark() : $this->text_watermark(); } // -------------------------------------------------------------------- @@ -865,10 +1148,9 @@ class CI_Image_lib { /** * Watermark - Graphic Version * - * @access public * @return bool */ - function overlay_watermark() + public function overlay_watermark() { if ( ! function_exists('imagecolortransparent')) { @@ -876,63 +1158,61 @@ class CI_Image_lib { return FALSE; } - // Fetch source image properties + // Fetch source image properties $this->get_image_properties(); - // Fetch watermark image properties - $props = $this->get_image_properties($this->wm_overlay_path, TRUE); + // Fetch watermark image properties + $props = $this->get_image_properties($this->wm_overlay_path, TRUE); $wm_img_type = $props['image_type']; - $wm_width = $props['width']; - $wm_height = $props['height']; + $wm_width = $props['width']; + $wm_height = $props['height']; - // Create two image resources + // Create two image resources $wm_img = $this->image_create_gd($this->wm_overlay_path, $wm_img_type); $src_img = $this->image_create_gd($this->full_src_path); // Reverse the offset if necessary // When the image is positioned at the bottom // we don't want the vertical offset to push it - // further down. We want the reverse, so we'll - // invert the offset. Same with the horizontal + // further down. We want the reverse, so we'll + // invert the offset. Same with the horizontal // offset when the image is at the right - $this->wm_vrt_alignment = strtoupper(substr($this->wm_vrt_alignment, 0, 1)); - $this->wm_hor_alignment = strtoupper(substr($this->wm_hor_alignment, 0, 1)); + $this->wm_vrt_alignment = strtoupper($this->wm_vrt_alignment[0]); + $this->wm_hor_alignment = strtoupper($this->wm_hor_alignment[0]); - if ($this->wm_vrt_alignment == 'B') + if ($this->wm_vrt_alignment === 'B') $this->wm_vrt_offset = $this->wm_vrt_offset * -1; - if ($this->wm_hor_alignment == 'R') + if ($this->wm_hor_alignment === 'R') $this->wm_hor_offset = $this->wm_hor_offset * -1; - // Set the base x and y axis values + // Set the base x and y axis values $x_axis = $this->wm_hor_offset + $this->wm_padding; $y_axis = $this->wm_vrt_offset + $this->wm_padding; - // Set the vertical position - switch ($this->wm_vrt_alignment) + // Set the vertical position + if ($this->wm_vrt_alignment === 'M') { - case 'T': - break; - case 'M': $y_axis += ($this->orig_height / 2) - ($wm_height / 2); - break; - case 'B': $y_axis += $this->orig_height - $wm_height; - break; + $y_axis += ($this->orig_height / 2) - ($wm_height / 2); + } + elseif ($this->wm_vrt_alignment === 'B') + { + $y_axis += $this->orig_height - $wm_height; } - // Set the horizontal position - switch ($this->wm_hor_alignment) + // Set the horizontal position + if ($this->wm_hor_alignment === 'C') { - case 'L': - break; - case 'C': $x_axis += ($this->orig_width / 2) - ($wm_width / 2); - break; - case 'R': $x_axis += $this->orig_width - $wm_width; - break; + $x_axis += ($this->orig_width / 2) - ($wm_width / 2); + } + elseif ($this->wm_hor_alignment === 'R') + { + $x_axis += $this->orig_width - $wm_width; } - // Build the finalized image - if ($wm_img_type == 3 AND function_exists('imagealphablending')) + // Build the finalized image + if ($wm_img_type === 3) { @imagealphablending($src_img, TRUE); } @@ -954,17 +1234,21 @@ class CI_Image_lib { imagecopymerge($src_img, $wm_img, $x_axis, $y_axis, 0, 0, $wm_width, $wm_height, $this->wm_opacity); } - // Output the image - if ($this->dynamic_output == TRUE) + // We can preserve transparency for PNG images + if ($this->image_type === 3) + { + imagealphablending($src_img, FALSE); + imagesavealpha($src_img, TRUE); + } + + // Output the image + if ($this->dynamic_output === TRUE) { $this->image_display_gd($src_img); } - else + elseif ( ! $this->image_save_gd($src_img)) // ... or save it { - if ( ! $this->image_save_gd($src_img)) - { - return FALSE; - } + return FALSE; } imagedestroy($src_img); @@ -978,62 +1262,63 @@ class CI_Image_lib { /** * Watermark - Text Version * - * @access public * @return bool */ - function text_watermark() + public function text_watermark() { if ( ! ($src_img = $this->image_create_gd())) { return FALSE; } - if ($this->wm_use_truetype == TRUE AND ! file_exists($this->wm_font_path)) + if ($this->wm_use_truetype === TRUE && ! file_exists($this->wm_font_path)) { $this->set_error('imglib_missing_font'); return FALSE; } - // Fetch source image properties + // Fetch source image properties $this->get_image_properties(); - // Set RGB values for text and shadow - $this->wm_font_color = str_replace('#', '', $this->wm_font_color); - $this->wm_shadow_color = str_replace('#', '', $this->wm_shadow_color); - - $R1 = hexdec(substr($this->wm_font_color, 0, 2)); - $G1 = hexdec(substr($this->wm_font_color, 2, 2)); - $B1 = hexdec(substr($this->wm_font_color, 4, 2)); - - $R2 = hexdec(substr($this->wm_shadow_color, 0, 2)); - $G2 = hexdec(substr($this->wm_shadow_color, 2, 2)); - $B2 = hexdec(substr($this->wm_shadow_color, 4, 2)); - - $txt_color = imagecolorclosest($src_img, $R1, $G1, $B1); - $drp_color = imagecolorclosest($src_img, $R2, $G2, $B2); - // Reverse the vertical offset // When the image is positioned at the bottom // we don't want the vertical offset to push it - // further down. We want the reverse, so we'll - // invert the offset. Note: The horizontal + // further down. We want the reverse, so we'll + // invert the offset. Note: The horizontal // offset flips itself automatically - if ($this->wm_vrt_alignment == 'B') + if ($this->wm_vrt_alignment === 'B') + { $this->wm_vrt_offset = $this->wm_vrt_offset * -1; + } - if ($this->wm_hor_alignment == 'R') + if ($this->wm_hor_alignment === 'R') + { $this->wm_hor_offset = $this->wm_hor_offset * -1; + } // Set font width and height // These are calculated differently depending on // whether we are using the true type font or not - if ($this->wm_use_truetype == TRUE) + if ($this->wm_use_truetype === TRUE) { - if ($this->wm_font_size == '') - $this->wm_font_size = '17'; + if (empty($this->wm_font_size)) + { + $this->wm_font_size = 17; + } + + if (function_exists('imagettfbbox')) + { + $temp = imagettfbbox($this->wm_font_size, 0, $this->wm_font_path, $this->wm_text); + $temp = $temp[2] - $temp[0]; + + $fontwidth = $temp / strlen($this->wm_text); + } + else + { + $fontwidth = $this->wm_font_size - ($this->wm_font_size / 4); + } - $fontwidth = $this->wm_font_size-($this->wm_font_size/4); $fontheight = $this->wm_font_size; $this->wm_vrt_offset += $this->wm_font_size; } @@ -1047,59 +1332,88 @@ class CI_Image_lib { $x_axis = $this->wm_hor_offset + $this->wm_padding; $y_axis = $this->wm_vrt_offset + $this->wm_padding; - // Set verticle alignment - if ($this->wm_use_drop_shadow == FALSE) + if ($this->wm_use_drop_shadow === FALSE) + { $this->wm_shadow_distance = 0; + } - $this->wm_vrt_alignment = strtoupper(substr($this->wm_vrt_alignment, 0, 1)); - $this->wm_hor_alignment = strtoupper(substr($this->wm_hor_alignment, 0, 1)); + $this->wm_vrt_alignment = strtoupper($this->wm_vrt_alignment[0]); + $this->wm_hor_alignment = strtoupper($this->wm_hor_alignment[0]); - switch ($this->wm_vrt_alignment) + // Set vertical alignment + if ($this->wm_vrt_alignment === 'M') { - case "T" : - break; - case "M": $y_axis += ($this->orig_height/2)+($fontheight/2); - break; - case "B": $y_axis += ($this->orig_height - $fontheight - $this->wm_shadow_distance - ($fontheight/2)); - break; + $y_axis += ($this->orig_height / 2) + ($fontheight / 2); + } + elseif ($this->wm_vrt_alignment === 'B') + { + $y_axis += $this->orig_height - $fontheight - $this->wm_shadow_distance - ($fontheight / 2); } - - $x_shad = $x_axis + $this->wm_shadow_distance; - $y_shad = $y_axis + $this->wm_shadow_distance; // Set horizontal alignment - switch ($this->wm_hor_alignment) + if ($this->wm_hor_alignment === 'R') { - case "L": - break; - case "R": - if ($this->wm_use_drop_shadow) - $x_shad += ($this->orig_width - $fontwidth*strlen($this->wm_text)); - $x_axis += ($this->orig_width - $fontwidth*strlen($this->wm_text)); - break; - case "C": - if ($this->wm_use_drop_shadow) - $x_shad += floor(($this->orig_width - $fontwidth*strlen($this->wm_text))/2); - $x_axis += floor(($this->orig_width -$fontwidth*strlen($this->wm_text))/2); - break; + $x_axis += $this->orig_width - ($fontwidth * strlen($this->wm_text)) - $this->wm_shadow_distance; + } + elseif ($this->wm_hor_alignment === 'C') + { + $x_axis += floor(($this->orig_width - ($fontwidth * strlen($this->wm_text))) / 2); } - // Add the text to the source image - if ($this->wm_use_truetype) + if ($this->wm_use_drop_shadow) { - if ($this->wm_use_drop_shadow) + // Offset from text + $x_shad = $x_axis + $this->wm_shadow_distance; + $y_shad = $y_axis + $this->wm_shadow_distance; + + /* Set RGB values for shadow + * + * First character is #, so we don't really need it. + * Get the rest of the string and split it into 2-length + * hex values: + */ + $drp_color = str_split(substr($this->wm_shadow_color, 1, 6), 2); + $drp_color = imagecolorclosest($src_img, hexdec($drp_color[0]), hexdec($drp_color[1]), hexdec($drp_color[2])); + + // Add the shadow to the source image + if ($this->wm_use_truetype) + { imagettftext($src_img, $this->wm_font_size, 0, $x_shad, $y_shad, $drp_color, $this->wm_font_path, $this->wm_text); - imagettftext($src_img, $this->wm_font_size, 0, $x_axis, $y_axis, $txt_color, $this->wm_font_path, $this->wm_text); + } + else + { + imagestring($src_img, $this->wm_font_size, $x_shad, $y_shad, $this->wm_text, $drp_color); + } + } + + /* Set RGB values for text + * + * First character is #, so we don't really need it. + * Get the rest of the string and split it into 2-length + * hex values: + */ + $txt_color = str_split(substr($this->wm_font_color, 1, 6), 2); + $txt_color = imagecolorclosest($src_img, hexdec($txt_color[0]), hexdec($txt_color[1]), hexdec($txt_color[2])); + + // Add the text to the source image + if ($this->wm_use_truetype) + { + imagettftext($src_img, $this->wm_font_size, 0, $x_axis, $y_axis, $txt_color, $this->wm_font_path, $this->wm_text); } else { - if ($this->wm_use_drop_shadow) - imagestring($src_img, $this->wm_font_size, $x_shad, $y_shad, $this->wm_text, $drp_color); - imagestring($src_img, $this->wm_font_size, $x_axis, $y_axis, $this->wm_text, $txt_color); + imagestring($src_img, $this->wm_font_size, $x_axis, $y_axis, $this->wm_text, $txt_color); } - // Output the final image - if ($this->dynamic_output == TRUE) + // We can preserve transparency for PNG images + if ($this->image_type === 3) + { + imagealphablending($src_img, FALSE); + imagesavealpha($src_img, TRUE); + } + + // Output the final image + if ($this->dynamic_output === TRUE) { $this->image_display_gd($src_img); } @@ -1121,53 +1435,60 @@ class CI_Image_lib { * This simply creates an image resource handle * based on the type of image being processed * - * @access public + * @param string * @param string * @return resource */ - function image_create_gd($path = '', $image_type = '') + public function image_create_gd($path = '', $image_type = '') { - if ($path == '') + if ($path === '') + { $path = $this->full_src_path; + } - if ($image_type == '') + if ($image_type === '') + { $image_type = $this->image_type; - + } switch ($image_type) { - case 1 : - if ( ! function_exists('imagecreatefromgif')) - { - $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_gif_not_supported')); - return FALSE; - } + case 1: + if ( ! function_exists('imagecreatefromgif')) + { + $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_gif_not_supported')); + return FALSE; + } - return imagecreatefromgif($path); - break; - case 2 : - if ( ! function_exists('imagecreatefromjpeg')) - { - $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_jpg_not_supported')); - return FALSE; - } + return imagecreatefromgif($path); + case 2: + if ( ! function_exists('imagecreatefromjpeg')) + { + $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_jpg_not_supported')); + return FALSE; + } - return imagecreatefromjpeg($path); - break; - case 3 : - if ( ! function_exists('imagecreatefrompng')) - { - $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_png_not_supported')); - return FALSE; - } + return imagecreatefromjpeg($path); + case 3: + if ( ! function_exists('imagecreatefrompng')) + { + $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_png_not_supported')); + return FALSE; + } - return imagecreatefrompng($path); - break; + return imagecreatefrompng($path); + case 18: + if ( ! function_exists('imagecreatefromwebp')) + { + $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_webp_not_supported')); + return FALSE; + } + return imagecreatefromwebp($path); + default: + $this->set_error(array('imglib_unsupported_imagecreate')); + return FALSE; } - - $this->set_error(array('imglib_unsupported_imagecreate')); - return FALSE; } // -------------------------------------------------------------------- @@ -1178,57 +1499,69 @@ class CI_Image_lib { * Takes an image resource as input and writes the file * to the specified destination * - * @access public * @param resource * @return bool */ - function image_save_gd($resource) + public function image_save_gd($resource) { switch ($this->image_type) { - case 1 : - if ( ! function_exists('imagegif')) - { - $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_gif_not_supported')); - return FALSE; - } + case 1: + if ( ! function_exists('imagegif')) + { + $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_gif_not_supported')); + return FALSE; + } - if ( ! @imagegif($resource, $this->full_dst_path)) - { - $this->set_error('imglib_save_failed'); - return FALSE; - } - break; - case 2 : - if ( ! function_exists('imagejpeg')) - { - $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_jpg_not_supported')); - return FALSE; - } + if ( ! @imagegif($resource, $this->full_dst_path)) + { + $this->set_error('imglib_save_failed'); + return FALSE; + } + break; + case 2: + if ( ! function_exists('imagejpeg')) + { + $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_jpg_not_supported')); + return FALSE; + } - if ( ! @imagejpeg($resource, $this->full_dst_path, $this->quality)) - { - $this->set_error('imglib_save_failed'); - return FALSE; - } - break; - case 3 : - if ( ! function_exists('imagepng')) - { - $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_png_not_supported')); - return FALSE; - } + if ( ! @imagejpeg($resource, $this->full_dst_path, $this->quality)) + { + $this->set_error('imglib_save_failed'); + return FALSE; + } + break; + case 3: + if ( ! function_exists('imagepng')) + { + $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_png_not_supported')); + return FALSE; + } - if ( ! @imagepng($resource, $this->full_dst_path)) - { - $this->set_error('imglib_save_failed'); - return FALSE; - } - break; - default : - $this->set_error(array('imglib_unsupported_imagecreate')); - return FALSE; - break; + if ( ! @imagepng($resource, $this->full_dst_path)) + { + $this->set_error('imglib_save_failed'); + return FALSE; + } + break; + case 18: + if ( ! function_exists('imagewebp')) + { + $this->set_error(array('imglib_unsupported_imagecreate', 'imglib_webp_not_supported')); + return FALSE; + } + + if ( ! @imagewebp($resource, $this->full_dst_path)) + { + $this->set_error('imglib_save_failed'); + return FALSE; + } + break; + default: + $this->set_error(array('imglib_unsupported_imagecreate')); + return FALSE; + break; } return TRUE; @@ -1239,26 +1572,36 @@ class CI_Image_lib { /** * Dynamically outputs an image * - * @access public * @param resource * @return void */ - function image_display_gd($resource) + public function image_display_gd($resource) { - header("Content-Disposition: filename={$this->source_image};"); - header("Content-Type: {$this->mime_type}"); + // RFC 6266 allows for multibyte filenames, but only in UTF-8, + // so we have to make it conditional ... + $filename = basename(empty($this->new_image) ? $this->source_image : $this->new_image); + $charset = strtoupper(config_item('charset')); + $utf8_filename = ($charset !== 'UTF-8') + ? get_instance()->utf8->convert_to_utf8($filename, $charset) + : $filename; + isset($utf8_filename[0]) && $utf8_filename = " filename*=UTF-8''".rawurlencode($utf8_filename); + + header('Content-Disposition: filename="'.$filename.'";'.$utf8_filename); + header('Content-Type: '.$this->mime_type); header('Content-Transfer-Encoding: binary'); header('Last-Modified: '.gmdate('D, d M Y H:i:s', time()).' GMT'); switch ($this->image_type) { - case 1 : imagegif($resource); + case 1 : imagegif($resource); break; - case 2 : imagejpeg($resource, '', $this->quality); + case 2 : imagejpeg($resource, NULL, $this->quality); break; - case 3 : imagepng($resource); + case 3 : imagepng($resource); break; - default : echo 'Unable to display the image'; + case 18 : imagewebp($resource); + break; + default: echo 'Unable to display the image'; break; } } @@ -1275,38 +1618,47 @@ class CI_Image_lib { * This function lets us re-proportion the width/height * if users choose to maintain the aspect ratio when resizing. * - * @access public * @return void */ - function image_reproportion() + public function image_reproportion() { - if ( ! is_numeric($this->width) OR ! is_numeric($this->height) OR $this->width == 0 OR $this->height == 0) - return; - - if ( ! is_numeric($this->orig_width) OR ! is_numeric($this->orig_height) OR $this->orig_width == 0 OR $this->orig_height == 0) - return; - - $new_width = ceil($this->orig_width*$this->height/$this->orig_height); - $new_height = ceil($this->width*$this->orig_height/$this->orig_width); - - $ratio = (($this->orig_height/$this->orig_width) - ($this->height/$this->width)); - - if ($this->master_dim != 'width' AND $this->master_dim != 'height') + if (($this->width === 0 && $this->height === 0) OR $this->orig_width === 0 OR $this->orig_height === 0 + OR ( ! ctype_digit((string) $this->width) && ! ctype_digit((string) $this->height)) + OR ! ctype_digit((string) $this->orig_width) OR ! ctype_digit((string) $this->orig_height)) { - $this->master_dim = ($ratio < 0) ? 'width' : 'height'; + return; } - if (($this->width != $new_width) AND ($this->height != $new_height)) + // Sanitize + $this->width = (int) $this->width; + $this->height = (int) $this->height; + + if ($this->master_dim !== 'width' && $this->master_dim !== 'height') { - if ($this->master_dim == 'height') + if ($this->width > 0 && $this->height > 0) { - $this->width = $new_width; + $this->master_dim = ((($this->orig_height/$this->orig_width) - ($this->height/$this->width)) < 0) + ? 'width' : 'height'; } else { - $this->height = $new_height; + $this->master_dim = ($this->height === 0) ? 'width' : 'height'; } } + elseif (($this->master_dim === 'width' && $this->width === 0) + OR ($this->master_dim === 'height' && $this->height === 0)) + { + return; + } + + if ($this->master_dim === 'width') + { + $this->height = (int) ceil($this->width*$this->orig_height/$this->orig_width); + } + else + { + $this->width = (int) ceil($this->orig_width*$this->height/$this->orig_height); + } } // -------------------------------------------------------------------- @@ -1316,17 +1668,19 @@ class CI_Image_lib { * * A helper function that gets info about the file * - * @access public * @param string + * @param bool * @return mixed */ - function get_image_properties($path = '', $return = FALSE) + public function get_image_properties($path = '', $return = FALSE) { // For now we require GD but we should // find a way to determine this using IM or NetPBM - if ($path == '') + if ($path === '') + { $path = $this->full_src_path; + } if ( ! file_exists($path)) { @@ -1334,28 +1688,32 @@ class CI_Image_lib { return FALSE; } - $vals = @getimagesize($path); + $vals = getimagesize($path); + if ($vals === FALSE) + { + $this->set_error('imglib_invalid_image'); + return FALSE; + } $types = array(1 => 'gif', 2 => 'jpeg', 3 => 'png'); + $mime = isset($types[$vals[2]]) ? 'image/'.$types[$vals[2]] : 'image/jpg'; - $mime = (isset($types[$vals['2']])) ? 'image/'.$types[$vals['2']] : 'image/jpg'; - - if ($return == TRUE) + if ($return === TRUE) { - $v['width'] = $vals['0']; - $v['height'] = $vals['1']; - $v['image_type'] = $vals['2']; - $v['size_str'] = $vals['3']; - $v['mime_type'] = $mime; - - return $v; + return array( + 'width' => $vals[0], + 'height' => $vals[1], + 'image_type' => $vals[2], + 'size_str' => $vals[3], + 'mime_type' => $mime + ); } - $this->orig_width = $vals['0']; - $this->orig_height = $vals['1']; - $this->image_type = $vals['2']; - $this->size_str = $vals['3']; - $this->mime_type = $mime; + $this->orig_width = $vals[0]; + $this->orig_height = $vals[1]; + $this->image_type = $vals[2]; + $this->size_str = $vals[3]; + $this->mime_type = $mime; return TRUE; } @@ -1366,21 +1724,20 @@ class CI_Image_lib { * Size calculator * * This function takes a known width x height and - * recalculates it to a new size. Only one + * recalculates it to a new size. Only one * new variable needs to be known * * $props = array( - * 'width' => $width, - * 'height' => $height, - * 'new_width' => 40, - * 'new_height' => '' - * ); + * 'width' => $width, + * 'height' => $height, + * 'new_width' => 40, + * 'new_height' => '' + * ); * - * @access public * @param array * @return array */ - function size_calculator($vals) + public function size_calculator($vals) { if ( ! is_array($vals)) { @@ -1391,20 +1748,22 @@ class CI_Image_lib { foreach ($allowed as $item) { - if ( ! isset($vals[$item]) OR $vals[$item] == '') + if (empty($vals[$item])) + { $vals[$item] = 0; + } } - if ($vals['width'] == 0 OR $vals['height'] == 0) + if ($vals['width'] === 0 OR $vals['height'] === 0) { return $vals; } - if ($vals['new_width'] == 0) + if ($vals['new_width'] === 0) { $vals['new_width'] = ceil($vals['width']*$vals['new_height']/$vals['height']); } - elseif ($vals['new_height'] == 0) + elseif ($vals['new_height'] === 0) { $vals['new_height'] = ceil($vals['new_width']*$vals['height']/$vals['width']); } @@ -1419,16 +1778,15 @@ class CI_Image_lib { * * This is a helper function that extracts the extension * from the source_image. This function lets us deal with - * source_images with multiple periods, like: my.cool.jpg + * source_images with multiple periods, like: my.cool.jpg * It returns an associative array with two elements: * $array['ext'] = '.jpg'; * $array['name'] = 'my.cool'; * - * @access public * @param array * @return array */ - function explode_name($source_image) + public function explode_name($source_image) { $ext = strrchr($source_image, '.'); $name = ($ext === FALSE) ? $source_image : substr($source_image, 0, -strlen($ext)); @@ -1441,17 +1799,16 @@ class CI_Image_lib { /** * Is GD Installed? * - * @access public * @return bool */ - function gd_loaded() + public function gd_loaded() { if ( ! extension_loaded('gd')) { - if ( ! dl('gd.so')) - { - return FALSE; - } + /* As it is stated in the PHP manual, dl() is not always available + * and even if so - it could generate an E_WARNING message on failure + */ + return (function_exists('dl') && @dl('gd.so')); } return TRUE; @@ -1462,17 +1819,14 @@ class CI_Image_lib { /** * Get GD version * - * @access public * @return mixed */ - function gd_version() + public function gd_version() { if (function_exists('gd_info')) { $gd_version = @gd_info(); - $gd_version = preg_replace("/\D/", "", $gd_version['GD Version']); - - return $gd_version; + return preg_replace('/\D/', '', $gd_version['GD Version']); } return FALSE; @@ -1483,11 +1837,10 @@ class CI_Image_lib { /** * Set error message * - * @access public * @param string * @return void */ - function set_error($msg) + public function set_error($msg) { $CI =& get_instance(); $CI->lang->load('imglib'); @@ -1496,15 +1849,14 @@ class CI_Image_lib { { foreach ($msg as $val) { - - $msg = ($CI->lang->line($val) == FALSE) ? $val : $CI->lang->line($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 = ($CI->lang->line($msg) === FALSE) ? $msg : $CI->lang->line($msg); $this->error_msg[] = $msg; log_message('error', $msg); } @@ -1515,23 +1867,13 @@ class CI_Image_lib { /** * Show error messages * - * @access public + * @param string * @param string * @return string */ - function display_errors($open = '<p>', $close = '</p>') + public function display_errors($open = '<p>', $close = '</p>') { - $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 Image_lib Class - -/* End of file Image_lib.php */ -/* Location: ./system/libraries/Image_lib.php */
\ No newline at end of file diff --git a/system/libraries/Javascript.php b/system/libraries/Javascript.php deleted file mode 100644 index a26bb8400..000000000 --- a/system/libraries/Javascript.php +++ /dev/null @@ -1,871 +0,0 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); -/** - * CodeIgniter - * - * An open source application development framework for PHP 5.1.6 or newer - * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 - * @filesource - */ - -// ------------------------------------------------------------------------ - -/** - * Javascript Class - * - * @package CodeIgniter - * @subpackage Libraries - * @category Javascript - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/javascript.html - */ -class CI_Javascript { - - var $_javascript_location = 'js'; - - public function __construct($params = array()) - { - $defaults = array('js_library_driver' => 'jquery', 'autoload' => TRUE); - - foreach ($defaults as $key => $val) - { - if (isset($params[$key]) && $params[$key] !== "") - { - $defaults[$key] = $params[$key]; - } - } - - extract($defaults); - - $this->CI =& get_instance(); - - // load the requested js library - $this->CI->load->library('javascript/'.$js_library_driver, array('autoload' => $autoload)); - // make js to refer to current library - $this->js =& $this->CI->$js_library_driver; - - log_message('debug', "Javascript Class Initialized and loaded. Driver used: $js_library_driver"); - } - - // -------------------------------------------------------------------- - // Event Code - // -------------------------------------------------------------------- - - /** - * Blur - * - * Outputs a javascript library blur event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function blur($element = 'this', $js = '') - { - return $this->js->_blur($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Change - * - * Outputs a javascript library change event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function change($element = 'this', $js = '') - { - return $this->js->_change($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Click - * - * Outputs a javascript library click event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @param boolean whether or not to return false - * @return string - */ - function click($element = 'this', $js = '', $ret_false = TRUE) - { - return $this->js->_click($element, $js, $ret_false); - } - - // -------------------------------------------------------------------- - - /** - * Double Click - * - * Outputs a javascript library dblclick event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function dblclick($element = 'this', $js = '') - { - return $this->js->_dblclick($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Error - * - * Outputs a javascript library error event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function error($element = 'this', $js = '') - { - return $this->js->_error($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Focus - * - * Outputs a javascript library focus event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function focus($element = 'this', $js = '') - { - return $this->js->__add_event($focus, $js); - } - - // -------------------------------------------------------------------- - - /** - * Hover - * - * Outputs a javascript library hover event - * - * @access public - * @param string - element - * @param string - Javascript code for mouse over - * @param string - Javascript code for mouse out - * @return string - */ - function hover($element = 'this', $over, $out) - { - return $this->js->__hover($element, $over, $out); - } - - // -------------------------------------------------------------------- - - /** - * Keydown - * - * Outputs a javascript library keydown event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function keydown($element = 'this', $js = '') - { - return $this->js->_keydown($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Keyup - * - * Outputs a javascript library keydown event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function keyup($element = 'this', $js = '') - { - return $this->js->_keyup($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Load - * - * Outputs a javascript library load event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function load($element = 'this', $js = '') - { - return $this->js->_load($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Mousedown - * - * Outputs a javascript library mousedown event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function mousedown($element = 'this', $js = '') - { - return $this->js->_mousedown($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Mouse Out - * - * Outputs a javascript library mouseout event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function mouseout($element = 'this', $js = '') - { - return $this->js->_mouseout($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Mouse Over - * - * Outputs a javascript library mouseover event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function mouseover($element = 'this', $js = '') - { - return $this->js->_mouseover($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Mouseup - * - * Outputs a javascript library mouseup event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function mouseup($element = 'this', $js = '') - { - return $this->js->_mouseup($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Output - * - * Outputs the called javascript to the screen - * - * @access public - * @param string The code to output - * @return string - */ - function output($js) - { - return $this->js->_output($js); - } - - // -------------------------------------------------------------------- - - /** - * Ready - * - * Outputs a javascript library mouseup event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function ready($js) - { - return $this->js->_document_ready($js); - } - - // -------------------------------------------------------------------- - - /** - * Resize - * - * Outputs a javascript library resize event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function resize($element = 'this', $js = '') - { - return $this->js->_resize($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Scroll - * - * Outputs a javascript library scroll event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function scroll($element = 'this', $js = '') - { - return $this->js->_scroll($element, $js); - } - - // -------------------------------------------------------------------- - - /** - * Unload - * - * Outputs a javascript library unload event - * - * @access public - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function unload($element = 'this', $js = '') - { - return $this->js->_unload($element, $js); - } - - // -------------------------------------------------------------------- - // Effects - // -------------------------------------------------------------------- - - - /** - * Add Class - * - * Outputs a javascript library addClass event - * - * @access public - * @param string - element - * @param string - Class to add - * @return string - */ - function addClass($element = 'this', $class = '') - { - return $this->js->_addClass($element, $class); - } - - // -------------------------------------------------------------------- - - /** - * Animate - * - * Outputs a javascript library animate event - * - * @access public - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function animate($element = 'this', $params = array(), $speed = '', $extra = '') - { - return $this->js->_animate($element, $params, $speed, $extra); - } - - // -------------------------------------------------------------------- - - /** - * Fade In - * - * Outputs a javascript library hide event - * - * @access public - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function fadeIn($element = 'this', $speed = '', $callback = '') - { - return $this->js->_fadeIn($element, $speed, $callback); - } - - // -------------------------------------------------------------------- - - /** - * Fade Out - * - * Outputs a javascript library hide event - * - * @access public - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function fadeOut($element = 'this', $speed = '', $callback = '') - { - return $this->js->_fadeOut($element, $speed, $callback); - } - // -------------------------------------------------------------------- - - /** - * Slide Up - * - * Outputs a javascript library slideUp event - * - * @access public - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function slideUp($element = 'this', $speed = '', $callback = '') - { - return $this->js->_slideUp($element, $speed, $callback); - - } - - // -------------------------------------------------------------------- - - /** - * Remove Class - * - * Outputs a javascript library removeClass event - * - * @access public - * @param string - element - * @param string - Class to add - * @return string - */ - function removeClass($element = 'this', $class = '') - { - return $this->js->_removeClass($element, $class); - } - - // -------------------------------------------------------------------- - - /** - * Slide Down - * - * Outputs a javascript library slideDown event - * - * @access public - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function slideDown($element = 'this', $speed = '', $callback = '') - { - return $this->js->_slideDown($element, $speed, $callback); - } - - // -------------------------------------------------------------------- - - /** - * Slide Toggle - * - * Outputs a javascript library slideToggle event - * - * @access public - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function slideToggle($element = 'this', $speed = '', $callback = '') - { - return $this->js->_slideToggle($element, $speed, $callback); - - } - - // -------------------------------------------------------------------- - - /** - * Hide - * - * Outputs a javascript library hide action - * - * @access public - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function hide($element = 'this', $speed = '', $callback = '') - { - return $this->js->_hide($element, $speed, $callback); - } - - // -------------------------------------------------------------------- - - /** - * Toggle - * - * Outputs a javascript library toggle event - * - * @access public - * @param string - element - * @return string - */ - function toggle($element = 'this') - { - return $this->js->_toggle($element); - - } - - // -------------------------------------------------------------------- - - /** - * Toggle Class - * - * Outputs a javascript library toggle class event - * - * @access public - * @param string - element - * @return string - */ - function toggleClass($element = 'this', $class='') - { - return $this->js->_toggleClass($element, $class); - } - - // -------------------------------------------------------------------- - - /** - * Show - * - * Outputs a javascript library show event - * - * @access public - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function show($element = 'this', $speed = '', $callback = '') - { - return $this->js->_show($element, $speed, $callback); - } - - - // -------------------------------------------------------------------- - - /** - * Compile - * - * gather together all script needing to be output - * - * @access public - * @param string The element to attach the event to - * @return string - */ - function compile($view_var = 'script_foot', $script_tags = TRUE) - { - $this->js->_compile($view_var, $script_tags); - } - - /** - * Clear Compile - * - * Clears any previous javascript collected for output - * - * @access public - * @return void - */ - function clear_compile() - { - $this->js->_clear_compile(); - } - - // -------------------------------------------------------------------- - - /** - * External - * - * Outputs a <script> tag with the source as an external js file - * - * @access public - * @param string The element to attach the event to - * @return string - */ - function external($external_file = '', $relative = FALSE) - { - if ($external_file !== '') - { - $this->_javascript_location = $external_file; - } - else - { - if ($this->CI->config->item('javascript_location') != '') - { - $this->_javascript_location = $this->CI->config->item('javascript_location'); - } - } - - if ($relative === TRUE OR strncmp($external_file, 'http://', 7) == 0 OR strncmp($external_file, 'https://', 8) == 0) - { - $str = $this->_open_script($external_file); - } - elseif (strpos($this->_javascript_location, 'http://') !== FALSE) - { - $str = $this->_open_script($this->_javascript_location.$external_file); - } - else - { - $str = $this->_open_script($this->CI->config->slash_item('base_url').$this->_javascript_location.$external_file); - } - - $str .= $this->_close_script(); - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Inline - * - * Outputs a <script> tag - * - * @access public - * @param string The element to attach the event to - * @param boolean If a CDATA section should be added - * @return string - */ - function inline($script, $cdata = TRUE) - { - $str = $this->_open_script(); - $str .= ($cdata) ? "\n// <![CDATA[\n{$script}\n// ]]>\n" : "\n{$script}\n"; - $str .= $this->_close_script(); - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Open Script - * - * Outputs an opening <script> - * - * @access private - * @param string - * @return string - */ - function _open_script($src = '') - { - $str = '<script type="text/javascript" charset="'.strtolower($this->CI->config->item('charset')).'"'; - $str .= ($src == '') ? '>' : ' src="'.$src.'">'; - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Close Script - * - * Outputs an closing </script> - * - * @access private - * @param string - * @return string - */ - function _close_script($extra = "\n") - { - return "</script>$extra"; - } - - - // -------------------------------------------------------------------- - // -------------------------------------------------------------------- - // AJAX-Y STUFF - still a testbed - // -------------------------------------------------------------------- - // -------------------------------------------------------------------- - - /** - * Update - * - * Outputs a javascript library slideDown event - * - * @access public - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function update($element = 'this', $speed = '', $callback = '') - { - return $this->js->_updater($element, $speed, $callback); - } - - // -------------------------------------------------------------------- - - /** - * Generate JSON - * - * Can be passed a database result or associative array and returns a JSON formatted string - * - * @param mixed result set or array - * @param bool match array types (defaults to objects) - * @return string a json formatted string - */ - function generate_json($result = NULL, $match_array_type = FALSE) - { - // JSON data can optionally be passed to this function - // either as a database result object or an array, or a user supplied array - if ( ! is_null($result)) - { - if (is_object($result)) - { - $json_result = $result->result_array(); - } - elseif (is_array($result)) - { - $json_result = $result; - } - else - { - return $this->_prep_args($result); - } - } - else - { - return 'null'; - } - - $json = array(); - $_is_assoc = TRUE; - - if ( ! is_array($json_result) AND empty($json_result)) - { - show_error("Generate JSON Failed - Illegal key, value pair."); - } - elseif ($match_array_type) - { - $_is_assoc = $this->_is_associative_array($json_result); - } - - foreach ($json_result as $k => $v) - { - if ($_is_assoc) - { - $json[] = $this->_prep_args($k, TRUE).':'.$this->generate_json($v, $match_array_type); - } - else - { - $json[] = $this->generate_json($v, $match_array_type); - } - } - - $json = implode(',', $json); - - return $_is_assoc ? "{".$json."}" : "[".$json."]"; - - } - - // -------------------------------------------------------------------- - - /** - * Is associative array - * - * Checks for an associative array - * - * @access public - * @param type - * @return type - */ - function _is_associative_array($arr) - { - foreach (array_keys($arr) as $key => $val) - { - if ($key !== $val) - { - return TRUE; - } - } - - return FALSE; - } - - // -------------------------------------------------------------------- - - /** - * Prep Args - * - * Ensures a standard json value and escapes values - * - * @access public - * @param type - * @return type - */ - function _prep_args($result, $is_key = FALSE) - { - if (is_null($result)) - { - return 'null'; - } - elseif (is_bool($result)) - { - return ($result === TRUE) ? 'true' : 'false'; - } - elseif (is_string($result) OR $is_key) - { - return '"'.str_replace(array('\\', "\t", "\n", "\r", '"', '/'), array('\\\\', '\\t', '\\n', "\\r", '\"', '\/'), $result).'"'; - } - elseif (is_scalar($result)) - { - return $result; - } - } - - // -------------------------------------------------------------------- -} -// END Javascript Class - -/* End of file Javascript.php */ -/* Location: ./system/libraries/Javascript.php */
\ No newline at end of file diff --git a/system/libraries/Log.php b/system/libraries/Log.php deleted file mode 100644 index 6d3f9094d..000000000 --- a/system/libraries/Log.php +++ /dev/null @@ -1,114 +0,0 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); -/** - * CodeIgniter - * - * An open source application development framework for PHP 5.1.6 or newer - * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 - * @filesource - */ - -// ------------------------------------------------------------------------ - -/** - * Logging Class - * - * @package CodeIgniter - * @subpackage Libraries - * @category Logging - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/general/errors.html - */ -class CI_Log { - - protected $_log_path; - protected $_threshold = 1; - protected $_date_fmt = 'Y-m-d H:i:s'; - protected $_enabled = TRUE; - protected $_levels = array('ERROR' => '1', 'DEBUG' => '2', 'INFO' => '3', 'ALL' => '4'); - - /** - * Constructor - */ - public function __construct() - { - $config =& get_config(); - - $this->_log_path = ($config['log_path'] != '') ? $config['log_path'] : APPPATH.'logs/'; - - if ( ! is_dir($this->_log_path) OR ! is_really_writable($this->_log_path)) - { - $this->_enabled = FALSE; - } - - if (is_numeric($config['log_threshold'])) - { - $this->_threshold = $config['log_threshold']; - } - - if ($config['log_date_format'] != '') - { - $this->_date_fmt = $config['log_date_format']; - } - } - - // -------------------------------------------------------------------- - - /** - * Write Log File - * - * Generally this function will be called using the global log_message() function - * - * @param string the error level - * @param string the error message - * @param bool whether the error is a native PHP error - * @return bool - */ - public function write_log($level = 'error', $msg, $php_error = FALSE) - { - if ($this->_enabled === FALSE) - { - return FALSE; - } - - $level = strtoupper($level); - - if ( ! isset($this->_levels[$level]) OR ($this->_levels[$level] > $this->_threshold)) - { - return FALSE; - } - - $filepath = $this->_log_path.'log-'.date('Y-m-d').'.php'; - $message = ''; - - if ( ! file_exists($filepath)) - { - $message .= "<"."?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); ?".">\n\n"; - } - - if ( ! $fp = @fopen($filepath, FOPEN_WRITE_CREATE)) - { - return FALSE; - } - - $message .= $level.' '.(($level == 'INFO') ? ' -' : '-').' '.date($this->_date_fmt). ' --> '.$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 241ce1e59..9ee92b6e8 100644 --- a/system/libraries/Migration.php +++ b/system/libraries/Migration.php @@ -1,19 +1,42 @@ -<?php defined('BASEPATH') OR exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author EllisLab Dev Team - * @copyright Copyright (c) 2006 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Migration Class @@ -29,26 +52,82 @@ */ class CI_Migration { + /** + * Whether the library is enabled + * + * @var bool + */ protected $_migration_enabled = FALSE; + + /** + * Migration numbering type + * + * @var bool + */ + protected $_migration_type = 'sequential'; + + /** + * Path to migration classes + * + * @var string + */ protected $_migration_path = NULL; + + /** + * Current migration version + * + * @var mixed + */ protected $_migration_version = 0; + /** + * Database table with migration info + * + * @var string + */ + protected $_migration_table = 'migrations'; + + /** + * Whether to automatically run migrations + * + * @var bool + */ + protected $_migration_auto_latest = FALSE; + + /** + * Migration basename regex + * + * @var string + */ + protected $_migration_regex; + + /** + * Error message + * + * @var string + */ protected $_error_string = ''; + /** + * Initialize Migration Class + * + * @param array $config + * @return void + */ public function __construct($config = array()) { - # Only run this constructor on main library load - if (get_parent_class($this) !== FALSE) + // Only run this constructor on main library load + if ( ! in_array(get_class($this), array('CI_Migration', config_item('subclass_prefix').'Migration'), TRUE)) { return; } foreach ($config as $key => $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 +136,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 +147,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($this->_migration_table, array('version' => 0)); + } - $this->db->insert('migrations', 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 +191,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)); + $this->_error_string = sprintf($this->lang->line('migration_not_found'), $target_version); + return FALSE; + } - // Only one migration per step is permitted - if (count($f) > 1) - { - $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $i); - return FALSE; - } + 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; + } - // Migration step not found - if (count($f) == 0) + // 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) + { + // 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 trying to migrate up to a version greater than the last - // existing one, migrate to the last one. - if ($step == 1) + if ($number <= $current_version) + { + continue; + } + elseif ($number > $target_version) { 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)) + else { - $match[1] = strtolower($match[1]); - - // Cannot repeat a migration at different steps - if (in_array($match[1], $migrations)) + if ($number > $current_version) { - $this->_error_string = sprintf($this->lang->line('migration_multiple_version'), $match[1]); - return FALSE; + continue; } - - include $f[0]; - $class = 'Migration_' . ucfirst($match[1]); - - if ( ! class_exists($class)) + elseif ($number <= $target_version) { - $this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class); - return FALSE; + break; } + } - if ( ! is_callable(array($class, $method))) + // Check for sequence gaps + if ($this->_migration_type === 'sequential') + { + if (isset($previous) && abs($number - $previous) > 1) { - $this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class); + $this->_error_string = sprintf($this->lang->line('migration_sequence_gap'), $number); return FALSE; } - $migrations[] = $match[1]; + $previous = $number; } - else + + 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_invalid_filename'), $file); + $this->_error_string = sprintf($this->lang->line('migration_class_doesnt_exist'), $class); + return FALSE; + } + elseif ( ! method_exists($class, $method) OR ! (new ReflectionMethod($class, $method))->isPublic()) + { + $this->_error_string = sprintf($this->lang->line('migration_missing_'.$method.'_method'), $class); return FALSE; } - } - - log_message('debug', 'Current migration: ' . $current_version); - $version = $i + ($step == 1 ? -1 : 0); + $pending[$number] = array($class, $method); + } - // If there is nothing to do so quit - if ($migrations === array()) + // Now just run the necessary migrations + foreach ($pending as $number => $migration) { - return TRUE; - } + log_message('debug', 'Migrating '.$method.' from version '.$current_version.' to version '.$number); - log_message('debug', 'Migrating from ' . $method . ' to version ' . $version); + $migration[0] = new $migration[0]; + call_user_func($migration); + $current_version = $number; + $this->_update_version($current_version); + } - // Loop through the migrations - foreach ($migrations AS $migration) + // 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) { - // Run the migration class - $class = 'Migration_' . ucfirst(strtolower($migration)); - call_user_func(array(new $class, $method)); - - $current_version += $step; + $current_version = $target_version; $this->_update_version($current_version); } log_message('debug', 'Finished migrating to '.$current_version); - return $current_version; } // -------------------------------------------------------------------- /** - * Set's the schema to the latest migration + * Sets the schema to the latest migration * - * @return mixed true if already latest, false if failed, int if upgraded + * @return mixed Current version string on success, FALSE on failure */ public function latest() { - if ( ! $migrations = $this->find_migrations()) + $migrations = $this->find_migrations(); + + if (empty($migrations)) { $this->_error_string = $this->lang->line('migration_none_found'); - return false; + return FALSE; } $last_migration = basename(end($migrations)); // Calculate the last migration step from existing migration - // filenames and procceed to the standard version migration - return $this->version((int) substr($last_migration, 0, 3)); + // filenames and proceed to the standard version migration + return $this->version($this->_get_migration_number($last_migration)); } // -------------------------------------------------------------------- /** - * Set's the schema to the migration version set in config + * Sets the schema to the migration version set in config * - * @return mixed true if already current, false if failed, int if upgraded + * @return mixed TRUE if no migrations are found, current version string on success, FALSE on failure */ public function current() { @@ -258,28 +372,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 +439,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 +452,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,7 +467,7 @@ class CI_Migration { /** * Enable the use of CI super-global * - * @param mixed $var + * @param string $var * @return mixed */ public function __get($var) @@ -324,6 +476,3 @@ class CI_Migration { } } - -/* End of file Migration.php */ -/* Location: ./system/libraries/Migration.php */
\ No newline at end of file diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php index 8b3aa8748..7d21b47b3 100644 --- a/system/libraries/Pagination.php +++ b/system/libraries/Pagination.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Pagination Class @@ -21,64 +44,308 @@ * @package CodeIgniter * @subpackage Libraries * @category Pagination - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/pagination.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/pagination.html */ class CI_Pagination { - var $base_url = ''; // The page we are linking to - var $prefix = ''; // A custom prefix added to the path. - var $suffix = ''; // A custom suffix added to the path. - - var $total_rows = 0; // Total number of items (database results) - var $per_page = 10; // Max number of items you want shown per page - var $num_links = 2; // Number of "digit" links to show before/after the currently viewed page - var $cur_page = 0; // The current page being viewed - var $use_page_numbers = FALSE; // Use page number for segment instead of offset - var $first_link = '‹ First'; - var $next_link = '>'; - var $prev_link = '<'; - var $last_link = 'Last ›'; - var $uri_segment = 3; - var $full_tag_open = ''; - var $full_tag_close = ''; - var $first_tag_open = ''; - var $first_tag_close = ' '; - var $last_tag_open = ' '; - var $last_tag_close = ''; - var $first_url = ''; // Alternative URL for the First Page. - var $cur_tag_open = ' <strong>'; - var $cur_tag_close = '</strong>'; - 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 = ''; + /** + * Base URL + * + * The page that we're linking to + * + * @var string + */ + protected $base_url = ''; + + /** + * 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 = '<strong>'; + + /** + * Current tag close + * + * @var string + */ + protected $cur_tag_close = '</strong>'; + + /** + * 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.'" '; - } + // _parse_attributes(), called by initialize(), needs to run at least once + // in order to enable "rel" attributes, and this triggers it. + isset($params['attributes']) OR $params['attributes'] = array(); - log_message('debug', "Pagination Class Initialized"); + $this->initialize($params); + log_message('info', 'Pagination Class Initialized'); } // -------------------------------------------------------------------- @@ -86,22 +353,36 @@ 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) + if (isset($params['attributes']) && is_array($params['attributes'])) + { + $this->_parse_attributes($params['attributes']); + unset($params['attributes']); + } + + foreach ($params as $key => $val) { - 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; } // -------------------------------------------------------------------- @@ -109,80 +390,143 @@ class CI_Pagination { /** * Generate the pagination links * - * @access public * @return string */ - function create_links() + public function create_links() { // If our item count or per-page total is zero there is no need to continue. + // Note: DO NOT change the operator to === here! if ($this->total_rows == 0 OR $this->per_page == 0) { return ''; } // Calculate the total number of pages - $num_pages = ceil($this->total_rows / $this->per_page); + $num_pages = (int) ceil($this->total_rows / $this->per_page); // Is there only one page? Hm... nothing more to do here then. - if ($num_pages == 1) + if ($num_pages === 1) { return ''; } - // Set the base page index for starting page number - if ($this->use_page_numbers) + // Check the user defined number of links. + $this->num_links = (int) $this->num_links; + + if ($this->num_links < 0) + { + show_error('Your number of links must be a non-negative number.'); + } + + // Keep any existing query string items. + // Note: Has nothing to do with any other query string option. + if ($this->reuse_query_string === TRUE) { - $base_page = 1; + $get = $this->CI->input->get(); + + // Unset the control, method, old-school routing options + unset($get['c'], $get['m'], $get[$this->query_string_segment]); } else { - $base_page = 0; + $get = array(); } - // Determine the current page number. - $CI =& get_instance(); + // 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) ? '?' : '&'; - if ($CI->config->item('enable_query_strings') === TRUE OR $this->page_query_string === TRUE) + // Are we using query strings? + if ($this->page_query_string === TRUE) { - if ($CI->input->get($this->query_string_segment) != $base_page) + // 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 === '') { - $this->cur_page = $CI->input->get($this->query_string_segment); + $first_url = $base_url; - // Prep the current page - no funny business! - $this->cur_page = (int) $this->cur_page; + // 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 { - if ($CI->uri->segment($this->uri_segment) != $base_page) + // Standard segment mode. + // Generate our saved query string to append later after the page number. + if ( ! empty($get)) { - $this->cur_page = $CI->uri->segment($this->uri_segment); + $query_string = $query_string_sep.http_build_query($get); + $this->suffix .= $query_string; + } - // Prep the current page - no funny business! - $this->cur_page = (int) $this->cur_page; + // 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, '/').'/'; } - - // Set current page to 1 if using page numbers instead of offset - if ($this->use_page_numbers AND $this->cur_page == 0) + + // 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 = $base_page; + $this->cur_page = $this->CI->input->get($this->query_string_segment); } + elseif (empty($this->cur_page)) + { + // Default to the last segment number if one hasn't been defined. + if ($this->uri_segment === 0) + { + $this->uri_segment = count($this->CI->uri->segment_array()); + } - $this->num_links = (int)$this->num_links; + $this->cur_page = $this->CI->uri->segment($this->uri_segment); - if ($this->num_links < 1) + // 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 { - show_error('Your number of links must be a positive number.'); + $this->cur_page = (string) $this->cur_page; } - if ( ! is_numeric($this->cur_page)) + // If something isn't quite right, back to the default base page. + if ( ! ctype_digit($this->cur_page) OR ($this->use_page_numbers && (int) $this->cur_page === 0)) { $this->cur_page = $base_page; } + else + { + // Make sure we're using integers for comparisons later. + $this->cur_page = (int) $this->cur_page; + } // Is the page number beyond the result range? - // If so we show the last page + // If so, we show the last page. if ($this->use_page_numbers) { if ($this->cur_page > $num_pages) @@ -190,67 +534,56 @@ class CI_Pagination { $this->cur_page = $num_pages; } } - else + elseif ($this->cur_page > $this->total_rows) { - if ($this->cur_page > $this->total_rows) - { - $this->cur_page = ($num_pages - 1) * $this->per_page; - } + $this->cur_page = ($num_pages - 1) * $this->per_page; } $uri_page_number = $this->cur_page; - + + // If we're using offset instead of page numbers, convert it + // to a page number, so we can generate the surrounding number links. if ( ! $this->use_page_numbers) { - $this->cur_page = floor(($this->cur_page/$this->per_page) + 1); + $this->cur_page = (int) floor(($this->cur_page/$this->per_page) + 1); } // Calculate the start and end numbers. These determine - // which number to start and end the digit links with - $start = (($this->cur_page - $this->num_links) > 0) ? $this->cur_page - ($this->num_links - 1) : 1; - $end = (($this->cur_page + $this->num_links) < $num_pages) ? $this->cur_page + $this->num_links : $num_pages; - - // Is pagination being used over GET or POST? If get, add a per_page query - // string. If post, add a trailing slash to the base URL if needed - if ($CI->config->item('enable_query_strings') === TRUE OR $this->page_query_string === TRUE) - { - $this->base_url = rtrim($this->base_url).'&'.$this->query_string_segment.'='; - } - else - { - $this->base_url = rtrim($this->base_url, '/') .'/'; - } + // which number to start and end the digit links with. + $start = (($this->cur_page - $this->num_links) > 0) ? $this->cur_page - ($this->num_links - 1) : 1; + $end = (($this->cur_page + $this->num_links) < $num_pages) ? $this->cur_page + $this->num_links : $num_pages; // And here we go... $output = ''; - // Render the "First" link - if ($this->first_link !== FALSE AND $this->cur_page > ($this->num_links + 1)) + // Render the "First" link. + if ($this->first_link !== FALSE && $this->cur_page > ($this->num_links + 1 + ! $this->num_links)) { - $first_url = ($this->first_url == '') ? $this->base_url : $this->first_url; - $output .= $this->first_tag_open.'<a '.$this->anchor_class.'href="'.$first_url.'">'.$this->first_link.'</a>'.$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.'<a href="'.$first_url.'"'.$attributes.$this->_attr_rel('start').'>' + .$this->first_link.'</a>'.$this->first_tag_close; } - // Render the "previous" link - if ($this->prev_link !== FALSE AND $this->cur_page != 1) + // Render the "Previous" link. + if ($this->prev_link !== FALSE && $this->cur_page !== 1) { - if ($this->use_page_numbers) - { - $i = $uri_page_number - 1; - } - else - { - $i = $uri_page_number - $this->per_page; - } + $i = ($this->use_page_numbers) ? $uri_page_number - 1 : $uri_page_number - $this->per_page; - if ($i == 0 && $this->first_url != '') + $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, ($this->cur_page - 1)); + + if ($i === $base_page) { - $output .= $this->prev_tag_open.'<a '.$this->anchor_class.'href="'.$this->first_url.'">'.$this->prev_link.'</a>'.$this->prev_tag_close; + // First page + $output .= $this->prev_tag_open.'<a href="'.$first_url.'"'.$attributes.$this->_attr_rel('prev').'>' + .$this->prev_link.'</a>'.$this->prev_tag_close; } else { - $i = ($i == 0) ? '' : $this->prefix.$i.$this->suffix; - $output .= $this->prev_tag_open.'<a '.$this->anchor_class.'href="'.$this->base_url.$i.'">'.$this->prev_link.'</a>'.$this->prev_tag_close; + $append = $this->prefix.$i.$this->suffix; + $output .= $this->prev_tag_open.'<a href="'.$base_url.$append.'"'.$attributes.$this->_attr_rel('prev').'>' + .$this->prev_link.'</a>'.$this->prev_tag_close; } } @@ -259,82 +592,106 @@ class CI_Pagination { if ($this->display_pages !== FALSE) { // Write the digit links - for ($loop = $start -1; $loop <= $end; $loop++) + for ($loop = $start - 1; $loop <= $end; $loop++) { - if ($this->use_page_numbers) - { - $i = $loop; - } - else - { - $i = ($loop * $this->per_page) - $this->per_page; - } + $i = ($this->use_page_numbers) ? $loop : ($loop * $this->per_page) - $this->per_page; + + $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, $loop); if ($i >= $base_page) { - if ($this->cur_page == $loop) + if ($this->cur_page === $loop) + { + // Current page + $output .= $this->cur_tag_open.$loop.$this->cur_tag_close; + } + elseif ($i === $base_page) { - $output .= $this->cur_tag_open.$loop.$this->cur_tag_close; // Current page + // First page + $output .= $this->num_tag_open.'<a href="'.$first_url.'"'.$attributes.$this->_attr_rel('start').'>' + .$loop.'</a>'.$this->num_tag_close; } else { - $n = ($i == $base_page) ? '' : $i; - - if ($n == '' && $this->first_url != '') - { - $output .= $this->num_tag_open.'<a '.$this->anchor_class.'href="'.$this->first_url.'">'.$loop.'</a>'.$this->num_tag_close; - } - else - { - $n = ($n == '') ? '' : $this->prefix.$n.$this->suffix; - - $output .= $this->num_tag_open.'<a '.$this->anchor_class.'href="'.$this->base_url.$n.'">'.$loop.'</a>'.$this->num_tag_close; - } + $append = $this->prefix.$i.$this->suffix; + $output .= $this->num_tag_open.'<a href="'.$base_url.$append.'"'.$attributes.'>' + .$loop.'</a>'.$this->num_tag_close; } } } } // Render the "next" link - if ($this->next_link !== FALSE AND $this->cur_page < $num_pages) + if ($this->next_link !== FALSE && $this->cur_page < $num_pages) { - if ($this->use_page_numbers) - { - $i = $this->cur_page + 1; - } - else - { - $i = ($this->cur_page * $this->per_page); - } + $i = ($this->use_page_numbers) ? $this->cur_page + 1 : $this->cur_page * $this->per_page; + + $attributes = sprintf('%s %s="%d"', $this->_attributes, $this->data_page_attr, $this->cur_page + 1); - $output .= $this->next_tag_open.'<a '.$this->anchor_class.'href="'.$this->base_url.$this->prefix.$i.$this->suffix.'">'.$this->next_link.'</a>'.$this->next_tag_close; + $output .= $this->next_tag_open.'<a href="'.$base_url.$this->prefix.$i.$this->suffix.'"'.$attributes + .$this->_attr_rel('next').'>'.$this->next_link.'</a>'.$this->next_tag_close; } // Render the "Last" link - if ($this->last_link !== FALSE AND ($this->cur_page + $this->num_links) < $num_pages) + if ($this->last_link !== FALSE && ($this->cur_page + $this->num_links + ! $this->num_links) < $num_pages) { - if ($this->use_page_numbers) - { - $i = $num_pages; - } - else - { - $i = (($num_pages * $this->per_page) - $this->per_page); - } - $output .= $this->last_tag_open.'<a '.$this->anchor_class.'href="'.$this->base_url.$this->prefix.$i.$this->suffix.'">'.$this->last_link.'</a>'.$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.'<a href="'.$base_url.$this->prefix.$i.$this->suffix.'"'.$attributes.'>' + .$this->last_link.'</a>'.$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 $this->full_tag_open.$output.$this->full_tag_close; + } + + // -------------------------------------------------------------------- - return $output; + /** + * 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 https://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 ''; } -} -// END Pagination Class -/* End of file Pagination.php */ -/* Location: ./system/libraries/Pagination.php */
\ No newline at end of file +} diff --git a/system/libraries/Parser.php b/system/libraries/Parser.php index 4d31f81c7..e0adec606 100644 --- a/system/libraries/Parser.php +++ b/system/libraries/Parser.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Parser Class @@ -21,22 +44,53 @@ * @package CodeIgniter * @subpackage Libraries * @category Parser - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/parser.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/parser.html */ class CI_Parser { - var $l_delim = '{'; - var $r_delim = '}'; - var $object; + /** + * Left delimiter character for pseudo vars + * + * @var string + */ + public $l_delim = '{'; + + /** + * Right delimiter character for pseudo vars + * + * @var string + */ + public $r_delim = '}'; + + /** + * Reference to CodeIgniter instance + * + * @var object + */ + protected $CI; + + // -------------------------------------------------------------------- /** - * Parse a template + * Class constructor + * + * @return void + */ + public function __construct() + { + $this->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 +98,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 +106,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 +124,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 +166,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 +181,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; - } - - $str = ''; - foreach ($data as $row) + $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 + ); + + foreach ($matches as $match) { - $temp = $match['1']; - foreach ($row as $key => $val) + $str = ''; + foreach ($data as $row) { - if ( ! is_array($val)) - { - $temp = $this->_parse_single($key, $val, $temp); - } - else + $temp = array(); + foreach ($row as $key => $val) { - $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 .= $temp; - } - return str_replace($match['0'], $str, $string); - } - - // -------------------------------------------------------------------- + $str .= strtr($match[1], $temp); + } - /** - * 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; + $replace[$match[0]] = $str; } - 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 2fe21db11..d423c1481 100644 --- a/system/libraries/Profiler.php +++ b/system/libraries/Profiler.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * CodeIgniter Profiler Class @@ -27,51 +50,68 @@ * @package CodeIgniter * @subpackage Libraries * @category Libraries - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/general/profiling.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/general/profiling.html */ class CI_Profiler { + /** + * List of profiler sections available to show + * + * @var array + */ protected $_available_sections = array( - 'benchmarks', - 'get', - 'memory_usage', - 'post', - 'uri_string', - 'controller_info', - 'queries', - 'http_headers', - 'session_data', - 'config' - ); + 'benchmarks', + 'get', + 'memory_usage', + 'post', + 'uri_string', + 'controller_info', + 'queries', + 'http_headers', + 'session_data', + 'config' + ); + /** + * Number of queries to show before making the additional queries togglable + * + * @var int + */ protected $_query_toggle_count = 25; + /** + * Reference to the CodeIgniter singleton + * + * @var object + */ protected $CI; // -------------------------------------------------------------------- + /** + * Class constructor + * + * Initialize Profiler + * + * @param array $config Parameters + */ public function __construct($config = array()) { $this->CI =& get_instance(); $this->CI->load->language('profiler'); - if (isset($config['query_toggle_count'])) - { - $this->_query_toggle_count = (int) $config['query_toggle_count']; - unset($config['query_toggle_count']); - } - // default all sections to display foreach ($this->_available_sections as $section) { if ( ! isset($config[$section])) { - $this->_compile_{$section} = TRUE; + $this->{'_compile_'.$section} = TRUE; } } $this->set_sections($config); + log_message('info', 'Profiler Class Initialized'); } // -------------------------------------------------------------------- @@ -81,16 +121,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); } } } @@ -114,36 +160,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 .= '<fieldset id="ci_profiler_benchmarks" style="border:1px solid #900;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">'; - $output .= "\n"; - $output .= '<legend style="color:#900;"> '.$this->CI->lang->line('profiler_benchmarks').' </legend>'; - $output .= "\n"; - $output .= "\n\n<table style='width:100%'>\n"; + $output = "\n\n" + .'<fieldset id="ci_profiler_benchmarks" style="border:1px solid #900;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee;">' + ."\n" + .'<legend style="color:#900;"> '.$this->CI->lang->line('profiler_benchmarks')." </legend>" + ."\n\n\n<table style=\"width:100%;\">\n"; foreach ($profile as $key => $val) { $key = ucwords(str_replace(array('_', '-'), ' ', $key)); - $output .= "<tr><td style='padding:5px;width:50%;color:#000;font-weight:bold;background-color:#ddd;'>".$key." </td><td style='padding:5px;width:50%;color:#900;font-weight:normal;background-color:#ddd;'>".$val."</td></tr>\n"; + $output .= '<tr><td style="padding:5px;width:50%;color:#000;font-weight:bold;background-color:#ddd;">' + .$key.' </td><td style="padding:5px;width:50%;color:#900;font-weight:normal;background-color:#ddd;">' + .$val."</td></tr>\n"; } - $output .= "</table>\n"; - $output .= "</fieldset>"; - - return $output; + return $output."</table>\n</fieldset>"; } // -------------------------------------------------------------------- @@ -158,27 +200,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 .= '<fieldset id="ci_profiler_queries" style="border:1px solid #0000FF;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">'; - $output .= "\n"; - $output .= '<legend style="color:#0000FF;"> '.$this->CI->lang->line('profiler_queries').' </legend>'; - $output .= "\n"; - $output .= "\n\n<table style='border:none; width:100%;'>\n"; - $output .="<tr><td style='width:100%;color:#0000FF;font-weight:normal;background-color:#eee;padding:5px'>".$this->CI->lang->line('profiler_no_db')."</td></tr>\n"; - $output .= "</table>\n"; - $output .= "</fieldset>"; - - return $output; + return "\n\n" + .'<fieldset id="ci_profiler_queries" style="border:1px solid #0000FF;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee;">' + ."\n" + .'<legend style="color:#0000FF;"> '.$this->CI->lang->line('profiler_queries').' </legend>' + ."\n\n\n<table style=\"border:none; width:100%;\">\n" + .'<tr><td style="width:100%;color:#0000FF;font-weight:normal;background-color:#eee;padding:5px;">' + .$this->CI->lang->line('profiler_no_db') + ."</td></tr>\n</table>\n</fieldset>"; } // Load the text helper so we can highlight the SQL @@ -188,58 +240,57 @@ class CI_Profiler { $highlight = array('SELECT', 'DISTINCT', 'FROM', 'WHERE', 'AND', 'LEFT JOIN', 'ORDER BY', 'GROUP BY', 'LIMIT', 'INSERT', 'INTO', 'VALUES', 'UPDATE', 'OR ', 'HAVING', 'OFFSET', 'NOT IN', 'IN', 'LIKE', 'NOT LIKE', 'COUNT', 'MAX', 'MIN', 'ON', 'AS', 'AVG', 'SUM', '(', ')'); $output = "\n\n"; - $count = 0; - foreach ($dbs as $db) + foreach ($dbs as $name => $db) { - $count++; - $hide_queries = (count($db->queries) > $this->_query_toggle_count) ? ' display:none' : ''; + $total_time = number_format(array_sum($db->query_times), 4).' '.$this->CI->lang->line('profiler_seconds'); $show_hide_js = '(<span style="cursor: pointer;" onclick="var s=document.getElementById(\'ci_profiler_queries_db_'.$count.'\').style;s.display=s.display==\'none\'?\'\':\'none\';this.innerHTML=this.innerHTML==\''.$this->CI->lang->line('profiler_section_hide').'\'?\''.$this->CI->lang->line('profiler_section_show').'\':\''.$this->CI->lang->line('profiler_section_hide').'\';">'.$this->CI->lang->line('profiler_section_hide').'</span>)'; - if ($hide_queries != '') + if ($hide_queries !== '') { $show_hide_js = '(<span style="cursor: pointer;" onclick="var s=document.getElementById(\'ci_profiler_queries_db_'.$count.'\').style;s.display=s.display==\'none\'?\'\':\'none\';this.innerHTML=this.innerHTML==\''.$this->CI->lang->line('profiler_section_show').'\'?\''.$this->CI->lang->line('profiler_section_hide').'\':\''.$this->CI->lang->line('profiler_section_show').'\';">'.$this->CI->lang->line('profiler_section_show').'</span>)'; } - $output .= '<fieldset style="border:1px solid #0000FF;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">'; - $output .= "\n"; - $output .= '<legend style="color:#0000FF;"> '.$this->CI->lang->line('profiler_database').': '.$db->database.' '.$this->CI->lang->line('profiler_queries').': '.count($db->queries).' '.$show_hide_js.'</legend>'; - $output .= "\n"; - $output .= "\n\n<table style='width:100%;{$hide_queries}' id='ci_profiler_queries_db_{$count}'>\n"; + $output .= '<fieldset style="border:1px solid #0000FF;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee;">' + ."\n" + .'<legend style="color:#0000FF;"> '.$this->CI->lang->line('profiler_database') + .': '.$db->database.' ('.$name.') '.$this->CI->lang->line('profiler_queries') + .': '.count($db->queries).' ('.$total_time.') '.$show_hide_js."</legend>\n\n\n" + .'<table style="width:100%;'.$hide_queries.'" id="ci_profiler_queries_db_'.$count."\">\n"; - if (count($db->queries) == 0) + if (count($db->queries) === 0) { - $output .= "<tr><td style='width:100%;color:#0000FF;font-weight:normal;background-color:#eee;padding:5px;'>".$this->CI->lang->line('profiler_no_queries')."</td></tr>\n"; + $output .= '<tr><td style="width:100%;color:#0000FF;font-weight:normal;background-color:#eee;padding:5px;">' + .$this->CI->lang->line('profiler_no_queries')."</td></tr>\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, '<strong>'.$bold.'</strong>', $val); } - $output .= "<tr><td style='padding:5px; vertical-align: top;width:1%;color:#900;font-weight:normal;background-color:#ddd;'>".$time." </td><td style='padding:5px; color:#000;font-weight:normal;background-color:#ddd;'>".$val."</td></tr>\n"; + $output .= '<tr><td style="padding:5px;vertical-align:top;width:1%;color:#900;font-weight:normal;background-color:#ddd;">' + .$time.' </td><td style="padding:5px;color:#000;font-weight:normal;background-color:#ddd;">' + .$val."</td></tr>\n"; } } - $output .= "</table>\n"; - $output .= "</fieldset>"; - + $output .= "</table>\n</fieldset>"; + $count++; } return $output; } - // -------------------------------------------------------------------- /** @@ -249,44 +300,35 @@ class CI_Profiler { */ protected function _compile_get() { - $output = "\n\n"; - $output .= '<fieldset id="ci_profiler_get" style="border:1px solid #cd6e00;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">'; - $output .= "\n"; - $output .= '<legend style="color:#cd6e00;"> '.$this->CI->lang->line('profiler_get_data').' </legend>'; - $output .= "\n"; + $output = "\n\n" + .'<fieldset id="ci_profiler_get" style="border:1px solid #cd6e00;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee;">' + ."\n" + .'<legend style="color:#cd6e00;"> '.$this->CI->lang->line('profiler_get_data')." </legend>\n"; - if (count($_GET) == 0) + if (count($_GET) === 0) { - $output .= "<div style='color:#cd6e00;font-weight:normal;padding:4px 0 4px 0'>".$this->CI->lang->line('profiler_no_get')."</div>"; + $output .= '<div style="color:#cd6e00;font-weight:normal;padding:4px 0 4px 0;">'.$this->CI->lang->line('profiler_no_get').'</div>'; } else { - $output .= "\n\n<table style='width:100%; border:none'>\n"; + $output .= "\n\n<table style=\"width:100%;border:none;\">\n"; foreach ($_GET as $key => $val) { - if ( ! is_numeric($key)) - { - $key = "'".$key."'"; - } - - $output .= "<tr><td style='width:50%;color:#000;background-color:#ddd;padding:5px'>$_GET[".$key."] </td><td style='width:50%;padding:5px;color:#cd6e00;font-weight:normal;background-color:#ddd;'>"; - if (is_array($val)) - { - $output .= "<pre>" . htmlspecialchars(stripslashes(print_r($val, true))) . "</pre>"; - } - else - { - $output .= htmlspecialchars(stripslashes($val)); - } - $output .= "</td></tr>\n"; + is_int($key) OR $key = "'".htmlspecialchars($key, ENT_QUOTES, config_item('charset'))."'"; + $val = (is_array($val) OR is_object($val)) + ? '<pre>'.htmlspecialchars(print_r($val, TRUE), ENT_QUOTES, config_item('charset')).'</pre>' + : htmlspecialchars($val, ENT_QUOTES, config_item('charset')); + + $output .= '<tr><td style="width:50%;color:#000;background-color:#ddd;padding:5px;">$_GET[' + .$key.'] </td><td style="width:50%;padding:5px;color:#cd6e00;font-weight:normal;background-color:#ddd;">' + .$val."</td></tr>\n"; } $output .= "</table>\n"; } - $output .= "</fieldset>"; - return $output; + return $output.'</fieldset>'; } // -------------------------------------------------------------------- @@ -298,44 +340,47 @@ class CI_Profiler { */ protected function _compile_post() { - $output = "\n\n"; - $output .= '<fieldset id="ci_profiler_post" style="border:1px solid #009900;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">'; - $output .= "\n"; - $output .= '<legend style="color:#009900;"> '.$this->CI->lang->line('profiler_post_data').' </legend>'; - $output .= "\n"; + $output = "\n\n" + .'<fieldset id="ci_profiler_post" style="border:1px solid #009900;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee;">' + ."\n" + .'<legend style="color:#009900;"> '.$this->CI->lang->line('profiler_post_data')." </legend>\n"; - if (count($_POST) == 0) + if (count($_POST) === 0 && count($_FILES) === 0) { - $output .= "<div style='color:#009900;font-weight:normal;padding:4px 0 4px 0'>".$this->CI->lang->line('profiler_no_post')."</div>"; + $output .= '<div style="color:#009900;font-weight:normal;padding:4px 0 4px 0;">'.$this->CI->lang->line('profiler_no_post').'</div>'; } else { - $output .= "\n\n<table style='width:100%'>\n"; + $output .= "\n\n<table style=\"width:100%;\">\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)) + ? '<pre>'.htmlspecialchars(print_r($val, TRUE), ENT_QUOTES, config_item('charset')).'</pre>' + : htmlspecialchars($val, ENT_QUOTES, config_item('charset')); + + $output .= '<tr><td style="width:50%;padding:5px;color:#000;background-color:#ddd;">$_POST[' + .$key.'] </td><td style="width:50%;padding:5px;color:#009900;font-weight:normal;background-color:#ddd;">' + .$val."</td></tr>\n"; + } - $output .= "<tr><td style='width:50%;padding:5px;color:#000;background-color:#ddd;'>$_POST[".$key."] </td><td style='width:50%;padding:5px;color:#009900;font-weight:normal;background-color:#ddd;'>"; - if (is_array($val)) - { - $output .= "<pre>" . htmlspecialchars(stripslashes(print_r($val, TRUE))) . "</pre>"; - } - else - { - $output .= htmlspecialchars(stripslashes($val)); - } - $output .= "</td></tr>\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)) + ? '<pre>'.htmlspecialchars(print_r($val, TRUE), ENT_QUOTES, config_item('charset')).'</pre>' + : htmlspecialchars($val, ENT_QUOTES, config_item('charset')); + + $output .= '<tr><td style="width:50%;padding:5px;color:#000;background-color:#ddd;">$_FILES[' + .$key.'] </td><td style="width:50%;padding:5px;color:#009900;font-weight:normal;background-color:#ddd;">' + .$val."</td></tr>\n"; } $output .= "</table>\n"; } - $output .= "</fieldset>"; - return $output; + return $output.'</fieldset>'; } // -------------------------------------------------------------------- @@ -347,24 +392,13 @@ class CI_Profiler { */ protected function _compile_uri_string() { - $output = "\n\n"; - $output .= '<fieldset id="ci_profiler_uri_string" style="border:1px solid #000;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">'; - $output .= "\n"; - $output .= '<legend style="color:#000;"> '.$this->CI->lang->line('profiler_uri_string').' </legend>'; - $output .= "\n"; - - if ($this->CI->uri->uri_string == '') - { - $output .= "<div style='color:#000;font-weight:normal;padding:4px 0 4px 0'>".$this->CI->lang->line('profiler_no_uri')."</div>"; - } - else - { - $output .= "<div style='color:#000;font-weight:normal;padding:4px 0 4px 0'>".$this->CI->uri->uri_string."</div>"; - } - - $output .= "</fieldset>"; - - return $output; + return "\n\n" + .'<fieldset id="ci_profiler_uri_string" style="border:1px solid #000;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee;">' + ."\n" + .'<legend style="color:#000;"> '.$this->CI->lang->line('profiler_uri_string')." </legend>\n" + .'<div style="color:#000;font-weight:normal;padding:4px 0 4px 0;">' + .($this->CI->uri->uri_string === '' ? $this->CI->lang->line('profiler_no_uri') : $this->CI->uri->uri_string) + .'</div></fieldset>'; } // -------------------------------------------------------------------- @@ -376,17 +410,12 @@ class CI_Profiler { */ protected function _compile_controller_info() { - $output = "\n\n"; - $output .= '<fieldset id="ci_profiler_controller_info" style="border:1px solid #995300;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">'; - $output .= "\n"; - $output .= '<legend style="color:#995300;"> '.$this->CI->lang->line('profiler_controller_info').' </legend>'; - $output .= "\n"; - - $output .= "<div style='color:#995300;font-weight:normal;padding:4px 0 4px 0'>".$this->CI->router->fetch_class()."/".$this->CI->router->fetch_method()."</div>"; - - $output .= "</fieldset>"; - - return $output; + return "\n\n" + .'<fieldset id="ci_profiler_controller_info" style="border:1px solid #995300;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee;">' + ."\n" + .'<legend style="color:#995300;"> '.$this->CI->lang->line('profiler_controller_info')." </legend>\n" + .'<div style="color:#995300;font-weight:normal;padding:4px 0 4px 0;">'.$this->CI->router->class.'/'.$this->CI->router->method + .'</div></fieldset>'; } // -------------------------------------------------------------------- @@ -400,24 +429,13 @@ class CI_Profiler { */ protected function _compile_memory_usage() { - $output = "\n\n"; - $output .= '<fieldset id="ci_profiler_memory_usage" style="border:1px solid #5a0099;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">'; - $output .= "\n"; - $output .= '<legend style="color:#5a0099;"> '.$this->CI->lang->line('profiler_memory_usage').' </legend>'; - $output .= "\n"; - - if (function_exists('memory_get_usage') && ($usage = memory_get_usage()) != '') - { - $output .= "<div style='color:#5a0099;font-weight:normal;padding:4px 0 4px 0'>".number_format($usage).' bytes</div>'; - } - else - { - $output .= "<div style='color:#5a0099;font-weight:normal;padding:4px 0 4px 0'>".$this->CI->lang->line('profiler_no_memory')."</div>"; - } - - $output .= "</fieldset>"; - - return $output; + return "\n\n" + .'<fieldset id="ci_profiler_memory_usage" style="border:1px solid #5a0099;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee;">' + ."\n" + .'<legend style="color:#5a0099;"> '.$this->CI->lang->line('profiler_memory_usage')." </legend>\n" + .'<div style="color:#5a0099;font-weight:normal;padding:4px 0 4px 0;">' + .(($usage = memory_get_usage()) != '' ? number_format($usage).' bytes' : $this->CI->lang->line('profiler_no_memory')) + .'</div></fieldset>'; } // -------------------------------------------------------------------- @@ -431,24 +449,21 @@ class CI_Profiler { */ protected function _compile_http_headers() { - $output = "\n\n"; - $output .= '<fieldset id="ci_profiler_http_headers" style="border:1px solid #000;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">'; - $output .= "\n"; - $output .= '<legend style="color:#000;"> '.$this->CI->lang->line('profiler_headers').' (<span style="cursor: pointer;" onclick="var s=document.getElementById(\'ci_profiler_httpheaders_table\').style;s.display=s.display==\'none\'?\'\':\'none\';this.innerHTML=this.innerHTML==\''.$this->CI->lang->line('profiler_section_show').'\'?\''.$this->CI->lang->line('profiler_section_hide').'\':\''.$this->CI->lang->line('profiler_section_show').'\';">'.$this->CI->lang->line('profiler_section_show').'</span>)</legend>'; - $output .= "\n"; - - $output .= "\n\n<table style='width:100%;display:none' id='ci_profiler_httpheaders_table'>\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) + $output = "\n\n" + .'<fieldset id="ci_profiler_http_headers" style="border:1px solid #000;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee;">' + ."\n" + .'<legend style="color:#000;"> '.$this->CI->lang->line('profiler_headers') + .' (<span style="cursor: pointer;" onclick="var s=document.getElementById(\'ci_profiler_httpheaders_table\').style;s.display=s.display==\'none\'?\'\':\'none\';this.innerHTML=this.innerHTML==\''.$this->CI->lang->line('profiler_section_show').'\'?\''.$this->CI->lang->line('profiler_section_hide').'\':\''.$this->CI->lang->line('profiler_section_show').'\';">'.$this->CI->lang->line('profiler_section_show')."</span>)</legend>\n\n\n" + .'<table style="width:100%;display:none;" id="ci_profiler_httpheaders_table">'."\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', 'HTTP_DNT') as $header) { - $val = (isset($_SERVER[$header])) ? $_SERVER[$header] : ''; - $output .= "<tr><td style='vertical-align: top;width:50%;padding:5px;color:#900;background-color:#ddd;'>".$header." </td><td style='width:50%;padding:5px;color:#000;background-color:#ddd;'>".$val."</td></tr>\n"; + $val = isset($_SERVER[$header]) ? htmlspecialchars($_SERVER[$header], ENT_QUOTES, config_item('charset')) : ''; + $output .= '<tr><td style="vertical-align:top;width:50%;padding:5px;color:#900;background-color:#ddd;">' + .$header.' </td><td style="width:50%;padding:5px;color:#000;background-color:#ddd;">'.$val."</td></tr>\n"; } - $output .= "</table>\n"; - $output .= "</fieldset>"; - - return $output; + return $output."</table>\n</fieldset>"; } // -------------------------------------------------------------------- @@ -462,28 +477,30 @@ class CI_Profiler { */ protected function _compile_config() { - $output = "\n\n"; - $output .= '<fieldset id="ci_profiler_config" style="border:1px solid #000;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">'; - $output .= "\n"; - $output .= '<legend style="color:#000;"> '.$this->CI->lang->line('profiler_config').' (<span style="cursor: pointer;" onclick="var s=document.getElementById(\'ci_profiler_config_table\').style;s.display=s.display==\'none\'?\'\':\'none\';this.innerHTML=this.innerHTML==\''.$this->CI->lang->line('profiler_section_show').'\'?\''.$this->CI->lang->line('profiler_section_hide').'\':\''.$this->CI->lang->line('profiler_section_show').'\';">'.$this->CI->lang->line('profiler_section_show').'</span>)</legend>'; - $output .= "\n"; + $output = "\n\n" + .'<fieldset id="ci_profiler_config" style="border:1px solid #000;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee;">' + ."\n" + .'<legend style="color:#000;"> '.$this->CI->lang->line('profiler_config').' (<span style="cursor: pointer;" onclick="var s=document.getElementById(\'ci_profiler_config_table\').style;s.display=s.display==\'none\'?\'\':\'none\';this.innerHTML=this.innerHTML==\''.$this->CI->lang->line('profiler_section_show').'\'?\''.$this->CI->lang->line('profiler_section_hide').'\':\''.$this->CI->lang->line('profiler_section_show').'\';">'.$this->CI->lang->line('profiler_section_show')."</span>)</legend>\n\n\n" + .'<table style="width:100%;display:none;" id="ci_profiler_config_table">'."\n"; - $output .= "\n\n<table style='width:100%; display:none' id='ci_profiler_config_table'>\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>' ; + $pre_close = '</pre>'; } - $output .= "<tr><td style='padding:5px; vertical-align: top;color:#900;background-color:#ddd;'>".$config." </td><td style='padding:5px; color:#000;background-color:#ddd;'>".htmlspecialchars($val)."</td></tr>\n"; + $output .= '<tr><td style="padding:5px;vertical-align:top;color:#900;background-color:#ddd;">' + .$config.' </td><td style="padding:5px;color:#000;background-color:#ddd;">'.$pre.htmlspecialchars((string) $val, ENT_QUOTES, config_item('charset')).$pre_close."</td></tr>\n"; } - $output .= "</table>\n"; - $output .= "</fieldset>"; - - return $output; + return $output."</table>\n</fieldset>"; } // -------------------------------------------------------------------- @@ -493,30 +510,35 @@ class CI_Profiler { * * @return string */ - private function _compile_session_data() + protected function _compile_session_data() { if ( ! isset($this->CI->session)) { return; } - $output = '<fieldset id="ci_profiler_csession" style="border:1px solid #000;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">'; - $output .= '<legend style="color:#000;"> '.$this->CI->lang->line('profiler_session_data').' (<span style="cursor: pointer;" onclick="var s=document.getElementById(\'ci_profiler_session_data\').style;s.display=s.display==\'none\'?\'\':\'none\';this.innerHTML=this.innerHTML==\''.$this->CI->lang->line('profiler_section_show').'\'?\''.$this->CI->lang->line('profiler_section_hide').'\':\''.$this->CI->lang->line('profiler_section_show').'\';">'.$this->CI->lang->line('profiler_section_show').'</span>)</legend>'; - $output .= "<table style='width:100%;display:none' id='ci_profiler_session_data'>"; + $output = '<fieldset id="ci_profiler_csession" style="border:1px solid #000;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee;">' + .'<legend style="color:#000;"> '.$this->CI->lang->line('profiler_session_data').' (<span style="cursor: pointer;" onclick="var s=document.getElementById(\'ci_profiler_session_data\').style;s.display=s.display==\'none\'?\'\':\'none\';this.innerHTML=this.innerHTML==\''.$this->CI->lang->line('profiler_section_show').'\'?\''.$this->CI->lang->line('profiler_section_hide').'\':\''.$this->CI->lang->line('profiler_section_show').'\';">'.$this->CI->lang->line('profiler_section_show').'</span>)</legend>' + .'<table style="width:100%;display:none;" id="ci_profiler_session_data">'; - foreach ($this->CI->session->all_userdata() as $key => $val) + 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>' ; + $pre_close = '</pre>'; } - $output .= "<tr><td style='padding:5px; vertical-align: top;color:#900;background-color:#ddd;'>".$key." </td><td style='padding:5px; color:#000;background-color:#ddd;'>".htmlspecialchars($val)."</td></tr>\n"; + $output .= '<tr><td style="padding:5px;vertical-align:top;color:#900;background-color:#ddd;">' + .$key.' </td><td style="padding:5px;color:#000;background-color:#ddd;">'.$pre.htmlspecialchars((string) $val, ENT_QUOTES, config_item('charset')).$pre_close."</td></tr>\n"; } - $output .= '</table>'; - $output .= "</fieldset>"; - return $output; + return $output."</table>\n</fieldset>"; } // -------------------------------------------------------------------- @@ -528,31 +550,26 @@ class CI_Profiler { */ public function run() { - $output = "<div id='codeigniter_profiler' style='clear:both;background-color:#fff;padding:10px;'>"; + $output = '<div id="codeigniter_profiler" style="clear:both;background-color:#fff;padding:10px;">'; $fields_displayed = 0; foreach ($this->_available_sections as $section) { - if ($this->_compile_{$section} !== FALSE) + 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 .= '<p style="border:1px solid #5a0099;padding:10px;margin:20px 0;background-color:#eee">'.$this->CI->lang->line('profiler_no_profiles').'</p>'; + $output .= '<p style="border:1px solid #5a0099;padding:10px;margin:20px 0;background-color:#eee;">' + .$this->CI->lang->line('profiler_no_profiles').'</p>'; } - $output .= '</div>'; - - return $output; + return $output.'</div>'; } -} - -// 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/Session.php b/system/libraries/Session.php deleted file mode 100644 index 5f4f60547..000000000 --- a/system/libraries/Session.php +++ /dev/null @@ -1,793 +0,0 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); -/** - * CodeIgniter - * - * An open source application development framework for PHP 5.1.6 or newer - * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 - * @filesource - */ - -// ------------------------------------------------------------------------ - -/** - * Session Class - * - * @package CodeIgniter - * @subpackage Libraries - * @category Sessions - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/sessions.html - */ -class CI_Session { - - var $sess_encrypt_cookie = FALSE; - var $sess_use_database = FALSE; - var $sess_table_name = ''; - var $sess_expiration = 7200; - var $sess_expire_on_close = FALSE; - var $sess_match_ip = FALSE; - var $sess_match_useragent = TRUE; - var $sess_cookie_name = 'ci_session'; - var $cookie_prefix = ''; - var $cookie_path = ''; - var $cookie_domain = ''; - var $cookie_secure = FALSE; - var $sess_time_to_update = 300; - var $encryption_key = ''; - var $flashdata_key = 'flash'; - var $time_reference = 'time'; - var $gc_probability = 5; - var $userdata = array(); - var $CI; - var $now; - - /** - * Session Constructor - * - * The constructor runs the session routines automatically - * whenever the class is instantiated. - */ - public function __construct($params = array()) - { - log_message('debug', "Session Class Initialized"); - - // Set the super object to a local variable for use throughout the class - $this->CI =& get_instance(); - - // Set all the session preferences, which can either be set - // manually via the $params array above or via the config file - foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'cookie_secure', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key) - { - $this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key); - } - - if ($this->encryption_key == '') - { - show_error('In order to use the Session class you are required to set an encryption key in your config file.'); - } - - // Load the string helper so we can use the strip_slashes() function - $this->CI->load->helper('string'); - - // Do we need encryption? If so, load the encryption class - if ($this->sess_encrypt_cookie == TRUE) - { - $this->CI->load->library('encrypt'); - } - - // Are we using a database? If so, load it - if ($this->sess_use_database === TRUE AND $this->sess_table_name != '') - { - $this->CI->load->database(); - } - - // Set the "now" time. Can either be GMT or server time, based on the - // config prefs. We use this to set the "last activity" time - $this->now = $this->_get_time(); - - // Set the session length. If the session expiration is - // set to zero we'll set the expiration two years from now. - if ($this->sess_expiration == 0) - { - $this->sess_expiration = (60*60*24*365*2); - } - - // Set the cookie name - $this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name; - - // Run the Session routine. If a session doesn't exist we'll - // create a new one. If it does, we'll update it. - if ( ! $this->sess_read()) - { - $this->sess_create(); - } - else - { - $this->sess_update(); - } - - // Delete 'old' flashdata (from last request) - $this->_flashdata_sweep(); - - // Mark all new flashdata as old (data will be deleted before next request) - $this->_flashdata_mark(); - - // Delete expired sessions if necessary - $this->_sess_gc(); - - log_message('debug', "Session routines successfully run"); - } - - // -------------------------------------------------------------------- - - /** - * Fetch the current session data if it exists - * - * @access public - * @return bool - */ - function sess_read() - { - // Fetch the cookie - $session = $this->CI->input->cookie($this->sess_cookie_name); - - // No cookie? Goodbye cruel world!... - if ($session === FALSE) - { - log_message('debug', 'A session cookie was not found.'); - return FALSE; - } - - // HMAC authentication - $len = strlen($session) - 40; - - if ($len <= 0) - { - log_message('error', 'Session: The session cookie was not signed.'); - return FALSE; - } - - // Check cookie authentication - $hmac = substr($session, $len); - $session = substr($session, 0, $len); - - // Time-attack-safe comparison - $hmac_check = hash_hmac('sha1', $session, $this->encryption_key); - $diff = 0; - - for ($i = 0; $i < 40; $i++) - { - $xor = ord($hmac[$i]) ^ ord($hmac_check[$i]); - $diff |= $xor; - } - - if ($diff !== 0) - { - log_message('error', 'Session: HMAC mismatch. The session cookie data did not match what was expected.'); - $this->sess_destroy(); - return FALSE; - } - - // Decrypt the cookie data - if ($this->sess_encrypt_cookie == TRUE) - { - $session = $this->CI->encrypt->decode($session); - } - - // Unserialize the session array - $session = $this->_unserialize($session); - - // Is the session data we unserialized an array with the correct format? - if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity'])) - { - $this->sess_destroy(); - return FALSE; - } - - // Is the session current? - if (($session['last_activity'] + $this->sess_expiration) < $this->now) - { - $this->sess_destroy(); - return FALSE; - } - - // Does the IP Match? - if ($this->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address()) - { - $this->sess_destroy(); - return FALSE; - } - - // Does the User Agent Match? - if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 120))) - { - $this->sess_destroy(); - return FALSE; - } - - // Is there a corresponding session in the DB? - if ($this->sess_use_database === TRUE) - { - $this->CI->db->where('session_id', $session['session_id']); - - if ($this->sess_match_ip == TRUE) - { - $this->CI->db->where('ip_address', $session['ip_address']); - } - - if ($this->sess_match_useragent == TRUE) - { - $this->CI->db->where('user_agent', $session['user_agent']); - } - - $query = $this->CI->db->get($this->sess_table_name); - - // No result? Kill it! - if ($query->num_rows() == 0) - { - $this->sess_destroy(); - return FALSE; - } - - // Is there custom data? If so, add it to the main session array - $row = $query->row(); - if (isset($row->user_data) AND $row->user_data != '') - { - $custom_data = $this->_unserialize($row->user_data); - - if (is_array($custom_data)) - { - foreach ($custom_data as $key => $val) - { - $session[$key] = $val; - } - } - } - } - - // Session is valid! - $this->userdata = $session; - unset($session); - - return TRUE; - } - - // -------------------------------------------------------------------- - - /** - * Write the session data - * - * @access public - * @return void - */ - function sess_write() - { - // Are we saving custom data to the DB? If not, all we do is update the cookie - if ($this->sess_use_database === FALSE) - { - $this->_set_cookie(); - return; - } - - // set the custom userdata, the session data we will set in a second - $custom_userdata = $this->userdata; - $cookie_userdata = array(); - - // Before continuing, we need to determine if there is any custom data to deal with. - // Let's determine this by removing the default indexes to see if there's anything left in the array - // and set the session data while we're at it - foreach (array('session_id','ip_address','user_agent','last_activity') as $val) - { - unset($custom_userdata[$val]); - $cookie_userdata[$val] = $this->userdata[$val]; - } - - // Did we find any custom data? If not, we turn the empty array into a string - // since there's no reason to serialize and store an empty array in the DB - if (count($custom_userdata) === 0) - { - $custom_userdata = ''; - } - else - { - // Serialize the custom data array so we can store it - $custom_userdata = $this->_serialize($custom_userdata); - } - - // Run the update query - $this->CI->db->where('session_id', $this->userdata['session_id']); - $this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata)); - - // Write the cookie. Notice that we manually pass the cookie data array to the - // _set_cookie() function. Normally that function will store $this->userdata, but - // in this case that array contains custom data, which we do not want in the cookie. - $this->_set_cookie($cookie_userdata); - } - - // -------------------------------------------------------------------- - - /** - * Create a new session - * - * @access public - * @return void - */ - function sess_create() - { - $sessid = ''; - while (strlen($sessid) < 32) - { - $sessid .= mt_rand(0, mt_getrandmax()); - } - - // To make the session ID even more secure we'll combine it with the user's IP - $sessid .= $this->CI->input->ip_address(); - - $this->userdata = array( - 'session_id' => md5(uniqid($sessid, TRUE)), - 'ip_address' => $this->CI->input->ip_address(), - 'user_agent' => substr($this->CI->input->user_agent(), 0, 120), - 'last_activity' => $this->now, - 'user_data' => '' - ); - - - // Save the data to the DB if needed - if ($this->sess_use_database === TRUE) - { - $this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata)); - } - - // Write the cookie - $this->_set_cookie(); - } - - // -------------------------------------------------------------------- - - /** - * Update an existing session - * - * @access public - * @return void - */ - function sess_update() - { - // We only update the session every five minutes by default - if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now) - { - return; - } - - // Save the old session id so we know which record to - // update in the database if we need it - $old_sessid = $this->userdata['session_id']; - $new_sessid = ''; - while (strlen($new_sessid) < 32) - { - $new_sessid .= mt_rand(0, mt_getrandmax()); - } - - // To make the session ID even more secure we'll combine it with the user's IP - $new_sessid .= $this->CI->input->ip_address(); - - // Turn it into a hash - $new_sessid = md5(uniqid($new_sessid, TRUE)); - - // Update the session data in the session data array - $this->userdata['session_id'] = $new_sessid; - $this->userdata['last_activity'] = $this->now; - - // _set_cookie() will handle this for us if we aren't using database sessions - // by pushing all userdata to the cookie. - $cookie_data = NULL; - - // Update the session ID and last_activity field in the DB if needed - if ($this->sess_use_database === TRUE) - { - // set cookie explicitly to only have our session data - $cookie_data = array(); - foreach (array('session_id','ip_address','user_agent','last_activity') as $val) - { - $cookie_data[$val] = $this->userdata[$val]; - } - - $this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid))); - } - - // Write the cookie - $this->_set_cookie($cookie_data); - } - - // -------------------------------------------------------------------- - - /** - * Destroy the current session - * - * @access public - * @return void - */ - function sess_destroy() - { - // Kill the session DB row - if ($this->sess_use_database === TRUE && isset($this->userdata['session_id'])) - { - $this->CI->db->where('session_id', $this->userdata['session_id']); - $this->CI->db->delete($this->sess_table_name); - } - - // Kill the cookie - setcookie( - $this->sess_cookie_name, - addslashes(serialize(array())), - ($this->now - 31500000), - $this->cookie_path, - $this->cookie_domain, - 0 - ); - - // Kill session data - $this->userdata = array(); - } - - // -------------------------------------------------------------------- - - /** - * Fetch a specific item from the session array - * - * @access public - * @param string - * @return string - */ - function userdata($item) - { - return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item]; - } - - // -------------------------------------------------------------------- - - /** - * Fetch all session data - * - * @access public - * @return array - */ - function all_userdata() - { - return $this->userdata; - } - - // -------------------------------------------------------------------- - - /** - * Add or change data in the "userdata" array - * - * @access public - * @param mixed - * @param string - * @return void - */ - function set_userdata($newdata = array(), $newval = '') - { - if (is_string($newdata)) - { - $newdata = array($newdata => $newval); - } - - if (count($newdata) > 0) - { - foreach ($newdata as $key => $val) - { - $this->userdata[$key] = $val; - } - } - - $this->sess_write(); - } - - // -------------------------------------------------------------------- - - /** - * Delete a session variable from the "userdata" array - * - * @access array - * @return void - */ - function unset_userdata($newdata = array()) - { - if (is_string($newdata)) - { - $newdata = array($newdata => ''); - } - - if (count($newdata) > 0) - { - foreach ($newdata as $key => $val) - { - unset($this->userdata[$key]); - } - } - - $this->sess_write(); - } - - // ------------------------------------------------------------------------ - - /** - * Add or change flashdata, only available - * until the next request - * - * @access public - * @param mixed - * @param string - * @return void - */ - function set_flashdata($newdata = array(), $newval = '') - { - if (is_string($newdata)) - { - $newdata = array($newdata => $newval); - } - - if (count($newdata) > 0) - { - foreach ($newdata as $key => $val) - { - $flashdata_key = $this->flashdata_key.':new:'.$key; - $this->set_userdata($flashdata_key, $val); - } - } - } - - // ------------------------------------------------------------------------ - - /** - * Keeps existing flashdata available to next request. - * - * @access public - * @param string - * @return void - */ - function keep_flashdata($key) - { - // 'old' flashdata gets removed. Here we mark all - // flashdata as 'new' to preserve it from _flashdata_sweep() - // Note the function will return FALSE if the $key - // provided cannot be found - $old_flashdata_key = $this->flashdata_key.':old:'.$key; - $value = $this->userdata($old_flashdata_key); - - $new_flashdata_key = $this->flashdata_key.':new:'.$key; - $this->set_userdata($new_flashdata_key, $value); - } - - // ------------------------------------------------------------------------ - - /** - * Fetch a specific flashdata item from the session array - * - * @access public - * @param string - * @return string - */ - function flashdata($key) - { - $flashdata_key = $this->flashdata_key.':old:'.$key; - return $this->userdata($flashdata_key); - } - - // ------------------------------------------------------------------------ - - /** - * Identifies flashdata as 'old' for removal - * when _flashdata_sweep() runs. - * - * @access private - * @return void - */ - function _flashdata_mark() - { - $userdata = $this->all_userdata(); - foreach ($userdata as $name => $value) - { - $parts = explode(':new:', $name); - if (is_array($parts) && count($parts) === 2) - { - $new_name = $this->flashdata_key.':old:'.$parts[1]; - $this->set_userdata($new_name, $value); - $this->unset_userdata($name); - } - } - } - - // ------------------------------------------------------------------------ - - /** - * Removes all flashdata marked as 'old' - * - * @access private - * @return void - */ - - function _flashdata_sweep() - { - $userdata = $this->all_userdata(); - foreach ($userdata as $key => $value) - { - if (strpos($key, ':old:')) - { - $this->unset_userdata($key); - } - } - - } - - // -------------------------------------------------------------------- - - /** - * Get the "now" time - * - * @access private - * @return string - */ - function _get_time() - { - if (strtolower($this->time_reference) == 'gmt') - { - $now = time(); - $time = mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now)); - } - else - { - $time = time(); - } - - return $time; - } - - // -------------------------------------------------------------------- - - /** - * Write the session cookie - * - * @access public - * @return void - */ - function _set_cookie($cookie_data = NULL) - { - if (is_null($cookie_data)) - { - $cookie_data = $this->userdata; - } - - // Serialize the userdata for the cookie - $cookie_data = $this->_serialize($cookie_data); - - if ($this->sess_encrypt_cookie == TRUE) - { - $cookie_data = $this->CI->encrypt->encode($cookie_data); - } - - $cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key); - - $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time(); - - // Set the cookie - setcookie( - $this->sess_cookie_name, - $cookie_data, - $expire, - $this->cookie_path, - $this->cookie_domain, - $this->cookie_secure - ); - } - - // -------------------------------------------------------------------- - - /** - * Serialize an array - * - * This function first converts any slashes found in the array to a temporary - * marker, so when it gets unserialized the slashes will be preserved - * - * @access private - * @param array - * @return string - */ - function _serialize($data) - { - if (is_array($data)) - { - foreach ($data as $key => $val) - { - if (is_string($val)) - { - $data[$key] = str_replace('\\', '{{slash}}', $val); - } - } - } - else - { - if (is_string($data)) - { - $data = str_replace('\\', '{{slash}}', $data); - } - } - - return serialize($data); - } - - // -------------------------------------------------------------------- - - /** - * Unserialize - * - * This function unserializes a data string, then converts any - * temporary slash markers back to actual slashes - * - * @access private - * @param array - * @return string - */ - function _unserialize($data) - { - $data = @unserialize(strip_slashes($data)); - - if (is_array($data)) - { - foreach ($data as $key => $val) - { - if (is_string($val)) - { - $data[$key] = str_replace('{{slash}}', '\\', $val); - } - } - - return $data; - } - - return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data; - } - - // -------------------------------------------------------------------- - - /** - * Garbage collection - * - * This deletes expired session rows from database - * if the probability percentage is met - * - * @access public - * @return void - */ - function _sess_gc() - { - if ($this->sess_use_database != TRUE) - { - return; - } - - srand(time()); - if ((rand() % 100) < $this->gc_probability) - { - $expire = $this->now - $this->sess_expiration; - - $this->CI->db->where("last_activity < {$expire}"); - $this->CI->db->delete($this->sess_table_name); - - log_message('debug', 'Session garbage collection performed.'); - } - } - - -} -// END Session Class - -/* End of file Session.php */ -/* Location: ./system/libraries/Session.php */ diff --git a/system/libraries/Session/CI_Session_driver_interface.php b/system/libraries/Session/CI_Session_driver_interface.php new file mode 100644 index 000000000..23a0dfd53 --- /dev/null +++ b/system/libraries/Session/CI_Session_driver_interface.php @@ -0,0 +1,60 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CI_Session_driver_interface + * + * A compatibility typeless SessionHandlerInterface alias + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author Andrey Andreev + * @link https://codeigniter.com/userguide3/libraries/sessions.html + */ +interface CI_Session_driver_interface { + + public function open($save_path, $name); + public function close(); + public function read($session_id); + public function write($session_id, $session_data); + public function destroy($session_id); + public function gc($maxlifetime); + public function updateTimestamp($session_id, $data); + public function validateId($session_id); +} diff --git a/system/libraries/Session/OldSessionWrapper.php b/system/libraries/Session/OldSessionWrapper.php new file mode 100644 index 000000000..d013c777f --- /dev/null +++ b/system/libraries/Session/OldSessionWrapper.php @@ -0,0 +1,98 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * OldSessionWrapper + * + * PHP 8 Session handler compatibility wrapper, pre-PHP8 version + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author Andrey Andreev + * @link https://codeigniter.com/userguide3/libraries/sessions.html + */ +class CI_SessionWrapper implements SessionHandlerInterface, SessionUpdateTimestampHandlerInterface { + + protected $driver; + + public function __construct(CI_Session_driver_interface $driver) + { + $this->driver = $driver; + } + + public function open($save_path, $name) + { + return $this->driver->open($save_path, $name); + } + + public function close() + { + return $this->driver->close(); + } + + public function read($id) + { + return $this->driver->read($id); + } + + public function write($id, $data) + { + return $this->driver->write($id, $data); + } + + public function destroy($id) + { + return $this->driver->destroy($id); + } + + public function gc($maxlifetime) + { + return $this->driver->gc($maxlifetime); + } + + public function updateTimestamp($id, $data) + { + return $this->driver->updateTimestamp($id, $data); + } + + public function validateId($id) + { + return $this->driver->validateId($id); + } +} diff --git a/system/libraries/Session/PHP8SessionWrapper.php b/system/libraries/Session/PHP8SessionWrapper.php new file mode 100644 index 000000000..41889bc61 --- /dev/null +++ b/system/libraries/Session/PHP8SessionWrapper.php @@ -0,0 +1,100 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * PHP8SessionWrapper + * + * PHP 8 Session handler compatibility wrapper + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author Andrey Andreev + * @link https://codeigniter.com/userguide3/libraries/sessions.html + */ +class CI_SessionWrapper implements SessionHandlerInterface, SessionUpdateTimestampHandlerInterface { + + protected CI_Session_driver_interface $driver; + + public function __construct(CI_Session_driver_interface $driver) + { + $this->driver = $driver; + } + + public function open(string $save_path, string $name): bool + { + return $this->driver->open($save_path, $name); + } + + public function close(): bool + { + return $this->driver->close(); + } + + #[\ReturnTypeWillChange] + public function read(string $id): mixed + { + return $this->driver->read($id); + } + + public function write(string $id, string $data): bool + { + return $this->driver->write($id, $data); + } + + public function destroy(string $id): bool + { + return $this->driver->destroy($id); + } + + #[\ReturnTypeWillChange] + public function gc(int $maxlifetime): mixed + { + return $this->driver->gc($maxlifetime); + } + + public function updateTimestamp(string $id, string$data): bool + { + return $this->driver->updateTimestamp($id, $data); + } + + public function validateId(string $id): bool + { + return $this->driver->validateId($id); + } +} diff --git a/system/libraries/Session/Session.php b/system/libraries/Session/Session.php new file mode 100644 index 000000000..2d55f822a --- /dev/null +++ b/system/libraries/Session/Session.php @@ -0,0 +1,1030 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 2.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CodeIgniter Session Class + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author Andrey Andreev + * @link https://codeigniter.com/userguide3/libraries/sessions.html + */ +class CI_Session { + + /** + * Userdata array + * + * Just a reference to $_SESSION, for BC purposes. + */ + public $userdata; + + protected $_driver = 'files'; + protected $_config; + protected $_sid_regexp; + + // ------------------------------------------------------------------------ + + /** + * Class constructor + * + * @param array $params Configuration parameters + * @return void + */ + public function __construct(array $params = array()) + { + // No sessions under CLI + if (is_cli()) + { + log_message('debug', 'Session: Initialization under CLI aborted.'); + return; + } + elseif ((bool) ini_get('session.auto_start')) + { + log_message('error', 'Session: session.auto_start is enabled in php.ini. Aborting.'); + return; + } + elseif ( ! empty($params['driver'])) + { + $this->_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); + $wrapper = new CI_SessionWrapper($class); + if (is_php('5.4')) + { + session_set_save_handler($wrapper, TRUE); + } + else + { + session_set_save_handler( + array($wrapper, 'open'), + array($wrapper, 'close'), + array($wrapper, 'read'), + array($wrapper, 'write'), + array($wrapper, 'destroy'), + array($wrapper, 'gc') + ); + + register_shutdown_function('session_write_close'); + } + + // 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()) + { + $expires = empty($this->_config['cookie_lifetime']) ? 0 : time() + $this->_config['cookie_lifetime']; + if (is_php('7.3')) + { + setcookie( + $this->_config['cookie_name'], + session_id(), + array( + 'expires' => $expires, + 'path' => $this->_config['cookie_path'], + 'domain' => $this->_config['cookie_domain'], + 'secure' => $this->_config['cookie_secure'], + 'httponly' => TRUE, + 'samesite' => $this->_config['cookie_samesite'] + ) + ); + } + else + { + $header = 'Set-Cookie: '.$this->_config['cookie_name'].'='.session_id(); + $header .= empty($expires) ? '' : '; Expires='.gmdate('D, d-M-Y H:i:s T', $expires).'; Max-Age='.$this->_config['cookie_lifetime']; + $header .= '; Path='.$this->_config['cookie_path']; + $header .= ($this->_config['cookie_domain'] !== '' ? '; Domain='.$this->_config['cookie_domain'] : ''); + $header .= ($this->_config['cookie_secure'] ? '; Secure' : '').'; HttpOnly; SameSite='.$this->_config['cookie_samesite']; + header($header); + } + + if ( ! $this->_config['cookie_secure'] && $this->_config['cookie_samesite'] === 'None') + { + log_message('error', "Session: '".$this->_config['cookie_name']."' cookie sent with SameSite=None, but without Secure attribute.'"); + } + } + + $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 7 compatibility + interface_exists('SessionUpdateTimestampHandlerInterface', FALSE) OR require_once(BASEPATH.'libraries/Session/SessionUpdateTimestampHandlerInterface.php'); + + require_once(BASEPATH.'libraries/Session/CI_Session_driver_interface.php'); + $wrapper = is_php('8.0') ? 'PHP8SessionWrapper' : 'OldSessionWrapper'; + require_once(BASEPATH.'libraries/Session/'.$wrapper.'.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; + } + + 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'); + + isset($params['cookie_samesite']) OR $params['cookie_samesite'] = config_item('sess_samesite'); + if ( ! isset($params['cookie_samesite']) && is_php('7.3')) + { + $params['cookie_samesite'] = ini_get('session.cookie_samesite'); + } + + if (isset($params['cookie_samesite'])) + { + $params['cookie_samesite'] = ucfirst(strtolower($params['cookie_samesite'])); + in_array($params['cookie_samesite'], array('Lax', 'Strict', 'None'), TRUE) OR $params['cookie_samesite'] = 'Lax'; + } + else + { + $params['cookie_samesite'] = 'Lax'; + } + + if (is_php('7.3')) + { + session_set_cookie_params(array( + 'lifetime' => $params['cookie_lifetime'], + 'path' => $params['cookie_path'], + 'domain' => $params['cookie_domain'], + 'secure' => $params['cookie_secure'], + 'httponly' => TRUE, + 'samesite' => $params['cookie_samesite'] + )); + } + else + { + session_set_cookie_params( + $params['cookie_lifetime'], + $params['cookie_path'].'; SameSite='.$params['cookie_samesite'], + $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'; + } + elseif ($value === 'old' || $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 temp + * + * @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 + * + * @return 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/SessionUpdateTimestampHandlerInterface.php b/system/libraries/Session/SessionUpdateTimestampHandlerInterface.php new file mode 100644 index 000000000..fe4a321ab --- /dev/null +++ b/system/libraries/Session/SessionUpdateTimestampHandlerInterface.php @@ -0,0 +1,56 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * SessionUpdateTimestampHandlerInterface + * + * PHP 7 compatibility interface + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author Andrey Andreev + * @link https://codeigniter.com/userguide3/libraries/sessions.html + */ +interface SessionUpdateTimestampHandlerInterface { + + public function updateTimestamp($session_id, $data); + public function validateId($session_id); +} diff --git a/system/libraries/Session/Session_driver.php b/system/libraries/Session/Session_driver.php new file mode 100644 index 000000000..24b4b465e --- /dev/null +++ b/system/libraries/Session/Session_driver.php @@ -0,0 +1,202 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CodeIgniter Session Driver Class + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author Andrey Andreev + * @link https://codeigniter.com/userguide3/libraries/sessions.html + */ +abstract class CI_Session_driver { + + protected $_config; + + /** + * Data fingerprint + * + * @var bool + */ + protected $_fingerprint; + + /** + * Lock placeholder + * + * @var mixed + */ + protected $_lock = FALSE; + + /** + * Read session ID + * + * Used to detect session_regenerate_id() calls because PHP only calls + * write() after regenerating the ID. + * + * @var string + */ + protected $_session_id; + + /** + * Success and failure return values + * + * Necessary due to a bug in all PHP 5 versions where return values + * from userspace handlers are not handled properly. PHP 7 fixes the + * bug, so we need to return different values depending on the version. + * + * @see https://wiki.php.net/rfc/session.user.return-value + * @var mixed + */ + protected $_success, $_failure; + + // ------------------------------------------------------------------------ + + /** + * Class constructor + * + * @param array $params Configuration parameters + * @return void + */ + public function __construct(&$params) + { + $this->_config =& $params; + + if (is_php('7')) + { + $this->_success = TRUE; + $this->_failure = FALSE; + } + else + { + $this->_success = 0; + $this->_failure = -1; + } + } + + // ------------------------------------------------------------------------ + + /** + * PHP 5.x validate ID + * + * Enforces session.use_strict_mode + * + * @return void + */ + public function php5_validate_id() + { + if ($this->_success === 0 && isset($_COOKIE[$this->_config['cookie_name']]) && ! $this->validateId($_COOKIE[$this->_config['cookie_name']])) + { + unset($_COOKIE[$this->_config['cookie_name']]); + } + } + + // ------------------------------------------------------------------------ + + /** + * Cookie destroy + * + * Internal method to force removal of a cookie by the client + * when session_destroy() is called. + * + * @return bool + */ + protected function _cookie_destroy() + { + if ( ! is_php('7.3')) + { + $header = 'Set-Cookie: '.$this->_config['cookie_name'].'='; + $header .= '; Expires='.gmdate('D, d-M-Y H:i:s T', 1).'; Max-Age=-1'; + $header .= '; Path='.$this->_config['cookie_path']; + $header .= ($this->_config['cookie_domain'] !== '' ? '; Domain='.$this->_config['cookie_domain'] : ''); + $header .= ($this->_config['cookie_secure'] ? '; Secure' : '').'; HttpOnly; SameSite='.$this->_config['cookie_samesite']; + header($header); + return; + } + + return setcookie( + $this->_config['cookie_name'], + '', + array( + 'expires' => 1, + 'path' => $this->_config['cookie_path'], + 'domain' => $this->_config['cookie_domain'], + 'secure' => $this->_config['cookie_secure'], + 'httponly' => TRUE, + 'samesite' => $this->_config['cookie_samesite'] + ) + ); + } + + // ------------------------------------------------------------------------ + + /** + * 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; + } +} 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 000000000..4b475364b --- /dev/null +++ b/system/libraries/Session/drivers/Session_database_driver.php @@ -0,0 +1,471 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CodeIgniter Session Database Driver + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author Andrey Andreev + * @link https://codeigniter.com/userguide3/libraries/sessions.html + */ +class CI_Session_database_driver extends CI_Session_driver implements CI_Session_driver_interface { + + /** + * DB object + * + * @var object + */ + protected $_db; + + /** + * Row exists flag + * + * @var bool + */ + protected $_row_exists = FALSE; + + /** + * Lock "driver" flag + * + * @var string + */ + protected $_platform; + + // ------------------------------------------------------------------------ + + /** + * Class constructor + * + * @param array $params Configuration parameters + * @return void + */ + public function __construct(&$params) + { + parent::__construct($params); + + $CI =& get_instance(); + isset($CI->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->_failure; + } + + $this->php5_validate_id(); + + 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) + { + return $this->_failure; + } + + // 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; + } + + // ------------------------------------------------------------------------ + + /** + * 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->_failure; + } + + $this->_row_exists = FALSE; + $this->_session_id = $session_id; + } + elseif ($this->_lock === FALSE) + { + return $this->_failure; + } + + 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->_failure; + } + + $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->_failure; + } + + // ------------------------------------------------------------------------ + + /** + * Close + * + * Releases locks + * + * @return bool + */ + public function close() + { + return ($this->_lock && ! $this->_release_lock()) + ? $this->_failure + : $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->_failure; + } + } + + if ($this->close() === $this->_success) + { + $this->_cookie_destroy(); + return $this->_success; + } + + return $this->_failure; + } + + // ------------------------------------------------------------------------ + + /** + * 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->_failure; + } + + // -------------------------------------------------------------------- + + /** + * Update Timestamp + * + * Update session timestamp without modifying data + * + * @param string $id Session ID + * @param string $data Unknown & unused + * @return bool + */ + public function updateTimestamp($id, $unknown) + { + // Prevent previous QB calls from messing with our queries + $this->_db->reset_query(); + + $this->_db->where('id', $id); + if ($this->_config['match_ip']) + { + $this->_db->where('ip_address', $_SERVER['REMOTE_ADDR']); + } + + return (bool) $this->_db->update($this->_config['save_path'], array('timestamp' => time())); + } + + // -------------------------------------------------------------------- + + /** + * Validate ID + * + * Checks whether a session ID record exists server-side, + * to enforce session.use_strict_mode. + * + * @param string $id Session ID + * @return bool + */ + public function validateId($id) + { + // Prevent previous QB calls from messing with our queries + $this->_db->reset_query(); + + $this->_db->select('1')->from($this->_config['save_path'])->where('id', $id); + empty($this->_config['match_ip']) OR $this->_db->where('ip_address', $_SERVER['REMOTE_ADDR']); + $result = $this->_db->get(); + empty($result) OR $result = $result->row(); + + return ! empty($result); + } + + // ------------------------------------------------------------------------ + + /** + * 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 000000000..be0dc9ede --- /dev/null +++ b/system/libraries/Session/drivers/Session_files_driver.php @@ -0,0 +1,449 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CodeIgniter Session Files Driver + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author Andrey Andreev + * @link https://codeigniter.com/userguide3/libraries/sessions.html + */ +class CI_Session_files_driver extends CI_Session_driver implements CI_Session_driver_interface { + + /** + * Save path + * + * @var string + */ + protected $_save_path; + + /** + * File handle + * + * @var resource + */ + protected $_file_handle; + + /** + * File name + * + * @var resource + */ + protected $_file_path; + + /** + * File new flag + * + * @var bool + */ + protected $_file_new; + + /** + * Validate SID regular expression + * + * @var string + */ + protected $_sid_regexp; + + /** + * mbstring.func_overload flag + * + * @var bool + */ + protected static $func_overload; + + // ------------------------------------------------------------------------ + + /** + * Class constructor + * + * @param array $params Configuration parameters + * @return void + */ + public function __construct(&$params) + { + parent::__construct($params); + + if (isset($this->_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 = ( ! is_php('8.0') && 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)) + { + log_message('error', "Session: Configured save path '".$this->_config['save_path']."' is not a directory, doesn't exist or cannot be created."); + return $this->_failure; + } + } + elseif ( ! is_writable($save_path)) + { + log_message('error', "Session: Configured save path '".$this->_config['save_path']."' is not writable by the PHP process."); + return $this->_failure; + } + + $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']) : ''); + + $this->php5_validate_id(); + + 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 ''; + } + + // Prevent possible data corruption + // See https://github.com/bcit-ci/CodeIgniter/issues/5857 + clearstatcache(TRUE, $this->_file_path.$session_id); + } + // 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; + } + + // -------------------------------------------------------------------- + + /** + * Update Timestamp + * + * Update session timestamp without modifying data + * + * @param string $id Session ID + * @param string $data Unknown & unused + * @return bool + */ + public function updateTimestamp($id, $unknown) + { + return touch($this->_file_path.$id); + } + + // -------------------------------------------------------------------- + + /** + * Validate ID + * + * Checks whether a session ID record exists server-side, + * to enforce session.use_strict_mode. + * + * @param string $id Session ID + * @return bool + */ + public function validateId($id) + { + $result = is_file($this->_file_path.$id); + clearstatcache(TRUE, $this->_file_path.$id); + return $result; + } + + // -------------------------------------------------------------------- + + /** + * 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 000000000..d1401630d --- /dev/null +++ b/system/libraries/Session/drivers/Session_memcached_driver.php @@ -0,0 +1,414 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CodeIgniter Session Memcached Driver + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author Andrey Andreev + * @link https://codeigniter.com/userguide3/libraries/sessions.html + */ +class CI_Session_memcached_driver extends CI_Session_driver implements CI_Session_driver_interface { + + /** + * Memcached instance + * + * @var Memcached + */ + protected $_memcached; + + /** + * Key prefix + * + * @var string + */ + protected $_key_prefix = 'ci_session:'; + + /** + * Lock key + * + * @var string + */ + protected $_lock_key; + + // ------------------------------------------------------------------------ + + /** + * Class constructor + * + * @param array $params Configuration parameters + * @return void + */ + public function __construct(&$params) + { + parent::__construct($params); + + if (empty($this->_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->_failure; + } + + 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->_failure; + } + + $this->php5_validate_id(); + + 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->_failure; + } + + // ------------------------------------------------------------------------ + + /** + * 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->_failure; + } + // Was the ID regenerated? + elseif ($session_id !== $this->_session_id) + { + if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id)) + { + return $this->_failure; + } + + $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->_failure; + } + 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->_failure; + } + + // ------------------------------------------------------------------------ + + /** + * Close + * + * Releases locks and closes connection. + * + * @return bool + */ + public function close() + { + if (isset($this->_memcached)) + { + $this->_release_lock(); + if ( ! $this->_memcached->quit()) + { + return $this->_failure; + } + + $this->_memcached = NULL; + return $this->_success; + } + + return $this->_failure; + } + + // ------------------------------------------------------------------------ + + /** + * 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->_failure; + } + + // ------------------------------------------------------------------------ + + /** + * 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; + } + + // -------------------------------------------------------------------- + + /** + * Update Timestamp + * + * Update session timestamp without modifying data + * + * @param string $id Session ID + * @param string $data Unknown & unused + * @return bool + */ + public function updateTimestamp($id, $unknown) + { + return $this->_memcached->touch($this->_key_prefix.$id, $this->_config['expiration']); + } + + // -------------------------------------------------------------------- + + /** + * Validate ID + * + * Checks whether a session ID record exists server-side, + * to enforce session.use_strict_mode. + * + * @param string $id Session ID + * @return bool + */ + public function validateId($id) + { + $this->_memcached->get($this->_key_prefix.$id); + return ($this->_memcached->getResultCode() === Memcached::RES_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; + } + + return TRUE; + } + + // 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 000000000..4d822d585 --- /dev/null +++ b/system/libraries/Session/drivers/Session_redis_driver.php @@ -0,0 +1,502 @@ +<?php +/** + * CodeIgniter + * + * An open source application development framework for PHP + * + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 3.0.0 + * @filesource + */ +defined('BASEPATH') OR exit('No direct script access allowed'); + +/** + * CodeIgniter Session Redis Driver + * + * @package CodeIgniter + * @subpackage Libraries + * @category Sessions + * @author Andrey Andreev + * @link https://codeigniter.com/userguide3/libraries/sessions.html + */ +class CI_Session_redis_driver extends CI_Session_driver implements CI_Session_driver_interface { + + /** + * phpRedis instance + * + * @var Redis + */ + protected $_redis; + + /** + * Key prefix + * + * @var string + */ + protected $_key_prefix = 'ci_session:'; + + /** + * Lock key + * + * @var string + */ + protected $_lock_key; + + /** + * Key exists flag + * + * @var bool + */ + protected $_key_exists = FALSE; + + /** + * Name of setTimeout() method in phpRedis + * + * Due to some deprecated methods in phpRedis, we need to call the + * specific methods depending on the version of phpRedis. + * + * @var string + */ + protected $_setTimeout_name; + + /** + * Name of delete() method in phpRedis + * + * Due to some deprecated methods in phpRedis, we need to call the + * specific methods depending on the version of phpRedis. + * + * @var string + */ + protected $_delete_name; + + /** + * Success return value of ping() method in phpRedis + * + * @var mixed + */ + protected $_ping_success; + + // ------------------------------------------------------------------------ + + /** + * Class constructor + * + * @param array $params Configuration parameters + * @return void + */ + public function __construct(&$params) + { + parent::__construct($params); + + // Detect the names of some methods in phpRedis instance + if (version_compare(phpversion('redis'), '5', '>=')) + { + $this->_setTimeout_name = 'expire'; + $this->_delete_name = 'del'; + $this->_ping_success = TRUE; + } + else + { + $this->_setTimeout_name = 'setTimeout'; + $this->_delete_name = 'delete'; + $this->_ping_success = '+PONG'; + } + + if (empty($this->_config['save_path'])) + { + log_message('error', 'Session: No Redis save path configured.'); + } + elseif (preg_match('#^unix://([^\?]+)(?<options>\?.+)?$#', $this->_config['save_path'], $matches)) + { + $save_path = array('path' => $matches[1]); + } + elseif (preg_match('#(?:(?:tcp|tls)://)?([^:?]+)(?:\:(\d+))?(?<options>\?.+)?#', $this->_config['save_path'], $matches)) + { + $save_path = array( + 'host' => $matches[1], + 'port' => empty($matches[2]) ? NULL : $matches[2], + 'timeout' => 0.0 // We always pass this to Redis::connect(), so it needs to exist + ); + } + else + { + log_message('error', 'Session: Invalid Redis save path format: '.$this->_config['save_path']); + } + + if (isset($save_path)) + { + if (isset($matches['options'])) + { + $save_path['password'] = preg_match('#auth=([^\s&]+)#', $matches['options'], $match) ? $match[1] : NULL; + $save_path['database'] = preg_match('#database=(\d+)#', $matches['options'], $match) ? (int) $match[1] : NULL; + $save_path['timeout'] = preg_match('#timeout=(\d+\.\d+)#', $matches['options'], $match) ? (float) $match[1] : NULL; + + preg_match('#prefix=([^\s&]+)#', $matches['options'], $match) && $this->_key_prefix = $match[1]; + } + + $this->_config['save_path'] = $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->_failure; + } + + $redis = new Redis(); + $connected = isset($this->_config['save_path']['path']) + ? $redis->connect($this->_config['save_path']['path']) + : $redis->connect( + $this->_config['save_path']['host'], + $this->_config['save_path']['port'], + $this->_config['save_path']['timeout'] + ); + + if ($connected) + { + if (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; + $this->php5_validate_id(); + return $this->_success; + } + } + else + { + $this->_redis = $redis; + $this->php5_validate_id(); + return $this->_success; + } + + return $this->_failure; + } + + // ------------------------------------------------------------------------ + + /** + * 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->_failure; + } + + // ------------------------------------------------------------------------ + + /** + * 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->_failure; + } + // Was the ID regenerated? + elseif ($session_id !== $this->_session_id) + { + if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id)) + { + return $this->_failure; + } + + $this->_key_exists = FALSE; + $this->_session_id = $session_id; + } + + $this->_redis->{$this->_setTimeout_name}($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->_failure; + } + + return ($this->_redis->{$this->_setTimeout_name}($this->_key_prefix.$session_id, $this->_config['expiration'])) + ? $this->_success + : $this->_failure; + } + + // ------------------------------------------------------------------------ + + /** + * Close + * + * Releases locks and closes connection. + * + * @return bool + */ + public function close() + { + if (isset($this->_redis)) + { + try { + if ($this->_redis->ping() === $this->_ping_success) + { + $this->_release_lock(); + if ($this->_redis->close() === FALSE) + { + return $this->_failure; + } + } + } + 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->{$this->_delete_name}($this->_key_prefix.$session_id)) !== 1) + { + log_message('debug', 'Session: Redis::'.$this->_delete_name.'() expected to return 1, got '.var_export($result, TRUE).' instead.'); + } + + $this->_cookie_destroy(); + return $this->_success; + } + + return $this->_failure; + } + + // ------------------------------------------------------------------------ + + /** + * 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; + } + + // -------------------------------------------------------------------- + + /** + * Update Timestamp + * + * Update session timestamp without modifying data + * + * @param string $id Session ID + * @param string $data Unknown & unused + * @return bool + */ + public function updateTimestamp($id, $unknown) + { + return $this->_redis->{$this->_setTimeout_name}($this->_key_prefix.$id, $this->_config['expiration']); + } + + // -------------------------------------------------------------------- + + /** + * Validate ID + * + * Checks whether a session ID record exists server-side, + * to enforce session.use_strict_mode. + * + * @param string $id Session ID + * @return bool + */ + public function validateId($id) + { + return (bool) $this->_redis->exists($this->_key_prefix.$id); + } + + // ------------------------------------------------------------------------ + + /** + * 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->{$this->_setTimeout_name}($this->_lock_key, 300); + } + + // 30 attempts to obtain a lock, in case another request already has it + $lock_key = $this->_key_prefix.$session_id.':lock'; + $attempt = 0; + do + { + if (($ttl = $this->_redis->ttl($lock_key)) > 0) + { + sleep(1); + continue; + } + + if ($ttl === -2 && ! $this->_redis->set($lock_key, time(), array('nx', 'ex' => 300))) + { + // Sleep for 1s to wait for lock releases. + sleep(1); + continue; + } + elseif ( ! $this->_redis->setex($lock_key, 300, time())) + { + log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id); + return FALSE; + } + + $this->_lock_key = $lock_key; + break; + } + while (++$attempt < 30); + + if ($attempt === 30) + { + log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 30 attempts, aborting.'); + return FALSE; + } + elseif ($ttl === -1) + { + log_message('debug', 'Session: Lock for '.$this->_key_prefix.$session_id.' had no TTL, overriding.'); + } + + $this->_lock = TRUE; + return TRUE; + } + + // ------------------------------------------------------------------------ + + /** + * Release lock + * + * Releases a previously acquired lock + * + * @return bool + */ + protected function _release_lock() + { + if (isset($this->_redis, $this->_lock_key) && $this->_lock) + { + if ( ! $this->_redis->{$this->_delete_name}($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 000000000..bcb7cae34 --- /dev/null +++ b/system/libraries/Session/drivers/index.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>403 Forbidden</title> +</head> +<body> + +<p>Directory access is forbidden.</p> + +</body> +</html> diff --git a/system/libraries/Session/index.html b/system/libraries/Session/index.html new file mode 100644 index 000000000..bcb7cae34 --- /dev/null +++ b/system/libraries/Session/index.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>403 Forbidden</title> +</head> +<body> + +<p>Directory access is forbidden.</p> + +</body> +</html> diff --git a/system/libraries/Sha1.php b/system/libraries/Sha1.php deleted file mode 100644 index 33778f965..000000000 --- a/system/libraries/Sha1.php +++ /dev/null @@ -1,251 +0,0 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); -/** - * CodeIgniter - * - * An open source application development framework for PHP 5.1.6 or newer - * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 - * @filesource - */ - -// ------------------------------------------------------------------------ - -/** - * SHA1 Encoding Class - * - * Purpose: Provides 160 bit hashing using The Secure Hash Algorithm - * developed at the National Institute of Standards and Technology. The 40 - * character SHA1 message hash is computationally infeasible to crack. - * - * This class is a fallback for servers that are not running PHP greater than - * 4.3, or do not have the MHASH library. - * - * This class is based on two scripts: - * - * Marcus Campbell's PHP implementation (GNU license) - * http://www.tecknik.net/sha-1/ - * - * ...which is based on Paul Johnston's JavaScript version - * (BSD license). http://pajhome.org.uk/ - * - * I encapsulated the functions and wrote one additional method to fix - * a hex conversion bug. - Rick Ellis - * - * @package CodeIgniter - * @subpackage Libraries - * @category Encryption - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/general/encryption.html - */ -class CI_SHA1 { - - public function __construct() - { - log_message('debug', "SHA1 Class Initialized"); - } - - /** - * Generate the Hash - * - * @access public - * @param string - * @return string - */ - function generate($str) - { - $n = ((strlen($str) + 8) >> 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 a2353d1e1..a033ced21 100644 --- a/system/libraries/Table.php +++ b/system/libraries/Table.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.3.1 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.3.1 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * HTML Table Generating Class @@ -23,23 +46,82 @@ * @package CodeIgniter * @subpackage Libraries * @category HTML Tables - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/uri.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/table.html */ class CI_Table { - var $rows = array(); - var $heading = array(); - var $auto_heading = TRUE; - var $caption = NULL; - var $template = NULL; - var $newline = "\n"; - var $empty_cells = ""; - var $function = FALSE; + /** + * Data for table rows + * + * @var array + */ + public $rows = array(); + + /** + * Data for table heading + * + * @var array + */ + public $heading = array(); + + /** + * Whether or not to automatically create the table header + * + * @var bool + */ + public $auto_heading = TRUE; + + /** + * Table caption + * + * @var string + */ + public $caption = NULL; + + /** + * Table layout template + * + * @var array + */ + public $template = NULL; + + /** + * Newline setting + * + * @var string + */ + public $newline = "\n"; + + /** + * Contents of empty cells + * + * @var string + */ + public $empty_cells = ''; + + /** + * Callback for custom table layout + * + * @var function + */ + public $function = NULL; - public function __construct() + /** + * Set the template from the table config file if it exists + * + * @param array $config (default: array()) + * @return void + */ + public function __construct($config = array()) { - log_message('debug', "Table Class Initialized"); + // initialize config + foreach ($config as $key => $val) + { + $this->template[$key] = $val; + } + + log_message('info', 'Table Class Initialized'); } // -------------------------------------------------------------------- @@ -47,11 +129,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 +140,7 @@ class CI_Table { } $this->template = $template; + return TRUE; } // -------------------------------------------------------------------- @@ -68,32 +150,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 +182,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 +202,7 @@ class CI_Table { $new[] = $temp; } + while (count($array) > 0); return $new; } @@ -133,13 +214,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 +230,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 +246,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 +272,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 +286,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 +314,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 .= '<caption>' . $this->caption . '</caption>'; - $out .= $this->newline; + $out .= '<caption>'.$this->caption.'</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 +341,22 @@ class CI_Table { foreach ($heading as $key => $val) { - if ($key != 'data') + if ($key !== 'data') { - $temp = str_replace('<th', "<th $key='$val'", $temp); + $temp = str_replace('<th', '<th '.$key.'="'.$val.'"', $temp); } } - $out .= $temp; - $out .= isset($heading['data']) ? $heading['data'] : ''; - $out .= $this->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 +367,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 +377,35 @@ class CI_Table { foreach ($cell as $key => $val) { - if ($key != 'data') + if ($key !== 'data') { - $temp = str_replace('<td', "<td $key='$val'", $temp); + $temp = str_replace('<td', '<td '.$key.'="'.$val.'"', $temp); } } $cell = isset($cell['data']) ? $cell['data'] : ''; $out .= $temp; - if ($cell === "" OR $cell === NULL) + if ($cell === '' OR $cell === NULL) { $out .= $this->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 +421,15 @@ 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; + $this->caption = NULL; + return $this; } // -------------------------------------------------------------------- @@ -393,36 +437,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 +459,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 +480,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 +505,36 @@ class CI_Table { /** * Default Template * - * @access private - * @return void + * @return array */ - function _default_template() + protected function _default_template() { - return array ( - 'table_open' => '<table border="0" cellpadding="4" cellspacing="0">', + return array( + 'table_open' => '<table border="0" cellpadding="4" cellspacing="0">', - 'thead_open' => '<thead>', - 'thead_close' => '</thead>', + 'thead_open' => '<thead>', + 'thead_close' => '</thead>', - 'heading_row_start' => '<tr>', - 'heading_row_end' => '</tr>', - 'heading_cell_start' => '<th>', - 'heading_cell_end' => '</th>', + 'heading_row_start' => '<tr>', + 'heading_row_end' => '</tr>', + 'heading_cell_start' => '<th>', + 'heading_cell_end' => '</th>', - 'tbody_open' => '<tbody>', - 'tbody_close' => '</tbody>', + 'tbody_open' => '<tbody>', + 'tbody_close' => '</tbody>', - 'row_start' => '<tr>', - 'row_end' => '</tr>', - 'cell_start' => '<td>', - 'cell_end' => '</td>', + 'row_start' => '<tr>', + 'row_end' => '</tr>', + 'cell_start' => '<td>', + 'cell_end' => '</td>', - 'row_alt_start' => '<tr>', - 'row_alt_end' => '</tr>', - 'cell_alt_start' => '<td>', - 'cell_alt_end' => '</td>', + 'row_alt_start' => '<tr>', + 'row_alt_end' => '</tr>', + 'cell_alt_start' => '<td>', + 'cell_alt_end' => '</td>', - 'table_close' => '</table>' - ); + 'table_close' => '</table>' + ); } - } - - -/* 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 898553cd1..9246ec6e9 100644 --- a/system/libraries/Trackback.php +++ b/system/libraries/Trackback.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Trackback Class @@ -23,26 +46,65 @@ * @package CodeIgniter * @subpackage Libraries * @category Trackbacks - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/trackback.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/trackback.html */ class CI_Trackback { - var $time_format = 'local'; - var $charset = 'UTF-8'; - var $data = array('url' => '', '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 +112,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 +134,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 +167,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 +187,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 +231,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 "<?xml version=\"1.0\" encoding=\"utf-8\"?".">\n<response>\n<error>1</error>\n<message>".$message."</message>\n</response>"; - exit; + exit('<?xml version="1.0" encoding="utf-8"?'.">\n<response>\n<error>1</error>\n<message>".$message."</message>\n</response>"); } // -------------------------------------------------------------------- @@ -192,13 +251,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 "<?xml version=\"1.0\" encoding=\"utf-8\"?".">\n<response>\n<error>0</error>\n</response>"; - exit; + exit('<?xml version="1.0" encoding="utf-8"?'.">\n<response>\n<error>0</error>\n</response>"); } // -------------------------------------------------------------------- @@ -206,13 +263,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 +277,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 +295,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, '<error>0</error>') === FALSE) + if (stripos($this->response, '<error>0</error>') === FALSE) { - $message = 'An unknown error was encountered'; - - if (preg_match("/<message>(.*?)<\/message>/is", $this->response, $match)) - { - $message = trim($match['1']); - } - + $message = preg_match('/<message>(.*?)<\/message>/is', $this->response, $match) + ? trim($match[1]) + : 'An unknown error was encountered'; $this->set_error($message); return FALSE; } @@ -293,32 +342,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); - - // 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); + // Remove the pesky white space and replace with a comma, then replace doubles. + $urls = str_replace(',,', ',', preg_replace('/\s*(\S+)\s*/', '\\1,', $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 +364,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 +382,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 +411,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 +423,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 = str_replace(array("&","<",">","\"", "'", "-"), - array("&", "<", ">", """, "'", "-"), - $str); + $str = preg_replace(array('/&#(\d+);/', '/&(\w+);/'), $temp.'\\1;', $str); - $str = preg_replace("/$temp(\d+);/","&#\\1;",$str); - $str = preg_replace("/$temp(\w+);/","&\\1;", $str); + $str = str_replace(array('&', '<', '>', '"', "'", '-'), + array('&', '<', '>', '"', ''', '-'), + $str); - return $str; + return preg_replace(array('/'.$temp.'(\d+);/', '/'.$temp.'(\w+);/'), array('&#\\1;', '&\\1;'), $str); } // -------------------------------------------------------------------- @@ -425,33 +446,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 +484,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 +503,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 +531,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 +545,13 @@ class CI_Trackback { /** * Show error messages * - * @access public * @param string * @param string * @return string */ - function display_errors($open = '<p>', $close = '</p>') + public function display_errors($open = '<p>', $close = '</p>') { - $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 b30582d8a..108bc7725 100644 --- a/system/libraries/Typography.php +++ b/system/libraries/Typography.php @@ -1,48 +1,95 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Typography Class * - * - * @access private + * @package CodeIgniter + * @subpackage Libraries * @category Helpers - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/helpers/ + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/typography.html */ class CI_Typography { - // Block level elements that should not be wrapped inside <p> 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 <p> 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 <p> and <br /> tags within them. - var $skip_elements = 'p|pre|ol|ul|dl|object|table|h\d'; + /** + * Elements that should not have <p> and <br /> 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 +102,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 +128,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, '<!--') !== FALSE) + if (strpos($str, '<!--') !== FALSE && preg_match_all('#(<!\-\-.*?\-\->)#s', $str, $matches)) { - if (preg_match_all("#(<!\-\-.*?\-\->)#s", $str, $matches)) + for ($i = 0, $total = count($matches[0]); $i < $total; $i++) { - for ($i = 0, $total = count($matches[0]); $i < $total; $i++) - { - $html_comments[] = $matches[0][$i]; - $str = str_replace($matches[0][$i], '{@HC'.$i.'}', $str); - } + $html_comments[] = $matches[0][$i]; + $str = str_replace($matches[0][$i], '{@HC'.$i.'}', $str); } } @@ -98,83 +141,79 @@ class CI_Typography { // not contain <pre> tags, and it keeps the PCRE patterns below simpler and faster if (strpos($str, '<pre') !== FALSE) { - $str = preg_replace_callback("#<pre.*?>.*?</pre>#si", array($this, '_protect_characters'), $str); + $str = preg_replace_callback('#<pre.*?>.*?</pre>#si', array($this, '_protect_characters'), $str); } // Convert quotes within tags to temporary markers. - $str = preg_replace_callback("#<.+?>#si", array($this, '_protect_characters'), $str); + $str = preg_replace_callback('#<.+?>#si', array($this, '_protect_characters'), $str); // Do the same with braces if necessary if ($this->protect_braced_quotes === TRUE) { - $str = preg_replace_callback("#\{.+?\}#si", array($this, '_protect_characters'), $str); + $str = preg_replace_callback('#\{.+?\}#si', array($this, '_protect_characters'), $str); } // Convert "ignore" tags to temporary marker. The parser splits out the string at every tag // it encounters. Certain inline tags, like image tags, links, span tags, etc. will be // adversely affected if they are split out so we'll convert the opening bracket < temporarily to: {@TAG} - $str = preg_replace("#<(/*)(".$this->inline_elements.")([ >])#i", "{@TAG}\\1\\2\\3", $str); - - // Split the string at every tag. This expression creates an array with this prototype: - // - // [array] - // { - // [0] = <opening tag> - // [1] = Content... - // [2] = <closing tag> - // Etc... - // } + $str = preg_replace('#<(/*)('.$this->inline_elements.')([ >])#i', '{@TAG}\\1\\2\\3', $str); + + /* Split the string at every tag. This expression creates an array with this prototype: + * + * [array] + * { + * [0] = <opening tag> + * [1] = Content... + * [2] = <closing tag> + * Etc... + * } + */ $chunks = preg_split('/(<(?:[^<>]+(?:"[^"]*"|\'[^\']*\')?)+>)/', $str, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY); // Build our finalized string. We cycle through the array, skipping tags, and processing the contained text $str = ''; $process = TRUE; - $paragraph = FALSE; - $current_chunk = 0; - $total_chunks = count($chunks); - foreach ($chunks as $chunk) + for ($i = 0, $c = count($chunks) - 1; $i <= $c; $i++) { - $current_chunk++; - // Are we dealing with a tag? If so, we'll skip the processing for this cycle. // Well also set the "process" flag which allows us to skip <pre> tags and a few other things. - if (preg_match("#<(/*)(".$this->block_elements.").*?>#", $chunk, $match)) + if (preg_match('#<(/*)('.$this->block_elements.').*?>#', $chunks[$i], $match)) { - if (preg_match("#".$this->skip_elements."#", $match[2])) + if (preg_match('#'.$this->skip_elements.'#', $match[2])) { - $process = ($match[1] == '/') ? TRUE : FALSE; + $process = ($match[1] === '/'); } - if ($match[1] == '') + if ($match[1] === '') { $this->last_block_element = $match[2]; } - $str .= $chunk; + $str .= $chunks[$i]; continue; } - if ($process == FALSE) + if ($process === FALSE) { - $str .= $chunk; + $str .= $chunks[$i]; continue; } // Force a newline to make sure end tags get processed by _format_newlines() - if ($current_chunk == $total_chunks) + if ($i === $c) { - $chunk .= "\n"; + $chunks[$i] .= "\n"; } // Convert Newlines into <p> and <br /> tags - $str .= $this->_format_newlines($chunk); + $str .= $this->_format_newlines($chunks[$i]); } - // No opening block level tag? Add it if needed. - if ( ! preg_match("/^\s*<(?:".$this->block_elements.")/i", $str)) + // No opening block level tag? Add it if needed. + if ( ! preg_match('/^\s*<(?:'.$this->block_elements.')/i', $str)) { - $str = preg_replace("/^(.*?)<(".$this->block_elements.")/i", '<p>$1</p><$2', $str); + $str = preg_replace('/^(.*?)<('.$this->block_elements.')/i', '<p>$1</p><$2', $str); } // Convert quotes, elipsis, em-dashes, non-breaking spaces, and ampersands @@ -203,7 +242,7 @@ class CI_Typography { // Clean up stray paragraph tags that appear before block level elements '#<p></p><('.$this->block_elements.')#' => '<$1', - // Clean up stray non-breaking spaces preceeding block elements + // Clean up stray non-breaking spaces preceding block elements '#( \s*)+<('.$this->block_elements.')#' => ' <$2', // Replace the temporary markers we added earlier @@ -221,7 +260,7 @@ class CI_Typography { // Similarly, there might be cases where a closing </block> will follow // a closing </p> tag, so we'll correct it by adding a newline in between - "#</p></#" => "</p>\n</" + '#</p></#' => "</p>\n</" ); // Do we need to reduce empty lines? @@ -249,11 +288,10 @@ class CI_Typography { * to curly entities, but it also converts em-dashes, * double spaces, and ampersands * - * @access public * @param string * @return string */ - function format_characters($str) + public function format_characters($str) { static $table; @@ -313,18 +351,12 @@ class CI_Typography { * * Converts newline characters into either <p> tags or <br /> * - * @access public * @param string * @return string */ - function _format_newlines($str) + protected function _format_newlines($str) { - if ($str == '') - { - return $str; - } - - if (strpos($str, "\n") === FALSE && ! in_array($this->last_block_element, $this->inner_block_required)) + if ($str === '' OR (strpos($str, "\n") === FALSE && ! in_array($this->last_block_element, $this->inner_block_required))) { return $str; } @@ -333,10 +365,10 @@ class CI_Typography { $str = str_replace("\n\n", "</p>\n\n<p>", $str); // Convert single spaces to <br /> tags - $str = preg_replace("/([^\n])(\n)([^\n])/", "\\1<br />\\2\\3", $str); + $str = preg_replace("/([^\n])(\n)([^\n])/", '\\1<br />\\2\\3', $str); // Wrap the whole enchilada in enclosing paragraphs - if ($str != "\n") + if ($str !== "\n") { // We trim off the right-side new line so that the closing </p> tag // will be positioned immediately following the string, matching @@ -346,9 +378,7 @@ class CI_Typography { // Remove empty paragraphs if they are on the first line, as this // is a potential unintended consequence of the previous code - $str = preg_replace("/<p><\/p>(.*)/", "\\1", $str, 1); - - return $str; + return preg_replace('/<p><\/p>(.*)/', '\\1', $str, 1); } // ------------------------------------------------------------------------ @@ -361,11 +391,10 @@ class CI_Typography { * and we don't want double dashes converted to emdash entities, so they are marked with {@DD} * likewise double spaces are converted to {@NBS} to prevent entity conversion * - * @access public * @param array * @return string */ - function _protect_characters($match) + protected function _protect_characters($match) { return str_replace(array("'",'"','--',' '), array('{@SQ}', '{@DQ}', '{@DD}', '{@NBS}'), $match[0]); } @@ -375,36 +404,22 @@ class CI_Typography { /** * Convert newlines to HTML line breaks except within PRE tags * - * @access public * @param string * @return string */ - function nl2br_except_pre($str) + public function nl2br_except_pre($str) { - $ex = explode("pre>",$str); - $ct = count($ex); - - $newstr = ""; - for ($i = 0; $i < $ct; $i++) + $newstr = ''; + for ($ex = explode('pre>', $str), $ct = count($ex), $i = 0; $i < $ct; $i++) { - if (($i % 2) == 0) - { - $newstr .= nl2br($ex[$i]); - } - else + $newstr .= (($i % 2) === 0) ? nl2br($ex[$i]) : $ex[$i]; + if ($ct - 1 !== $i) { - $newstr .= $ex[$i]; + $newstr .= 'pre>'; } - - if ($ct - 1 != $i) - $newstr .= "pre>"; } return $newstr; } } -// END Typography Class - -/* End of file Typography.php */ -/* Location: ./system/libraries/Typography.php */
\ No newline at end of file diff --git a/system/libraries/Unit_test.php b/system/libraries/Unit_test.php index b8919e1e5..e1b94f0a8 100644 --- a/system/libraries/Unit_test.php +++ b/system/libraries/Unit_test.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.3.1 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.3.1 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Unit Testing Class @@ -23,32 +46,73 @@ * @package CodeIgniter * @subpackage Libraries * @category UnitTesting - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/uri.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/unit_testing.html */ class CI_Unit_test { - var $active = TRUE; - var $results = array(); - var $strict = FALSE; - var $_template = NULL; - var $_template_rows = NULL; - var $_test_items_visible = array(); + /** + * Active flag + * + * @var bool + */ + public $active = TRUE; + + /** + * Test results + * + * @var array + */ + public $results = array(); + + /** + * Strict comparison flag + * + * Whether to use === or == when comparing + * + * @var bool + */ + public $strict = FALSE; + + /** + * Template + * + * @var string + */ + protected $_template = NULL; + + /** + * Template rows + * + * @var string + */ + protected $_template_rows = NULL; + + /** + * List of visible test items + * + * @var array + */ + protected $_test_items_visible = array( + 'test_name', + 'test_datatype', + 'res_datatype', + 'result', + 'file', + 'line', + 'notes' + ); + + // -------------------------------------------------------------------- + /** + * Constructor + * + * @return void + */ public function __construct() { - // These are the default items visible when a test is run. - $this->_test_items_visible = array ( - 'test_name', - 'test_datatype', - 'res_datatype', - 'result', - 'file', - 'line', - 'notes' - ); - - log_message('debug', "Unit Testing Class Initialized"); + log_message('info', 'Unit Testing Class Initialized'); } // -------------------------------------------------------------------- @@ -58,13 +122,12 @@ class CI_Unit_test { * * Runs the supplied tests * - * @access public - * @param array + * @param array $items * @return void */ - function set_test_items($items = array()) + public function set_test_items($items) { - if ( ! empty($items) AND is_array($items)) + if ( ! empty($items) && is_array($items)) { $this->_test_items_visible = $items; } @@ -77,50 +140,45 @@ class CI_Unit_test { * * Runs the supplied tests * - * @access public - * @param mixed - * @param mixed - * @param string + * @param mixed $test + * @param mixed $expected + * @param string $test_name + * @param string $notes * @return string */ - function run($test, $expected = TRUE, $test_name = 'undefined', $notes = '') + public function run($test, $expected = TRUE, $test_name = 'undefined', $notes = '') { - if ($this->active == FALSE) + if ($this->active === FALSE) { return FALSE; } - if (in_array($expected, array('is_object', 'is_string', 'is_bool', 'is_true', 'is_false', 'is_int', 'is_numeric', 'is_float', 'is_double', 'is_array', 'is_null'), TRUE)) + if (in_array($expected, array('is_object', 'is_string', 'is_bool', 'is_true', 'is_false', 'is_int', 'is_numeric', 'is_float', 'is_double', 'is_array', 'is_null', 'is_resource'), TRUE)) { - $expected = str_replace('is_float', 'is_double', $expected); - $result = ($expected($test)) ? TRUE : FALSE; + $result = $expected($test); $extype = str_replace(array('true', 'false'), 'bool', str_replace('is_', '', $expected)); } else { - if ($this->strict == TRUE) - $result = ($test === $expected) ? TRUE : FALSE; - else - $result = ($test == $expected) ? TRUE : FALSE; - + $result = ($this->strict === TRUE) ? ($test === $expected) : ($test == $expected); $extype = gettype($expected); } $back = $this->_backtrace(); - $report[] = array ( - 'test_name' => $test_name, - 'test_datatype' => gettype($test), - 'res_datatype' => $extype, - 'result' => ($result === TRUE) ? 'passed' : 'failed', - 'file' => $back['file'], - 'line' => $back['line'], - 'notes' => $notes - ); + $report = array ( + 'test_name' => $test_name, + 'test_datatype' => gettype($test), + 'res_datatype' => $extype, + 'result' => ($result === TRUE) ? 'passed' : 'failed', + 'file' => $back['file'], + 'line' => $back['line'], + 'notes' => $notes + ); $this->results[] = $report; - return($this->report($this->result($report))); + return $this->report($this->result(array($report))); } // -------------------------------------------------------------------- @@ -130,12 +188,12 @@ class CI_Unit_test { * * Displays a table with the test data * - * @access public + * @param array $result * @return string */ - function report($result = array()) + public function report($result = array()) { - if (count($result) == 0) + if (count($result) === 0) { $result = $this->result(); } @@ -152,22 +210,19 @@ class CI_Unit_test { foreach ($res as $key => $val) { - if ($key == $CI->lang->line('ut_result')) + if ($key === $CI->lang->line('ut_result')) { - if ($val == $CI->lang->line('ut_passed')) + if ($val === $CI->lang->line('ut_passed')) { $val = '<span style="color: #0C0;">'.$val.'</span>'; } - elseif ($val == $CI->lang->line('ut_failed')) + elseif ($val === $CI->lang->line('ut_failed')) { $val = '<span style="color: #C00;">'.$val.'</span>'; } } - $temp = $this->_template_rows; - $temp = str_replace('{item}', $key, $temp); - $temp = str_replace('{result}', $val, $temp); - $table .= $temp; + $table .= str_replace(array('{item}', '{result}'), array($key, $val), $this->_template_rows); } $r .= str_replace('{rows}', $table, $this->_template); @@ -183,13 +238,12 @@ class CI_Unit_test { * * Causes the evaluation to use === rather than == * - * @access public - * @param bool - * @return null + * @param bool $state + * @return void */ - function use_strict($state = TRUE) + public function use_strict($state = TRUE) { - $this->strict = ($state == FALSE) ? FALSE : TRUE; + $this->strict = (bool) $state; } // -------------------------------------------------------------------- @@ -199,13 +253,12 @@ class CI_Unit_test { * * Enables/disables unit testing * - * @access public * @param bool - * @return null + * @return void */ - function active($state = TRUE) + public function active($state = TRUE) { - $this->active = ($state == FALSE) ? FALSE : TRUE; + $this->active = (bool) $state; } // -------------------------------------------------------------------- @@ -215,15 +268,15 @@ class CI_Unit_test { * * Returns the raw result data * - * @access public + * @param array $results * @return array */ - function result($results = array()) + public function result($results = array()) { $CI =& get_instance(); $CI->load->language('unit_test'); - if (count($results) == 0) + if (count($results) === 0) { $results = $this->results; } @@ -238,26 +291,15 @@ class CI_Unit_test { { continue; } - - if (is_array($val)) - { - foreach ($val as $k => $v) - { - if (FALSE !== ($line = $CI->lang->line(strtolower('ut_'.$v)))) - { - $v = $line; - } - $temp[$CI->lang->line('ut_'.$k)] = $v; - } - } - else + elseif (in_array($key, array('test_name', 'test_datatype', 'res_datatype', 'result'), TRUE)) { - if (FALSE !== ($line = $CI->lang->line(strtolower('ut_'.$val)))) + if (FALSE !== ($line = $CI->lang->line(strtolower('ut_'.$val), FALSE))) { $val = $line; } - $temp[$CI->lang->line('ut_'.$key)] = $val; } + + $temp[$CI->lang->line('ut_'.$key, FALSE)] = $val; } $retval[] = $temp; @@ -273,11 +315,10 @@ class CI_Unit_test { * * This lets us set the template to be used to display results * - * @access public * @param string * @return void */ - function set_template($template) + public function set_template($template) { $this->_template = $template; } @@ -289,21 +330,15 @@ class CI_Unit_test { * * This lets us show file names and line numbers * - * @access private * @return array */ - function _backtrace() + protected function _backtrace() { - if (function_exists('debug_backtrace')) - { - $back = debug_backtrace(); - - $file = ( ! isset($back['1']['file'])) ? '' : $back['1']['file']; - $line = ( ! isset($back['1']['line'])) ? '' : $back['1']['line']; - - return array('file' => $file, 'line' => $line); - } - return array('file' => 'Unknown', 'line' => 'Unknown'); + $back = debug_backtrace(); + return array( + 'file' => (isset($back[1]['file']) ? $back[1]['file'] : ''), + 'line' => (isset($back[1]['line']) ? $back[1]['line'] : '') + ); } // -------------------------------------------------------------------- @@ -311,19 +346,14 @@ class CI_Unit_test { /** * Get Default Template * - * @access private * @return string */ - function _default_template() + protected function _default_template() { - $this->_template = "\n".'<table style="width:100%; font-size:small; margin:10px 0; border-collapse:collapse; border:1px solid #CCC;">'; - $this->_template .= '{rows}'; - $this->_template .= "\n".'</table>'; - - $this->_template_rows = "\n\t".'<tr>'; - $this->_template_rows .= "\n\t\t".'<th style="text-align: left; border-bottom:1px solid #CCC;">{item}</th>'; - $this->_template_rows .= "\n\t\t".'<td style="border-bottom:1px solid #CCC;">{result}</td>'; - $this->_template_rows .= "\n\t".'</tr>'; + $this->_template = "\n".'<table style="width:100%; font-size:small; margin:10px 0; border-collapse:collapse; border:1px solid #CCC;">{rows}'."\n</table>"; + + $this->_template_rows = "\n\t<tr>\n\t\t".'<th style="text-align: left; border-bottom:1px solid #CCC;">{item}</th>' + ."\n\t\t".'<td style="border-bottom:1px solid #CCC;">{result}</td>'."\n\t</tr>"; } // -------------------------------------------------------------------- @@ -333,51 +363,45 @@ class CI_Unit_test { * * Harvests the data within the template {pseudo-variables} * - * @access private * @return void */ - function _parse_template() + protected function _parse_template() { - if ( ! is_null($this->_template_rows)) - { - return; - } - - if (is_null($this->_template)) + if ($this->_template_rows !== NULL) { - $this->_default_template(); return; } - if ( ! preg_match("/\{rows\}(.*?)\{\/rows\}/si", $this->_template, $match)) + if ($this->_template === NULL OR ! preg_match('/\{rows\}(.*?)\{\/rows\}/si', $this->_template, $match)) { $this->_default_template(); return; } - $this->_template_rows = $match['1']; - $this->_template = str_replace($match['0'], '{rows}', $this->_template); + $this->_template_rows = $match[1]; + $this->_template = str_replace($match[0], '{rows}', $this->_template); } } -// END Unit_test Class /** - * Helper functions to test boolean true/false + * Helper function to test boolean TRUE * - * - * @access private + * @param mixed $test * @return bool */ function is_true($test) { - return (is_bool($test) AND $test === TRUE) ? TRUE : FALSE; + return ($test === TRUE); } + +/** + * Helper function to test boolean FALSE + * + * @param mixed $test + * @return bool + */ function is_false($test) { - return (is_bool($test) AND $test === FALSE) ? TRUE : FALSE; + return ($test === FALSE); } - - -/* End of file Unit_test.php */ -/* Location: ./system/libraries/Unit_test.php */
\ No newline at end of file diff --git a/system/libraries/Upload.php b/system/libraries/Upload.php index c188c39bc..168211a91 100644 --- a/system/libraries/Upload.php +++ b/system/libraries/Upload.php @@ -1,19 +1,42 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * File Uploading Class @@ -21,52 +44,260 @@ * @package CodeIgniter * @subpackage Libraries * @category Uploads - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/file_uploading.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/file_uploading.html */ class CI_Upload { - public $max_size = 0; - public $max_width = 0; - public $max_height = 0; - public $max_filename = 0; - public $allowed_types = ""; - public $file_temp = ""; - public $file_name = ""; - public $orig_name = ""; - public $file_type = ""; - public $file_size = ""; - public $file_ext = ""; - public $upload_path = ""; - public $overwrite = FALSE; - public $encrypt_name = FALSE; - public $is_image = FALSE; - public $image_width = ''; - public $image_height = ''; - public $image_type = ''; - public $image_size_str = ''; - public $error_msg = array(); - public $mimes = array(); - public $remove_spaces = TRUE; - public $xss_clean = FALSE; - public $temp_prefix = "temp_file_"; - public $client_name = ''; - - protected $_file_name_override = ''; + /** + * Maximum file size + * + * @var int + */ + public $max_size = 0; + + /** + * Maximum image width + * + * @var int + */ + public $max_width = 0; + + /** + * Maximum image height + * + * @var int + */ + public $max_height = 0; + + /** + * Minimum image width + * + * @var int + */ + public $min_width = 0; + + /** + * Minimum image height + * + * @var int + */ + public $min_height = 0; + + /** + * Maximum filename length + * + * @var int + */ + public $max_filename = 0; + + /** + * Maximum duplicate filename increment ID + * + * @var int + */ + public $max_filename_increment = 100; + + /** + * Allowed file types + * + * @var string + */ + public $allowed_types = ''; + + /** + * Temporary filename + * + * @var string + */ + public $file_temp = ''; + + /** + * Filename + * + * @var string + */ + public $file_name = ''; + + /** + * Original filename + * + * @var string + */ + public $orig_name = ''; + + /** + * File type + * + * @var string + */ + public $file_type = ''; + + /** + * File size + * + * @var int + */ + public $file_size = NULL; + + /** + * Filename extension + * + * @var string + */ + public $file_ext = ''; + + /** + * Force filename extension to lowercase + * + * @var string + */ + public $file_ext_tolower = FALSE; + + /** + * Upload path + * + * @var string + */ + public $upload_path = ''; + + /** + * Overwrite flag + * + * @var bool + */ + public $overwrite = FALSE; + + /** + * Obfuscate filename flag + * + * @var bool + */ + public $encrypt_name = FALSE; + + /** + * Is image flag + * + * @var bool + */ + public $is_image = FALSE; + + /** + * Image width + * + * @var int + */ + public $image_width = NULL; + + /** + * Image height + * + * @var int + */ + public $image_height = NULL; + + /** + * Image type + * + * @var string + */ + public $image_type = ''; + + /** + * Image size string + * + * @var string + */ + public $image_size_str = ''; + + /** + * Error messages list + * + * @var array + */ + public $error_msg = array(); + + /** + * Remove spaces flag + * + * @var bool + */ + public $remove_spaces = TRUE; + + /** + * MIME detection flag + * + * @var bool + */ + public $detect_mime = TRUE; + + /** + * XSS filter flag + * + * @var bool + */ + public $xss_clean = FALSE; + + /** + * Apache mod_mime fix flag + * + * @var bool + */ + public $mod_mime_fix = TRUE; + + /** + * Temporary filename prefix + * + * @var string + */ + public $temp_prefix = 'temp_file_'; + + /** + * Filename sent by the client + * + * @var bool + */ + public $client_name = ''; + + // -------------------------------------------------------------------- + + /** + * Filename override + * + * @var string + */ + protected $_file_name_override = ''; + + /** + * MIME types list + * + * @var array + */ + protected $_mimes = array(); + + /** + * CI Singleton + * + * @var object + */ + protected $_CI; + + // -------------------------------------------------------------------- /** * Constructor * - * @access public + * @param array $config + * @return void */ - public function __construct($props = array()) + public function __construct($config = array()) { - if (count($props) > 0) - { - $this->initialize($props); - } + empty($config) OR $this->initialize($config, FALSE); + + $this->_mimes =& get_mimes(); + $this->_CI =& get_instance(); - log_message('debug', "Upload Class Initialized"); + log_message('info', 'Upload Class Initialized'); } // -------------------------------------------------------------------- @@ -74,63 +305,63 @@ class CI_Upload { /** * Initialize preferences * - * @param array - * @return void + * @param array $config + * @param bool $reset + * @return CI_Upload */ - public function initialize($config = array()) + public function initialize(array $config = array(), $reset = TRUE) { - $defaults = array( - 'max_size' => 0, - 'max_width' => 0, - 'max_height' => 0, - 'max_filename' => 0, - 'allowed_types' => "", - 'file_temp' => "", - 'file_name' => "", - 'orig_name' => "", - 'file_type' => "", - 'file_size' => "", - 'file_ext' => "", - 'upload_path' => "", - 'overwrite' => FALSE, - 'encrypt_name' => FALSE, - 'is_image' => FALSE, - 'image_width' => '', - 'image_height' => '', - 'image_type' => '', - 'image_size_str' => '', - 'error_msg' => array(), - 'mimes' => array(), - 'remove_spaces' => TRUE, - 'xss_clean' => FALSE, - 'temp_prefix' => "temp_file_", - 'client_name' => '' - ); - - - foreach ($defaults as $key => $val) + $reflection = new ReflectionClass($this); + + if ($reset === TRUE) { - if (isset($config[$key])) + $defaults = $reflection->getDefaultProperties(); + foreach (array_keys($defaults) as $key) { - $method = 'set_'.$key; - if (method_exists($this, $method)) + if ($key[0] === '_') + { + continue; + } + + if (isset($config[$key])) { - $this->$method($config[$key]); + if ($reflection->hasMethod('set_'.$key)) + { + $this->{'set_'.$key}($config[$key]); + } + else + { + $this->$key = $config[$key]; + } } else { - $this->$key = $config[$key]; + $this->$key = $defaults[$key]; } } - else + } + else + { + foreach ($config as $key => &$value) { - $this->$key = $val; + if ($key[0] !== '_' && $reflection->hasProperty($key)) + { + if ($reflection->hasMethod('set_'.$key)) + { + $this->{'set_'.$key}($value); + } + else + { + $this->$key = $value; + } + } } } // if a file_name was provided in the config, use it instead of the user input // supplied file name for all uploads until initialized again $this->_file_name_override = $this->file_name; + return $this; } // -------------------------------------------------------------------- @@ -138,15 +369,36 @@ class CI_Upload { /** * Perform the file upload * + * @param string $field * @return bool */ public function do_upload($field = 'userfile') { + // Is $_FILES[$field] set? If not, no reason to continue. + if (isset($_FILES[$field])) + { + $_file = $_FILES[$field]; + } + // Does the field name contain array notation? + elseif (($c = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $field, $matches)) > 1) + { + $_file = $_FILES; + for ($i = 0; $i < $c; $i++) + { + // We can't track numeric iterations, only full field names are accepted + if (($field = trim($matches[0][$i], '[]')) === '' OR ! isset($_file[$field])) + { + $_file = NULL; + break; + } + + $_file = $_file[$field]; + } + } - // Is $_FILES[$field] set? If not, no reason to continue. - if ( ! isset($_FILES[$field])) + if ( ! isset($_file)) { - $this->set_error('upload_no_file_selected'); + $this->set_error('upload_no_file_selected', 'debug'); return FALSE; } @@ -158,60 +410,66 @@ class CI_Upload { } // Was the file able to be uploaded? If not, determine the reason why. - if ( ! is_uploaded_file($_FILES[$field]['tmp_name'])) + if ( ! is_uploaded_file($_file['tmp_name'])) { - $error = ( ! isset($_FILES[$field]['error'])) ? 4 : $_FILES[$field]['error']; + $error = isset($_file['error']) ? $_file['error'] : 4; - switch($error) + switch ($error) { - case 1: // UPLOAD_ERR_INI_SIZE - $this->set_error('upload_file_exceeds_limit'); + case UPLOAD_ERR_INI_SIZE: + $this->set_error('upload_file_exceeds_limit', 'info'); break; - case 2: // UPLOAD_ERR_FORM_SIZE - $this->set_error('upload_file_exceeds_form_limit'); + case UPLOAD_ERR_FORM_SIZE: + $this->set_error('upload_file_exceeds_form_limit', 'info'); break; - case 3: // UPLOAD_ERR_PARTIAL - $this->set_error('upload_file_partial'); + case UPLOAD_ERR_PARTIAL: + $this->set_error('upload_file_partial', 'debug'); break; - case 4: // UPLOAD_ERR_NO_FILE - $this->set_error('upload_no_file_selected'); + case UPLOAD_ERR_NO_FILE: + $this->set_error('upload_no_file_selected', 'debug'); break; - case 6: // UPLOAD_ERR_NO_TMP_DIR - $this->set_error('upload_no_temp_directory'); + case UPLOAD_ERR_NO_TMP_DIR: + $this->set_error('upload_no_temp_directory', 'error'); break; - case 7: // UPLOAD_ERR_CANT_WRITE - $this->set_error('upload_unable_to_write_file'); + case UPLOAD_ERR_CANT_WRITE: + $this->set_error('upload_unable_to_write_file', 'error'); break; - case 8: // UPLOAD_ERR_EXTENSION - $this->set_error('upload_stopped_by_extension'); + case UPLOAD_ERR_EXTENSION: + $this->set_error('upload_stopped_by_extension', 'debug'); break; - default : $this->set_error('upload_no_file_selected'); + default: + $this->set_error('upload_no_file_selected', 'debug'); break; } return FALSE; } - // Set the uploaded data as class variables - $this->file_temp = $_FILES[$field]['tmp_name']; - $this->file_size = $_FILES[$field]['size']; - $this->_file_mime_type($_FILES[$field]); - $this->file_type = preg_replace("/^(.+?);.*$/", "\\1", $this->file_type); + $this->file_temp = $_file['tmp_name']; + $this->file_size = $_file['size']; + + // Skip MIME type detection? + if ($this->detect_mime !== FALSE) + { + $this->_file_mime_type($_file); + } + + $this->file_type = preg_replace('/^(.+?);.*$/', '\\1', $this->file_type); $this->file_type = strtolower(trim(stripslashes($this->file_type), '"')); - $this->file_name = $this->_prep_filename($_FILES[$field]['name']); + $this->file_name = $this->_prep_filename($_file['name']); $this->file_ext = $this->get_extension($this->file_name); $this->client_name = $this->file_name; // Is the file type allowed to be uploaded? if ( ! $this->is_allowed_filetype()) { - $this->set_error('upload_invalid_filetype'); + $this->set_error('upload_invalid_filetype', 'debug'); return FALSE; } // if we're overriding, let's now make sure the new name and type is allowed - if ($this->_file_name_override != '') + if ($this->_file_name_override !== '') { $this->file_name = $this->_prep_filename($this->_file_name_override); @@ -220,16 +478,15 @@ class CI_Upload { { $this->file_name .= $this->file_ext; } - - // An extension was provided, lets have it! else { - $this->file_ext = $this->get_extension($this->_file_name_override); + // An extension was provided, let's have it! + $this->file_ext = $this->get_extension($this->_file_name_override); } if ( ! $this->is_allowed_filetype(TRUE)) { - $this->set_error('upload_invalid_filetype'); + $this->set_error('upload_invalid_filetype', 'debug'); return FALSE; } } @@ -243,20 +500,20 @@ class CI_Upload { // Is the file size within the allowed maximum? if ( ! $this->is_allowed_filesize()) { - $this->set_error('upload_invalid_filesize'); + $this->set_error('upload_invalid_filesize', 'info'); return FALSE; } // Are the image dimensions within the allowed size? - // Note: This can fail if the server has an open_basdir restriction. + // Note: This can fail if the server has an open_basedir restriction. if ( ! $this->is_allowed_dimensions()) { - $this->set_error('upload_invalid_dimensions'); + $this->set_error('upload_invalid_dimensions', 'info'); return FALSE; } // Sanitize the file name for security - $this->file_name = $this->clean_file_name($this->file_name); + $this->file_name = $this->_CI->security->sanitize_filename($this->file_name); // Truncate the file name if it's too long if ($this->max_filename > 0) @@ -265,9 +522,15 @@ class CI_Upload { } // Remove white spaces in the name - if ($this->remove_spaces == TRUE) + if ($this->remove_spaces === TRUE) { - $this->file_name = preg_replace("/\s+/", "_", $this->file_name); + $this->file_name = preg_replace('/\s+/', '_', $this->file_name); + } + + if ($this->file_ext_tolower && ($ext_length = strlen($this->file_ext))) + { + // file_ext was previously lower-cased by a get_extension() call + $this->file_name = substr($this->file_name, 0, -$ext_length).$this->file_ext; } /* @@ -277,44 +540,35 @@ class CI_Upload { * If it returns false there was a problem. */ $this->orig_name = $this->file_name; - - if ($this->overwrite == FALSE) + if (FALSE === ($this->file_name = $this->set_filename($this->upload_path, $this->file_name))) { - $this->file_name = $this->set_filename($this->upload_path, $this->file_name); - - if ($this->file_name === FALSE) - { - return FALSE; - } + return FALSE; } /* * Run the file through the XSS hacking filter * This helps prevent malicious code from being - * embedded within a file. Scripts can easily + * embedded within a file. Scripts can easily * be disguised as images or other file types. */ - if ($this->xss_clean) + if ($this->xss_clean && $this->do_xss_clean() === FALSE) { - if ($this->do_xss_clean() === FALSE) - { - $this->set_error('upload_unable_to_write_file'); - return FALSE; - } + $this->set_error('upload_unable_to_write_file', 'error'); + return FALSE; } /* * Move the file to the final destination * To deal with different server configurations - * we'll attempt to use copy() first. If that fails - * we'll use move_uploaded_file(). One of the two should + * we'll attempt to use copy() first. If that fails + * we'll use move_uploaded_file(). One of the two should * reliably work in most environments */ if ( ! @copy($this->file_temp, $this->upload_path.$this->file_name)) { if ( ! @move_uploaded_file($this->file_temp, $this->upload_path.$this->file_name)) { - $this->set_error('upload_destination_error'); + $this->set_error('upload_destination_error', 'error'); return FALSE; } } @@ -322,7 +576,7 @@ class CI_Upload { /* * Set the finalized image dimensions * This sets the image width/height (assuming the - * file was an image). We use this information + * file was an image). We use this information * in the "data" function. */ $this->set_image_properties($this->upload_path.$this->file_name); @@ -338,26 +592,34 @@ class CI_Upload { * Returns an associative array containing all of the information * related to the upload, allowing the developer easy access in one array. * - * @return array + * @param string $index + * @return mixed */ - public function data() + public function data($index = NULL) { - return array ( - 'file_name' => $this->file_name, - 'file_type' => $this->file_type, - 'file_path' => $this->upload_path, - 'full_path' => $this->upload_path.$this->file_name, - 'raw_name' => str_replace($this->file_ext, '', $this->file_name), - 'orig_name' => $this->orig_name, - 'client_name' => $this->client_name, - 'file_ext' => $this->file_ext, - 'file_size' => $this->file_size, - 'is_image' => $this->is_image(), - 'image_width' => $this->image_width, - 'image_height' => $this->image_height, - 'image_type' => $this->image_type, - 'image_size_str' => $this->image_size_str, - ); + $data = array( + 'file_name' => $this->file_name, + 'file_type' => $this->file_type, + 'file_path' => $this->upload_path, + 'full_path' => $this->upload_path.$this->file_name, + 'raw_name' => substr($this->file_name, 0, -strlen($this->file_ext)), + 'orig_name' => $this->orig_name, + 'client_name' => $this->client_name, + 'file_ext' => $this->file_ext, + 'file_size' => $this->file_size, + 'is_image' => $this->is_image(), + 'image_width' => $this->image_width, + 'image_height' => $this->image_height, + 'image_type' => $this->image_type, + 'image_size_str' => $this->image_size_str, + ); + + if ( ! empty($index)) + { + return isset($data[$index]) ? $data[$index] : NULL; + } + + return $data; } // -------------------------------------------------------------------- @@ -365,13 +627,14 @@ class CI_Upload { /** * Set Upload Path * - * @param string - * @return void + * @param string $path + * @return CI_Upload */ public function set_upload_path($path) { // Make sure it has a trailing slash $this->upload_path = rtrim($path, '/').'/'; + return $this; } // -------------------------------------------------------------------- @@ -383,19 +646,18 @@ class CI_Upload { * existence of a file with the same name. If found, it will append a * number to the end of the filename to avoid overwriting a pre-existing file. * - * @param string - * @param string + * @param string $path + * @param string $filename * @return string */ public function set_filename($path, $filename) { - if ($this->encrypt_name == TRUE) + if ($this->encrypt_name === TRUE) { - mt_srand(); $filename = md5(uniqid(mt_rand())).$this->file_ext; } - if ( ! file_exists($path.$filename)) + if ($this->overwrite === TRUE OR ! file_exists($path.$filename)) { return $filename; } @@ -403,7 +665,7 @@ class CI_Upload { $filename = str_replace($this->file_ext, '', $filename); $new_filename = ''; - for ($i = 1; $i < 100; $i++) + for ($i = 1; $i < $this->max_filename_increment; $i++) { if ( ! file_exists($path.$filename.$i.$this->file_ext)) { @@ -412,15 +674,13 @@ class CI_Upload { } } - if ($new_filename == '') + if ($new_filename === '') { - $this->set_error('upload_bad_filename'); + $this->set_error('upload_bad_filename', 'debug'); return FALSE; } - else - { - return $new_filename; - } + + return $new_filename; } // -------------------------------------------------------------------- @@ -428,12 +688,29 @@ class CI_Upload { /** * Set Maximum File Size * - * @param integer - * @return void + * @param int $n + * @return CI_Upload */ public function set_max_filesize($n) { - $this->max_size = ((int) $n < 0) ? 0: (int) $n; + $this->max_size = ($n < 0) ? 0 : (int) $n; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Set Maximum File Size + * + * An internal alias to set_max_filesize() to help with configuration + * as initialize() will look for a set_<property_name>() method ... + * + * @param int $n + * @return CI_Upload + */ + protected function set_max_size($n) + { + return $this->set_max_filesize($n); } // -------------------------------------------------------------------- @@ -441,12 +718,13 @@ class CI_Upload { /** * Set Maximum File Name Length * - * @param integer - * @return void + * @param int $n + * @return CI_Upload */ public function set_max_filename($n) { - $this->max_filename = ((int) $n < 0) ? 0: (int) $n; + $this->max_filename = ($n < 0) ? 0 : (int) $n; + return $this; } // -------------------------------------------------------------------- @@ -454,12 +732,13 @@ class CI_Upload { /** * Set Maximum Image Width * - * @param integer - * @return void + * @param int $n + * @return CI_Upload */ public function set_max_width($n) { - $this->max_width = ((int) $n < 0) ? 0: (int) $n; + $this->max_width = ($n < 0) ? 0 : (int) $n; + return $this; } // -------------------------------------------------------------------- @@ -467,12 +746,41 @@ class CI_Upload { /** * Set Maximum Image Height * - * @param integer - * @return void + * @param int $n + * @return CI_Upload */ public function set_max_height($n) { - $this->max_height = ((int) $n < 0) ? 0: (int) $n; + $this->max_height = ($n < 0) ? 0 : (int) $n; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Set minimum image width + * + * @param int $n + * @return CI_Upload + */ + public function set_min_width($n) + { + $this->min_width = ($n < 0) ? 0 : (int) $n; + return $this; + } + + // -------------------------------------------------------------------- + + /** + * Set minimum image height + * + * @param int $n + * @return CI_Upload + */ + public function set_min_height($n) + { + $this->min_height = ($n < 0) ? 0 : (int) $n; + return $this; } // -------------------------------------------------------------------- @@ -480,17 +788,15 @@ class CI_Upload { /** * Set Allowed File Types * - * @param string - * @return void + * @param mixed $types + * @return CI_Upload */ public function set_allowed_types($types) { - if ( ! is_array($types) && $types == '*') - { - $this->allowed_types = '*'; - return; - } - $this->allowed_types = explode('|', $types); + $this->allowed_types = (is_array($types) OR $types === '*') + ? $types + : explode('|', $types); + return $this; } // -------------------------------------------------------------------- @@ -500,28 +806,25 @@ class CI_Upload { * * Uses GD to determine the width/height/type of image * - * @param string - * @return void + * @param string $path + * @return CI_Upload */ public function set_image_properties($path = '') { - if ( ! $this->is_image()) - { - return; - } - - if (function_exists('getimagesize')) + if ($this->is_image() && function_exists('getimagesize')) { if (FALSE !== ($D = @getimagesize($path))) { $types = array(1 => 'gif', 2 => 'jpeg', 3 => 'png'); - $this->image_width = $D['0']; - $this->image_height = $D['1']; - $this->image_type = ( ! isset($types[$D['2']])) ? 'unknown' : $types[$D['2']]; - $this->image_size_str = $D['3']; // string containing height and width + $this->image_width = $D[0]; + $this->image_height = $D[1]; + $this->image_type = isset($types[$D[2]]) ? $types[$D[2]] : 'unknown'; + $this->image_size_str = $D[3]; // string containing height and width } } + + return $this; } // -------------------------------------------------------------------- @@ -532,12 +835,13 @@ class CI_Upload { * Enables the XSS flag so that the file that was uploaded * will be run through the XSS filter. * - * @param bool - * @return void + * @param bool $flag + * @return CI_Upload */ public function set_xss_clean($flag = FALSE) { - $this->xss_clean = ($flag == TRUE) ? TRUE : FALSE; + $this->xss_clean = ($flag === TRUE); + return $this; } // -------------------------------------------------------------------- @@ -559,19 +863,14 @@ class CI_Upload { { $this->file_type = 'image/png'; } - - if (in_array($this->file_type, $jpeg_mimes)) + elseif (in_array($this->file_type, $jpeg_mimes)) { $this->file_type = 'image/jpeg'; } - $img_mimes = array( - 'image/gif', - 'image/jpeg', - 'image/png', - ); + $img_mimes = array('image/gif', 'image/jpeg', 'image/png', 'image/webp'); - return (in_array($this->file_type, $img_mimes, TRUE)) ? TRUE : FALSE; + return in_array($this->file_type, $img_mimes, TRUE); } // -------------------------------------------------------------------- @@ -579,37 +878,33 @@ class CI_Upload { /** * Verify that the filetype is allowed * + * @param bool $ignore_mime * @return bool */ public function is_allowed_filetype($ignore_mime = FALSE) { - if ($this->allowed_types == '*') + if ($this->allowed_types === '*') { return TRUE; } - if (count($this->allowed_types) == 0 OR ! is_array($this->allowed_types)) + if (empty($this->allowed_types) OR ! is_array($this->allowed_types)) { - $this->set_error('upload_no_file_types'); + $this->set_error('upload_no_file_types', 'debug'); return FALSE; } $ext = strtolower(ltrim($this->file_ext, '.')); - if ( ! in_array($ext, $this->allowed_types)) + if ( ! in_array($ext, $this->allowed_types, TRUE)) { return FALSE; } // Images get some additional checks - $image_types = array('gif', 'jpg', 'jpeg', 'png', 'jpe'); - - if (in_array($ext, $image_types)) + if (in_array($ext, array('gif', 'jpg', 'jpeg', 'jpe', 'png', 'webp'), TRUE) && @getimagesize($this->file_temp) === FALSE) { - if (getimagesize($this->file_temp) === FALSE) - { - return FALSE; - } + return FALSE; } if ($ignore_mime === TRUE) @@ -617,18 +912,11 @@ class CI_Upload { return TRUE; } - $mime = $this->mimes_types($ext); - - if (is_array($mime)) + if (isset($this->_mimes[$ext])) { - if (in_array($this->file_type, $mime, TRUE)) - { - return TRUE; - } - } - elseif ($mime == $this->file_type) - { - return TRUE; + return is_array($this->_mimes[$ext]) + ? in_array($this->file_type, $this->_mimes[$ext], TRUE) + : ($this->_mimes[$ext] === $this->file_type); } return FALSE; @@ -643,14 +931,7 @@ class CI_Upload { */ public function is_allowed_filesize() { - if ($this->max_size != 0 AND $this->file_size > $this->max_size) - { - return FALSE; - } - else - { - return TRUE; - } + return ($this->max_size === 0 OR $this->max_size > $this->file_size); } // -------------------------------------------------------------------- @@ -671,17 +952,25 @@ class CI_Upload { { $D = @getimagesize($this->file_temp); - if ($this->max_width > 0 AND $D['0'] > $this->max_width) + if ($this->max_width > 0 && $D[0] > $this->max_width) { return FALSE; } - if ($this->max_height > 0 AND $D['1'] > $this->max_height) + if ($this->max_height > 0 && $D[1] > $this->max_height) { return FALSE; } - return TRUE; + if ($this->min_width > 0 && $D[0] < $this->min_width) + { + return FALSE; + } + + if ($this->min_height > 0 && $D[1] < $this->min_height) + { + return FALSE; + } } return TRUE; @@ -694,35 +983,34 @@ class CI_Upload { * * Verifies that it is a valid upload path with proper permissions. * - * * @return bool */ public function validate_upload_path() { - if ($this->upload_path == '') + if ($this->upload_path === '') { - $this->set_error('upload_no_filepath'); + $this->set_error('upload_no_filepath', 'error'); return FALSE; } - if (function_exists('realpath') AND @realpath($this->upload_path) !== FALSE) + if (realpath($this->upload_path) !== FALSE) { - $this->upload_path = str_replace("\\", "/", realpath($this->upload_path)); + $this->upload_path = str_replace('\\', '/', realpath($this->upload_path)); } - if ( ! @is_dir($this->upload_path)) + if ( ! is_dir($this->upload_path)) { - $this->set_error('upload_no_filepath'); + $this->set_error('upload_no_filepath', 'error'); return FALSE; } if ( ! is_really_writable($this->upload_path)) { - $this->set_error('upload_not_writable'); + $this->set_error('upload_not_writable', 'error'); return FALSE; } - $this->upload_path = preg_replace("/(.+?)\/*$/", "\\1/", $this->upload_path); + $this->upload_path = preg_replace('/(.+?)\/*$/', '\\1/', $this->upload_path); return TRUE; } @@ -731,57 +1019,20 @@ class CI_Upload { /** * Extract the file extension * - * @param string + * @param string $filename * @return string */ public function get_extension($filename) { $x = explode('.', $filename); - return '.'.end($x); - } - // -------------------------------------------------------------------- + if (count($x) === 1) + { + return ''; + } - /** - * Clean the file name for security - * - * @param string - * @return string - */ - public function clean_file_name($filename) - { - $bad = array( - "<!--", - "-->", - "'", - "<", - ">", - '"', - '&', - '$', - '=', - ';', - '?', - '/', - "%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; } // -------------------------------------------------------------------- @@ -789,7 +1040,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) @@ -819,7 +1071,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() { @@ -830,23 +1082,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; - - // 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, '.', ''); + $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; + } + } - 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. @@ -864,14 +1127,8 @@ class CI_Upload { // <a, <body, <head, <html, <img, <plaintext, <pre, <script, <table, <title // title is basically just in SVG, but we filter it anyhow - if ( ! preg_match('/<(a|body|head|html|img|plaintext|pre|script|table|title)[\s>]/i', $opening_bytes)) - { - return TRUE; // its an image, no "triggers" detected in the first 256 bytes, we're good - } - else - { - return FALSE; - } + // 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) @@ -879,8 +1136,7 @@ class CI_Upload { return FALSE; } - $CI =& get_instance(); - return $CI->security->xss_clean($data, TRUE); + return $this->_CI->security->xss_clean($data, TRUE); } // -------------------------------------------------------------------- @@ -888,29 +1144,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; } // -------------------------------------------------------------------- @@ -918,56 +1167,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 = '<p>', $close = '</p>') { - $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 (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/mimes.php')) - { - include(APPPATH.'config/'.ENVIRONMENT.'/mimes.php'); - } - elseif (is_file(APPPATH.'config/mimes.php')) - { - include(APPPATH.'config//mimes.php'); - } - else - { - return FALSE; - } - - $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 : ''; } // -------------------------------------------------------------------- @@ -975,38 +1181,24 @@ 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. + * + * @link https://httpd.apache.org/docs/1.3/mod/mod_mime.html#multipleext * - * @param string + * @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; } // -------------------------------------------------------------------- @@ -1017,7 +1209,7 @@ class CI_Upload { * Detects the (actual) MIME type of the uploaded file, if possible. * The input array is expected to be $_FILES[$field] * - * @param array + * @param array $file * @return void */ protected function _file_mime_type($file) @@ -1025,15 +1217,18 @@ class CI_Upload { // We'll need this to validate the MIME info string (e.g. text/plain; charset=us-ascii) $regexp = '/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s.+)?$/'; - /* Fileinfo extension - most reliable method + /** + * Fileinfo extension - most reliable method * - * Unfortunately, prior to PHP 5.3 - it's only available as a PECL extension and the - * more convenient FILEINFO_MIME_TYPE flag doesn't exist. + * 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 + $finfo = @finfo_open(FILEINFO_MIME); + if ($finfo !== FALSE) // 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); @@ -1059,16 +1254,16 @@ class CI_Upload { * 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_exists() checks + * due to security concerns, hence the function_usable() checks */ if (DIRECTORY_SEPARATOR !== '\\') { - $cmd = 'file --brief --mime ' . escapeshellarg($file['tmp_name']) . ' 2>&1'; + $cmd = 'file --brief --mime '.escapeshellarg($file['tmp_name']).' 2>&1'; - if (function_exists('exec')) + 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 neeed the last line, which is the actual return value of exec(), and as such - it overwrites + * 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. */ @@ -1080,7 +1275,7 @@ class CI_Upload { } } - if ( (bool) @ini_get('safe_mode') === FALSE && function_exists('shell_exec')) + if (function_usable('shell_exec')) { $mime = @shell_exec($cmd); if (strlen($mime) > 0) @@ -1094,7 +1289,7 @@ class CI_Upload { } } - if (function_exists('popen')) + if (function_usable('popen')) { $proc = @popen($cmd, 'r'); if (is_resource($proc)) @@ -1114,7 +1309,7 @@ class CI_Upload { } } - // Fall back to the deprecated mime_content_type(), if available (still better than $_FILES[$field]['type']) + // 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']); @@ -1127,10 +1322,4 @@ class CI_Upload { $this->file_type = $file['type']; } - // -------------------------------------------------------------------- - } -// END Upload Class - -/* End of file Upload.php */ -/* Location: ./system/libraries/Upload.php */ diff --git a/system/libraries/User_agent.php b/system/libraries/User_agent.php index 9b0d87134..6dfabda2e 100644 --- a/system/libraries/User_agent.php +++ b/system/libraries/User_agent.php @@ -1,77 +1,188 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * User Agent Class * - * Identifies the platform, browser, robot, or mobile devise of the browsing agent + * Identifies the platform, browser, robot, or mobile device of the browsing agent * * @package CodeIgniter * @subpackage Libraries * @category User Agent - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/user_agent.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/user_agent.html */ class CI_User_agent { - var $agent = NULL; + /** + * Current user-agent + * + * @var string + */ + public $agent = NULL; + + /** + * Flag for if the user-agent belongs to a browser + * + * @var bool + */ + public $is_browser = FALSE; + + /** + * Flag for if the user-agent is a robot + * + * @var bool + */ + public $is_robot = FALSE; + + /** + * Flag for if the user-agent is a mobile browser + * + * @var bool + */ + public $is_mobile = FALSE; + + /** + * Languages accepted by the current user agent + * + * @var array + */ + public $languages = array(); + + /** + * Character sets accepted by the current user agent + * + * @var array + */ + public $charsets = array(); - var $is_browser = FALSE; - var $is_robot = FALSE; - var $is_mobile = FALSE; + /** + * List of platforms to compare against current user agent + * + * @var array + */ + public $platforms = array(); - var $languages = array(); - var $charsets = array(); + /** + * List of browsers to compare against current user agent + * + * @var array + */ + public $browsers = array(); - var $platforms = array(); - var $browsers = array(); - var $mobiles = array(); - var $robots = array(); + /** + * List of mobile browsers to compare against current user agent + * + * @var array + */ + public $mobiles = array(); - var $platform = ''; - var $browser = ''; - var $version = ''; - var $mobile = ''; - var $robot = ''; + /** + * List of robots to compare against current user agent + * + * @var array + */ + public $robots = array(); + + /** + * Current user-agent platform + * + * @var string + */ + public $platform = ''; + + /** + * Current user-agent browser + * + * @var string + */ + public $browser = ''; + + /** + * Current user-agent version + * + * @var string + */ + public $version = ''; + + /** + * Current user-agent mobile name + * + * @var string + */ + public $mobile = ''; + + /** + * Current user-agent robot name + * + * @var string + */ + public $robot = ''; + + /** + * HTTP Referer + * + * @var mixed + */ + public $referer; + + // -------------------------------------------------------------------- /** * Constructor * * Sets the User Agent and runs the compilation routine * - * @access public * @return void */ public function __construct() { + $this->_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,20 +190,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 (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/user_agents.php')) + if (($found = file_exists(APPPATH.'config/user_agents.php'))) { - include(APPPATH.'config/'.ENVIRONMENT.'/user_agents.php'); + include(APPPATH.'config/user_agents.php'); } - elseif (is_file(APPPATH.'config/user_agents.php')) + + if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/user_agents.php')) { - include(APPPATH.'config/user_agents.php'); + include(APPPATH.'config/'.ENVIRONMENT.'/user_agents.php'); + $found = TRUE; } - else + + if ($found !== TRUE) { return FALSE; } @@ -135,10 +248,9 @@ class CI_User_agent { /** * Compile the User Agent Data * - * @access private * @return bool */ - private function _compile_data() + protected function _compile_data() { $this->_set_platform(); @@ -156,23 +268,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; } // -------------------------------------------------------------------- @@ -180,16 +293,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]; @@ -199,6 +311,7 @@ class CI_User_agent { } } } + return FALSE; } @@ -207,23 +320,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; } @@ -232,16 +346,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; @@ -249,6 +362,7 @@ class CI_User_agent { } } } + return FALSE; } @@ -257,19 +371,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'); } @@ -280,19 +391,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'); } @@ -303,7 +411,7 @@ class CI_User_agent { /** * Is Browser * - * @access public + * @param string $key * @return bool */ public function is_browser($key = NULL) @@ -320,7 +428,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]); } // -------------------------------------------------------------------- @@ -328,7 +436,7 @@ class CI_User_agent { /** * Is Robot * - * @access public + * @param string $key * @return bool */ public function is_robot($key = NULL) @@ -345,7 +453,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]); } // -------------------------------------------------------------------- @@ -353,7 +461,7 @@ class CI_User_agent { /** * Is Mobile * - * @access public + * @param string $key * @return bool */ public function is_mobile($key = NULL) @@ -370,7 +478,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]); } // -------------------------------------------------------------------- @@ -378,16 +486,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((string) config_item('base_url'), PHP_URL_HOST); + + $this->referer = ($referer_host && $referer_host !== $own_host); + } } - return TRUE; + + return $this->referer; } // -------------------------------------------------------------------- @@ -395,7 +513,6 @@ class CI_User_agent { /** * Agent String * - * @access public * @return string */ public function agent_string() @@ -408,7 +525,6 @@ class CI_User_agent { /** * Get Platform * - * @access public * @return string */ public function platform() @@ -421,7 +537,6 @@ class CI_User_agent { /** * Get Browser Name * - * @access public * @return string */ public function browser() @@ -434,7 +549,6 @@ class CI_User_agent { /** * Get the Browser Version * - * @access public * @return string */ public function version() @@ -447,7 +561,6 @@ class CI_User_agent { /** * Get The Robot Name * - * @access public * @return string */ public function robot() @@ -459,7 +572,6 @@ class CI_User_agent { /** * Get the Mobile Device * - * @access public * @return string */ public function mobile() @@ -472,12 +584,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']); } // -------------------------------------------------------------------- @@ -485,12 +596,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(); } @@ -503,12 +613,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(); } @@ -521,12 +630,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); } // -------------------------------------------------------------------- @@ -534,16 +643,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 f0f53cefe..8587015e8 100644 --- a/system/libraries/Xmlrpc.php +++ b/system/libraries/Xmlrpc.php @@ -1,24 +1,48 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ +defined('BASEPATH') OR exit('No direct script access allowed'); if ( ! function_exists('xml_parser_create')) { show_error('Your PHP installation does not support XML'); } - // ------------------------------------------------------------------------ /** @@ -27,51 +51,219 @@ if ( ! function_exists('xml_parser_create')) * @package CodeIgniter * @subpackage Libraries * @category XML-RPC - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/xmlrpc.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/xmlrpc.html */ class CI_Xmlrpc { - var $debug = FALSE; // Debugging on or off - var $xmlrpcI4 = 'i4'; - var $xmlrpcInt = 'int'; - var $xmlrpcBoolean = 'boolean'; - var $xmlrpcDouble = 'double'; - var $xmlrpcString = 'string'; - var $xmlrpcDateTime = 'dateTime.iso8601'; - var $xmlrpcBase64 = 'base64'; - var $xmlrpcArray = 'array'; - var $xmlrpcStruct = 'struct'; - - var $xmlrpcTypes = array(); - var $valid_parents = array(); - var $xmlrpcerr = array(); // Response numbers - var $xmlrpcstr = array(); // Response strings - - var $xmlrpc_defencoding = 'UTF-8'; - var $xmlrpcName = 'XML-RPC for CodeIgniter'; - var $xmlrpcVersion = '1.1'; - var $xmlrpcerruser = 800; // Start of user errors - var $xmlrpcerrxml = 100; // Start of XML Parse errors - var $xmlrpc_backslash = ''; // formulate backslashes for escaping regexp - - var $client; - var $method; - var $data; - var $message = ''; - var $error = ''; // Error string for request - var $result; - var $response = array(); // Response from remote server - - var $xss_clean = TRUE; - - //------------------------------------- - // VALUES THAT MULTIPLE CLASSES NEED - //------------------------------------- - + /** + * Debug flag + * + * @var bool + */ + public $debug = FALSE; + + /** + * I4 data type + * + * @var string + */ + public $xmlrpcI4 = 'i4'; + + /** + * Integer data type + * + * @var string + */ + public $xmlrpcInt = 'int'; + + /** + * Boolean data type + * + * @var string + */ + public $xmlrpcBoolean = 'boolean'; + + /** + * Double data type + * + * @var string + */ + public $xmlrpcDouble = 'double'; + + /** + * String data type + * + * @var string + */ + public $xmlrpcString = 'string'; + + /** + * DateTime format + * + * @var string + */ + public $xmlrpcDateTime = 'dateTime.iso8601'; + + /** + * Base64 data type + * + * @var string + */ + public $xmlrpcBase64 = 'base64'; + + /** + * Array data type + * + * @var string + */ + public $xmlrpcArray = 'array'; + + /** + * Struct data type + * + * @var string + */ + public $xmlrpcStruct = 'struct'; + + /** + * Data types list + * + * @var array + */ + public $xmlrpcTypes = array(); + + /** + * Valid parents list + * + * @var array + */ + public $valid_parents = array(); + + /** + * Response error numbers list + * + * @var array + */ + public $xmlrpcerr = array(); + + /** + * Response error messages list + * + * @var string[] + */ + public $xmlrpcstr = array(); + + /** + * Encoding charset + * + * @var string + */ + public $xmlrpc_defencoding = 'UTF-8'; + + /** + * XML-RPC client name + * + * @var string + */ + public $xmlrpcName = 'XML-RPC for CodeIgniter'; + + /** + * XML-RPC version + * + * @var string + */ + public $xmlrpcVersion = '1.1'; + + /** + * Start of user errors + * + * @var int + */ + public $xmlrpcerruser = 800; + + /** + * Start of XML parse errors + * + * @var int + */ + public $xmlrpcerrxml = 100; + + /** + * Backslash replacement value + * + * @var string + */ + public $xmlrpc_backslash = ''; + + /** + * XML-RPC Client object + * + * @var object + */ + public $client; + + /** + * XML-RPC Method name + * + * @var string + */ + public $method; + + /** + * XML-RPC Data + * + * @var array + */ + public $data; + + /** + * XML-RPC Message + * + * @var string + */ + public $message = ''; + + /** + * Request error message + * + * @var string + */ + public $error = ''; + + /** + * XML-RPC result object + * + * @var object + */ + public $result; + + /** + * XML-RPC Response + * + * @var array + */ + public $response = array(); // Response from remote server + + /** + * XSS Filter flag + * + * @var bool + */ + public $xss_clean = TRUE; + + // -------------------------------------------------------------------- + + /** + * Constructor + * + * Initializes property default values + * + * @param array $config + * @return void + */ public function __construct($config = array()) { - $this->xmlrpcName = $this->xmlrpcName; $this->xmlrpc_backslash = chr(92).chr(92); // Types for info sent back and forth @@ -85,54 +277,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 +339,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']; + } + + $path = isset($parts['path']) ? $parts['path'] : '/'; - if (isset($parts['query']) && $parts['query'] != '') + 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 +427,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'); - } + $temp = new XML_RPC_Values($value[0], (is_array($value[0]) ? 'array' : 'string')); } - elseif (is_array($value['0']) && ($value['1'] == 'struct' OR $value['1'] == 'array')) + else { - while (list($k) = each($value['0'])) + if (is_array($value[0]) && ($value[1] === 'struct' OR $value[1] === 'array')) { - $value['0'][$k] = $this->values_parsing($value['0'][$k], TRUE); + 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 - { - $temp = new XML_RPC_Values($value['0'], $value['1']); + $temp = new XML_RPC_Values($value[0], $value[1]); } } else @@ -264,132 +477,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/userguide3/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; - - public function __construct($path, $server, $port=80) + /** + * Path + * + * @var string + */ + public $path = ''; + + /** + * 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 +728,121 @@ 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 + 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 https://secure.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 ( ! fputs($fp, $op, strlen($op))) + 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/userguide3/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, ENT_XML1 | 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,172 +851,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 = "<methodResponse>\n"; - if ($this->errno) - { - $result .= '<fault> + return "<methodResponse>\n" + .($this->errno + ? '<fault> <value> <struct> <member> <name>faultCode</name> - <value><int>' . $this->errno . '</int></value> + <value><int>'.$this->errno.'</int></value> </member> <member> <name>faultString</name> - <value><string>' . $this->errstr . '</string></value> + <value><string>'.$this->errstr.'</string></value> </member> </struct> </value> -</fault>'; - } - else - { - $result .= "<params>\n<param>\n" . - $this->val->serialize_class() . - "</param>\n</params>"; - } - $result .= "\n</methodResponse>"; - return $result; +</fault>' + : "<params>\n<param>\n".$this->val->serialize_class()."</param>\n</params>") + ."\n</methodResponse>"; } - function decode($array=FALSE) + // -------------------------------------------------------------------- + + /** + * Decode + * + * @param mixed $array + * @return array + */ + public function decode($array = NULL) { $CI =& get_instance(); - - if ($array !== FALSE && is_array($array)) + + if (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/userguide3/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; $i<count($pars); $i++) + for ($i = 0, $c = count($pars); $i < $c; $i++) { // $pars[$i] = XML_RPC_Values $this->params[] = $pars[$i]; @@ -629,17 +1086,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 = "<?xml version=\"1.0\"?".">\r\n<methodCall>\r\n"; - $this->payload .= '<methodName>' . $this->method_name . "</methodName>\r\n"; - $this->payload .= "<params>\r\n"; + $this->payload = '<?xml version="1.0"?'.">\r\n<methodCall>\r\n" + .'<methodName>'.$this->method_name."</methodName>\r\n" + ."<params>\r\n"; - for ($i=0; $i<count($this->params); $i++) + for ($i = 0, $c = count($this->params); $i < $c; $i++) { // $p = XML_RPC_Values $p = $this->params[$i]; @@ -649,11 +1109,15 @@ class XML_RPC_Message extends CI_Xmlrpc $this->payload .= "</params>\r\n</methodCall>\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 = ''; @@ -662,65 +1126,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 "<pre>"; - echo "---DATA---\n" . htmlspecialchars($data) . "\n---END DATA---\n\n"; - echo "</pre>"; + echo "<pre>---DATA---\n".htmlspecialchars($data)."\n---END DATA---\n\n</pre>"; } - //------------------------------------- - // 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))) { @@ -728,87 +1175,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 - //------------------------------------- - - if ( ! xml_parse($parser, $data, count($data))) + // Parse XML data + if ( ! xml_parse($parser, $data, TRUE)) { $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 "<pre>"; + echo '<pre>'; - 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---</pre>"; } - //------------------------------------- - // 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; @@ -821,10 +1248,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 // ------------------------------------ @@ -839,63 +1268,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': @@ -903,70 +1332,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 <member> to nothing to prevent errors later if no <name> 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': @@ -976,63 +1411,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-.<space> 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-<space> 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 <VALUE></VALUE> - 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; @@ -1041,7 +1459,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; @@ -1051,57 +1469,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'] = ''; } @@ -1110,83 +1535,104 @@ class XML_RPC_Message extends CI_Xmlrpc } } + // -------------------------------------------------------------------- + + /** + * Add parameter + * + * @param mixed + * @return void + */ + public function addParam($par) + { + $this->params[] = $par; + } - 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 ($array !== FALSE && is_array($array)) + + if ( ! empty($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); } @@ -1195,33 +1641,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/userguide3/libraries/xmlrpc.html */ class XML_RPC_Values extends CI_Xmlrpc { - var $me = array(); - var $mytype = 0; - - public function __construct($val=-1, $type='') + /** + * Value data + * + * @var array + */ + public $me = array(); + + /** + * 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) { @@ -1234,11 +1698,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 '<strong>XML_RPC_Values</strong>: scalar can have only one value<br />'; return 0; @@ -1246,23 +1719,16 @@ class XML_RPC_Values extends CI_Xmlrpc if ($typeof != 1) { - echo '<strong>XML_RPC_Values</strong>: not a scalar type (${typeof})<br />'; + echo "<strong>XML_RPC_Values</strong>: not a scalar type ($typeof)<br />"; 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']; @@ -1275,14 +1741,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 '<strong>XML_RPC_Values</strong>: already initialized as a [' . $this->kindOf() . ']<br />'; + echo '<strong>XML_RPC_Values</strong>: already initialized as a ['.$this->kindOf().']<br />'; return 0; } @@ -1291,11 +1766,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 '<strong>XML_RPC_Values</strong>: already initialized as a [' . $this->kindOf() . ']<br />'; + echo '<strong>XML_RPC_Values</strong>: already initialized as a ['.$this->kindOf().']<br />'; return 0; } $this->mytype = $this->xmlrpcTypes['struct']; @@ -1303,121 +1786,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 .= "<struct>\n"; reset($val); - while (list($key2, $val2) = each($val)) + foreach ($val as $key2 => &$val2) { - $rs .= "<member>\n<name>{$key2}</name>\n"; - $rs .= $this->serializeval($val2); - $rs .= "</member>\n"; + $rs .= "<member>\n<name>{$key2}</name>\n".$this->serializeval($val2)."</member>\n"; } $rs .= '</struct>'; - break; + break; case 2: // array $rs .= "<array>\n<data>\n"; - for($i=0; $i < count($val); $i++) + for ($i = 0, $c = count($val); $i < $c; $i++) { $rs .= $this->serializeval($val[$i]); } - $rs.="</data>\n</array>\n"; + $rs .= "</data>\n</array>\n"; break; case 1: // others switch ($typ) { case $this->xmlrpcBase64: - $rs .= "<{$typ}>" . base64_encode((string)$val) . "</{$typ}>\n"; - break; + $rs .= '<'.$typ.'>'.base64_encode( (string) $val).'</'.$typ.">\n"; + break; case $this->xmlrpcBoolean: - $rs .= "<{$typ}>" . ((bool)$val ? '1' : '0') . "</{$typ}>\n"; - break; + $rs .= '<'.$typ.'>'.( (bool) $val ? '1' : '0').'</'.$typ.">\n"; + break; case $this->xmlrpcString: - $rs .= "<{$typ}>" . htmlspecialchars((string)$val). "</{$typ}>\n"; - break; + $rs .= '<'.$typ.'>'.htmlspecialchars( (string) $val).'</'.$typ.">\n"; + break; default: - $rs .= "<{$typ}>{$val}</{$typ}>\n"; - break; + $rs .= '<'.$typ.'>'.$val.'</'.$typ.">\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 = "<value>\n".$this->serializedata($typ, $val)."</value>\n"; - return $rs; - } + // -------------------------------------------------------------------- - function scalarval() + /** + * Serialize value + * + * @param object + * @return string + */ + public function serializeval($o) { - reset($this->me); - list($a,$b) = each($this->me); - return $b; + $array = $o->me; + list($value, $type) = array(reset($array), key($array)); + return "<value>\n".$this->serializedata($type, $value)."</value>\n"; } + // -------------------------------------------------------------------- - //------------------------------------- - // Encode time in ISO-8601 form. - //------------------------------------- - - // Useful for sending time in XML-RPC - - function iso8601_encode($time, $utc=0) + /** + * Scalar value + * + * @return mixed + */ + public function scalarval() { - if ($utc == 1) - { - $t = strftime("%Y%m%dT%H:%i:%s", $time); - } - else - { - if (function_exists('gmstrftime')) - $t = gmstrftime("%Y%m%dT%H:%i:%s", $time); - else - $t = strftime("%Y%m%dT%H:%i:%s", $time - date('Z')); - } - return $t; + return reset($this->me); } -} -// END XML_RPC_Values Class + // -------------------------------------------------------------------- + + /** + * 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) + { + return ($utc) ? date('Ymd\TH:i:s', $time) : gmdate('Ymd\TH:i:s', $time); + } -/* 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 d9d53c8a1..eb5a24c49 100644 --- a/system/libraries/Xmlrpcs.php +++ b/system/libraries/Xmlrpcs.php @@ -1,24 +1,49 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ +defined('BASEPATH') OR exit('No direct script access allowed'); if ( ! function_exists('xml_parser_create')) { show_error('Your PHP installation does not support XML'); } -if ( ! class_exists('CI_Xmlrpc')) +if ( ! class_exists('CI_Xmlrpc', FALSE)) { show_error('You must load the Xmlrpc class before loading the Xmlrpcs class in order to create a server.'); } @@ -31,22 +56,46 @@ if ( ! class_exists('CI_Xmlrpc')) * @package CodeIgniter * @subpackage Libraries * @category XML-RPC - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/xmlrpc.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/xmlrpc.html */ -class CI_Xmlrpcs extends CI_Xmlrpc -{ - var $methods = array(); //array of methods mapped to function names and signatures - var $debug_msg = ''; // Debug Message - var $system_methods = array(); // XML RPC Server methods - var $controller_obj; +class CI_Xmlrpcs extends CI_Xmlrpc { + + /** + * Array of methods mapped to function names and signatures + * + * @var array + */ + public $methods = array(); + + /** + * Debug Message + * + * @var string + */ + public $debug_msg = ''; + + /** + * XML RPC Server methods + * + * @var array + */ + public $system_methods = array(); - var $object = FALSE; + /** + * Configuration object + * + * @var object + */ + public $object = FALSE; /** - * Constructor + * Initialize XMLRPC class + * + * @param array $config + * @return void */ - public function __construct($config=array()) + public function __construct($config = array()) { parent::__construct(); $this->set_system_methods(); @@ -56,7 +105,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 +113,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 +144,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 +173,15 @@ class CI_Xmlrpcs extends CI_Xmlrpc /** * Main Server Function * - * @access public * @return void */ - function serve() + public function serve() { $r = $this->parseRequest(); - $payload = '<?xml version="1.0" encoding="'.$this->xmlrpc_defencoding.'"?'.'>'."\n"; - $payload .= $this->debug_msg; - $payload .= $r->prepare_response(); + $payload = '<?xml version="1.0" encoding="'.$this->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 +190,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 +210,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 +233,39 @@ class CI_Xmlrpcs extends CI_Xmlrpc //------------------------------------- $parser = xml_parser_create($this->xmlrpc_defencoding); - $parser_object = new XML_RPC_Message("filler"); - - $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 = new XML_RPC_Message('filler'); + $pname = (string) $parser; + + $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 +273,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 "<pre>"; - echo "---PLIST---\n" . $plist . "\n---PLIST END---\n\n"; - echo "</pre>"; + echo "<pre>---PLIST---\n".$plist."\n---PLIST END---\n\n</pre>"; } $r = $this->_execute($m); } //------------------------------------- - // SET DEBUGGING MESSAGE + // SET DEBUGGING MESSAGE //------------------------------------- if ($this->debug === TRUE) @@ -268,24 +311,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 +336,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 && ( ! method_exists($method_parts[0], $method_parts[1]) OR ! (new ReflectionMethod($method_parts[0], $method_parts[1]))->isPublic())) + 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; $i<count($sig); $i++) + for ($i = 0, $c = count($sig); $i < $c; $i++) { $current_sig = $sig[$i]; - if (count($current_sig) == count($m->params)+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 +382,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,45 +390,35 @@ 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); } - else + elseif ($this->object === FALSE) { - 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 get_instance()->{$method_parts[1]}($m); } + + return $this->object->{$method_parts[1]}($m); } - else - { - return call_user_func($this->methods[$methName]['function'], $m); - } + + 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 +430,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 +457,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; $j<count($inSig); $j++) + for ($j = 0, $jc = count($inSig); $j < $jc; $j++) { $cursig[]= new XML_RPC_Values($inSig[$j], 'string'); } - $sigs[]= new XML_RPC_Values($cursig, 'array'); + $sigs[] = new XML_RPC_Values($cursig, 'array'); } - $r = new XML_RPC_Response(new XML_RPC_Values($sigs, 'array')); - } - else - { - $r = new XML_RPC_Response(new XML_RPC_Values('undef', 'string')); + + return new XML_RPC_Response(new XML_RPC_Values($sigs, 'array')); } + + return new XML_RPC_Response(new XML_RPC_Values('undef', 'string')); } - else - { - $r = new XML_RPC_Response(0,$this->xmlrpcerr['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]; @@ -475,22 +496,19 @@ class CI_Xmlrpcs extends CI_Xmlrpc return new XML_RPC_Response(new XML_RPC_Values($docstring, 'string')); } - else - { - return new XML_RPC_Response(0, $this->xmlrpcerr['introspect_unknown'], $this->xmlrpcstr['introspect_unknown']); - } + + 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 +520,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,16 +544,15 @@ 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(); - $code = is_string($err) ? $this->xmlrpcerr["multicall_${err}"] : $err->faultCode(); + $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'); $struct['faultString'] = new XML_RPC_Values($str, 'string'); @@ -548,15 +563,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 +579,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 +594,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 +618,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 ffff3f340..6b9b1816b 100644 --- a/system/libraries/Zip.php +++ b/system/libraries/Zip.php @@ -1,25 +1,48 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); +<?php /** * CodeIgniter * - * An open source application development framework for PHP 5.1.6 or newer + * An open source application development framework for PHP * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. - * @license http://codeigniter.com/user_guide/license.html - * @link http://codeigniter.com - * @since Version 1.0 + * This content is released under the MIT License (MIT) + * + * Copyright (c) 2019 - 2022, CodeIgniter Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @package CodeIgniter + * @author EllisLab Dev Team + * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) + * @copyright Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/) + * @copyright Copyright (c) 2019 - 2022, CodeIgniter Foundation (https://codeigniter.com/) + * @license https://opensource.org/licenses/MIT MIT License + * @link https://codeigniter.com + * @since Version 1.0.0 * @filesource */ - -// ------------------------------------------------------------------------ +defined('BASEPATH') OR exit('No direct script access allowed'); /** * Zip Compression Class * * This class is based on a library I found at Zend: - * http://www.zend.com/codex.php?id=696&single=1 + * https://www.zend.com/codex.php?id=696&single=1 * * The original library is a little rough around the edges so I * refactored it and added several additional methods -- Rick Ellis @@ -27,26 +50,80 @@ * @package CodeIgniter * @subpackage Libraries * @category Encryption - * @author ExpressionEngine Dev Team - * @link http://codeigniter.com/user_guide/libraries/zip.html + * @author EllisLab Dev Team + * @link https://codeigniter.com/userguide3/libraries/zip.html */ -class CI_Zip { +class CI_Zip { + + /** + * Zip data in string form + * + * @var string + */ + public $zipdata = ''; + + /** + * Zip data for a directory in string form + * + * @var string + */ + public $directory = ''; + + /** + * Number of files/folder in zip file + * + * @var int + */ + public $entries = 0; - var $zipdata = ''; - var $directory = ''; - var $entries = 0; - var $file_num = 0; - var $offset = 0; - var $now; + /** + * Number of files in zip + * + * @var int + */ + public $file_num = 0; /** - * Constructor + * relative offset of local header + * + * @var int + */ + public $offset = 0; + + /** + * Reference to time at init + * + * @var int + */ + public $now; + + /** + * The level of compression + * + * Ranges from 0 to 9, with 9 being the highest level. + * + * @var int + */ + public $compression_level = 2; + + /** + * mbstring.func_overload flag + * + * @var bool + */ + protected static $func_overload; + + /** + * Initialize zip compression class + * + * @return void */ public function __construct() { - log_message('debug', "Zip Compression Class Initialized"); + isset(self::$func_overload) OR self::$func_overload = ( ! is_php('8.0') && extension_loaded('mbstring') && @ini_get('mbstring.func_overload')); $this->now = time(); + log_message('info', 'Zip Compression Class Initialized'); } // -------------------------------------------------------------------- @@ -56,21 +133,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 +153,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 +176,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 +192,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 +207,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 +216,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 +226,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 +255,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 +277,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 +289,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 +298,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 +308,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)) - { - return FALSE; - } - - if (FALSE !== ($data = file_get_contents($path))) + if (file_exists($path) && FALSE !== ($data = file_get_contents($path))) { - $name = str_replace("\\", "/", $path); - - if ($preserve_filepath === FALSE) + if (is_string($archive_filepath)) + { + $name = str_replace('\\', '/', $archive_filepath); + } + else { - $name = preg_replace("|.*/(.+)|", "\\1", $name); + $name = str_replace('\\', '/', $path); + + if ($archive_filepath === FALSE) + { + $name = preg_replace('|.*/(.+)|', '\\1', $name); + } } $this->add_data($name, $data); return TRUE; } + return FALSE; } @@ -267,15 +343,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 +362,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 === '.' OR $file === '..') { 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 +397,24 @@ 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; + // @see https://github.com/bcit-ci/CodeIgniter/issues/5864 + $footer = $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 + return $this->zipdata.$footer; } // -------------------------------------------------------------------- @@ -351,23 +424,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 +455,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 +477,55 @@ 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; } -} + // -------------------------------------------------------------------- + + /** + * Byte-safe strlen() + * + * @param string $str + * @return int + */ + protected static function strlen($str) + { + return (self::$func_overload) + ? mb_strlen($str, '8bit') + : strlen($str); + } -/* End of file Zip.php */ -/* Location: ./system/libraries/Zip.php */
\ No newline at end of file + // -------------------------------------------------------------------- + + /** + * 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) + { + 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 c942a79ce..bcb7cae34 100644 --- a/system/libraries/index.html +++ b/system/libraries/index.html @@ -1,4 +1,5 @@ -<html> +<!DOCTYPE html> +<html lang="en"> <head> <title>403 Forbidden</title> </head> @@ -7,4 +8,4 @@ <p>Directory access is forbidden.</p> </body> -</html>
\ No newline at end of file +</html> diff --git a/system/libraries/javascript/Jquery.php b/system/libraries/javascript/Jquery.php deleted file mode 100644 index 48d8b3e57..000000000 --- a/system/libraries/javascript/Jquery.php +++ /dev/null @@ -1,1071 +0,0 @@ -<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); - -/** - * CodeIgniter - * - * An open source application development framework for PHP 5.1.6 or newer - * - * @package CodeIgniter - * @author ExpressionEngine Dev Team - * @copyright Copyright (c) 2008 - 2011, EllisLab, Inc. - * @license http://www.codeigniter.com/user_guide/license.html - * @link http://www.codeigniter.com - * @since Version 1.0 - * @filesource - */ - -/** - * Jquery Class - * - * @package CodeIgniter - * @subpackage Libraries - * @author ExpressionEngine Dev Team - * @category Loader - * @link http://www.codeigniter.com/user_guide/libraries/javascript.html - */ - -class CI_Jquery extends CI_Javascript { - - var $_javascript_folder = 'js'; - var $jquery_code_for_load = array(); - var $jquery_code_for_compile = array(); - var $jquery_corner_active = FALSE; - var $jquery_table_sorter_active = FALSE; - var $jquery_table_sorter_pager_active = FALSE; - var $jquery_ajax_img = ''; - - public function __construct($params) - { - $this->CI =& get_instance(); - extract($params); - - if ($autoload === TRUE) - { - $this->script(); - } - - log_message('debug', "Jquery Class Initialized"); - } - - // -------------------------------------------------------------------- - // Event Code - // -------------------------------------------------------------------- - - /** - * Blur - * - * Outputs a jQuery blur event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _blur($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'blur'); - } - - // -------------------------------------------------------------------- - - /** - * Change - * - * Outputs a jQuery change event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _change($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'change'); - } - - // -------------------------------------------------------------------- - - /** - * Click - * - * Outputs a jQuery click event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @param boolean whether or not to return false - * @return string - */ - function _click($element = 'this', $js = '', $ret_false = TRUE) - { - if ( ! is_array($js)) - { - $js = array($js); - } - - if ($ret_false) - { - $js[] = "return false;"; - } - - return $this->_add_event($element, $js, 'click'); - } - - // -------------------------------------------------------------------- - - /** - * Double Click - * - * Outputs a jQuery dblclick event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _dblclick($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'dblclick'); - } - - // -------------------------------------------------------------------- - - /** - * Error - * - * Outputs a jQuery error event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _error($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'error'); - } - - // -------------------------------------------------------------------- - - /** - * Focus - * - * Outputs a jQuery focus event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _focus($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'focus'); - } - - // -------------------------------------------------------------------- - - /** - * Hover - * - * Outputs a jQuery hover event - * - * @access private - * @param string - element - * @param string - Javascript code for mouse over - * @param string - Javascript code for mouse out - * @return string - */ - function _hover($element = 'this', $over, $out) - { - $event = "\n\t$(" . $this->_prep_element($element) . ").hover(\n\t\tfunction()\n\t\t{\n\t\t\t{$over}\n\t\t}, \n\t\tfunction()\n\t\t{\n\t\t\t{$out}\n\t\t});\n"; - - $this->jquery_code_for_compile[] = $event; - - return $event; - } - - // -------------------------------------------------------------------- - - /** - * Keydown - * - * Outputs a jQuery keydown event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _keydown($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'keydown'); - } - - // -------------------------------------------------------------------- - - /** - * Keyup - * - * Outputs a jQuery keydown event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _keyup($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'keyup'); - } - - // -------------------------------------------------------------------- - - /** - * Load - * - * Outputs a jQuery load event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _load($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'load'); - } - - // -------------------------------------------------------------------- - - /** - * Mousedown - * - * Outputs a jQuery mousedown event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _mousedown($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'mousedown'); - } - - // -------------------------------------------------------------------- - - /** - * Mouse Out - * - * Outputs a jQuery mouseout event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _mouseout($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'mouseout'); - } - - // -------------------------------------------------------------------- - - /** - * Mouse Over - * - * Outputs a jQuery mouseover event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _mouseover($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'mouseover'); - } - - // -------------------------------------------------------------------- - - /** - * Mouseup - * - * Outputs a jQuery mouseup event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _mouseup($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'mouseup'); - } - - // -------------------------------------------------------------------- - - /** - * Output - * - * Outputs script directly - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _output($array_js = '') - { - if ( ! is_array($array_js)) - { - $array_js = array($array_js); - } - - foreach ($array_js as $js) - { - $this->jquery_code_for_compile[] = "\t$js\n"; - } - } - - // -------------------------------------------------------------------- - - /** - * Resize - * - * Outputs a jQuery resize event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _resize($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'resize'); - } - - // -------------------------------------------------------------------- - - /** - * Scroll - * - * Outputs a jQuery scroll event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _scroll($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'scroll'); - } - - // -------------------------------------------------------------------- - - /** - * Unload - * - * Outputs a jQuery unload event - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @return string - */ - function _unload($element = 'this', $js = '') - { - return $this->_add_event($element, $js, 'unload'); - } - - // -------------------------------------------------------------------- - // Effects - // -------------------------------------------------------------------- - - /** - * Add Class - * - * Outputs a jQuery addClass event - * - * @access private - * @param string - element - * @return string - */ - function _addClass($element = 'this', $class='') - { - $element = $this->_prep_element($element); - $str = "$({$element}).addClass(\"$class\");"; - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Animate - * - * Outputs a jQuery animate event - * - * @access private - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function _animate($element = 'this', $params = array(), $speed = '', $extra = '') - { - $element = $this->_prep_element($element); - $speed = $this->_validate_speed($speed); - - $animations = "\t\t\t"; - - foreach ($params as $param=>$value) - { - $animations .= $param.': \''.$value.'\', '; - } - - $animations = substr($animations, 0, -2); // remove the last ", " - - if ($speed != '') - { - $speed = ', '.$speed; - } - - if ($extra != '') - { - $extra = ', '.$extra; - } - - $str = "$({$element}).animate({\n$animations\n\t\t}".$speed.$extra.");"; - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Fade In - * - * Outputs a jQuery hide event - * - * @access private - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function _fadeIn($element = 'this', $speed = '', $callback = '') - { - $element = $this->_prep_element($element); - $speed = $this->_validate_speed($speed); - - if ($callback != '') - { - $callback = ", function(){\n{$callback}\n}"; - } - - $str = "$({$element}).fadeIn({$speed}{$callback});"; - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Fade Out - * - * Outputs a jQuery hide event - * - * @access private - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function _fadeOut($element = 'this', $speed = '', $callback = '') - { - $element = $this->_prep_element($element); - $speed = $this->_validate_speed($speed); - - if ($callback != '') - { - $callback = ", function(){\n{$callback}\n}"; - } - - $str = "$({$element}).fadeOut({$speed}{$callback});"; - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Hide - * - * Outputs a jQuery hide action - * - * @access private - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function _hide($element = 'this', $speed = '', $callback = '') - { - $element = $this->_prep_element($element); - $speed = $this->_validate_speed($speed); - - if ($callback != '') - { - $callback = ", function(){\n{$callback}\n}"; - } - - $str = "$({$element}).hide({$speed}{$callback});"; - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Remove Class - * - * Outputs a jQuery remove class event - * - * @access private - * @param string - element - * @return string - */ - function _removeClass($element = 'this', $class='') - { - $element = $this->_prep_element($element); - $str = "$({$element}).removeClass(\"$class\");"; - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Slide Up - * - * Outputs a jQuery slideUp event - * - * @access private - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function _slideUp($element = 'this', $speed = '', $callback = '') - { - $element = $this->_prep_element($element); - $speed = $this->_validate_speed($speed); - - if ($callback != '') - { - $callback = ", function(){\n{$callback}\n}"; - } - - $str = "$({$element}).slideUp({$speed}{$callback});"; - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Slide Down - * - * Outputs a jQuery slideDown event - * - * @access private - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function _slideDown($element = 'this', $speed = '', $callback = '') - { - $element = $this->_prep_element($element); - $speed = $this->_validate_speed($speed); - - if ($callback != '') - { - $callback = ", function(){\n{$callback}\n}"; - } - - $str = "$({$element}).slideDown({$speed}{$callback});"; - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Slide Toggle - * - * Outputs a jQuery slideToggle event - * - * @access public - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function _slideToggle($element = 'this', $speed = '', $callback = '') - { - $element = $this->_prep_element($element); - $speed = $this->_validate_speed($speed); - - if ($callback != '') - { - $callback = ", function(){\n{$callback}\n}"; - } - - $str = "$({$element}).slideToggle({$speed}{$callback});"; - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Toggle - * - * Outputs a jQuery toggle event - * - * @access private - * @param string - element - * @return string - */ - function _toggle($element = 'this') - { - $element = $this->_prep_element($element); - $str = "$({$element}).toggle();"; - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Toggle Class - * - * Outputs a jQuery toggle class event - * - * @access private - * @param string - element - * @return string - */ - function _toggleClass($element = 'this', $class='') - { - $element = $this->_prep_element($element); - $str = "$({$element}).toggleClass(\"$class\");"; - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Show - * - * Outputs a jQuery show event - * - * @access private - * @param string - element - * @param string - One of 'slow', 'normal', 'fast', or time in milliseconds - * @param string - Javascript callback function - * @return string - */ - function _show($element = 'this', $speed = '', $callback = '') - { - $element = $this->_prep_element($element); - $speed = $this->_validate_speed($speed); - - if ($callback != '') - { - $callback = ", function(){\n{$callback}\n}"; - } - - $str = "$({$element}).show({$speed}{$callback});"; - - return $str; - } - - // -------------------------------------------------------------------- - - /** - * Updater - * - * An Ajax call that populates the designated DOM node with - * returned content - * - * @access private - * @param string The element to attach the event to - * @param string the controller to run the call against - * @param string optional parameters - * @return string - */ - - function _updater($container = 'this', $controller, $options = '') - { - $container = $this->_prep_element($container); - - $controller = (strpos('://', $controller) === FALSE) ? $controller : $this->CI->config->site_url($controller); - - // ajaxStart and ajaxStop are better choices here... but this is a stop gap - if ($this->CI->config->item('javascript_ajax_img') == '') - { - $loading_notifier = "Loading..."; - } - else - { - $loading_notifier = '<img src=\'' . $this->CI->config->slash_item('base_url') . $this->CI->config->item('javascript_ajax_img') . '\' alt=\'Loading\' />'; - } - - $updater = "$($container).empty();\n"; // anything that was in... get it out - $updater .= "\t\t$($container).prepend(\"$loading_notifier\");\n"; // to replace with an image - - $request_options = ''; - if ($options != '') - { - $request_options .= ", {"; - $request_options .= (is_array($options)) ? "'".implode("', '", $options)."'" : "'".str_replace(":", "':'", $options)."'"; - $request_options .= "}"; - } - - $updater .= "\t\t$($container).load('$controller'$request_options);"; - return $updater; - } - - - // -------------------------------------------------------------------- - // Pre-written handy stuff - // -------------------------------------------------------------------- - - /** - * Zebra tables - * - * @access private - * @param string table name - * @param string plugin location - * @return string - */ - function _zebraTables($class = '', $odd = 'odd', $hover = '') - { - $class = ($class != '') ? '.'.$class : ''; - - $zebra = "\t\$(\"table{$class} tbody tr:nth-child(even)\").addClass(\"{$odd}\");"; - - $this->jquery_code_for_compile[] = $zebra; - - if ($hover != '') - { - $hover = $this->hover("table{$class} tbody tr", "$(this).addClass('hover');", "$(this).removeClass('hover');"); - } - - return $zebra; - } - - - - // -------------------------------------------------------------------- - // Plugins - // -------------------------------------------------------------------- - - /** - * Corner Plugin - * - * http://www.malsup.com/jquery/corner/ - * - * @access public - * @param string target - * @return string - */ - function corner($element = '', $corner_style = '') - { - // may want to make this configurable down the road - $corner_location = '/plugins/jquery.corner.js'; - - if ($corner_style != '') - { - $corner_style = '"'.$corner_style.'"'; - } - - return "$(" . $this->_prep_element($element) . ").corner(".$corner_style.");"; - } - - // -------------------------------------------------------------------- - - /** - * modal window - * - * Load a thickbox modal window - * - * @access public - * @return void - */ - function modal($src, $relative = FALSE) - { - $this->jquery_code_for_load[] = $this->external($src, $relative); - } - - // -------------------------------------------------------------------- - - /** - * Effect - * - * Load an Effect library - * - * @access public - * @return void - */ - function effect($src, $relative = FALSE) - { - $this->jquery_code_for_load[] = $this->external($src, $relative); - } - - // -------------------------------------------------------------------- - - /** - * Plugin - * - * Load a plugin library - * - * @access public - * @return void - */ - function plugin($src, $relative = FALSE) - { - $this->jquery_code_for_load[] = $this->external($src, $relative); - } - - // -------------------------------------------------------------------- - - /** - * UI - * - * Load a user interface library - * - * @access public - * @return void - */ - function ui($src, $relative = FALSE) - { - $this->jquery_code_for_load[] = $this->external($src, $relative); - } - // -------------------------------------------------------------------- - - /** - * Sortable - * - * Creates a jQuery sortable - * - * @access public - * @return void - */ - function sortable($element, $options = array()) - { - - if (count($options) > 0) - { - $sort_options = array(); - foreach ($options as $k=>$v) - { - $sort_options[] = "\n\t\t".$k.': '.$v.""; - } - $sort_options = implode(",", $sort_options); - } - else - { - $sort_options = ''; - } - - return "$(" . $this->_prep_element($element) . ").sortable({".$sort_options."\n\t});"; - } - - // -------------------------------------------------------------------- - - /** - * Table Sorter Plugin - * - * @access public - * @param string table name - * @param string plugin location - * @return string - */ - function tablesorter($table = '', $options = '') - { - $this->jquery_code_for_compile[] = "\t$(" . $this->_prep_element($table) . ").tablesorter($options);\n"; - } - - // -------------------------------------------------------------------- - // Class functions - // -------------------------------------------------------------------- - - /** - * Add Event - * - * Constructs the syntax for an event, and adds to into the array for compilation - * - * @access private - * @param string The element to attach the event to - * @param string The code to execute - * @param string The event to pass - * @return string - */ - function _add_event($element, $js, $event) - { - if (is_array($js)) - { - $js = implode("\n\t\t", $js); - - } - - $event = "\n\t$(" . $this->_prep_element($element) . ").{$event}(function(){\n\t\t{$js}\n\t});\n"; - $this->jquery_code_for_compile[] = $event; - return $event; - } - - // -------------------------------------------------------------------- - - /** - * Compile - * - * As events are specified, they are stored in an array - * This funciton compiles them all for output on a page - * - * @access private - * @return string - */ - function _compile($view_var = 'script_foot', $script_tags = TRUE) - { - // External references - $external_scripts = implode('', $this->jquery_code_for_load); - $this->CI->load->vars(array('library_src' => $external_scripts)); - - if (count($this->jquery_code_for_compile) == 0 ) - { - // no inline references, let's just return - return; - } - - // Inline references - $script = '$(document).ready(function() {' . "\n"; - $script .= implode('', $this->jquery_code_for_compile); - $script .= '});'; - - $output = ($script_tags === FALSE) ? $script : $this->inline($script); - - $this->CI->load->vars(array($view_var => $output)); - - } - - // -------------------------------------------------------------------- - - /** - * Clear Compile - * - * Clears the array of script events collected for output - * - * @access public - * @return void - */ - function _clear_compile() - { - $this->jquery_code_for_compile = array(); - } - - // -------------------------------------------------------------------- - - /** - * Document Ready - * - * A wrapper for writing document.ready() - * - * @access private - * @return string - */ - function _document_ready($js) - { - if ( ! is_array($js)) - { - $js = array ($js); - - } - - foreach ($js as $script) - { - $this->jquery_code_for_compile[] = $script; - } - } - - // -------------------------------------------------------------------- - - /** - * Script Tag - * - * Outputs the script tag that loads the jquery.js file into an HTML document - * - * @access public - * @param string - * @return string - */ - function script($library_src = '', $relative = FALSE) - { - $library_src = $this->external($library_src, $relative); - $this->jquery_code_for_load[] = $library_src; - return $library_src; - } - - // -------------------------------------------------------------------- - - /** - * Prep Element - * - * Puts HTML element in quotes for use in jQuery code - * unless the supplied element is the Javascript 'this' - * object, in which case no quotes are added - * - * @access public - * @param string - * @return string - */ - function _prep_element($element) - { - if ($element != 'this') - { - $element = '"'.$element.'"'; - } - - return $element; - } - - // -------------------------------------------------------------------- - - /** - * Validate Speed - * - * Ensures the speed parameter is valid for jQuery - * - * @access private - * @param string - * @return string - */ - function _validate_speed($speed) - { - if (in_array($speed, array('slow', 'normal', 'fast'))) - { - $speed = '"'.$speed.'"'; - } - elseif (preg_match("/[^0-9]/", $speed)) - { - $speed = ''; - } - - return $speed; - } - -} - -/* End of file Jquery.php */ -/* Location: ./system/libraries/Jquery.php */
\ No newline at end of file |