+
+
diff --git a/system/Debug/Toolbar/toolbarloader.js.php b/system/Debug/Toolbar/toolbarloader.js.php
index d77e96e82a..7aafb90ad6 100644
--- a/system/Debug/Toolbar/toolbarloader.js.php
+++ b/system/Debug/Toolbar/toolbarloader.js.php
@@ -1,24 +1,60 @@
+
+document.addEventListener('DOMContentLoaded', loadDoc, false);
-document.addEventListener('DOMContentLoaded', loadDoc, false );
+function loadDoc(time) {
+ if (isNaN(time)) {
+ time = document.getElementById("debugbar_loader").getAttribute("data-time");
+ localStorage.setItem('debugbar-time', time);
+ }
-function loadDoc() {
- var time = document.getElementById("debugbar_loader").getAttribute("data-time");
- var url = "";
+ localStorage.setItem('debugbar-time-new', time);
- var xhttp = new XMLHttpRequest();
- xhttp.onreadystatechange = function () {
- if (this.readyState == 4 && this.status == 200) {
- var toolbar = document.createElement( 'div' );
- toolbar.setAttribute( 'id', 'toolbarContainer' );
- toolbar.innerHTML = this.responseText;
- document.body.appendChild( toolbar );
- eval(document.getElementById("toolbar_js").innerHTML);
- if (typeof ciDebugBar === 'object') {
- ciDebugBar.init();
- }
- }
- };
+ var url = "= rtrim(site_url(), '/') ?>";
- xhttp.open("GET", url + "?debugbar_time=" + time, true);
- xhttp.send();
+ var xhttp = new XMLHttpRequest();
+ xhttp.onreadystatechange = function() {
+ if (this.readyState === 4 && this.status === 200) {
+ var toolbar = document.getElementById("toolbarContainer");
+ if (!toolbar) {
+ toolbar = document.createElement('div');
+ toolbar.setAttribute('id', 'toolbarContainer');
+ toolbar.innerHTML = this.responseText;
+ document.body.appendChild(toolbar);
+ } else {
+ toolbar.innerHTML = this.responseText;
+ }
+ eval(document.getElementById("toolbar_js").innerHTML);
+ if (typeof ciDebugBar === 'object') {
+ ciDebugBar.init();
+ }
+ } else if (this.readyState === 4 && this.status === 404) {
+ console.log('CodeIgniter DebugBar: File "WRITEPATH/debugbar/debugbar_' + time + '" not found.');
+ }
+ };
+
+ xhttp.open("GET", url + "?debugbar_time=" + time, true);
+ xhttp.send();
}
+
+// Track all AJAX requests
+var oldXHR = window.XMLHttpRequest;
+
+function newXHR() {
+ var realXHR = new oldXHR();
+ realXHR.addEventListener("readystatechange", function() {
+ // Only success responses and URLs that do not contains "debugbar_time" are tracked
+ if (realXHR.readyState === 4 && realXHR.status.toString()[0] === '2' && realXHR.responseURL.indexOf('debugbar_time') === -1) {
+ var debugbarTime = realXHR.getResponseHeader('Debugbar-Time');
+ if (debugbarTime) {
+ var h2 = document.querySelector('#ci-history > h2');
+ h2.innerHTML = 'History You have new debug data. ';
+ var badge = document.querySelector('a[data-tab="ci-history"] > span > .badge');
+ badge.className += ' active';
+ }
+ }
+ }, false);
+ return realXHR;
+}
+
+window.XMLHttpRequest = newXHR;
+
diff --git a/system/Email/Email.php b/system/Email/Email.php
index 04f04462f1..4a8750ee84 100644
--- a/system/Email/Email.php
+++ b/system/Email/Email.php
@@ -7,7 +7,7 @@
*
* This content is released under the MIT License (MIT)
*
- * Copyright (c) 2014 - 2017, British Columbia Institute of Technology
+ * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -27,16 +27,17 @@
* 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 - 2017, British Columbia Institute of Technology (http://bcit.ca/)
- * @license http://opensource.org/licenses/MIT MIT License
- * @link https://codeigniter.com
- * @since Version 1.0.0
+ * @package CodeIgniter
+ * @author EllisLab Dev Team
+ * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
+ * @copyright Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
+ * @license http://opensource.org/licenses/MIT MIT License
+ * @link https://codeigniter.com
+ * @since Version 1.0.0
* @filesource
*/
+use CodeIgniter\Config\BaseConfig;
use Config\Mimes;
@@ -45,132 +46,141 @@ use Config\Mimes;
*
* Permits email to be sent using Mail, Sendmail, or SMTP.
*
- * @package CodeIgniter
- * @subpackage Libraries
- * @category Libraries
- * @author EllisLab Dev Team
- * @link https://codeigniter.com/user_guide/libraries/email.html
+ * @package CodeIgniter
+ * @subpackage Libraries
+ * @category Libraries
+ * @author EllisLab Dev Team
+ * @link https://codeigniter.com/user_guide/libraries/email.html
*/
class Email
{
+ /**
+ * @var string
+ */
+ public $fromEmail;
+
+ /**
+ * @var string
+ */
+ public $fromName;
/**
* Used as the User-Agent and X-Mailer headers' value.
*
- * @var string
+ * @var string
*/
public $userAgent = 'CodeIgniter';
/**
* Path to the Sendmail binary.
*
- * @var string
+ * @var string
*/
public $mailPath = '/usr/sbin/sendmail'; // Sendmail path
/**
* Which method to use for sending e-mails.
*
- * @var string 'mail', 'sendmail' or 'smtp'
+ * @var string 'mail', 'sendmail' or 'smtp'
*/
public $protocol = 'mail'; // mail/sendmail/smtp
/**
* STMP Server host
*
- * @var string
+ * @var string
*/
public $SMTPHost = '';
/**
* SMTP Username
*
- * @var string
+ * @var string
*/
public $SMTPUser = '';
/**
* SMTP Password
*
- * @var string
+ * @var string
*/
public $SMTPPass = '';
/**
* SMTP Server port
*
- * @var int
+ * @var int
*/
public $SMTPPort = 25;
/**
* SMTP connection timeout in seconds
*
- * @var int
+ * @var int
*/
public $SMTPTimeout = 5;
/**
* SMTP persistent connection
*
- * @var bool
+ * @var bool
*/
public $SMTPKeepAlive = false;
/**
* SMTP Encryption
*
- * @var string empty, 'tls' or 'ssl'
+ * @var string Empty, 'tls' or 'ssl'
*/
public $SMTPCrypto = '';
/**
* Whether to apply word-wrapping to the message body.
*
- * @var bool
+ * @var bool
*/
public $wordWrap = true;
/**
* Number of characters to wrap at.
*
- * @see CI_Email::$wordwrap
- * @var int
+ * @see Email::$wordWrap
+ * @var int
*/
public $wrapChars = 76;
/**
* Message format.
*
- * @var string 'text' or 'html'
+ * @var string 'text' or 'html'
*/
public $mailType = 'text';
/**
* Character set (default: utf-8)
*
- * @var string
+ * @var string
*/
public $charset = 'utf-8';
/**
* Alternative message (for HTML messages only)
*
- * @var string
+ * @var string
*/
public $altMessage = '';
/**
* Whether to validate e-mail addresses.
*
- * @var bool
+ * @var bool
*/
public $validate = true;
/**
* X-Priority header value.
*
- * @var int 1-5
+ * @var int 1-5
*/
public $priority = 3; // Default priority (1 - 5)
@@ -178,8 +188,8 @@ class Email
* Newline character sequence.
* Use "\r\n" to comply with RFC 822.
*
- * @link http://www.ietf.org/rfc/rfc822.txt
- * @var string "\r\n" or "\n"
+ * @link http://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)
@@ -192,15 +202,15 @@ class Email
* switching to "\n", while improper, is the only solution
* that seems to work for all environments.
*
- * @link http://www.ietf.org/rfc/rfc822.txt
- * @var string
+ * @link http://www.ietf.org/rfc/rfc822.txt
+ * @var string
*/
public $CRLF = "\n";
/**
* Whether to use Delivery Status Notification.
*
- * @var bool
+ * @var bool
*/
public $DSN = false;
@@ -208,22 +218,22 @@ class Email
* Whether to send multipart alternatives.
* Yahoo! doesn't seem to like these.
*
- * @var bool
+ * @var bool
*/
public $sendMultipart = true;
/**
* Whether to send messages to BCC recipients in batches.
*
- * @var bool
+ * @var bool
*/
public $BCCBatchMode = false;
/**
* BCC Batch max number size.
*
- * @see CI_Email::$bcc_batch_mode
- * @var int
+ * @see Email::$BCCBatchMode
+ * @var int
*/
public $BCCBatchSize = 200;
@@ -232,107 +242,107 @@ class Email
/**
* Subject header
*
- * @var string
+ * @var string
*/
protected $subject = '';
/**
* Message body
*
- * @var string
+ * @var string
*/
protected $body = '';
/**
* Final message body to be sent.
*
- * @var string
+ * @var string
*/
protected $finalBody = '';
/**
* Final headers to send
*
- * @var string
+ * @var string
*/
protected $headerStr = '';
/**
* SMTP Connection socket placeholder
*
- * @var resource
+ * @var resource
*/
protected $SMTPConnect = '';
/**
* Mail encoding
*
- * @var string '8bit' or '7bit'
+ * @var string '8bit' or '7bit'
*/
protected $encoding = '8bit';
/**
* Whether to perform SMTP authentication
*
- * @var bool
+ * @var bool
*/
protected $SMTPAuth = false;
/**
* Whether to send a Reply-To header
*
- * @var bool
+ * @var bool
*/
protected $replyToFlag = false;
/**
* Debug messages
*
- * @see CI_Email::print_debugger()
- * @var string
+ * @see Email::printDebugger()
+ * @var array
*/
protected $debugMessage = [];
/**
* Recipients
*
- * @var string[]
+ * @var array
*/
protected $recipients = [];
/**
* CC Recipients
*
- * @var string[]
+ * @var array
*/
protected $CCArray = [];
/**
* BCC Recipients
*
- * @var string[]
+ * @var array
*/
protected $BCCArray = [];
/**
* Message headers
*
- * @var string[]
+ * @var array
*/
protected $headers = [];
/**
* Attachment data
*
- * @var array
+ * @var array
*/
protected $attachments = [];
/**
* Valid $protocol values
*
- * @see CI_Email::$protocol
- * @var string[]
+ * @see Email::$protocol
+ * @var array
*/
protected $protocols = ['mail', 'sendmail', 'smtp'];
@@ -342,7 +352,7 @@ class Email
* Character sets valid for 7-bit encoding,
* excluding language suffix.
*
- * @var string[]
+ * @var array
*/
protected $baseCharsets = ['us-ascii', 'iso-2022-'];
@@ -351,8 +361,8 @@ class Email
*
* Valid mail encodings
*
- * @see CI_Email::$_encoding
- * @var string[]
+ * @see Email::$encoding
+ * @var array
*/
protected $bitDepths = ['7bit', '8bit'];
@@ -361,7 +371,7 @@ class Email
*
* Actual values to send with the X-Priority header
*
- * @var string[]
+ * @var array
*/
protected $priorities = [
1 => '1 (Highest)',
@@ -374,7 +384,7 @@ class Email
/**
* mbstring.func_overload flag
*
- * @var bool
+ * @var bool
*/
protected static $func_overload;
@@ -385,15 +395,13 @@ class Email
*
* The constructor can be passed an array of config values
*
- * @param array $config = array()
- *
- * @return void
+ * @param array|null $config
*/
public function __construct($config = null)
{
$this->initialize($config);
- isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload'));
+ isset(self::$func_overload) || self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload'));
log_message('info', 'Email Class Initialized');
}
@@ -403,27 +411,32 @@ class Email
/**
* Initialize preferences
*
- * @param array $config
+ * @param array|\Config\Email $config
*
- * @return $this
+ * @return Email
*/
public function initialize($config)
{
$this->clear();
+ if ($config instanceof \Config\Email)
+ {
+ $config = get_object_vars($config);
+ }
+
foreach (get_class_vars(get_class($this)) as $key => $value)
{
- if (isset($this->$key) && isset($config->$key))
+ if (property_exists($this, $key) && isset($config[$key]))
{
$method = 'set'.ucfirst($key);
if (method_exists($this, $method))
{
- $this->$method($config->$key);
+ $this->$method($config[$key]);
}
else
{
- $this->$key = $config->$key;
+ $this->$key = $config[$key];
}
}
}
@@ -439,9 +452,9 @@ class Email
/**
* Initialize the Email Data
*
- * @param bool
+ * @param bool $clearAttachments
*
- * @return $this
+ * @return Email
*/
public function clear($clearAttachments = false)
{
@@ -471,11 +484,11 @@ class Email
/**
* Set FROM
*
- * @param string $from
- * @param string $name
- * @param string $returnPath = NULL Return-Path
+ * @param string $from
+ * @param string $name
+ * @param string|null $returnPath Return-Path
*
- * @return $this
+ * @return Email
*/
public function setFrom($from, $name = '', $returnPath = null)
{
@@ -510,7 +523,7 @@ class Email
$this->setHeader('From', $name.' <'.$from.'>');
- isset($returnPath) OR $returnPath = $from;
+ isset($returnPath) || $returnPath = $from;
$this->setHeader('Return-Path', '<'.$returnPath.'>');
return $this;
@@ -521,10 +534,10 @@ class Email
/**
* Set Reply-to
*
- * @param string
- * @param string
+ * @param string $replyto
+ * @param string $name
*
- * @return $this
+ * @return Email
*/
public function setReplyTo($replyto, $name = '')
{
@@ -563,9 +576,9 @@ class Email
/**
* Set Recipients
*
- * @param string
+ * @param string $to
*
- * @return $this
+ * @return Email
*/
public function setTo($to)
{
@@ -592,9 +605,9 @@ class Email
/**
* Set CC
*
- * @param string
+ * @param string $cc
*
- * @return $this
+ * @return Email
*/
public function setCC($cc)
{
@@ -620,10 +633,10 @@ class Email
/**
* Set BCC
*
- * @param string
- * @param string
+ * @param string $bcc
+ * @param string $limit
*
- * @return $this
+ * @return Email
*/
public function setBCC($bcc, $limit = '')
{
@@ -640,7 +653,7 @@ class Email
$this->validateEmail($bcc);
}
- if ($this->getProtocol() === 'smtp' OR ($this->BCCBatchMode && count($bcc) > $this->BCCBatchSize))
+ if ($this->getProtocol() === 'smtp' || ($this->BCCBatchMode && count($bcc) > $this->BCCBatchSize))
{
$this->BCCArray = $bcc;
}
@@ -657,9 +670,9 @@ class Email
/**
* Set Email Subject
*
- * @param string
+ * @param string $subject
*
- * @return $this
+ * @return Email
*/
public function setSubject($subject)
{
@@ -674,9 +687,9 @@ class Email
/**
* Set Body
*
- * @param string
+ * @param string $body
*
- * @return $this
+ * @return Email
*/
public function setMessage($body)
{
@@ -690,12 +703,12 @@ class Email
/**
* 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 = ''
+ * @param string $file Can be local path, URL or buffered content
+ * @param string $disposition 'attachment'
+ * @param string|null $newname
+ * @param string $mime
*
- * @return $this
+ * @return Email
*/
public function attach($file, $disposition = '', $newname = null, $mime = '')
{
@@ -743,9 +756,9 @@ class Email
*
* Useful for attached inline pictures
*
- * @param string $filename
+ * @param string $filename
*
- * @return string
+ * @return string
*/
public function setAttachmentCID($filename)
{
@@ -768,10 +781,10 @@ class Email
/**
* Add a Header Item
*
- * @param string
- * @param string
+ * @param string $header
+ * @param string $value
*
- * @return $this
+ * @return Email
*/
public function setHeader($header, $value)
{
@@ -785,9 +798,9 @@ class Email
/**
* Convert a String to an Array
*
- * @param string
+ * @param string $email
*
- * @return array
+ * @return array
*/
protected function stringToArray($email)
{
@@ -806,9 +819,9 @@ class Email
/**
* Set Multipart Value
*
- * @param string
+ * @param string $str
*
- * @return $this
+ * @return Email
*/
public function setAltMessage($str)
{
@@ -822,9 +835,9 @@ class Email
/**
* Set Mailtype
*
- * @param string
+ * @param string $type
*
- * @return $this
+ * @return Email
*/
public function setMailType($type = 'text')
{
@@ -838,9 +851,9 @@ class Email
/**
* Set Wordwrap
*
- * @param bool
+ * @param bool $wordWrap
*
- * @return $this
+ * @return Email
*/
public function setWordWrap($wordWrap = true)
{
@@ -854,9 +867,9 @@ class Email
/**
* Set Protocol
*
- * @param string
+ * @param string $protocol
*
- * @return $this
+ * @return Email
*/
public function setProtocol($protocol = 'mail')
{
@@ -870,9 +883,9 @@ class Email
/**
* Set Priority
*
- * @param int
+ * @param int $n
*
- * @return $this
+ * @return Email
*/
public function setPriority($n = 3)
{
@@ -886,9 +899,9 @@ class Email
/**
* Set Newline Character
*
- * @param string
+ * @param string $newline
*
- * @return $this
+ * @return Email
*/
public function setNewline($newline = "\n")
{
@@ -902,9 +915,9 @@ class Email
/**
* Set CRLF
*
- * @param string
+ * @param string $CRLF
*
- * @return $this
+ * @return Email
*/
public function setCRLF($CRLF = "\n")
{
@@ -918,7 +931,7 @@ class Email
/**
* Get the Message ID
*
- * @return string
+ * @return string
*/
protected function getMessageID()
{
@@ -932,12 +945,12 @@ class Email
/**
* Get Mail Protocol
*
- * @return mixed
+ * @return string
*/
protected function getProtocol()
{
$this->protocol = strtolower($this->protocol);
- in_array($this->protocol, $this->protocols, true) OR $this->protocol = 'mail';
+ in_array($this->protocol, $this->protocols, true) || $this->protocol = 'mail';
return $this->protocol;
}
@@ -947,11 +960,11 @@ class Email
/**
* Get Mail Encoding
*
- * @return string
+ * @return string
*/
protected function getEncoding()
{
- in_array($this->encoding, $this->bitDepths) OR $this->encoding = '8bit';
+ in_array($this->encoding, $this->bitDepths) || $this->encoding = '8bit';
foreach ($this->baseCharsets as $charset)
{
@@ -969,7 +982,7 @@ class Email
/**
* Get content type (text/html/attachment)
*
- * @return string
+ * @return string
*/
protected function getContentType()
{
@@ -992,7 +1005,7 @@ class Email
/**
* Set RFC 822 Date
*
- * @return string
+ * @return string
*/
protected function setDate()
{
@@ -1009,7 +1022,7 @@ class Email
/**
* Mime message
*
- * @return string
+ * @return string
*/
protected function getMimeMessage()
{
@@ -1021,9 +1034,9 @@ class Email
/**
* Validate Email Address
*
- * @param string
+ * @param string $email
*
- * @return bool
+ * @return bool
*/
public function validateEmail($email)
{
@@ -1052,13 +1065,13 @@ class Email
/**
* Email Validation
*
- * @param string
+ * @param string $email
*
- * @return bool
+ * @return bool
*/
public function isValidEmail($email)
{
- if (function_exists('idn_to_ascii') && $atpos = strpos($email, '@'))
+ if (function_exists('idn_to_ascii') && defined('INTL_IDNA_VARIANT_UTS46') && $atpos = strpos($email, '@'))
{
$email = self::substr($email, 0, ++$atpos).idn_to_ascii(self::substr($email, $atpos), 0,
INTL_IDNA_VARIANT_UTS46);
@@ -1072,9 +1085,9 @@ class Email
/**
* Clean Extended Email Address: Joe Smith
*
- * @param string
+ * @param string $email
*
- * @return string
+ * @return string
*/
public function cleanEmail($email)
{
@@ -1103,7 +1116,7 @@ class Email
* If the user hasn't specified his own alternative message
* it creates one by stripping the HTML
*
- * @return string
+ * @return string
*/
protected function getAltMessage()
{
@@ -1135,10 +1148,10 @@ class Email
/**
* Word Wrap
*
- * @param string
- * @param int line-length limit
+ * @param string $str
+ * @param int|null $charlim Line-length limit
*
- * @return string
+ * @return string
*/
public function wordWrap($str, $charlim = null)
{
@@ -1226,8 +1239,6 @@ class Email
/**
* Build final headers
- *
- * @return void
*/
protected function buildHeaders()
{
@@ -1243,8 +1254,6 @@ class Email
/**
* Write Headers as a string
- *
- * @return void
*/
protected function writeHeaders()
{
@@ -1280,8 +1289,6 @@ class Email
/**
* Build Final Body and attachments
- *
- * @return void
*/
protected function buildMessage()
{
@@ -1470,11 +1477,11 @@ class Email
/**
* 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
+ * @param string &$body Message body to append to
+ * @param string $boundary Multipart boundary
+ * @param string|null $multipart When provided, only attachments of this type will be processed
*
- * @return string
+ * @return string
*/
protected function appendAttachments(&$body, $boundary, $multipart = null)
{
@@ -1501,7 +1508,7 @@ class Email
// $name won't be set if no attachments were appended,
// and therefore a boundary wouldn't be necessary
- empty($name) OR $body .= '--'.$boundary.'--';
+ empty($name) || $body .= '--'.$boundary.'--';
}
//--------------------------------------------------------------------
@@ -1512,9 +1519,9 @@ class Email
* Prepares string for Quoted-Printable Content-Transfer-Encoding
* Refer to RFC 2045 http://www.ietf.org/rfc/rfc2045.txt
*
- * @param string
+ * @param string $str
*
- * @return string
+ * @return string
*/
protected function prepQuotedPrintable($str)
{
@@ -1641,7 +1648,7 @@ class Email
$ascii = ord($char);
// Convert spaces and tabs but only if it's the end of the line
- if ($ascii === 32 OR $ascii === 9)
+ if ($ascii === 32 || $ascii === 9)
{
if ($i === ($length-1))
{
@@ -1690,9 +1697,9 @@ class Email
* It's related but not identical to quoted-printable, so it has its
* own method.
*
- * @param string
+ * @param string $str
*
- * @return string
+ * @return string
*/
protected function prepQEncoding($str)
{
@@ -1732,7 +1739,7 @@ class Email
}
// We might already have this set for UTF-8
- isset($chars) OR $chars = self::strlen($str);
+ isset($chars) || $chars = self::strlen($str);
$output = '=?'.$this->charset.'?Q?';
for ($i = 0, $length = self::strlen($output); $i < $chars; $i++)
@@ -1745,7 +1752,7 @@ class Email
// We'll append ?= to the end of each line though.
if ($length+($l = self::strlen($chr)) > 74)
{
- $output .= '?='.$this->crlf // EOL
+ $output .= '?='.$this->CRLF // EOL
.' =?'.$this->charset.'?Q?'.$chr; // New line
$length = 6+self::strlen($this->charset)+$l; // Reset the length for the new line
} else
@@ -1764,12 +1771,17 @@ class Email
/**
* Send Email
*
- * @param bool $autoClear = TRUE
+ * @param bool $autoClear
*
- * @return bool
+ * @return bool
*/
public function send($autoClear = true)
{
+ if (! isset($this->headers['From']) && ! empty($this->fromEmail))
+ {
+ $this->setFrom($this->fromEmail, $this->fromName);
+ }
+
if (! isset($this->headers['From']))
{
$this->setErrorMessage(lang('email.noFrom'));
@@ -1821,8 +1833,6 @@ class Email
/**
* Batch Bcc Send. Sends groups of BCCs in batches
- *
- * @return void
*/
public function batchBCCSend()
{
@@ -1874,8 +1884,6 @@ class Email
/**
* Unwrap special elements
- *
- * @return void
*/
protected function unwrapSpecials()
{
@@ -1888,13 +1896,13 @@ class Email
/**
* Strip line-breaks via callback
*
- * @param string $matches
+ * @param string $matches
*
- * @return string
+ * @return string
*/
protected function removeNLCallback($matches)
{
- if (strpos($matches[1], "\r") !== false OR strpos($matches[1], "\n") !== false)
+ if (strpos($matches[1], "\r") !== false || strpos($matches[1], "\n") !== false)
{
$matches[1] = str_replace(["\r\n", "\r", "\n"], '', $matches[1]);
}
@@ -1907,7 +1915,7 @@ class Email
/**
* Spool mail to the mail server
*
- * @return bool
+ * @return bool
*/
protected function spoolEmail()
{
@@ -1941,9 +1949,9 @@ class Email
*
* Credits for the base concept go to Paul Buonopane
*
- * @param string $email
+ * @param string &$email
*
- * @return bool
+ * @return bool
*/
protected function validateEmailForShell(&$email)
{
@@ -1962,7 +1970,7 @@ class Email
/**
* Send using mail()
*
- * @return bool
+ * @return bool
*/
protected function sendWithMail()
{
@@ -1990,7 +1998,7 @@ class Email
/**
* Send using Sendmail
*
- * @return bool
+ * @return bool
*/
protected function sendWithSendmail()
{
@@ -2007,7 +2015,7 @@ class Email
}
// is popen() enabled?
- if (! function_usable('popen') OR false === ($fp = @popen($this->mailPath.' -oi '.$from.' -t', 'w')))
+ if (! function_usable('popen') || 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;
@@ -2034,7 +2042,7 @@ class Email
/**
* Send using SMTP
*
- * @return bool
+ * @return bool
*/
protected function sendWithSmtp()
{
@@ -2045,7 +2053,7 @@ class Email
return false;
}
- if (! $this->SMTPConnect() OR ! $this->SMTPAuthenticate())
+ if (! $this->SMTPConnect() || ! $this->SMTPAuthenticate())
{
return false;
}
@@ -2119,8 +2127,6 @@ class Email
* SMTP End
*
* Shortcut to send RSET or QUIT depending on keep-alive
- *
- * @return void
*/
protected function SMTPEnd()
{
@@ -2132,7 +2138,7 @@ class Email
/**
* SMTP Connect
*
- * @return string
+ * @return string
*/
protected function SMTPConnect()
{
@@ -2184,17 +2190,17 @@ class Email
/**
* Send SMTP command
*
- * @param string
- * @param string
+ * @param string $cmd
+ * @param string $data
*
- * @return bool
+ * @return bool
*/
protected function sendCommand($cmd, $data = '')
{
switch ($cmd)
{
case 'hello':
- if ($this->SMTPAuth OR $this->getEncoding() === '8bit')
+ if ($this->SMTPAuth || $this->getEncoding() === '8bit')
{
$this->sendData('EHLO '.$this->getHostname());
}
@@ -2262,7 +2268,7 @@ class Email
/**
* SMTP Authenticate
*
- * @return bool
+ * @return bool
*/
protected function SMTPAuthenticate()
{
@@ -2325,9 +2331,9 @@ class Email
/**
* Send SMTP data
*
- * @param string $data
+ * @param string $data
*
- * @return bool
+ * @return bool
*/
protected function sendData($data)
{
@@ -2375,7 +2381,7 @@ class Email
/**
* Get SMTP data
*
- * @return string
+ * @return string
*/
protected function getSMTPData()
{
@@ -2403,9 +2409,10 @@ class Email
* 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 http://cbl.abuseat.org/namingproblems.html
- * @return string
+ * @link https://tools.ietf.org/html/rfc5321#section-2.3.5
+ * @link http://cbl.abuseat.org/namingproblems.html
+ *
+ * @return string
*/
protected function getHostname()
{
@@ -2422,10 +2429,10 @@ class Email
/**
* Get Debug Message
*
- * @param array $include List of raw data chunks to include in the output
- * Valid options are: 'headers', 'subject', 'body'
+ * @param array $include List of raw data chunks to include in the output
+ * Valid options are: 'headers', 'subject', 'body'
*
- * @return string
+ * @return string
*/
public function printDebugger($include = ['headers', 'subject', 'body'])
{
@@ -2433,7 +2440,7 @@ class Email
// Determine which parts of our raw data needs to be printed
$raw_data = '';
- is_array($include) OR $include = [$include];
+ is_array($include) || $include = [$include];
in_array('headers', $include, true) && $raw_data = htmlspecialchars($this->headerStr)."\n";
in_array('subject', $include, true) && $raw_data .= htmlspecialchars($this->subject)."\n";
@@ -2447,9 +2454,7 @@ class Email
/**
* Set Message
*
- * @param string $msg
- *
- * @return void
+ * @param string $msg
*/
protected function setErrorMessage($msg)
{
@@ -2461,9 +2466,9 @@ class Email
/**
* Mime Types
*
- * @param string
+ * @param string $ext
*
- * @return string
+ * @return string
*/
protected function mimeTypes($ext = '')
{
@@ -2478,8 +2483,6 @@ class Email
/**
* Destructor
- *
- * @return void
*/
public function __destruct()
{
@@ -2491,9 +2494,9 @@ class Email
/**
* Byte-safe strlen()
*
- * @param string $str
+ * @param string $str
*
- * @return int
+ * @return int
*/
protected static function strlen($str)
{
@@ -2507,11 +2510,11 @@ class Email
/**
* Byte-safe substr()
*
- * @param string $str
- * @param int $start
- * @param int $length
+ * @param string $str
+ * @param int $start
+ * @param int|null $length
*
- * @return string
+ * @return string
*/
protected static function substr($str, $start, $length = null)
{
diff --git a/system/Entity.php b/system/Entity.php
index 0d429df847..7090211593 100644
--- a/system/Entity.php
+++ b/system/Entity.php
@@ -144,7 +144,7 @@ class Entity
$result = $this->mutateDate($result);
}
// Or cast it as something?
- else if (array_key_exists($key, $this->_options['casts']))
+ else if (isset($this->_options['casts'][$key]) && ! empty($this->_options['casts'][$key]))
{
$result = $this->castAs($result, $this->_options['casts'][$key]);
}
@@ -267,7 +267,12 @@ class Entity
*/
protected function mapProperty(string $key)
{
- if (array_key_exists($key, $this->_options['datamap']))
+ if (empty($this->_options['datamap']))
+ {
+ return $key;
+ }
+
+ if (isset($this->_options['datamap'][$key]) && ! empty($this->_options['datamap'][$key]))
{
return $this->_options['datamap'][$key];
}
diff --git a/system/Events/Events.php b/system/Events/Events.php
index 798c66a767..108690efa9 100644
--- a/system/Events/Events.php
+++ b/system/Events/Events.php
@@ -65,7 +65,7 @@ class Events
*
* @var string
*/
- protected static $eventsFile;
+ protected static $eventsFile = '';
/**
* If true, events will not actually be fired.
@@ -290,7 +290,7 @@ class Events
*
* @param string $path
*/
- public function setFile(string $path)
+ public static function setFile(string $path)
{
self::$eventsFile = $path;
}
diff --git a/system/Exceptions/AlertError.php b/system/Exceptions/AlertError.php
new file mode 100644
index 0000000000..d229358bf9
--- /dev/null
+++ b/system/Exceptions/AlertError.php
@@ -0,0 +1,9 @@
+size / 1024, 3);
- break;
case 'mb':
return number_format(($this->size / 1024) / 1024, 3);
- break;
}
- return $this->size;
+ return (int) $this->size;
}
//--------------------------------------------------------------------
@@ -175,7 +173,7 @@ class File extends SplFileInfo
if ( ! @rename($this->getPath(), $destination))
{
$error = error_get_last();
- throw new \RuntimeException(sprintf('Could not move file %s to %s (%s)', $this->getBasename(), $targetPath, strip_tags($error['message'])));
+ throw FileException::forUnableToMove($this->getBasename(), $targetPath, strip_tags($error['message']));
}
@chmod($targetPath, 0777 & ~umask());
diff --git a/system/Filters/Exceptions/FilterException.php b/system/Filters/Exceptions/FilterException.php
new file mode 100644
index 0000000000..b2aba94bf6
--- /dev/null
+++ b/system/Filters/Exceptions/FilterException.php
@@ -0,0 +1,17 @@
+config->aliases))
{
- throw new \InvalidArgumentException("'{$alias}' filter must have a matching alias defined.");
+ throw FilterException::forNoAlias($alias);
}
$class = new $this->config->aliases[$alias]();
if ( ! $class instanceof FilterInterface)
{
- throw new \RuntimeException(get_class($class) . ' must implement CodeIgniter\Filters\FilterInterface.');
+ throw FilterException::forIncorrectInterface(get_class($class));
}
if ($position == 'before')
@@ -134,10 +136,11 @@ class Filters
// then send it and quit.
if ($result instanceof ResponseInterface)
{
- $result->send();
- exit(EXIT_ERROR);
+ // short circuit - bypass any other filters
+ return $result;
}
+ // Ignore an empty result
if (empty($result))
{
continue;
diff --git a/system/Format/Exceptions/FormatException.php b/system/Format/Exceptions/FormatException.php
new file mode 100644
index 0000000000..442be8f3e2
--- /dev/null
+++ b/system/Format/Exceptions/FormatException.php
@@ -0,0 +1,17 @@
+");
diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php
index 143ecaf532..8569d79d47 100644
--- a/system/HTTP/CURLRequest.php
+++ b/system/HTTP/CURLRequest.php
@@ -35,6 +35,7 @@
* @since Version 3.0.0
* @filesource
*/
+use CodeIgniter\HTTP\Exceptions\HTTPException;
use Config\App;
/**
@@ -113,7 +114,7 @@ class CURLRequest extends Request
{
if ( ! function_exists('curl_version'))
{
- throw new \RuntimeException('CURL must be enabled to use the CURLRequest class.');
+ throw HTTPException::forMissingCurl();
}
parent::__construct($config);
@@ -570,9 +571,9 @@ class CURLRequest extends Request
$cert = $cert[0];
}
- if ( ! file_exists($cert))
+ if (! file_exists($cert))
{
- throw new \InvalidArgumentException('SSL certificate not found at: ' . $cert);
+ throw HTTPException::forSSLCertNotFound($cert);
}
$curl_options[CURLOPT_SSLCERT] = $cert;
@@ -585,11 +586,11 @@ class CURLRequest extends Request
{
$file = realpath($config['ssl_key']);
- if ( ! $file)
+ if (! $file)
{
- throw new \InvalidArgumentException('Cannot set SSL Key. ' . $config['ssl_key'] .
- ' is not a valid file.');
+ throw HTTPException::forInvalidSSLKey($config['ssl_key']);
}
+
$curl_options[CURLOPT_CAINFO] = $file;
$curl_options[CURLOPT_SSL_VERIFYPEER] = 1;
}
@@ -737,7 +738,7 @@ class CURLRequest extends Request
if ($output === false)
{
- throw new \RuntimeException(curl_errno($ch) . ': ' . curl_error($ch));
+ throw HTTPException::forCurlError(curl_errno($ch), curl_error($ch));
}
curl_close($ch);
diff --git a/system/HTTP/ContentSecurityPolicy.php b/system/HTTP/ContentSecurityPolicy.php
index 61f74d9721..33cc6325e8 100644
--- a/system/HTTP/ContentSecurityPolicy.php
+++ b/system/HTTP/ContentSecurityPolicy.php
@@ -140,6 +140,12 @@ class ContentSecurityPolicy
* @var array
*/
protected $styleSrc = [];
+
+ /**
+ * Used for security enforcement
+ * @var array
+ */
+ protected $manifestSrc = [];
/**
* Used for security enforcement
@@ -432,6 +438,26 @@ class ContentSecurityPolicy
return $this;
}
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Adds a new valid endpoint for manifest sources. Can be either
+ * a URI class or simple string.
+ *
+ * @see https://www.w3.org/TR/CSP/#directive-manifest-src
+ *
+ * @param $uri
+ * @param bool $reportOnly
+ *
+ * @return $this
+ */
+ public function addManifestSrc($uri, bool $reportOnly = false)
+ {
+ $this->addOption($uri, 'manifestSrc', $reportOnly);
+
+ return $this;
+ }
//--------------------------------------------------------------------
@@ -688,6 +714,7 @@ class ContentSecurityPolicy
'plugin-types' => 'pluginTypes',
'script-src' => 'scriptSrc',
'style-src' => 'styleSrc',
+ 'manifest-src' => 'manifestSrc',
'sandbox' => 'sandbox',
'report-uri' => 'reportURI'
];
diff --git a/system/HTTP/Exceptions/HTTPException.php b/system/HTTP/Exceptions/HTTPException.php
new file mode 100644
index 0000000000..a8f2ae4642
--- /dev/null
+++ b/system/HTTP/Exceptions/HTTPException.php
@@ -0,0 +1,180 @@
+body = $body;
$this->config = $config;
+ $this->userAgent = $userAgent;
parent::__construct($config);
@@ -314,7 +322,7 @@ class IncomingRequest extends Request
*/
public function getVar($index = null, $filter = null, $flags = null)
{
- return $this->fetchGlobal(INPUT_REQUEST, $index, $filter, $flags);
+ return $this->fetchGlobal('request', $index, $filter, $flags);
}
//--------------------------------------------------------------------
@@ -367,7 +375,7 @@ class IncomingRequest extends Request
*/
public function getGet($index = null, $filter = null, $flags = null)
{
- return $this->fetchGlobal(INPUT_GET, $index, $filter, $flags);
+ return $this->fetchGlobal('get', $index, $filter, $flags);
}
//--------------------------------------------------------------------
@@ -383,7 +391,7 @@ class IncomingRequest extends Request
*/
public function getPost($index = null, $filter = null, $flags = null)
{
- return $this->fetchGlobal(INPUT_POST, $index, $filter, $flags);
+ return $this->fetchGlobal('post', $index, $filter, $flags);
}
//--------------------------------------------------------------------
@@ -437,7 +445,7 @@ class IncomingRequest extends Request
*/
public function getCookie($index = null, $filter = null, $flags = null)
{
- return $this->fetchGlobal(INPUT_COOKIE, $index, $filter, $flags);
+ return $this->fetchGlobal('cookie', $index, $filter, $flags);
}
//--------------------------------------------------------------------
@@ -449,9 +457,9 @@ class IncomingRequest extends Request
*
* @return mixed
*/
- public function getUserAgent($filter = null)
+ public function getUserAgent()
{
- return $this->fetchGlobal(INPUT_SERVER, 'HTTP_USER_AGENT', $filter);
+ return $this->userAgent;
}
//--------------------------------------------------------------------
@@ -459,7 +467,7 @@ class IncomingRequest extends Request
/**
* Attempts to get old Input data that has been flashed to the session
* with redirect_with_input(). It first checks for the data in the old
- * POST data, then the old GET data.
+ * POST data, then the old GET data and finally check for dot arrays
*
* @param string $key
*
@@ -470,7 +478,9 @@ class IncomingRequest extends Request
// If the session hasn't been started, or no
// data was previously saved, we're done.
if (empty($_SESSION['_ci_old_input']))
+ {
return;
+ }
// Check for the value in the POST array first.
if (isset($_SESSION['_ci_old_input']['post'][$key]))
@@ -483,6 +493,28 @@ class IncomingRequest extends Request
{
return $_SESSION['_ci_old_input']['get'][$key];
}
+
+ helper('array');
+
+ // Check for an array value in POST.
+ if (isset($_SESSION['_ci_old_input']['post']))
+ {
+ $value = dot_array_search($key, $_SESSION['_ci_old_input']['post']);
+ if ( ! is_null($value))
+ {
+ return $value;
+ }
+ }
+
+ // Check for an array value in GET.
+ if (isset($_SESSION['_ci_old_input']['get']))
+ {
+ $value = dot_array_search($key, $_SESSION['_ci_old_input']['get']);
+ if ( ! is_null($value))
+ {
+ return $value;
+ }
+ }
}
/**
@@ -556,17 +588,6 @@ class IncomingRequest extends Request
else
{
throw FrameworkException::forEmptyBaseURL();
-
-// $this->isSecure() ? $this->uri->setScheme('https') : $this->uri->setScheme('http');
-//
-// // While both SERVER_NAME and HTTP_HOST are open to security issues,
-// // if we have to choose, we will go with the server-controlled version first.
-// ! empty($_SERVER['SERVER_NAME']) ? (isset($_SERVER['SERVER_NAME']) ? $this->uri->setHost($_SERVER['SERVER_NAME']) : null) : (isset($_SERVER['HTTP_HOST']) ? $this->uri->setHost($_SERVER['HTTP_HOST']) : null);
-//
-// if ( ! empty($_SERVER['SERVER_PORT']))
-// {
-// $this->uri->setPort($_SERVER['SERVER_PORT']);
-// }
}
}
@@ -597,7 +618,7 @@ class IncomingRequest extends Request
break;
case 'PATH_INFO':
default:
- $path = $_SERVER[$protocol] ?? $this->parseRequestURI();
+ $path = $this->fetchGlobal('server', $protocol) ?? $this->parseRequestURI();
break;
}
@@ -639,7 +660,7 @@ class IncomingRequest extends Request
break;
}
- throw new \InvalidArgumentException($type . ' is not a valid negotiation type.');
+ throw HTTPException::forInvalidNegotiationType($type);
}
//--------------------------------------------------------------------
diff --git a/system/HTTP/Message.php b/system/HTTP/Message.php
index 38b4a9568f..1d9634e1dd 100644
--- a/system/HTTP/Message.php
+++ b/system/HTTP/Message.php
@@ -1,5 +1,7 @@
protocolVersion;
+ return $this->protocolVersion ?? '1.1';
}
//--------------------------------------------------------------------
@@ -381,7 +383,7 @@ class Message
if ( ! in_array($version, $this->validProtocolVersions))
{
- throw new \InvalidArgumentException('Invalid HTTP Protocol Version. Must be one of: ' . implode(', ', $this->validProtocolVersions));
+ throw HTTPException::forInvalidHTTPProtocol(implode(', ', $this->validProtocolVersions));
}
$this->protocolVersion = $version;
diff --git a/system/HTTP/Negotiate.php b/system/HTTP/Negotiate.php
index 390c0d5359..0f23152423 100644
--- a/system/HTTP/Negotiate.php
+++ b/system/HTTP/Negotiate.php
@@ -36,6 +36,8 @@
* @filesource
*/
+use CodeIgniter\HTTP\Exceptions\HTTPException;
+
/**
* Class Negotiate
*
@@ -199,7 +201,7 @@ class Negotiate
{
if (empty($supported))
{
- throw new \InvalidArgumentException('You must provide an array of supported values to all Negotiations.');
+ throw HTTPException::forEmptySupportedNegotiations();
}
if (empty($header))
diff --git a/system/HTTP/RedirectResponse.php b/system/HTTP/RedirectResponse.php
index 1f23fb0f08..86c03b10ab 100644
--- a/system/HTTP/RedirectResponse.php
+++ b/system/HTTP/RedirectResponse.php
@@ -35,6 +35,7 @@
* @since Version 3.0.0
* @filesource
*/
+use CodeIgniter\HTTP\Exceptions\HTTPException;
use Config\Services;
class RedirectResponse extends Response
@@ -81,7 +82,7 @@ class RedirectResponse extends Response
if (! $route)
{
- throw new \InvalidArgumentException(lang('HTTP.invalidRoute', [$route]));
+ throw HTTPException::forInvalidRedirectRoute($route);
}
return $this->redirect($route, $method, $code);
@@ -139,8 +140,8 @@ class RedirectResponse extends Response
/**
* Adds a key and message to the session as Flashdata.
*
- * @param string $key
- * @param string $message
+ * @param string $key
+ * @param string|array $message
*
* @return $this
*/
diff --git a/system/HTTP/Request.php b/system/HTTP/Request.php
index 2adc533edd..139a457efe 100644
--- a/system/HTTP/Request.php
+++ b/system/HTTP/Request.php
@@ -64,6 +64,13 @@ class Request extends Message implements RequestInterface
*/
protected $method;
+ /**
+ * Stores values we've retrieved from
+ * PHP globals.
+ * @var array
+ */
+ protected $globals = [];
+
//--------------------------------------------------------------------
/**
@@ -141,7 +148,7 @@ class Request extends Message implements RequestInterface
}
// We have a subnet ... now the heavy lifting begins
- isset($separator) OR $separator = $this->isValidIP($this->ipAddress, 'ipv6') ? ':' : '.';
+ isset($separator) || $separator = $this->isValidIP($this->ipAddress, 'ipv6') ? ':' : '.';
// If the proxy entry doesn't match the IP protocol - skip it
if (strpos($proxy_ips[$i], $separator) === FALSE)
@@ -281,7 +288,7 @@ class Request extends Message implements RequestInterface
*/
public function getServer($index = null, $filter = null, $flags = null)
{
- return $this->fetchGlobal(INPUT_SERVER, $index, $filter, $flags);
+ return $this->fetchGlobal('server', $index, $filter, $flags);
}
//--------------------------------------------------------------------
@@ -297,7 +304,24 @@ class Request extends Message implements RequestInterface
*/
public function getEnv($index = null, $filter = null, $flags = null)
{
- return $this->fetchGlobal(INPUT_ENV, $index, $filter, $flags);
+ return $this->fetchGlobal('env', $index, $filter, $flags);
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Allows manually setting the value of PHP global, like $_GET, $_POST, etc.
+ *
+ * @param string $method
+ * @param $value
+ *
+ * @return $this
+ */
+ public function setGlobal(string $method, $value)
+ {
+ $this->globals[$method] = $value;
+
+ return $this;
}
//--------------------------------------------------------------------
@@ -312,45 +336,37 @@ class Request extends Message implements RequestInterface
*
* http://php.net/manual/en/filter.filters.sanitize.php
*
- * @param int $type Input filter constant
+ * @param int $method Input filter constant
* @param string|array $index
* @param int $filter Filter constant
* @param null $flags
*
* @return mixed
*/
- protected function fetchGlobal($type, $index = null, $filter = null, $flags = null )
+ public function fetchGlobal($method, $index = null, $filter = null, $flags = null )
{
+ $method = strtolower($method);
+
+ if (! isset($this->globals[$method]))
+ {
+ $this->populateGlobals($method);
+ }
+
// Null filters cause null values to return.
if (is_null($filter))
{
$filter = FILTER_DEFAULT;
}
- $loopThrough = [];
- switch ($type)
- {
- case INPUT_GET : $loopThrough = $_GET;
- break;
- case INPUT_POST : $loopThrough = $_POST;
- break;
- case INPUT_COOKIE : $loopThrough = $_COOKIE;
- break;
- case INPUT_SERVER : $loopThrough = $_SERVER;
- break;
- case INPUT_ENV : $loopThrough = $_ENV;
- break;
- case INPUT_REQUEST : $loopThrough = $_REQUEST;
- break;
- }
-
- // If $index is null, it means that the whole input type array is requested
+ // Return all values when $index is null
if (is_null($index))
{
$values = [];
- foreach ($loopThrough as $key => $value)
+ foreach ($this->globals[$method] as $key => $value)
{
- $values[$key] = is_array($value) ? $this->fetchGlobal($type, $key, $filter, $flags) : filter_var($value, $filter, $flags);
+ $values[$key] = is_array($value)
+ ? $this->fetchGlobal($method, $key, $filter, $flags)
+ : filter_var($value, $filter, $flags);
}
return $values;
@@ -363,7 +379,7 @@ class Request extends Message implements RequestInterface
foreach ($index as $key)
{
- $output[$key] = $this->fetchGlobal($type, $key, $filter, $flags);
+ $output[$key] = $this->fetchGlobal($method, $key, $filter, $flags);
}
return $output;
@@ -372,7 +388,7 @@ class Request extends Message implements RequestInterface
// Does the index contain array notation?
if (($count = preg_match_all('/(?:^[^\[]+)|\[[^]]*\]/', $index, $matches)) > 1)
{
- $value = $loopThrough;
+ $value = $this->globals[$method];
for ($i = 0; $i < $count; $i++)
{
$key = trim($matches[0][$i], '[]');
@@ -393,14 +409,12 @@ class Request extends Message implements RequestInterface
}
}
- // Due to issues with FastCGI and testing,
- // we need to do these all manually instead
- // of the simpler filter_input();
if (empty($value))
{
- $value = $loopThrough[$index] ?? null;
+ $value = $this->globals[$method][$index] ?? null;
}
+ // Cannot filter these types of data automatically...
if (is_array($value) || is_object($value) || is_null($value))
{
return $value;
@@ -410,4 +424,39 @@ class Request extends Message implements RequestInterface
}
//--------------------------------------------------------------------
+
+ /**
+ * Saves a copy of the current state of one of several PHP globals
+ * so we can retrieve them later.
+ *
+ * @param string $method
+ */
+ protected function populateGlobals(string $method)
+ {
+ if (! isset($this->globals[$method]))
+ {
+ $this->globals[$method] = [];
+ }
+
+ // Don't populate ENV as it might contain
+ // sensitive data that we don't want to get logged.
+ switch($method)
+ {
+ case 'get':
+ $this->globals['get'] = $_GET;
+ break;
+ case 'post':
+ $this->globals['post'] = $_POST;
+ break;
+ case 'request':
+ $this->globals['request'] = $_REQUEST;
+ break;
+ case 'cookie':
+ $this->globals['cookie'] = $_COOKIE;
+ break;
+ case 'server':
+ $this->globals['server'] = $_SERVER;
+ break;
+ }
+ }
}
diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php
index 0276d2528e..44791bbc45 100644
--- a/system/HTTP/Response.php
+++ b/system/HTTP/Response.php
@@ -35,7 +35,10 @@
* @since Version 3.0.0
* @filesource
*/
+use CodeIgniter\HTTP\Exceptions\HTTPException;
+use CodeIgniter\Services;
use Config\App;
+use Config\Format;
use Config\Mimes;
/**
@@ -70,75 +73,75 @@ class Response extends Message implements ResponseInterface
*/
protected static $statusCodes = [
// 1xx: Informational
- 100 => 'Continue',
- 101 => 'Switching Protocols',
- 102 => 'Processing', // http://www.iana.org/go/rfc2518
- 103 => 'Early Hints', // http://www.ietf.org/rfc/rfc8297.txt
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing', // http://www.iana.org/go/rfc2518
+ 103 => 'Early Hints', // http://www.ietf.org/rfc/rfc8297.txt
// 2xx: Success
- 200 => 'OK',
- 201 => 'Created',
- 202 => 'Accepted',
- 203 => 'Non-Authoritative Information', // 1.1
- 204 => 'No Content',
- 205 => 'Reset Content',
- 206 => 'Partial Content',
- 207 => 'Multi-Status', // http://www.iana.org/go/rfc4918
- 208 => 'Already Reported', // http://www.iana.org/go/rfc5842
- 226 => 'IM Used', // 1.1; http://www.ietf.org/rfc/rfc3229.txt
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information', // 1.1
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status', // http://www.iana.org/go/rfc4918
+ 208 => 'Already Reported', // http://www.iana.org/go/rfc5842
+ 226 => 'IM Used', // 1.1; http://www.ietf.org/rfc/rfc3229.txt
// 3xx: Redirection
- 300 => 'Multiple Choices',
- 301 => 'Moved Permanently',
- 302 => 'Found', // Formerly 'Moved Temporarily'
- 303 => 'See Other', // 1.1
- 304 => 'Not Modified',
- 305 => 'Use Proxy', // 1.1
- 306 => 'Switch Proxy', // No longer used
- 307 => 'Temporary Redirect', // 1.1
- 308 => 'Permanent Redirect', // 1.1; Experimental; http://www.ietf.org/rfc/rfc7238.txt
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found', // Formerly 'Moved Temporarily'
+ 303 => 'See Other', // 1.1
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy', // 1.1
+ 306 => 'Switch Proxy', // No longer used
+ 307 => 'Temporary Redirect', // 1.1
+ 308 => 'Permanent Redirect', // 1.1; Experimental; http://www.ietf.org/rfc/rfc7238.txt
// 4xx: Client error
- 400 => 'Bad Request',
- 401 => 'Unauthorized',
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required',
- 408 => 'Request Timeout',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed',
- 413 => 'Request Entity Too Large',
- 414 => 'Request-URI Too Long',
- 415 => 'Unsupported Media Type',
- 416 => 'Requested Range Not Satisfiable',
- 417 => 'Expectation Failed',
- 418 => "I'm a teapot", // April's Fools joke; http://www.ietf.org/rfc/rfc2324.txt
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => "I'm a teapot", // April's Fools joke; http://www.ietf.org/rfc/rfc2324.txt
// 419 (Authentication Timeout) is a non-standard status code with unknown origin
- 421 => 'Misdirected Request', // http://www.iana.org/go/rfc7540 Section 9.1.2
- 422 => 'Unprocessable Entity', // http://www.iana.org/go/rfc4918
- 423 => 'Locked', // http://www.iana.org/go/rfc4918
- 424 => 'Failed Dependency', // http://www.iana.org/go/rfc4918
- 426 => 'Upgrade Required',
- 428 => 'Precondition Required', // 1.1; http://www.ietf.org/rfc/rfc6585.txt
- 429 => 'Too Many Requests', // 1.1; http://www.ietf.org/rfc/rfc6585.txt
- 431 => 'Request Header Fields Too Large', // 1.1; http://www.ietf.org/rfc/rfc6585.txt
- 451 => 'Unavailable For Legal Reasons', // http://tools.ietf.org/html/rfc7725
- 499 => 'Client Closed Request', // http://lxr.nginx.org/source/src/http/ngx_http_request.h#0133
+ 421 => 'Misdirected Request', // http://www.iana.org/go/rfc7540 Section 9.1.2
+ 422 => 'Unprocessable Entity', // http://www.iana.org/go/rfc4918
+ 423 => 'Locked', // http://www.iana.org/go/rfc4918
+ 424 => 'Failed Dependency', // http://www.iana.org/go/rfc4918
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required', // 1.1; http://www.ietf.org/rfc/rfc6585.txt
+ 429 => 'Too Many Requests', // 1.1; http://www.ietf.org/rfc/rfc6585.txt
+ 431 => 'Request Header Fields Too Large', // 1.1; http://www.ietf.org/rfc/rfc6585.txt
+ 451 => 'Unavailable For Legal Reasons', // http://tools.ietf.org/html/rfc7725
+ 499 => 'Client Closed Request', // http://lxr.nginx.org/source/src/http/ngx_http_request.h#0133
// 5xx: Server error
- 500 => 'Internal Server Error',
- 501 => 'Not Implemented',
- 502 => 'Bad Gateway',
- 503 => 'Service Unavailable',
- 504 => 'Gateway Timeout',
- 505 => 'HTTP Version Not Supported',
- 506 => 'Variant Also Negotiates', // 1.1; http://www.ietf.org/rfc/rfc2295.txt
- 507 => 'Insufficient Storage', // http://www.iana.org/go/rfc4918
- 508 => 'Loop Detected', // http://www.iana.org/go/rfc5842
- 510 => 'Not Extended', // http://www.ietf.org/rfc/rfc2774.txt
- 511 => 'Network Authentication Required', // http://www.ietf.org/rfc/rfc6585.txt
- 599 => 'Network Connect Timeout Error', // https://httpstatuses.com/599
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 506 => 'Variant Also Negotiates', // 1.1; http://www.ietf.org/rfc/rfc2295.txt
+ 507 => 'Insufficient Storage', // http://www.iana.org/go/rfc4918
+ 508 => 'Loop Detected', // http://www.iana.org/go/rfc5842
+ 510 => 'Not Extended', // http://www.ietf.org/rfc/rfc2774.txt
+ 511 => 'Network Authentication Required', // http://www.ietf.org/rfc/rfc6585.txt
+ 599 => 'Network Connect Timeout Error', // https://httpstatuses.com/599
];
/**
@@ -205,6 +208,28 @@ class Response extends Message implements ResponseInterface
*/
protected $cookieHTTPOnly = false;
+ /**
+ * Stores all cookies that were set in the response.
+ *
+ * @var array
+ */
+ protected $cookies = [];
+
+ /**
+ * If true, will not write output. Useful during testing.
+ *
+ * @var bool
+ */
+ protected $pretend = false;
+
+ /**
+ * Type of format the body is in.
+ * Valid: html, json, xml
+ *
+ * @var string
+ */
+ protected $bodyFormat = 'html';
+
//--------------------------------------------------------------------
/**
@@ -221,14 +246,14 @@ class Response extends Message implements ResponseInterface
// Are we enforcing a Content Security Policy?
if ($config->CSPEnabled === true)
{
- $this->CSP = new ContentSecurityPolicy(new \Config\ContentSecurityPolicy());
+ $this->CSP = new ContentSecurityPolicy(new \Config\ContentSecurityPolicy());
$this->CSPEnabled = true;
}
- $this->cookiePrefix = $config->cookiePrefix;
- $this->cookieDomain = $config->cookieDomain;
- $this->cookiePath = $config->cookiePath;
- $this->cookieSecure = $config->cookieSecure;
+ $this->cookiePrefix = $config->cookiePrefix;
+ $this->cookieDomain = $config->cookieDomain;
+ $this->cookiePath = $config->cookiePath;
+ $this->cookieSecure = $config->cookieSecure;
$this->cookieHTTPOnly = $config->cookieHTTPOnly;
// Default to an HTML Content-Type. Devs can override if needed.
@@ -237,6 +262,20 @@ class Response extends Message implements ResponseInterface
//--------------------------------------------------------------------
+ /**
+ * Turns "pretend" mode on or off to aid in testing.
+ *
+ * @param bool $pretend
+ *
+ * @return $this
+ */
+ public function pretend(bool $pretend = true)
+ {
+ $this->pretend = $pretend;
+
+ return $this;
+ }
+
/**
* Gets the response status code.
*
@@ -249,7 +288,7 @@ class Response extends Message implements ResponseInterface
{
if (empty($this->statusCode))
{
- throw new \BadMethodCallException('HTTP Response is missing a status code');
+ throw HTTPException::forMissingResponseStatus();
}
return $this->statusCode;
@@ -279,18 +318,18 @@ class Response extends Message implements ResponseInterface
// Valid range?
if ($code < 100 || $code > 599)
{
- throw new \InvalidArgumentException($code . ' is not a valid HTTP return status code');
+ throw HTTPException::forInvalidStatusCode($code);
}
// Unknown and no message?
- if ( ! array_key_exists($code, static::$statusCodes) && empty($reason))
+ if (! array_key_exists($code, static::$statusCodes) && empty($reason))
{
- throw new \InvalidArgumentException('Unknown HTTP status code provided with no message');
+ throw HTTPException::forUnkownStatusCode($code);
}
$this->statusCode = $code;
- if ( ! empty($reason))
+ if (! empty($reason))
{
$this->reason = $reason;
}
@@ -338,7 +377,7 @@ class Response extends Message implements ResponseInterface
{
$date->setTimezone(new \DateTimeZone('UTC'));
- $this->setHeader('Date', $date->format('D, d M Y H:i:s') . ' GMT');
+ $this->setHeader('Date', $date->format('D, d M Y H:i:s').' GMT');
return $this;
}
@@ -359,7 +398,7 @@ class Response extends Message implements ResponseInterface
// add charset attribute if not already there and provided as parm
if ((strpos($mime, 'charset=') < 1) && ! empty($charset))
{
- $mime .= '; charset=' . $charset;
+ $mime .= '; charset='.$charset;
}
$this->removeHeader('Content-Type'); // replace existing content type
@@ -369,6 +408,114 @@ class Response extends Message implements ResponseInterface
}
//--------------------------------------------------------------------
+
+ /**
+ * Converts the $body into JSON and sets the Content Type header.
+ *
+ * @param $body
+ *
+ * @return $this
+ */
+ public function setJSON($body)
+ {
+ $this->body = $this->formatBody($body, 'json');
+
+ return $this;
+
+ return $this;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Returns the current body, converted to JSON is it isn't already.
+ *
+ * @return mixed|string
+ */
+ public function getJSON()
+ {
+ $body = $this->body;
+
+ if ($this->bodyFormat != 'json')
+ {
+ $config = new Format();
+ $formatter = $config->getFormatter('application/json');
+
+ $body = $formatter->format($body);
+ }
+
+ return $body ?: null;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Converts $body into XML, and sets the correct Content-Type.
+ *
+ * @param $body
+ *
+ * @return $this
+ */
+ public function setXML($body)
+ {
+ $this->body = $this->formatBody($body, 'xml');
+
+ return $this;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Retrieves the current body into XML and returns it.
+ *
+ * @return mixed|string
+ */
+ public function getXML()
+ {
+ $body = $this->body;
+
+ if ($this->bodyFormat != 'xml')
+ {
+ $config = new Format();
+ $formatter = $config->getFormatter('application/xml');
+
+ $body = $formatter->format($body);
+ }
+
+ return $body;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Handles conversion of the of the data into the appropriate format,
+ * and sets the correct Content-Type header for our response.
+ *
+ * @param $body
+ * @param string $format Valid: json, xml
+ *
+ * @return mixed
+ */
+ protected function formatBody($body, string $format)
+ {
+ $mime = "application/{$format}";
+ $this->setContentType($mime);
+ $this->bodyFormat = $format;
+
+ // Nothing much to do for a string...
+ if (! is_string($body))
+ {
+ $config = new Format();
+ $formatter = $config->getFormatter($mime);
+
+ $body = $formatter->format($body);
+ }
+
+ return $body;
+ }
+
+ //--------------------------------------------------------------------
+
//--------------------------------------------------------------------
// Cache Control Methods
//
@@ -467,7 +614,7 @@ class Response extends Message implements ResponseInterface
if ($date instanceof \DateTime)
{
$date->setTimezone(new \DateTimeZone('UTC'));
- $this->setHeader('Last-Modified', $date->format('D, d M Y H:i:s') . ' GMT');
+ $this->setHeader('Last-Modified', $date->format('D, d M Y H:i:s').' GMT');
}
elseif (is_string($date))
{
@@ -498,6 +645,7 @@ class Response extends Message implements ResponseInterface
$this->sendHeaders();
$this->sendBody();
+ $this->sendCookies();
return $this;
}
@@ -525,12 +673,13 @@ class Response extends Message implements ResponseInterface
}
// HTTP Status
- header(sprintf('HTTP/%s %s %s', $this->protocolVersion, $this->statusCode, $this->reason), true, $this->statusCode);
+ header(sprintf('HTTP/%s %s %s', $this->protocolVersion, $this->statusCode, $this->reason), true,
+ $this->statusCode);
// Send all of our headers
foreach ($this->getHeaders() as $name => $values)
{
- header($name . ': ' . $this->getHeaderLine($name), false, $this->statusCode);
+ header($name.': '.$this->getHeaderLine($name), false, $this->statusCode);
}
return $this;
@@ -552,6 +701,16 @@ class Response extends Message implements ResponseInterface
//--------------------------------------------------------------------
+ /**
+ * Grabs the current body.
+ *
+ * @return mixed|string
+ */
+ public function getBody()
+ {
+ return $this->body;
+ }
+
/**
* Perform a redirect to a new URL, in two flavors: header or location.
*
@@ -565,16 +724,18 @@ class Response extends Message implements ResponseInterface
public function redirect(string $uri, string $method = 'auto', int $code = null)
{
// IIS environment likely? Use 'refresh' for better compatibility
- if ($method === 'auto' && isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false)
+ if ($method === 'auto' && isset($_SERVER['SERVER_SOFTWARE'])
+ && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false)
{
$method = 'refresh';
}
elseif ($method !== 'refresh' && (empty($code) || ! is_numeric($code)))
{
- if (isset($_SERVER['SERVER_PROTOCOL'], $_SERVER['REQUEST_METHOD']) && $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.1')
+ if (isset($_SERVER['SERVER_PROTOCOL'], $_SERVER['REQUEST_METHOD']) && $this->getProtocolVersion() >= 1.1)
{
- $code = ($_SERVER['REQUEST_METHOD'] !== 'GET') ? 303 // reference: http://en.wikipedia.org/wiki/Post/Redirect/Get
- : 307;
+ $code = ($_SERVER['REQUEST_METHOD'] !== 'GET') ? 303
+ // reference: http://en.wikipedia.org/wiki/Post/Redirect/Get
+ : 307;
}
else
{
@@ -585,7 +746,7 @@ class Response extends Message implements ResponseInterface
switch ($method)
{
case 'refresh':
- $this->setHeader('Refresh', '0;url=' . $uri);
+ $this->setHeader('Refresh', '0;url='.$uri);
break;
default:
$this->setHeader('Location', $uri);
@@ -617,9 +778,15 @@ class Response extends Message implements ResponseInterface
* @param bool|false $httponly Whether only make the cookie accessible via HTTP (no javascript)
*/
public function setCookie(
- $name, $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = false, $httponly = false
- )
- {
+ $name,
+ $value = '',
+ $expire = '',
+ $domain = '',
+ $path = '/',
+ $prefix = '',
+ $secure = false,
+ $httponly = false
+ ) {
if (is_array($name))
{
// always leave 'name' in last place, as the loop will break otherwise, due to $$item
@@ -657,20 +824,111 @@ class Response extends Message implements ResponseInterface
$httponly = $this->cookieHTTPOnly;
}
- if ( ! is_numeric($expire))
+ if (! is_numeric($expire))
{
- $expire = time() - 86500;
+ $expire = time()-86500;
}
else
{
- $expire = ($expire > 0) ? time() + $expire : 0;
+ $expire = ($expire > 0) ? time()+$expire : 0;
}
- setcookie($prefix . $name, $value, $expire, $path, $domain, $secure, $httponly);
+ $this->cookies[] = [
+ 'name' => $prefix.$name,
+ 'value' => $value,
+ 'expires' => $expire,
+ 'path' => $path,
+ 'domain' => $domain,
+ 'secure' => $secure,
+ 'httponly' => $httponly,
+ ];
+
+ return $this;
}
//--------------------------------------------------------------------
+ /**
+ * Checks to see if the Response has a specified cookie or not.
+ *
+ * @param string $name
+ * @param null $value
+ * @param string $prefix
+ *
+ * @return bool
+ */
+ public function hasCookie(string $name, $value = null, string $prefix = '')
+ {
+ if ($prefix === '' && $this->cookiePrefix !== '')
+ {
+ $prefix = $this->cookiePrefix;
+ }
+
+ $name = $prefix.$name;
+
+ foreach ($this->cookies as $cookie)
+ {
+ if ($cookie['name'] != $prefix.$name)
+ {
+ continue;
+ }
+
+ if ($value === null)
+ {
+ return true;
+ }
+
+ return $cookie['value'] == $value;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the cookie
+ *
+ * @param string $name
+ * @param string $prefix
+ *
+ * @return mixed
+ */
+ public function getCookie(string $name, string $prefix = '')
+ {
+ if ($prefix === '' && $this->cookiePrefix !== '')
+ {
+ $prefix = $this->cookiePrefix;
+ }
+
+ $name = $prefix.$name;
+
+ foreach ($this->cookies as $cookie)
+ {
+ if ($cookie['name'] == $name)
+ {
+ return $cookie;
+ }
+ }
+ }
+
+ /**
+ * Actually sets the cookies.
+ */
+ protected function sendCookies()
+ {
+ if ($this->pretend)
+ {
+ return;
+ }
+
+ foreach ($this->cookies as $params)
+ {
+ // PHP cannot unpack array with string keys
+ $params = array_values($params);
+
+ setcookie(...$params);
+ }
+ }
+
/**
* Force a download.
*
@@ -689,7 +947,7 @@ class Response extends Message implements ResponseInterface
}
elseif ($data === null)
{
- if ( ! @is_file($filename) || ($filesize = @filesize($filename)) === false)
+ if (! @is_file($filename) || ($filesize = @filesize($filename)) === false)
{
return;
}
@@ -706,12 +964,12 @@ class Response extends Message implements ResponseInterface
// Set the default MIME type to send
$mime = 'application/octet-stream';
- $x = explode('.', $filename);
+ $x = explode('.', $filename);
$extension = end($x);
if ($setMime === true)
{
- if (count($x) === 1 OR $extension === '')
+ if (count($x) === 1 || $extension === '')
{
/* If we're going to detect the MIME type,
* we'll need a file extension.
@@ -728,10 +986,11 @@ class Response extends Message implements ResponseInterface
*
* Reference: http://digiblog.de/2011/04/19/android-and-the-download-file-headers/
*/
- if (count($x) !== 1 && isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/Android\s(1|2\.[01])/', $_SERVER['HTTP_USER_AGENT']))
+ if (count($x) !== 1 && isset($_SERVER['HTTP_USER_AGENT'])
+ && preg_match('/Android\s(1|2\.[01])/', $_SERVER['HTTP_USER_AGENT']))
{
- $x[count($x) - 1] = strtoupper($extension);
- $filename = implode('.', $x);
+ $x[count($x)-1] = strtoupper($extension);
+ $filename = implode('.', $x);
}
if ($data === null && ($fp = @fopen($filepath, 'rb')) === false)
@@ -746,11 +1005,11 @@ class Response extends Message implements ResponseInterface
}
// Generate the server headers
- header('Content-Type: ' . $mime);
- header('Content-Disposition: attachment; filename="' . $filename . '"');
+ header('Content-Type: '.$mime);
+ header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Expires: 0');
header('Content-Transfer-Encoding: binary');
- header('Content-Length: ' . $filesize);
+ header('Content-Length: '.$filesize);
header('Cache-Control: private, no-transform, no-store, must-revalidate');
// If we have raw data - just dump it
@@ -760,7 +1019,7 @@ class Response extends Message implements ResponseInterface
}
// Flush 1MB chunks of data
- while ( ! feof($fp) && ($data = fread($fp, 1048576)) !== false)
+ while (! feof($fp) && ($data = fread($fp, 1048576)) !== false)
{
echo $data;
}
@@ -769,5 +1028,4 @@ class Response extends Message implements ResponseInterface
exit;
}
- //--------------------------------------------------------------------
}
diff --git a/system/HTTP/ResponseInterface.php b/system/HTTP/ResponseInterface.php
index 8107cf0bf6..f0a9e82eb3 100644
--- a/system/HTTP/ResponseInterface.php
+++ b/system/HTTP/ResponseInterface.php
@@ -60,6 +60,8 @@ interface ResponseInterface
// Informational
const HTTP_CONTINUE = 100;
const HTTP_SWITCHING_PROTOCOLS = 101;
+ const HTTP_PROCESSING = 102;
+ const HTTP_EARLY_HINTS = 103;
// Success
const HTTP_OK = 200;
const HTTP_CREATED = 201;
diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php
index 3ece306e54..56ac99f02e 100644
--- a/system/HTTP/URI.php
+++ b/system/HTTP/URI.php
@@ -1,5 +1,7 @@
applyParts($parts);
@@ -463,7 +465,7 @@ class URI
if ($number > count($this->segments))
{
- throw new \InvalidArgumentException('Request URI segment is our of range.');
+ throw HTTPException::forURISegmentOutOfRange($number);
}
return $this->segments[$number] ?? '';
@@ -639,7 +641,7 @@ class URI
if ($port <= 0 || $port > 65535)
{
- throw new \InvalidArgumentException('Invalid port given.');
+ throw HTTPException::forInvalidPort($port);
}
$this->port = $port;
@@ -679,7 +681,7 @@ class URI
{
if (strpos($query, '#') !== false)
{
- throw new \InvalidArgumentException('Query strings may not include URI fragments.');
+ throw HTTPException::forMalformedQueryString();
}
// Can't have leading ?
@@ -698,11 +700,11 @@ class URI
// Only 1 part?
if (is_null($value))
{
- $parts[$this->filterQuery($key)] = null;
+ $parts[$key] = null;
continue;
}
- $parts[$this->filterQuery($key)] = $this->filterQuery($value);
+ $parts[$key] = $value;
}
$this->query = $parts;
@@ -736,27 +738,6 @@ class URI
//--------------------------------------------------------------------
- /**
- * Ensures the query string has only acceptable characters
- * per RFC 3986
- *
- * @see http://tools.ietf.org/html/rfc3986
- *
- * @param $str
- *
- * @return string The filtered query value.
- */
- protected function filterQuery($str)
- {
- return preg_replace_callback(
- '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/', function(array $matches) {
- return rawurlencode($matches[0]);
- }, $str
- );
- }
-
- //--------------------------------------------------------------------
-
/**
* A convenience method to pass an array of items in as the Query
* portion of the URI.
@@ -924,7 +905,7 @@ class URI
}
if ( ! empty($parts['fragment']))
{
- $this->fragment = $this->filterQuery($parts['fragment']);
+ $this->fragment = $parts['fragment'];
}
// Scheme
@@ -946,7 +927,7 @@ class URI
if (1 > $port || 0xffff < $port)
{
- throw new \InvalidArgumentException('Ports must be between 1 and 65535');
+ throw HTTPException::forInvalidPort($port);
}
$this->port = $port;
diff --git a/system/HTTP/UserAgent.php b/system/HTTP/UserAgent.php
new file mode 100644
index 0000000000..94684cce96
--- /dev/null
+++ b/system/HTTP/UserAgent.php
@@ -0,0 +1,463 @@
+config = new UserAgents();
+ }
+
+ if (isset($_SERVER['HTTP_USER_AGENT']))
+ {
+ $this->agent = trim($_SERVER['HTTP_USER_AGENT']);
+ $this->compileData();
+ }
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Is Browser
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function isBrowser($key = null)
+ {
+ if (! $this->isBrowser)
+ {
+ return false;
+ }
+
+ // No need to be specific, it's a browser
+ if ($key === null)
+ {
+ return true;
+ }
+
+ // Check for a specific browser
+ return (isset($this->config->browsers[$key]) && $this->browser === $this->config->browsers[$key]);
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Is Robot
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function isRobot($key = null)
+ {
+ if (! $this->isRobot)
+ {
+ return false;
+ }
+
+ // No need to be specific, it's a robot
+ if ($key === null)
+ {
+ return true;
+ }
+
+ // Check for a specific robot
+ return (isset($this->config->robots[$key]) && $this->robot === $this->config->robots[$key]);
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Is Mobile
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function isMobile($key = null)
+ {
+ if (! $this->isMobile)
+ {
+ return false;
+ }
+
+ // No need to be specific, it's a mobile
+ if ($key === null)
+ {
+ return true;
+ }
+
+ // Check for a specific robot
+ return (isset($this->config->mobiles[$key]) && $this->mobile === $this->config->mobiles[$key]);
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Is this a referral from another site?
+ *
+ * @return bool
+ */
+ public function isReferral()
+ {
+ if (! isset($this->referrer))
+ {
+ if (empty($_SERVER['HTTP_REFERER']))
+ {
+ $this->referrer = false;
+ }
+ else
+ {
+ $referer_host = @parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
+ $own_host = parse_url(\base_url(), PHP_URL_HOST);
+
+ $this->referrer = ($referer_host && $referer_host !== $own_host);
+ }
+ }
+
+ return $this->referrer;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Agent String
+ *
+ * @return string
+ */
+ public function getAgentString()
+ {
+ return $this->agent;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Get Platform
+ *
+ * @return string
+ */
+ public function getPlatform()
+ {
+ return $this->platform;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Get Browser Name
+ *
+ * @return string
+ */
+ public function getBrowser()
+ {
+ return $this->browser;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Get the Browser Version
+ *
+ * @return string
+ */
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Get The Robot Name
+ *
+ * @return string
+ */
+ public function getRobot()
+ {
+ return $this->robot;
+ }
+ //--------------------------------------------------------------------
+
+ /**
+ * Get the Mobile Device
+ *
+ * @return string
+ */
+ public function getMobile()
+ {
+ return $this->mobile;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Get the referrer
+ *
+ * @return bool
+ */
+ public function getReferrer()
+ {
+ return empty($_SERVER['HTTP_REFERER']) ? '' : trim($_SERVER['HTTP_REFERER']);
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Parse a custom user-agent string
+ *
+ * @param string $string
+ *
+ * @return void
+ */
+ public function parse($string)
+ {
+ // Reset values
+ $this->isBrowser = false;
+ $this->isRobot = false;
+ $this->isMobile = 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->compileData();
+ }
+ }
+ //--------------------------------------------------------------------
+
+ /**
+ * Compile the User Agent Data
+ *
+ * @return bool
+ */
+ protected function compileData()
+ {
+ $this->setPlatform();
+
+ foreach (['setRobot', 'setBrowser', 'setMobile'] as $function)
+ {
+ if ($this->$function() === true)
+ {
+ break;
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Set the Platform
+ *
+ * @return bool
+ */
+ protected function setPlatform()
+ {
+ if (is_array($this->config->platforms) && count($this->config->platforms) > 0)
+ {
+ foreach ($this->config->platforms as $key => $val)
+ {
+ if (preg_match('|'.preg_quote($key).'|i', $this->agent))
+ {
+ $this->platform = $val;
+
+ return true;
+ }
+ }
+ }
+
+ $this->platform = 'Unknown Platform';
+
+ return false;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Set the Browser
+ *
+ * @return bool
+ */
+ protected function setBrowser()
+ {
+ if (is_array($this->config->browsers) && count($this->config->browsers) > 0)
+ {
+ foreach ($this->config->browsers as $key => $val)
+ {
+ if (preg_match('|'.$key.'.*?([0-9\.]+)|i', $this->agent, $match))
+ {
+ $this->isBrowser = true;
+ $this->version = $match[1];
+ $this->browser = $val;
+ $this->setMobile();
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Set the Robot
+ *
+ * @return bool
+ */
+ protected function setRobot()
+ {
+ if (is_array($this->config->robots) && count($this->config->robots) > 0)
+ {
+ foreach ($this->config->robots as $key => $val)
+ {
+ if (preg_match('|'.preg_quote($key).'|i', $this->agent))
+ {
+ $this->isRobot = true;
+ $this->robot = $val;
+ $this->setMobile();
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Set the Mobile Device
+ *
+ * @return bool
+ */
+ protected function setMobile()
+ {
+ if (is_array($this->config->mobiles) && count($this->config->mobiles) > 0)
+ {
+ foreach ($this->config->mobiles as $key => $val)
+ {
+ if (false !== (stripos($this->agent, $key)))
+ {
+ $this->isMobile = true;
+ $this->mobile = $val;
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Outputs the original Agent String when cast as a string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getAgentString();
+ }
+
+}
diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php
index d4c09f7aa8..346404b613 100755
--- a/system/Helpers/cookie_helper.php
+++ b/system/Helpers/cookie_helper.php
@@ -73,10 +73,7 @@ if ( ! function_exists('set_cookie'))
// The following line shows as a syntax error in NetBeans IDE
//(\Config\Services::response())->setcookie
$response = \Config\Services::response();
- $response->setcookie
- (
- $name, $value, $expire, $domain, $path, $prefix, $secure, $httpOnly
- );
+ $response->setcookie($name, $value, $expire, $domain, $path, $prefix, $secure, $httpOnly);
}
}
diff --git a/system/Helpers/filesystem_helper.php b/system/Helpers/filesystem_helper.php
index 0e274a4710..f947305a10 100644
--- a/system/Helpers/filesystem_helper.php
+++ b/system/Helpers/filesystem_helper.php
@@ -1,4 +1,5 @@
0) ? @rmdir($path) : true;
+ }
+ catch (\Exception $fe)
{
return false;
}
-
- while (false !== ($filename = @readdir($current_dir)))
- {
- if ($filename !== '.' && $filename !== '..')
- {
- if (is_dir($path . DIRECTORY_SEPARATOR . $filename) && $filename[0] !== '.')
- {
- delete_files($path . DIRECTORY_SEPARATOR . $filename, $delDir, $htdocs, $_level + 1);
- }
- elseif ($htdocs !== true || ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename))
- {
- @unlink($path . DIRECTORY_SEPARATOR . $filename);
- }
- }
- }
-
- closedir($current_dir);
-
- return ($delDir === true && $_level > 0) ? @rmdir($path) : true;
}
}
@@ -217,8 +228,9 @@ if ( ! function_exists('get_filenames'))
{
static $filedata = [];
- if ($fp = @opendir($source_dir))
+ try
{
+ $fp = opendir($source_dir);
// reset the array and make sure $source_dir has a trailing slash on the initial call
if ($recursion === false)
{
@@ -241,8 +253,10 @@ if ( ! function_exists('get_filenames'))
closedir($fp);
return $filedata;
}
-
- return [];
+ catch (\Exception $fe)
+ {
+ return [];
+ }
}
}
@@ -271,34 +285,38 @@ if ( ! function_exists('get_dir_file_info'))
static $filedata = [];
$relative_path = $source_dir;
- if ($fp = @opendir($source_dir))
+ try
{
- // reset the array and make sure $source_dir has a trailing slash on the initial call
- if ($recursion === false)
- {
- $filedata = [];
- $source_dir = rtrim(realpath($source_dir), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
- }
-
- // Used to be foreach (scandir($source_dir, 1) as $file), but scandir() is simply not as fast
- while (false !== ($file = readdir($fp)))
- {
- if (is_dir($source_dir . $file) && $file[0] !== '.' && $top_level_only === false)
+ $fp = @opendir($source_dir); {
+ // reset the array and make sure $source_dir has a trailing slash on the initial call
+ if ($recursion === false)
{
- get_dir_file_info($source_dir . $file . DIRECTORY_SEPARATOR, $top_level_only, true);
+ $filedata = [];
+ $source_dir = rtrim(realpath($source_dir), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
}
- elseif ($file[0] !== '.')
- {
- $filedata[$file] = get_file_info($source_dir . $file);
- $filedata[$file]['relative_path'] = $relative_path;
- }
- }
- closedir($fp);
- return $filedata;
+ // Used to be foreach (scandir($source_dir, 1) as $file), but scandir() is simply not as fast
+ while (false !== ($file = readdir($fp)))
+ {
+ if (is_dir($source_dir . $file) && $file[0] !== '.' && $top_level_only === false)
+ {
+ get_dir_file_info($source_dir . $file . DIRECTORY_SEPARATOR, $top_level_only, true);
+ }
+ elseif ($file[0] !== '.')
+ {
+ $filedata[$file] = get_file_info($source_dir . $file);
+ $filedata[$file]['relative_path'] = $relative_path;
+ }
+ }
+
+ closedir($fp);
+ return $filedata;
+ }
+ }
+ catch (\Exception $fe)
+ {
+ return [];
}
-
- return [];
}
}
@@ -319,9 +337,9 @@ if ( ! function_exists('get_file_info'))
* @param string $file Path to file
* @param mixed $returned_values Array or comma separated string of information returned
*
- * @return array
+ * @return array|null
*/
- function get_file_info(string $file, $returned_values = ['name', 'server_path', 'size', 'date']): array
+ function get_file_info(string $file, $returned_values = ['name', 'server_path', 'size', 'date'])
{
if ( ! file_exists($file))
{
@@ -335,8 +353,7 @@ if ( ! function_exists('get_file_info'))
foreach ($returned_values as $key)
{
- switch ($key)
- {
+ switch ($key) {
case 'name':
$fileinfo['name'] = basename($file);
break;
diff --git a/system/Helpers/form_helper.php b/system/Helpers/form_helper.php
index f9180b74cc..dcb7ab750d 100644
--- a/system/Helpers/form_helper.php
+++ b/system/Helpers/form_helper.php
@@ -236,7 +236,7 @@ if ( ! function_exists('form_password'))
*/
function form_password($data = '', string $value = '', $extra = ''): string
{
- is_array($data) OR $data = ['name' => $data];
+ is_array($data) || $data = ['name' => $data];
$data['type'] = 'password';
return form_input($data, $value, $extra);
@@ -263,7 +263,7 @@ if ( ! function_exists('form_upload'))
function form_upload($data = '', string $value = '', $extra = ''): string
{
$defaults = ['type' => 'file', 'name' => ''];
- is_array($data) OR $data = ['name' => $data];
+ is_array($data) || $data = ['name' => $data];
$data['type'] = 'file';
return '\n";
@@ -292,7 +292,7 @@ if ( ! function_exists('form_textarea'))
'cols' => '40',
'rows' => '10',
];
- if ( ! is_array($data) OR ! isset($data['value']))
+ if ( ! is_array($data) || ! isset($data['value']))
{
$val = $value;
}
@@ -374,8 +374,8 @@ if ( ! function_exists('form_dropdown'))
$defaults = ['name' => $data];
}
- is_array($selected) OR $selected = [$selected];
- is_array($options) OR $options = [$options];
+ is_array($selected) || $selected = [$selected];
+ is_array($options) || $options = [$options];
// If no selected state was submitted we will attempt to set it automatically
if (empty($selected))
@@ -490,7 +490,7 @@ if ( ! function_exists('form_radio'))
*/
function form_radio($data = '', string $value = '', bool $checked = false, $extra = ''): string
{
- is_array($data) OR $data = ['name' => $data];
+ is_array($data) || $data = ['name' => $data];
$data['type'] = 'radio';
return form_checkbox($data, $value, $checked, $extra);
@@ -868,7 +868,7 @@ if ( ! function_exists('set_checkbox'))
}
// Unchecked checkbox and radio inputs are not even submitted by browsers ...
- if ($_POST)
+ if (! empty($request->getPost()) || ! empty(old($field)))
{
return ($input === $value) ? ' checked="checked"' : '';
}
@@ -925,7 +925,7 @@ if ( ! function_exists('set_radio'))
}
// Unchecked checkbox and radio inputs are not even submitted by browsers ...
- if ($_POST)
+ if ($request->getPost())
{
return ($input === $value) ? ' checked="checked"' : '';
}
diff --git a/system/Helpers/number_helper.php b/system/Helpers/number_helper.php
index 0c75a5f68e..356d1989a6 100644
--- a/system/Helpers/number_helper.php
+++ b/system/Helpers/number_helper.php
@@ -268,7 +268,7 @@ if ( ! function_exists('number_to_roman'))
function number_to_roman($num)
{
$num = (int) $num;
- if ($num < 1 OR $num > 3999)
+ if ($num < 1 || $num > 3999)
{
return;
}
diff --git a/system/Helpers/text_helper.php b/system/Helpers/text_helper.php
index 87b102b2d6..4133fe708d 100755
--- a/system/Helpers/text_helper.php
+++ b/system/Helpers/text_helper.php
@@ -441,7 +441,7 @@ if ( ! function_exists('word_wrap'))
function word_wrap(string $str, int $charlim = 76): string
{
// Set the character limit
- is_numeric($charlim) OR $charlim = 76;
+ is_numeric($charlim) || $charlim = 76;
// Reduce multiple spaces
$str = preg_replace('| +|', ' ', $str);
diff --git a/system/Helpers/url_helper.php b/system/Helpers/url_helper.php
index ba1a74592b..e45fbe8b8f 100644
--- a/system/Helpers/url_helper.php
+++ b/system/Helpers/url_helper.php
@@ -565,14 +565,14 @@ if ( ! function_exists('prep_url'))
*/
function prep_url($str = ''): string
{
- if ($str === 'http://' OR $str === '')
+ if ($str === 'http://' || $str === '')
{
return '';
}
$url = parse_url($str);
- if ( ! $url OR ! isset($url['scheme']))
+ if ( ! $url || ! isset($url['scheme']))
{
return 'http://' . $str;
}
diff --git a/system/Honeypot/Exceptions/HoneypotException.php b/system/Honeypot/Exceptions/HoneypotException.php
new file mode 100644
index 0000000000..c6085655c9
--- /dev/null
+++ b/system/Honeypot/Exceptions/HoneypotException.php
@@ -0,0 +1,28 @@
+config = $config;
+
+ if($this->config->hidden === '')
+ {
+ throw HoneypotException::forNoHiddenValue();
+ }
+
+ if($this->config->template === '')
+ {
+ throw HoneypotException::forNoTemplate();
+ }
+
+ if($this->config->name === '')
+ {
+ throw HoneypotException::forNoNameField();
+ }
+ }
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Checks the request if honeypot field has data.
+ *
+ * @param \CodeIgniter\HTTP\RequestInterface $request
+ *
+ */
+ public function hasContent(RequestInterface $request)
+ {
+ if($request->getVar($this->config->name))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attachs Honeypot template to response.
+ *
+ * @param \CodeIgniter\HTTP\ResponseInterface $response
+ */
+ public function attachHoneypot(ResponseInterface $response)
+ {
+ $prep_field = $this->prepareTemplate($this->config->template);
+
+ $body = $response->getBody();
+ $body = str_ireplace('', $prep_field, $body);
+ $response->setBody($body);
+ }
+
+ /**
+ * Prepares the template by adding label
+ * content and field name.
+ *
+ * @param string $template
+ * @return string
+ */
+ protected function prepareTemplate($template): string
+ {
+ $template = str_ireplace('{label}', $this->config->label, $template);
+ $template = str_ireplace('{name}', $this->config->name, $template);
+
+ if($this->config->hidden)
+ {
+ $template = '
'. $template . '
';
+ }
+ return $template;
+ }
+
+}
\ No newline at end of file
diff --git a/system/I18n/Exceptions/I18nException.php b/system/I18n/Exceptions/I18nException.php
new file mode 100644
index 0000000000..2c50055170
--- /dev/null
+++ b/system/I18n/Exceptions/I18nException.php
@@ -0,0 +1,32 @@
+ 12)
{
- throw new \InvalidArgumentException(lang('time.invalidMonth'));
+ throw I18nException::forInvalidMonth($value);
}
if (is_string($value) && ! is_numeric($value))
@@ -640,7 +641,7 @@ class Time extends DateTime
{
if ($value < 0 || $value > 31)
{
- throw new \InvalidArgumentException(lang('time.invalidDay'));
+ throw I18nException::forInvalidDay($value);
}
return $this->setValue('day', $value);
@@ -657,7 +658,7 @@ class Time extends DateTime
{
if ($value < 0 || $value > 23)
{
- throw new \InvalidArgumentException(lang('time.invalidHours'));
+ throw I18nException::forInvalidHour($value);
}
return $this->setValue('hour', $value);
@@ -674,7 +675,7 @@ class Time extends DateTime
{
if ($value < 0 || $value > 59)
{
- throw new \InvalidArgumentException(lang('time.invalidMinutes'));
+ throw I18nException::forInvalidMinutes($value);
}
return $this->setValue('minute', $value);
@@ -691,7 +692,7 @@ class Time extends DateTime
{
if ($value < 0 || $value > 59)
{
- throw new \InvalidArgumentException(lang('time.invalidSeconds'));
+ throw I18nException::forInvalidSeconds($value);
}
return $this->setValue('second', $value);
diff --git a/system/Images/Exceptions/ImageException.php b/system/Images/Exceptions/ImageException.php
index 9615192b7d..cf8d355d24 100644
--- a/system/Images/Exceptions/ImageException.php
+++ b/system/Images/Exceptions/ImageException.php
@@ -1,3 +1,42 @@
image = new Image($path, true);
- $this->image->getProperties();
+ $this->image->getProperties(false);
+ $this->width = $this->image->origWidth;
+ $this->height = $this->image->origHeight;
return $this;
}
//--------------------------------------------------------------------
+ /**
+ * Make the image resource object if needed
+ */
+ protected function ensureResource()
+ {
+ if ($this->resource == null)
+ {
+ $path = $this->image->getPathname();
+ // if valid image type, make corresponding image resource
+ switch ($this->image->imageType)
+ {
+ case IMAGETYPE_GIF:
+ $this->resource = imagecreatefromgif($path);
+ break;
+ case IMAGETYPE_JPEG:
+ $this->resource = imagecreatefromjpeg($path);
+ break;
+ case IMAGETYPE_PNG:
+ $this->resource = imagecreatefrompng($path);
+ break;
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------
+
/**
* Returns the image instance.
*
@@ -140,6 +168,7 @@ abstract class BaseHandler implements ImageHandlerInterface
*/
public function getResource()
{
+ $this->ensureResource();
return $this->resource;
}
@@ -228,19 +257,18 @@ abstract class BaseHandler implements ImageHandlerInterface
if ($angle === '' || ! in_array($angle, $degs))
{
- throw new ImageException(lang('images.rotationAngleRequired'));
+ throw ImageException::forMissingAngle();
}
+ // cast angle as an int, for our use
+ $angle = (int) $angle;
+
// Reassign the width and height
- if ($angle === 90 OR $angle === 270)
+ if ($angle === 90 || $angle === 270)
{
- $this->width = $this->image->origHeight;
- $this->height = $this->image->origWidth;
- }
- else
- {
- $this->width = $this->image->origWidth;
- $this->height = $this->image->origHeight;
+ $temp = $this->height;
+ $this->width = $this->height;
+ $this->height = $temp;
}
// Call the Handler-specific version.
@@ -303,13 +331,13 @@ abstract class BaseHandler implements ImageHandlerInterface
*
* @return $this
*/
- public function flip(string $dir)
+ public function flip(string $dir = 'vertical')
{
$dir = strtolower($dir);
if ($dir !== 'vertical' && $dir !== 'horizontal')
{
- throw new ImageException(lang('images.invalidDirection'));
+ throw ImageException::forInvalidDirection($dir);
}
return $this->_flip($dir);
@@ -347,7 +375,7 @@ abstract class BaseHandler implements ImageHandlerInterface
* @param string $text
* @param array $options
*
- * @return BaseHandler
+ * @return $this
*/
public function text(string $text, array $options = [])
{
@@ -437,12 +465,9 @@ abstract class BaseHandler implements ImageHandlerInterface
{
return null;
}
-
- throw new ImageException(lang('images.exifNotSupported'));
}
$exif = exif_read_data($this->image->getPathname());
-
if ( ! is_null($key) && is_array($exif))
{
$exif = array_key_exists($key, $exif) ? $exif[$key] : false;
@@ -662,7 +687,12 @@ abstract class BaseHandler implements ImageHandlerInterface
*/
protected function reproportion()
{
- if (($this->width === 0 && $this->height === 0) || $this->image->origWidth === 0 || $this->image->origHeight === 0 || ( ! ctype_digit((string) $this->width) && ! ctype_digit((string) $this->height)) || ! ctype_digit((string) $this->image->origWidth) || ! ctype_digit((string) $this->image->origHeight)
+ if (($this->width === 0 && $this->height === 0) ||
+ $this->image->origWidth === 0 ||
+ $this->image->origHeight === 0 ||
+ ( ! ctype_digit((string) $this->width) && ! ctype_digit((string) $this->height)) ||
+ ! ctype_digit((string) $this->image->origWidth) ||
+ ! ctype_digit((string) $this->image->origHeight)
)
{
return;
@@ -700,4 +730,16 @@ abstract class BaseHandler implements ImageHandlerInterface
}
//--------------------------------------------------------------------
+ // accessor for testing; not part of interface
+ public function getWidth()
+ {
+ return ($this->resource != null) ? $this->_getWidth() : $this->width;
+ }
+
+ // accessor for testing; not part of interface
+ public function getHeight()
+ {
+ return ($this->resource != null) ? $this->_getHeight() : $this->height;
+ }
+
}
diff --git a/system/Images/Handlers/GDHandler.php b/system/Images/Handlers/GDHandler.php
index bbda86a8cb..6719b560c5 100644
--- a/system/Images/Handlers/GDHandler.php
+++ b/system/Images/Handlers/GDHandler.php
@@ -42,21 +42,17 @@ class GDHandler extends BaseHandler
public $version;
- /**
- * Stores image resource in memory.
- *
- * @var
- */
- protected $resource;
-
public function __construct($config = null)
{
parent::__construct($config);
+ // We should never see this, so can't test it
+ // @codeCoverageIgnoreStart
if ( ! extension_loaded('gd'))
{
- throw new ImageException('GD Extension is not loaded.');
+ throw ImageException::forMissingExtension('GD');
}
+ // @codeCoverageIgnoreEnd
}
//--------------------------------------------------------------------
@@ -72,10 +68,7 @@ class GDHandler extends BaseHandler
protected function _rotate(int $angle)
{
// Create the image handle
- if ( ! ($srcImg = $this->createImage()))
- {
- return false;
- }
+ $srcImg = $this->createImage();
// Set the background color
// This won't work with transparent PNG files so we are
@@ -85,7 +78,7 @@ class GDHandler extends BaseHandler
$white = imagecolorallocate($srcImg, 255, 255, 255);
// Rotate it!
- $destImg = imagerotate($this->resource, $angle, $white);
+ $destImg = imagerotate($srcImg, $angle, $white);
// Kill the file handles
imagedestroy($srcImg);
@@ -106,12 +99,9 @@ class GDHandler extends BaseHandler
*
* @return $this
*/
- public function _flatten(int $red = 255, int $green = 255, int $blue = 255) {
-
- if ( ! ($src = $this->createImage()))
- {
- return false;
- }
+ public function _flatten(int $red = 255, int $green = 255, int $blue = 255)
+ {
+ $srcImg = $this->createImage();
if (function_exists('imagecreatetruecolor'))
{
@@ -128,15 +118,14 @@ class GDHandler extends BaseHandler
$matte = imagecolorallocate($dest, $red, $green, $blue);
imagefilledrectangle($dest, 0, 0, $this->width, $this->height, $matte);
- imagecopy($dest, $src, 0, 0, 0, 0, $this->width, $this->height);
+ imagecopy($dest, $srcImg, 0, 0, 0, 0, $this->width, $this->height);
// Kill the file handles
- imagedestroy($src);
+ imagedestroy($srcImg);
$this->resource = $dest;
return $this;
-
}
//--------------------------------------------------------------------
@@ -157,7 +146,7 @@ class GDHandler extends BaseHandler
if ($direction === 'horizontal')
{
- for ($i = 0; $i < $height; $i ++ )
+ for ($i = 0; $i < $height; $i ++)
{
$left = 0;
$right = $width - 1;
@@ -177,7 +166,7 @@ class GDHandler extends BaseHandler
}
else
{
- for ($i = 0; $i < $width; $i ++ )
+ for ($i = 0; $i < $width; $i ++)
{
$top = 0;
$bottom = $height - 1;
@@ -326,38 +315,38 @@ class GDHandler extends BaseHandler
case IMAGETYPE_GIF:
if ( ! function_exists('imagegif'))
{
- throw new ImageException(lang('images.unsupportedImagecreate') . ' ' . lang('images.gifNotSupported'));
+ throw ImageException::forInvalidImageCreate(lang('images.gifNotSupported'));
}
if ( ! @imagegif($this->resource, $target))
{
- throw new ImageException(lang('images.saveFailed'));
+ throw ImageException::forSaveFailed();
}
break;
case IMAGETYPE_JPEG:
if ( ! function_exists('imagejpeg'))
{
- throw new ImageException(lang('images.unsupportedImagecreate') . ' ' . lang('images.jpgNotSupported'));
+ throw ImageException::forInvalidImageCreate(lang('images.jpgNotSupported'));
}
if ( ! @imagejpeg($this->resource, $target, $quality))
{
- throw new ImageException(lang('images.saveFailed'));
+ throw ImageException::forSaveFailed();
}
break;
case IMAGETYPE_PNG:
if ( ! function_exists('imagepng'))
{
- throw new ImageException(lang('images.unsupportedImagecreate') . ' ' . lang('images.pngNotSupported'));
+ throw ImageException::forInvalidImageCreate(lang('images.pngNotSupported'));
}
if ( ! @imagepng($this->resource, $target))
{
- throw new ImageException(lang('images.saveFailed'));
+ throw ImageException::forSaveFailed();
}
break;
default:
- throw new ImageException(lang('images.unsupportedImagecreate'));
+ throw ImageException::forInvalidImageCreate();
break;
}
@@ -403,26 +392,26 @@ class GDHandler extends BaseHandler
case IMAGETYPE_GIF:
if ( ! function_exists('imagecreatefromgif'))
{
- throw new ImageException(lang('images.gifNotSupported'));
+ throw ImageException::forInvalidImageCreate(lang('images.gifNotSupported'));
}
return imagecreatefromgif($path);
case IMAGETYPE_JPEG:
if ( ! function_exists('imagecreatefromjpeg'))
{
- throw new ImageException(lang('images.jpgNotSupported'));
+ throw ImageException::forInvalidImageCreate(lang('images.jpgNotSupported'));
}
return imagecreatefromjpeg($path);
case IMAGETYPE_PNG:
if ( ! function_exists('imagecreatefrompng'))
{
- throw new ImageException(lang('images.pngNotSupported'));
+ throw ImageException::forInvalidImageCreate(lang('images.pngNotSupported'));
}
return imagecreatefrompng($path);
default:
- throw new ImageException(lang('images.unsupportedImagecreate'));
+ throw ImageException::forInvalidImageCreate('Ima');
}
}
@@ -560,4 +549,15 @@ class GDHandler extends BaseHandler
}
//--------------------------------------------------------------------
+
+ public function _getWidth()
+ {
+ return imagesx($this->resource);
+ }
+
+ public function _getHeight()
+ {
+ return imagesy($this->resource);
+ }
+
}
diff --git a/system/Images/Handlers/ImageMagickHandler.php b/system/Images/Handlers/ImageMagickHandler.php
index 9634360767..a720730909 100644
--- a/system/Images/Handlers/ImageMagickHandler.php
+++ b/system/Images/Handlers/ImageMagickHandler.php
@@ -36,6 +36,7 @@
* @filesource
*/
use CodeIgniter\Images\Exceptions\ImageException;
+use CodeIgniter\Images\Image;
/**
* Class ImageMagickHandler
@@ -43,6 +44,10 @@ use CodeIgniter\Images\Exceptions\ImageException;
* To make this library as compatible as possible with the broadest
* number of installations, we do not use the Imagick extension,
* but simply use the command line version.
+ *
+ * hmm - the width & height accessors at the end use the imagick extension.
+ *
+ * FIXME - This needs conversion & unit testing, to use the imagick extension
*
* @package CodeIgniter\Images\Handlers
*/
@@ -60,6 +65,13 @@ class ImageMagickHandler extends BaseHandler
//--------------------------------------------------------------------
+ public function __construct($config = null)
+ {
+ parent::__construct($config);
+ }
+
+ //--------------------------------------------------------------------
+
/**
* Handles the actual resizing of the image.
*
@@ -75,7 +87,8 @@ class ImageMagickHandler extends BaseHandler
//todo FIX THIS HANDLER PROPERLY
$escape = "\\";
- if (strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN') {
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
+ {
$escape = "";
}
@@ -140,9 +153,10 @@ class ImageMagickHandler extends BaseHandler
*
* @return $this
*/
- public function _flatten(int $red = 255, int $green = 255, int $blue = 255){
+ public function _flatten(int $red = 255, int $green = 255, int $blue = 255)
+ {
- $flatten = "-background RGB({$red},{$green},{$blue}) -flatten";
+ $flatten = "-background RGB({$red},{$green},{$blue}) -flatten";
$source = ! empty($this->resource) ? $this->resource : $this->image->getPathname();
$destination = $this->getResourcePath();
@@ -209,7 +223,7 @@ class ImageMagickHandler extends BaseHandler
// Do we have a vaild library path?
if (empty($this->config->libraryPath))
{
- throw new ImageException(lang('images.libPathInvalid'));
+ throw ImageException::forInvalidImageLibraryPath($this->config->libraryPath);
}
if ( ! preg_match('/convert$/i', $this->config->libraryPath))
@@ -230,7 +244,7 @@ class ImageMagickHandler extends BaseHandler
// Did it work?
if ($retval > 0)
{
- throw new ImageException(lang('imageProcessFailed'));
+ throw ImageException::forImageProcessFailed();
}
return $output;
@@ -407,4 +421,18 @@ class ImageMagickHandler extends BaseHandler
}
//--------------------------------------------------------------------
+
+ //--------------------------------------------------------------------
+
+ public function _getWidth()
+ {
+ return imagesx($this->resource);
+ }
+
+ public function _getHeight()
+ {
+ return imagesy($this->resource);
+ }
+
+
}
diff --git a/system/Images/Image.php b/system/Images/Image.php
index 92a7e48bce..7c7d5c87f4 100644
--- a/system/Images/Image.php
+++ b/system/Images/Image.php
@@ -36,6 +36,7 @@
* @filesource
*/
use CodeIgniter\Files\File;
+use CodeIgniter\HTTP\Exceptions\HTTPException;
use CodeIgniter\Images\Exceptions\ImageException;
class Image extends File
@@ -96,7 +97,7 @@ class Image extends File
if (empty($targetName))
{
- throw new ImageException('Invalid file name.');
+ throw ImageException::forInvalidFile($targetName);
}
if ( ! is_dir($targetPath))
@@ -106,7 +107,7 @@ class Image extends File
if ( ! copy($this->getPathname(), "{$targetPath}{$targetName}"))
{
- throw new ImageException('Unable to copy image to new destination.');
+ throw ImageException::forCopyError($targetPath);
}
chmod("{$targetPath}/{$targetName}", $perms);
@@ -131,6 +132,7 @@ class Image extends File
$vals = getimagesize($path);
$types = [1 => 'gif', 2 => 'jpeg', 3 => 'png'];
+
$mime = 'image/' . ($types[$vals[2]] ?? 'jpg');
if ($return === true)
diff --git a/system/Images/ImageHandlerInterface.php b/system/Images/ImageHandlerInterface.php
index 87ec21362b..f33774353e 100644
--- a/system/Images/ImageHandlerInterface.php
+++ b/system/Images/ImageHandlerInterface.php
@@ -44,8 +44,9 @@ interface ImageHandlerInterface
* @param int $width
* @param int $height
* @param bool $maintainRatio If true, will get the closest match possible while keeping aspect ratio true.
+ * @param string $masterDim
*/
- public function resize(int $width, int $height, bool $maintainRatio = false);
+ public function resize(int $width, int $height, bool $maintainRatio = false, string $masterDim = 'auto');
//--------------------------------------------------------------------
@@ -58,10 +59,12 @@ interface ImageHandlerInterface
* @param int|null $height
* @param int|null $x X-axis coord to start cropping from the left of image
* @param int|null $y Y-axis coord to start cropping from the top of image
+ * @param bool $maintainRatio
+ * @param string $masterDim
*
* @return mixed
*/
- public function crop(int $width = null, int $height = null, int $x = null, int $y = null);
+ public function crop(int $width = null, int $height = null, int $x = null, int $y = null, bool $maintainRatio = false, string $masterDim = 'auto');
//--------------------------------------------------------------------
@@ -110,6 +113,17 @@ interface ImageHandlerInterface
//--------------------------------------------------------------------
+ /**
+ * Flip an image horizontally or vertically
+ *
+ * @param string $dir Direction to flip, either 'vertical' or 'horizontal'
+ *
+ * @return mixed
+ */
+ public function flip(string $dir = 'vertical');
+
+ //--------------------------------------------------------------------
+
/**
* Combine cropping and resizing into a single command.
*
@@ -133,4 +147,42 @@ interface ImageHandlerInterface
public function fit(int $width, int $height, string $position);
//--------------------------------------------------------------------
+
+ /**
+ * Overlays a string of text over the image.
+ *
+ * Valid options:
+ *
+ * - color Text Color (hex number)
+ * - shadowColor Color of the shadow (hex number)
+ * - hAlign Horizontal alignment: left, center, right
+ * - vAlign Vertical alignment: top, middle, bottom
+ * - hOffset
+ * - vOffset
+ * - fontPath
+ * - fontSize
+ * - shadowOffset
+ *
+ * @param string $text
+ * @param array $options
+ *
+ * @return $this
+ */
+ public function text(string $text, array $options = []);
+
+ //--------------------------------------------------------------------
+
+ /**
+ * Saves any changes that have been made to file.
+ *
+ * Example:
+ * $image->resize(100, 200, true)
+ * ->save($target);
+ *
+ * @param string $target
+ * @param int $quality
+ *
+ * @return mixed
+ */
+ public function save(string $target = null, int $quality = 90);
}
diff --git a/system/Language/Language.php b/system/Language/Language.php
index 40a8c934d7..96cbdf73e3 100644
--- a/system/Language/Language.php
+++ b/system/Language/Language.php
@@ -35,7 +35,6 @@
* @since Version 3.0.0
* @filesource
*/
-
class Language
{
@@ -85,6 +84,23 @@ class Language
//--------------------------------------------------------------------
+ /**
+ * Sets the current locale to use when performing string lookups.
+ *
+ * @param string $locale
+ *
+ * @return $this
+ */
+ public function setLocale(string $locale = null)
+ {
+ if ( ! is_null($locale))
+ {
+ $this->locale = $locale;
+ }
+
+ return $this;
+ }
+
/**
* Parses the language string for a file, loads the file, if necessary,
* getting the line.
@@ -98,15 +114,14 @@ class Language
{
// Parse out the file name and the actual alias.
// Will load the language file and strings.
- list($file, $line) = $this->parseLine($line);
+ list($file, $parsedLine) = $this->parseLine($line);
- $output = $this->language[$file][$line] ?? $line;
+ $output = $this->language[$this->locale][$file][$parsedLine] ?? $line;
- if (! empty($args))
+ if ( ! empty($args))
{
$output = $this->formatMessage($output, $args);
}
-
return $output;
}
@@ -143,7 +158,7 @@ class Language
return [
$file,
- $this->language[$line] ?? $line
+ $this->language[$this->locale][$line] ?? $line
];
}
@@ -191,30 +206,40 @@ class Language
*/
protected function load(string $file, string $locale, bool $return = false)
{
- if (in_array($file, $this->loadedFiles))
+ if ( ! array_key_exists($locale, $this->loadedFiles))
{
+ $this->loadedFiles[$locale] = [];
+ }
+
+ if (in_array($file, $this->loadedFiles[$locale]))
+ {
+ // Don't load it more than once.
return [];
}
- if ( ! array_key_exists($file, $this->language))
+ if ( ! array_key_exists($locale, $this->language))
{
- $this->language[$file] = [];
+ $this->language[$locale] = [];
+ }
+
+ if ( ! array_key_exists($file, $this->language[$locale]))
+ {
+ $this->language[$locale][$file] = [];
}
$path = "Language/{$locale}/{$file}.php";
$lang = $this->requireFile($path);
- // Don't load it more than once.
- $this->loadedFiles[] = $file;
-
if ($return)
{
return $lang;
}
+ $this->loadedFiles[$locale][] = $file;
+
// Merge our string
- $this->language[$file] = $lang;
+ $this->language[$this->locale][$file] = $lang;
}
//--------------------------------------------------------------------
diff --git a/system/Language/en/CLI.php b/system/Language/en/CLI.php
index 5c3945bf1f..9b7dc156b5 100644
--- a/system/Language/en/CLI.php
+++ b/system/Language/en/CLI.php
@@ -1,44 +1,22 @@
'Usage:',
- 'helpDescription' => 'Description:',
- 'helpOptions' => 'Options:',
- 'helpArguments' => 'Arguments:',
-];
\ No newline at end of file
+ 'helpUsage' => 'Usage:',
+ 'helpDescription' => 'Description:',
+ 'helpOptions' => 'Options:',
+ 'helpArguments' => 'Arguments:',
+ 'invalidColor' => 'Invalid {0} color: {1}.',
+];
diff --git a/system/Language/en/Cache.php b/system/Language/en/Cache.php
index 85591c2929..e0dbfed024 100644
--- a/system/Language/en/Cache.php
+++ b/system/Language/en/Cache.php
@@ -1,43 +1,20 @@
'Cache config must have an array of $validHandlers.',
- 'cacheNoBackup' => 'Cache config must have a handler and backupHandler set.',
- 'cacheHandlerNotFound' => 'Cache config has an invalid handler or backup handler specified.',
+ 'invalidHandlers' => 'Cache config must have an array of $validHandlers.',
+ 'noBackup' => 'Cache config must have a handler and backupHandler set.',
+ 'handlerNotFound' => 'Cache config has an invalid handler or backup handler specified.',
];
diff --git a/system/Language/en/Core.php b/system/Language/en/Core.php
new file mode 100644
index 0000000000..97d7b95195
--- /dev/null
+++ b/system/Language/en/Core.php
@@ -0,0 +1,21 @@
+ 'Invalid file: {0}',
+ 'copyError' => 'An error was encountered while attempting to replace the file. Please make sure your file directory is writable.',
+ 'missingExtension' => '{0} extension is not loaded.',
+ 'noHandlers' => '{0} must provide at least one Handler.',
+];
diff --git a/system/Language/en/Database.php b/system/Language/en/Database.php
index 197e6d90e1..5181b9b448 100644
--- a/system/Language/en/Database.php
+++ b/system/Language/en/Database.php
@@ -1,5 +1,21 @@
'{0, string} is not a valid Model Event callback.',
+ 'invalidEvent' => '{0} is not a valid Model Event callback.',
+ 'invalidArgument' => 'You must provide a valid {0}.',
+ 'invalidAllowedFields' => 'Allowed fields must be specified for model: {0}',
+ 'emptyDataset' => 'There is no data to {0}.',
];
diff --git a/system/Language/en/Email.php b/system/Language/en/Email.php
index b923cdc1b9..49d26f7c71 100644
--- a/system/Language/en/Email.php
+++ b/system/Language/en/Email.php
@@ -1,10 +1,23 @@
'The email validation method must be passed an array.',
- 'invalidAddress' => 'Invalid email address: {0, string}',
- 'attachmentMissing' => 'Unable to locate the following email attachment: {0, string}',
- 'attachmentUnreadable' => 'Unable to open this attachment: {0, string}',
+ 'invalidAddress' => 'Invalid email address: {0}',
+ 'attachmentMissing' => 'Unable to locate the following email attachment: {0}',
+ 'attachmentUnreadable' => 'Unable to open this attachment: {0}',
'noFrom' => 'Cannot send mail with no "From" header.',
'noRecipients' => 'You must include recipients: To, Cc, or Bcc',
'sendFailurePHPMail' => 'Unable to send email using PHP mail(). Your server might not be configured to send mail using this method.',
@@ -13,11 +26,11 @@ return [
'sent' => 'Your message has been successfully sent using the following protocol: {0, string}',
'noSocket' => 'Unable to open a socket to Sendmail. Please check settings.',
'noHostname' => 'You did not specify a SMTP hostname.',
- 'SMYPError' => 'The following SMTP error was encountered: {0, string}',
+ 'SMTPError' => 'The following SMTP error was encountered: {0}',
'noSMTPAuth' => 'Error: You must assign a SMTP username and password.',
- 'failedSMTPLogin' => 'Failed to send AUTH LOGIN command. Error: {0, string}',
- 'SMTPAuthUsername' => 'Failed to authenticate username. Error: {0, string}',
- 'SMTPAuthPassword' => 'Failed to authenticate password. Error: {0, string}',
- 'SMTPDataFailure' => 'Unable to send data: {0, string}',
- 'exitStatus' => 'Exit status code: {0, string}',
+ 'failedSMTPLogin' => 'Failed to send AUTH LOGIN command. Error: {0}',
+ 'SMTPAuthUsername' => 'Failed to authenticate username. Error: {0}',
+ 'SMTPAuthPassword' => 'Failed to authenticate password. Error: {0}',
+ 'SMTPDataFailure' => 'Unable to send data: {0}',
+ 'exitStatus' => 'Exit status code: {0}',
];
diff --git a/system/Language/en/Files.php b/system/Language/en/Files.php
new file mode 100644
index 0000000000..6e1eb5f319
--- /dev/null
+++ b/system/Language/en/Files.php
@@ -0,0 +1,20 @@
+ 'File not found: {0}',
+ 'cannotMove' => 'Could not move file {0} to {1} ({2})',
+ 'invalidFilename' => 'Target filename missing or invalid: {0}',
+ 'cannotCopy' => 'Could not copy to {0} - make sure the folder is writeable',
+];
diff --git a/system/Language/en/Filters.php b/system/Language/en/Filters.php
new file mode 100644
index 0000000000..3c84d5903d
--- /dev/null
+++ b/system/Language/en/Filters.php
@@ -0,0 +1,19 @@
+ '\'{0}\' filter must have a matching alias defined.',
+ 'incorrectInterface' => '{0} must implement CodeIgniter\Filters\FilterInterface.',
+];
diff --git a/system/Language/en/Format.php b/system/Language/en/Format.php
new file mode 100644
index 0000000000..2cf35607ed
--- /dev/null
+++ b/system/Language/en/Format.php
@@ -0,0 +1,19 @@
+ 'Failed to parse json string, error: "{0}".',
+ 'missingExtension' => 'The SimpleXML extension is required to format XML.',
+];
diff --git a/system/Language/en/HTTP.php b/system/Language/en/HTTP.php
index 37dadc58f4..49bf72fb1b 100644
--- a/system/Language/en/HTTP.php
+++ b/system/Language/en/HTTP.php
@@ -1,5 +1,54 @@
'{0, string} is not a valid route.',
+ // CurlRequest
+ 'missingCurl' => 'CURL must be enabled to use the CURLRequest class.',
+ 'invalidSSLKey' => 'Cannot set SSL Key. {0} is not a valid file.',
+ 'sslCertNotFound' => 'SSL certificate not found at: {0}',
+ 'curlError' => '{0} : {1}',
+
+ // IncomingRequest
+ 'invalidNegotiationType' => '{0} is not a valid negotiation type. Must be one of: media, charset, encoding, language.',
+
+ // Message
+ 'invalidHTTPProtocol' => 'Invalid HTTP Protocol Version. Must be one of: {0}',
+
+ // Negotiate
+ 'emptySupportedNegotiations' => 'You must provide an array of supported values to all Negotiations.',
+
+ // RedirectResponse
+ 'invalidRoute' => '{0, string} is not a valid route.',
+
+ // Response
+ 'missingResponseStatus' => 'HTTP Response is missing a status code',
+ 'invalidStatusCode' => '{0, string} is not a valid HTTP return status code',
+ 'unknownStatusCode' => 'Unknown HTTP status code provided with no message: {0}',
+
+ // URI
+ 'cannotParseURI' => 'Unable to parse URI: {0}',
+ 'segmentOutOfRange' => 'Request URI segment is our of range: {0}',
+ 'invalidPort' => 'Ports must be between 0 and 65535. Given: {0}',
+ 'malformedQueryString' => 'Query strings may not include URI fragments.',
+
+ // Page Not Found
+ 'pageNotFound' => 'Page Not Found',
+ 'emptyController' => 'No Controller specified.',
+ 'controllerNotFound' => 'Controller or its method is not found: {0}::{1}',
+ 'methodNotFound' => 'Controller method is not found: {0}',
+
+ // CSRF
+ 'disallowedAction' => 'The action you requested is not allowed.',
];
diff --git a/system/Language/en/Images.php b/system/Language/en/Images.php
index 05e77c98db..a31b0bca78 100644
--- a/system/Language/en/Images.php
+++ b/system/Language/en/Images.php
@@ -1,5 +1,18 @@
'You must specify a source image in your preferences.',
'gdRequired' => 'The GD image library is required to use this feature.',
@@ -9,16 +22,14 @@ return [
'pngNotSupported' => 'PNG images are not supported.',
'unsupportedImagecreate' => 'Your server does not support the GD function required to process this type of image.',
'jpgOrPngRequired' => 'The image resize protocol specified in your preferences only works with JPEG or PNG image types.',
- 'copyError' => 'An error was encountered while attempting to replace the file. Please make sure your file directory is writable.',
'rotateUnsupported' => 'Image rotation does not appear to be supported by your server.',
- 'libPathInvalid' => 'The path to your image library is not correct. Please set the correct path in your image preferences.',
+ 'libPathInvalid' => 'The path to your image library is not correct. Please set the correct path in your image preferences. {0, string)',
'imageProcessFailed' => 'Image processing failed. Please verify that your server supports the chosen protocol and that the path to your image library is correct.',
'rotationAngleRequired' => 'An angle of rotation is required to rotate the image.',
'invalidPath' => 'The path to the image is not correct.',
'copyFailed' => 'The image copy routine failed.',
'missingFont' => 'Unable to find a font to use.',
'saveFailed' => 'Unable to save the image. Please make sure the image and file directory are writable.',
- 'invalidDirection' => 'Flip direction can be only `vertical` or `horizontal`.',
+ 'invalidDirection' => 'Flip direction can be only `vertical` or `horizontal`. Given: {0}',
'exifNotSupported' => 'Reading EXIF data is not supported by this PHP installation.',
- 'pageNotFound' => 'Page Not Found',
];
diff --git a/system/Language/en/Language.php b/system/Language/en/Language.php
index a20e1ce033..eca9718f8f 100644
--- a/system/Language/en/Language.php
+++ b/system/Language/en/Language.php
@@ -1,5 +1,18 @@
'Get line must be a string or array of strings.'
];
diff --git a/system/Language/en/Log.php b/system/Language/en/Log.php
new file mode 100644
index 0000000000..bd35bb914a
--- /dev/null
+++ b/system/Language/en/Log.php
@@ -0,0 +1,18 @@
+ '{0} is an invalid log level.',
+];
diff --git a/system/Language/en/Migrations.php b/system/Language/en/Migrations.php
index e6006289b9..98e52cb39e 100644
--- a/system/Language/en/Migrations.php
+++ b/system/Language/en/Migrations.php
@@ -1,78 +1,54 @@
'Migrations table must be set.',
- 'migInvalidType' => 'An invalid migration numbering type was specified: ',
- 'migDisabled' => 'Migrations have been loaded but are disabled or setup incorrectly.',
- 'migNotFound' => 'Migration file not found: ',
- 'migEmpty' => 'No Migration files found',
- 'migGap' => 'There is a gap in the migration sequence near version number: ',
- 'migClassNotFound' => 'The migration class "%s" could not be found.',
- 'migMissingMethod' => 'The migration class is missing an "%s" method.',
- 'migMultiple' => 'There are multiple migrations with the same version number: ',
+ 'missingTable' => 'Migrations table must be set.',
+ 'invalidType' => 'An invalid migration numbering type was specified: {0}',
+ 'disabled' => 'Migrations have been loaded but are disabled or setup incorrectly.',
+ 'notFound' => 'Migration file not found: ',
+ 'empty' => 'No Migration files found',
+ 'gap' => 'There is a gap in the migration sequence near version number: ',
+ 'classNotFound' => 'The migration class "%s" could not be found.',
+ 'missingMethod' => 'The migration class is missing an "%s" method.',
// Migration Command
- 'migHelpLatest' => "\t\tMigrates database to latest available migration.",
- 'migHelpCurrent' => "\t\tMigrates database to version set as 'current' in configuration.",
- 'migHelpVersion' => "\tMigrates database to version {v}.",
- 'migHelpRollback' => "\tRuns all migrations 'down' to version 0.",
- 'migHelpRefresh' => "\t\tUninstalls and re-runs all migrations to freshen database.",
- 'migHelpSeed' => "\tRuns the seeder named [name].",
- 'migCreate' => "\tCreates a new migration named [name]",
- 'migNameMigration' => "Name the migration file",
- 'migBadCreateName' => 'You must provide a migration file name.',
- 'migWriteError' => 'Error trying to create file.',
+ 'migHelpLatest' => "\t\tMigrates database to latest available migration.",
+ 'migHelpCurrent' => "\t\tMigrates database to version set as 'current' in configuration.",
+ 'migHelpVersion' => "\tMigrates database to version {v}.",
+ 'migHelpRollback' => "\tRuns all migrations 'down' to version 0.",
+ 'migHelpRefresh' => "\t\tUninstalls and re-runs all migrations to freshen database.",
+ 'migHelpSeed' => "\tRuns the seeder named [name].",
+ 'migCreate' => "\tCreates a new migration named [name]",
+ 'nameMigration' => "Name the migration file",
+ 'badCreateName' => 'You must provide a migration file name.',
+ 'writeError' => 'Error trying to create file.',
- 'migToLatest' => 'Migrating to latest version...',
+ 'toLatest' => 'Migrating to latest version...',
'migInvalidVersion' => 'Invalid version number provided.',
- 'migToVersionPH' => 'Migrating to version %s...',
- 'migToVersion' => 'Migrating to current version...',
- 'migRollingBack' => "Rolling back all migrations...",
- 'migNoneFound' => 'No migrations were found.',
- 'migOn' => 'Migrated On: ',
+ 'toVersionPH' => 'Migrating to version %s...',
+ 'toVersion' => 'Migrating to current version...',
+ 'rollingBack' => "Rolling back all migrations...",
+ 'noneFound' => 'No migrations were found.',
+ 'on' => 'Migrated On: ',
'migSeeder' => 'Seeder name',
'migMissingSeeder' => 'You must provide a seeder name.',
- 'migHistoryFor' => 'Migration history For ',
- 'migRemoved' => 'Rolling back: ',
- 'migAdded' => 'Running: ',
+ 'historyFor' => 'Migration history For ',
+ 'removed' => 'Rolling back: ',
+ 'added' => 'Running: ',
- 'version' => 'Version',
- 'filename' => 'Filename',
+ 'version' => 'Version',
+ 'filename' => 'Filename',
];
diff --git a/system/Language/en/Number.php b/system/Language/en/Number.php
index 98a2c0e94a..418a470cb3 100644
--- a/system/Language/en/Number.php
+++ b/system/Language/en/Number.php
@@ -1,50 +1,30 @@
'TB',
'gigabyteAbbr' => 'GB',
'megabyteAbbr' => 'MB',
'kilobyteAbbr' => 'KB',
- 'bytes' => 'Bytes',
+ 'bytes' => 'Bytes',
+
// don't forget the space in front of these!
- 'thousand' => ' thousand',
- 'million' => ' million',
- 'billion' => ' billion',
- 'trillion' => ' trillion',
- 'quadrillion' => ' quadrillion',
+ 'thousand' => ' thousand',
+ 'million' => ' million',
+ 'billion' => ' billion',
+ 'trillion' => ' trillion',
+ 'quadrillion' => ' quadrillion',
];
diff --git a/system/Language/en/Pager.php b/system/Language/en/Pager.php
index ac09920c8a..340149b4e7 100644
--- a/system/Language/en/Pager.php
+++ b/system/Language/en/Pager.php
@@ -1,31 +1,7 @@
'Page navigation',
- 'first' => 'First',
- 'previous' => 'Previous',
- 'next' => 'Next',
- 'last' => 'Last',
- 'older' => 'Older',
- 'newer' => 'Newer',
+ 'pageNavigation' => 'Page navigation',
+ 'first' => 'First',
+ 'previous' => 'Previous',
+ 'next' => 'Next',
+ 'last' => 'Last',
+ 'older' => 'Older',
+ 'newer' => 'Newer',
+ 'invalidTemplate' => '{0} is not a valid Pager template.',
+ 'invalidPaginationGroup' => '{0} is not a valid Pagination group.',
];
diff --git a/system/Language/en/Router.php b/system/Language/en/Router.php
new file mode 100644
index 0000000000..969403197b
--- /dev/null
+++ b/system/Language/en/Router.php
@@ -0,0 +1,19 @@
+ 'A parameter does not match the expected type.',
+ 'missingDefaultRoute' => 'Unable to determine what should be displayed. A default route has not been specified in the routing file.',
+];
diff --git a/system/Language/en/Session.php b/system/Language/en/Session.php
new file mode 100644
index 0000000000..06beb720e5
--- /dev/null
+++ b/system/Language/en/Session.php
@@ -0,0 +1,22 @@
+ '`sessionSavePath` must have the table name for the Database Session Handler to work.',
+ 'invalidSavePath' => "Session: Configured save path '{0}' is not a directory, doesn't exist or cannot be created.",
+ 'writeProtectedSavePath' => "Session: Configured save path '{0}' is not writable by the PHP process.",
+ 'emptySavePath' => 'Session: No save path configured.',
+ 'invalidSavePathFormat' => 'Session: Invalid Redis save path format: {0}',
+];
diff --git a/system/Language/en/Time.php b/system/Language/en/Time.php
index 12fa336e3e..d27ca3ef09 100644
--- a/system/Language/en/Time.php
+++ b/system/Language/en/Time.php
@@ -1,21 +1,34 @@
'Months must be between 0 and 12.',
- 'invalidDay' => 'Days must be between 0 and 31.',
- 'invalidHours' => 'Hours must be between 0 and 23.',
- 'invalidMinutes' => 'Minutes must be between 0 and 59.',
- 'invalidSeconds' => 'Seconds must be between 0 and 59.',
- 'years' => '{0, plural, =1{# year} other{# years}}',
- 'months' => '{0, plural, =1{# month} other{# months}}',
- 'weeks' => '{0, plural, =1{# week} other{# weeks}}',
- 'days' => '{0, plural, =1{# day} other{# days}}',
- 'hours' => '{0, plural, =1{# hour} other{# hours}}',
- 'minutes' => '{0, plural, =1{# minute} other{# minutes}}',
- 'seconds' => '{0, plural, =1{# second} other{# seconds}}',
- 'ago' => '{0} ago',
- 'inFuture' => 'in {0}',
- 'yesterday' => 'Yesterday',
- 'tomorrow' => 'Tomorrow',
- 'now' => 'Just now',
+ 'invalidMonth' => 'Months must be between 0 and 12. Given: {0}',
+ 'invalidDay' => 'Days must be between 0 and 31. Given: {0}',
+ 'invalidHours' => 'Hours must be between 0 and 23. Given: {0}',
+ 'invalidMinutes' => 'Minutes must be between 0 and 59. Given: {0}',
+ 'invalidSeconds' => 'Seconds must be between 0 and 59. Given: {0}',
+ 'years' => '{0, plural, =1{# year} other{# years}}',
+ 'months' => '{0, plural, =1{# month} other{# months}}',
+ 'weeks' => '{0, plural, =1{# week} other{# weeks}}',
+ 'days' => '{0, plural, =1{# day} other{# days}}',
+ 'hours' => '{0, plural, =1{# hour} other{# hours}}',
+ 'minutes' => '{0, plural, =1{# minute} other{# minutes}}',
+ 'seconds' => '{0, plural, =1{# second} other{# seconds}}',
+ 'ago' => '{0} ago',
+ 'inFuture' => 'in {0}',
+ 'yesterday' => 'Yesterday',
+ 'tomorrow' => 'Tomorrow',
+ 'now' => 'Just now',
];
diff --git a/system/Language/en/Validation.php b/system/Language/en/Validation.php
index 8059e58864..74aaf5c449 100644
--- a/system/Language/en/Validation.php
+++ b/system/Language/en/Validation.php
@@ -1,31 +1,7 @@
'No rulesets specified in Validation configuration.',
- 'ruleNotFound' => '{rule} is not a valid rule.',
- 'groupNotFound' => '%s is not a validation rules group.',
- 'groupNotArray' => '%s rule group must be an array.',
+ 'ruleNotFound' => '{0} is not a valid rule.',
+ 'groupNotFound' => '{0} is not a validation rules group.',
+ 'groupNotArray' => '{0} rule group must be an array.',
+ 'invalidTemplate' => '{0} is not a valid Validation template.',
// Rule Messages
'alpha' => 'The {field} field may only contain alphabetical characters.',
'alpha_dash' => 'The {field} field may only contain alpha-numeric characters, underscores, and dashes.',
'alpha_numeric' => 'The {field} field may only contain alpha-numeric characters.',
- 'alpha_numeric_space' => 'The {field} field may only contain alpha-numeric characters and spaces.',
+ 'alpha_numeric_space' => 'The {field} field may only contain alpha-numeric characters and spaces.',
'alpha_space' => 'The {field} field may only contain alphabetical characters and spaces.',
'decimal' => 'The {field} field must contain a decimal number.',
'differs' => 'The {field} field must differ from the {param} field.',
@@ -68,7 +47,7 @@ return [
'regex_match' => 'The {field} field is not in the correct format.',
'required' => 'The {field} field is required.',
'required_with' => 'The {field} field is required when {param} is present.',
- 'required_without' => 'The {field} field is required when {param} in not present.',
+ 'required_without' => 'The {field} field is required when {param} is not present.',
'timezone' => 'The {field} field must be a valid timezone.',
'valid_base64' => 'The {field} field must be a valid base64 string.',
'valid_email' => 'The {field} field must contain a valid email address.',
@@ -87,5 +66,4 @@ return [
'mime_in' => '{field} does not have a valid mime type.',
'ext_in' => '{field} does not have a valid file extension.',
'max_dims' => '{field} is either not an image, or it is too wide or tall.',
- '',
];
diff --git a/system/Language/en/View.php b/system/Language/en/View.php
new file mode 100644
index 0000000000..4000850cef
--- /dev/null
+++ b/system/Language/en/View.php
@@ -0,0 +1,22 @@
+ '{class}::{method} is not a valid method."',
+ 'missingCellParameters' => '{class}::{method} has no params.',
+ 'invalidCellParameter' => '{0} is not a valid param name.',
+ 'noCellClass' => 'No view cell class provided.',
+ 'invalidCellClass' => 'Unable to locate view cell class: {0}.',
+ 'tagSyntaxError' => 'You have a syntax error in your Parser tags: {0}',
+];
diff --git a/system/Log/Exceptions/LogException.php b/system/Log/Exceptions/LogException.php
new file mode 100644
index 0000000000..1768efd76e
--- /dev/null
+++ b/system/Log/Exceptions/LogException.php
@@ -0,0 +1,14 @@
+levels))
+ if (array_key_exists($level, $this->levels))
{
$type = $this->levels[$level];
}
diff --git a/system/Log/Handlers/FileHandler.php b/system/Log/Handlers/FileHandler.php
index dc2b14a8a0..abbf05aabd 100644
--- a/system/Log/Handlers/FileHandler.php
+++ b/system/Log/Handlers/FileHandler.php
@@ -105,7 +105,7 @@ class FileHandler extends BaseHandler implements HandlerInterface
// Only add protection to php files
if ($this->fileExtension === 'php')
{
- $msg .= "\n\n";
+ $msg .= "\n\n";
}
}
diff --git a/system/Log/Logger.php b/system/Log/Logger.php
index 3d7688d4e6..4de39d1bc8 100644
--- a/system/Log/Logger.php
+++ b/system/Log/Logger.php
@@ -36,6 +36,7 @@
* @filesource
*/
use Psr\Log\LoggerInterface;
+use CodeIgniter\Log\Exceptions\LogException;
/**
* The CodeIgntier Logger
@@ -143,7 +144,7 @@ class Logger implements LoggerInterface
/**
* Constructor.
- *
+ *
* @param type $config
* @param bool $debug
* @throws \RuntimeException
@@ -170,7 +171,7 @@ class Logger implements LoggerInterface
if ( ! is_array($config->handlers) || empty($config->handlers))
{
- throw new \RuntimeException('LoggerConfig must provide at least one Handler.');
+ throw LogException::forNoHandlers('LoggerConfig');
}
// Save the handler configuration for later.
@@ -336,7 +337,7 @@ class Logger implements LoggerInterface
// Is the level a valid level?
if ( ! array_key_exists($level, $this->logLevels))
{
- throw new \InvalidArgumentException($level . ' is an invalid log level.');
+ throw LogException::forInvalidLogLevel($level);
}
// Does the app want to log this right now?
diff --git a/system/Model.php b/system/Model.php
index 29f82f8f7a..4669891ebe 100644
--- a/system/Model.php
+++ b/system/Model.php
@@ -35,6 +35,7 @@
* @since Version 3.0.0
* @filesource
*/
+use CodeIgniter\Database\Exceptions\DatabaseException;
use Config\App;
use Config\Database;
use CodeIgniter\I18n\Time;
@@ -44,7 +45,7 @@ use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\ConnectionInterface;
use CodeIgniter\Validation\ValidationInterface;
-use CodeIgniter\Database\Exceptions\DatabaseException;
+use CodeIgniter\Database\Exceptions\DataException;
/**
* Class Model
@@ -586,7 +587,7 @@ class Model
if (empty($data))
{
- throw new \InvalidArgumentException('No data to insert.');
+ throw DataException::forEmptyDataset('insert');
}
// Must use the set() method to ensure objects get converted to arrays
@@ -662,7 +663,7 @@ class Model
if (empty($data))
{
- throw new \InvalidArgumentException('No data to update.');
+ throw DataException::forEmptyDataset('update');
}
// Must use the set() method to ensure objects get converted to arrays
@@ -728,14 +729,14 @@ class Model
* @param bool $purge Allows overriding the soft deletes setting.
*
* @return mixed
- * @throws \CodeIgniter\Database\Exceptions\DatabaseException
+ * @throws \CodeIgniter\Database\Exceptions\DataException
*/
public function deleteWhere($key, $value = null, $purge = false)
{
// Don't let them shoot themselves in the foot...
if (empty($key))
{
- throw new DatabaseException('You must provided a valid key to deleteWhere.');
+ throw DataException::forInvalidArgument('key');
}
$this->trigger('beforeDelete', ['key' => $key, 'value' => $value, 'purge' => $purge]);
@@ -866,7 +867,7 @@ class Model
* @param int $size
* @param \Closure $userFunc
*
- * @throws \CodeIgniter\Database\Exceptions\DatabaseException
+ * @throws \CodeIgniter\Database\Exceptions\DataException
*/
public function chunk($size = 100, \Closure $userFunc)
{
@@ -883,7 +884,7 @@ class Model
if ($rows === false)
{
- throw new DatabaseException('Unable to get results from the query.');
+ throw DataException::forEmptyDataset('chunk');
}
$rows = $rows->getResult();
@@ -993,7 +994,7 @@ class Model
* @param array $data
*
* @return array
- * @throws \CodeIgniter\Database\Exceptions\DatabaseException
+ * @throws \CodeIgniter\Database\Exceptions\DataException
*/
protected function doProtectFields($data)
{
@@ -1004,7 +1005,7 @@ class Model
if (empty($this->allowedFields))
{
- throw new DatabaseException('No Allowed fields specified for model: ' . get_class($this));
+ throw DataException::forInvalidAllowedFields(get_class($this));
}
foreach ($data as $key => $val)
@@ -1150,7 +1151,11 @@ class Model
}
else
{
- $this->validation->setRules($this->validationRules, $this->validationMessages);
+ // Replace any placeholders (i.e. {id}) in the rules with
+ // the value found in $data, if exists.
+ $rules = $this->fillPlaceholders($this->validationRules, $data);
+
+ $this->validation->setRules($rules, $this->validationMessages);
$valid = $this->validation->run($data);
}
@@ -1159,6 +1164,57 @@ class Model
//--------------------------------------------------------------------
+ /**
+ * Replace any placeholders within the rules with the values that
+ * match the 'key' of any properties being set. For example, if
+ * we had the following $data array:
+ *
+ * [ 'id' => 13 ]
+ *
+ * and the following rule:
+ *
+ * 'required|is_unique[users,email,id,{id}]'
+ *
+ * The value of {id} would be replaced with the actual id in the form data:
+ *
+ * 'required|is_unique[users,email,id,13]'
+ *
+ * @param array $rules
+ * @param array $data
+ *
+ * @return array
+ */
+ protected function fillPlaceholders(array $rules, array $data)
+ {
+ $replacements = [];
+
+ foreach ($data as $key => $value)
+ {
+ $replacements["{{$key}}"] = $value;
+ }
+
+ if (! empty($replacements))
+ {
+ foreach ($rules as &$rule)
+ {
+ if (is_array($rule))
+ {
+ foreach ($rule as &$row)
+ {
+ $row = strtr($row, $replacements);
+ }
+ continue;
+ }
+
+ $rule = strtr($rule, $replacements);
+ }
+ }
+
+ return $rules;
+ }
+
+ //--------------------------------------------------------------------
+
/**
* Returns the model's defined validation rules so that they
* can be used elsewhere, if needed.
@@ -1213,6 +1269,7 @@ class Model
* @param array $data
*
* @return mixed
+ * @throws \CodeIgniter\Database\Exceptions\DataException
*/
protected function trigger(string $event, array $data)
{
@@ -1226,7 +1283,7 @@ class Model
{
if ( ! method_exists($this, $callback))
{
- throw new \BadMethodCallException(lang('Database.invalidEvent', [$callback]));
+ throw DataException::forInvalidMethodTriggered($callback);
}
$data = $this->{$callback}($data);
@@ -1279,11 +1336,11 @@ class Model
if (method_exists($this->db, $name))
{
- $result = call_user_func_array([$this->db, $name], $params);
+ $result = $this->db->$name(...$params);
}
- elseif (method_exists($this->builder(), $name))
+ elseif (method_exists($builder = $this->builder(), $name))
{
- $result = call_user_func_array([$this->builder(), $name], $params);
+ $result = $builder->$name(...$params);
}
// Don't return the builder object unless specifically requested
diff --git a/system/Pager/Exceptions/PagerException.php b/system/Pager/Exceptions/PagerException.php
new file mode 100644
index 0000000000..239d7939e9
--- /dev/null
+++ b/system/Pager/Exceptions/PagerException.php
@@ -0,0 +1,17 @@
+config->templates))
{
- throw new \InvalidArgumentException($template . ' is not a valid Pager template.');
+ throw PagerException::forInvalidTemplate($template);
}
return $this->view->setVar('pager', $pager)
@@ -295,11 +303,11 @@ class Pager implements PagerInterface
/**
* Returns the URI for a specific page for the specified group.
*
- * @param int $page
- * @param string $group
- * @param bool $returnObject
+ * @param int|null $page
+ * @param string $group
+ * @param bool $returnObject
*
- * @return string
+ * @return string|\CodeIgniter\HTTP\URI
*/
public function getPageURI(int $page = null, string $group = 'default', $returnObject = false)
{
@@ -307,7 +315,18 @@ class Pager implements PagerInterface
$uri = $this->groups[$group]['uri'];
- $uri->addQuery('page', $page);
+ if ($this->only)
+ {
+ $query = array_intersect_key($_GET, array_flip($this->only));
+
+ $query['page'] = $page;
+
+ $uri->setQueryArray($query);
+ }
+ else
+ {
+ $uri->addQuery('page', $page);
+ }
return $returnObject === true ? $uri : (string) $uri;
}
@@ -406,7 +425,7 @@ class Pager implements PagerInterface
{
if ( ! array_key_exists($group, $this->groups))
{
- throw new \InvalidArgumentException($group . ' is not a valid Pagination group.');
+ throw PagerException::forInvalidPaginationGroup($group);
}
$newGroup = $this->groups[$group];
@@ -419,6 +438,22 @@ class Pager implements PagerInterface
//--------------------------------------------------------------------
+ /**
+ * Sets only allowed queries on pagination links.
+ *
+ * @param array $queries
+ *
+ * @return Pager
+ */
+ public function only(array $queries):Pager
+ {
+ $this->only = $queries;
+
+ return $this;
+ }
+
+ //--------------------------------------------------------------------
+
/**
* Ensures that an array exists for the group specified.
*
diff --git a/system/Pager/PagerRenderer.php b/system/Pager/PagerRenderer.php
index 26d7377aff..66e60d0e55 100644
--- a/system/Pager/PagerRenderer.php
+++ b/system/Pager/PagerRenderer.php
@@ -74,11 +74,11 @@ class PagerRenderer
* side of the current page. Adjusts the first and last counts
* to reflect it.
*
- * @param int $count
+ * @param int|null $count
*
* @return PagerRenderer
*/
- public function setSurroundCount(int $count)
+ public function setSurroundCount(int $count = null)
{
$this->updatePages($count);
@@ -226,7 +226,7 @@ class PagerRenderer
* which is the number of links surrounding the active page
* to show.
*
- * @param int|null $count
+ * @param int|null $count The new "surroundCount"
*/
protected function updatePages(int $count = null)
{
diff --git a/system/Pager/Views/default_full.php b/system/Pager/Views/default_full.php
index 5f57afcfe8..4eb10aa792 100644
--- a/system/Pager/Views/default_full.php
+++ b/system/Pager/Views/default_full.php
@@ -3,16 +3,16 @@