From ed178ca631b26825d5adaa335f504181a7e4b7da Mon Sep 17 00:00:00 2001 From: Alex Tselegidis Date: Wed, 23 Feb 2022 09:07:21 +0100 Subject: [PATCH] PHP 8.1 support (#1209) --- application/libraries/Availability.php | 4 +- application/libraries/Ics_calendar.php | 422 ++++++++++++++++++ application/libraries/Ics_file.php | 33 +- application/libraries/Ics_provider.php | 175 ++++++++ composer.json | 6 - composer.lock | 42 +- .../Session/drivers/Session_files_driver.php | 16 +- 7 files changed, 658 insertions(+), 40 deletions(-) create mode 100644 application/libraries/Ics_calendar.php create mode 100644 application/libraries/Ics_provider.php diff --git a/application/libraries/Availability.php b/application/libraries/Availability.php index 204a03e2..0b775832 100644 --- a/application/libraries/Availability.php +++ b/application/libraries/Availability.php @@ -89,7 +89,9 @@ class Availability { $working_plan = json_decode($provider['settings']['working_plan'], TRUE); // Get the provider's working plan exceptions. - $working_plan_exceptions = json_decode($provider['settings']['working_plan_exceptions'], TRUE); + $working_plan_exceptions_json = $provider['settings']['working_plan_exceptions']; + + $working_plan_exceptions = $working_plan_exceptions_json ? json_decode($provider['settings']['working_plan_exceptions'], TRUE) : NULL; $conditions = [ 'id_users_provider' => $provider['id'], diff --git a/application/libraries/Ics_calendar.php b/application/libraries/Ics_calendar.php new file mode 100644 index 00000000..932fd12f --- /dev/null +++ b/application/libraries/Ics_calendar.php @@ -0,0 +1,422 @@ + + * @copyright Copyright (c) 2013 - 2020, Alex Tselegidis + * @license http://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link http://easyappointments.org + * @since v1.4.3 + * ---------------------------------------------------------------------------- */ + +use Jsvrcek\ICS\Model\Calendar; +use Jsvrcek\ICS\Model\CalendarEvent; +use Jsvrcek\ICS\Model\CalendarFreeBusy; +use Jsvrcek\ICS\Model\CalendarTodo; +use Jsvrcek\ICS\Utility\Provider; + +/** + * Class Ics_calendar + * + * This class replaces the Jsvrcek\ICS\Model\Calendar so that it uses the new Ics_provider instances, which is + * compatible to PHP 8.1. + * + * There is no other change to the original file. + */ +class Ics_calendar extends Calendar { + /** + * @var string + */ + private $version = '2.0'; + + /** + * @var string + */ + private $prodId = ''; + + /** + * @var string + */ + private $name = ''; + + /** + * @var string + */ + private $calendarScale = 'GREGORIAN'; + + /** + * @var string + */ + private $method = 'PUBLISH'; + + /** + * @var array + */ + private $image = []; + + /** + * @var array + */ + private $customHeaders = []; + + /** + * @var \DateTimeZone + */ + private $timezone; + + /** + * @var Provider + */ + private $events; + + /** + * @var Provider + */ + private $todos; + + /** + * @var Provider + */ + private $freeBusy; + + /** + * @var string + */ + private $color; + + /** + * Calendar constructor. + */ + public function __construct() + { + $this->timezone = new \DateTimeZone('America/New_York'); + $this->events = new Ics_provider(); + $this->todos = new Ics_provider(); + $this->freeBusy = new Ics_provider(); + } + + /** + * For use if you want CalendarExport::getStream to get events in batches from a database during + * the output of the ics feed, instead of adding all events to the Calendar object before outputting + * the ics feed. + * - CalendarExport::getStream iterates through the Calendar::$events internal data array. The $eventsProvider + * closure will be called every time this data array reaches its end during iteration, and the closure should + * return the next batch of events + * - A $startKey argument with the current key of the data array will be passed to the $eventsProvider closure + * - The $eventsProvider must return an array of CalendarEvent objects + * + * Example: Calendar::setEventsProvider(function($startKey){ + * //get database rows starting with $startKey + * //return an array of CalendarEvent objects + * }) + * + * @param \Closure $eventsProvider + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function setEventsProvider(\Closure $eventsProvider) + { + $this->events = new Ics_provider($eventsProvider); + return $this; + } + + /** + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * @param string $version + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function setVersion($version) + { + $this->version = $version; + return $this; + } + + /** + * @return string + */ + public function getProdId() + { + return $this->prodId; + } + + /** + * @param string $prodId + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function setProdId($prodId) + { + $this->prodId = $prodId; + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the RFC-7986 "Name" field for the calendar + * + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getCalendarScale() + { + return $this->calendarScale; + } + + /** + * @param string $calendarScale + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function setCalendarScale($calendarScale) + { + $this->calendarScale = $calendarScale; + return $this; + } + + /** + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * @param string $method + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function setMethod($method) + { + $this->method = $method; + return $this; + } + + /** + * @return array + */ + public function getImage() + { + return $this->image; + } + + /** + * Images can come in one of two formats: + * 1: URI - where a URI to the relevant image is provided + * 2: BINARY - Where a Binary representation of the image is provided, normally Base64 Encoded. + * + * If sending a URI for the image, set the "VALUE" key to be "URI" and provide a URI key with the relevant URI. + * IE: + * $calendar->setImage( + * 'VALUE' => 'URL', + * 'URI' => 'https://some.domain.com/path/to/image.jpg' + * ); + * It is optional to add a FMTTYPE key as well in the array, to indicate relevant mime type. + * IE: 'FMTTYPE' => 'image/jpg' + * + * When sending Binary version, you must provide the encoding type of the image, as well as the encoded string. + * IE: + * $calendar->setImage( + * 'VALUE' => 'BINARY', + * 'ENCODING' => 'BASE64', + * 'BINARY' => $base64_encoded_string + * ); + * For Binary, it is RECOMMENDED to add the FMTTYPE as well, but still not REQUIRED + * + * @param array $image + */ + public function setImage($image) + { + // Do some validation on provided data. + if (array_key_exists('VALUE', $image) && in_array($image['VALUE'], ['URI', 'BINARY'])) + { + if ($image['VALUE'] == 'URI' && $image['URI']) + { + $new_image = [ + 'VALUE' => 'URI', + 'URI' => $image['URI'] + ]; + + } + elseif ($image['VALUE'] == 'BINARY' && $image['ENCODING'] && $image['BINARY']) + { + $new_image = [ + 'VALUE' => 'BINARY', + 'ENCODING' => $image['ENCODING'], + 'BINARY' => $image['BINARY'] + ]; + } + else + { + return; + } + $new_image['DISPLAY'] = isset($image['DISPLAY']) ? $image['DISPLAY'] : ''; + $new_image['FMTTYPE'] = isset($image['FMTTYPE']) ? $image['FMTTYPE'] : ''; + $this->image = $new_image; + } + } + + /** + * @return array + */ + public function getCustomHeaders() + { + return $this->customHeaders; + } + + /** + * use to add custom headers as array key-value pairs
+ * Example: $customHeaders = array('X-WR-TIMEZONE' => 'America/New_York') + * + * @param array $customHeaders + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function setCustomHeaders(array $customHeaders) + { + $this->customHeaders = $customHeaders; + return $this; + } + + /** + * @param string $key + * @param string $value + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function addCustomHeader($key, $value) + { + $this->customHeaders[$key] = $value; + return $this; + } + + /** + * @return \DateTimeZone + */ + public function getTimezone() + { + return $this->timezone; + } + + /** + * @param \DateTimeZone $timezone + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function setTimezone(\DateTimeZone $timezone) + { + $this->timezone = $timezone; + return $this; + } + + /** + * @return Provider + */ + public function getEvents() + { + return $this->events; + } + + /** + * @param CalendarEvent $event + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function addEvent(CalendarEvent $event) + { + $this->events->add($event); + return $this; + } + + /** + * @return Provider returs array of CalendarTodo objects + */ + public function getTodos() + { + return $this->todos; + } + + /** + * @param CalendarTodo $todo + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function addTodo(CalendarTodo $todo) + { + $this->todos[] = $todo; + return $this; + } + + /** + * @param array $todos + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function setTodos(array $todos) + { + $this->todos = $todos; + return $this; + } + + /** + * @return Provider returs array of CalendarFreeBusy objects + */ + public function getFreeBusy() + { + return $this->freeBusy; + } + + /** + * @param CalendarFreeBusy $todo + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function addFreeBusy(CalendarFreeBusy $todo) + { + $this->freeBusy[] = $todo; + return $this; + } + + /** + * @param array $freeBusy + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function setFreeBusy(array $freeBusy) + { + $this->freeBusy = $freeBusy; + return $this; + } + + /** + * @return string + */ + public function getColor() + { + return $this->color; + } + + + /** + * Set color as CSS3 string + * + * @param string $color + * @return \Jsvrcek\ICS\Model\Calendar + */ + public function setColor($color) + { + $this->color = $color; + return $this; + } +} diff --git a/application/libraries/Ics_file.php b/application/libraries/Ics_file.php index bfdcea95..5024a69f 100644 --- a/application/libraries/Ics_file.php +++ b/application/libraries/Ics_file.php @@ -14,7 +14,6 @@ use Jsvrcek\ICS\CalendarExport; use Jsvrcek\ICS\CalendarStream; use Jsvrcek\ICS\Exception\CalendarEventException; -use Jsvrcek\ICS\Model\Calendar; use Jsvrcek\ICS\Model\CalendarAlarm; use Jsvrcek\ICS\Model\CalendarEvent; use Jsvrcek\ICS\Model\Description\Location; @@ -25,12 +24,30 @@ use Jsvrcek\ICS\Utility\Formatter; /** * Class Ics_file * - * An ICS file is a calendar file saved in a universal calendar format used by several email and calendar programs, - * including Microsoft Outlook, Google Calendar, and Apple Calendar. + * An ICS file is a calendar file saved in a universal calendar format used by email and calendar clients, including + * Microsoft Outlook, Google Calendar, and Apple Calendar. * * Depends on the Jsvrcek\ICS composer package. + * + * Notice: The Ics_calendar and Ics_provider classes are used for PHP 8.1 compatibility. */ class Ics_file { + /** + * @var EA_Controller + */ + protected $CI; + + /** + * Availability constructor. + */ + public function __construct() + { + $this->CI =& get_instance(); + + $this->CI->load->library('ics_provider'); + $this->CI->load->library('ics_calendar'); + } + /** * Get the ICS file contents for the provided arguments. * @@ -46,7 +63,7 @@ class Ics_file { */ public function get_stream($appointment, $service, $provider, $customer) { - $appointment_timezone = new DateTimeZone($provider['timezone']); + $appointment_timezone = new DateTimeZone($provider['timezone']); $appointment_start = new DateTime($appointment['start_datetime'], $appointment_timezone); $appointment_end = new DateTime($appointment['end_datetime'], $appointment_timezone); @@ -61,7 +78,7 @@ class Ics_file { ->setSummary($service['name']) ->setUid($appointment['id']); - if (!empty($service['location'])) + if ( ! empty($service['location'])) { $location = new Location(); $location->setName((string)$service['location']); @@ -73,7 +90,7 @@ class Ics_file { lang('provider'), '', lang('name') . ': ' . $provider['first_name'] . ' ' . $provider['last_name'], - lang('email') .': ' . $provider['email'], + lang('email') . ': ' . $provider['email'], lang('phone_number') . ': ' . $provider['phone_number'], lang('address') . ': ' . $provider['address'], lang('city') . ': ' . $provider['city'], @@ -82,7 +99,7 @@ class Ics_file { lang('customer'), '', lang('name') . ': ' . $customer['first_name'] . ' ' . $customer['last_name'], - lang('email') .': ' . $customer['email'], + lang('email') . ': ' . $customer['email'], lang('phone_number') . ': ' . $customer['phone_number'], lang('address') . ': ' . $customer['address'], lang('city') . ': ' . $customer['city'], @@ -152,7 +169,7 @@ class Ics_file { $event->setOrganizer($organizer); // Setup calendar. - $calendar = new Calendar(); + $calendar = new Ics_calendar(); $calendar ->setProdId('-//EasyAppointments//Open Source Web Scheduler//EN') diff --git a/application/libraries/Ics_provider.php b/application/libraries/Ics_provider.php new file mode 100644 index 00000000..717f4e58 --- /dev/null +++ b/application/libraries/Ics_provider.php @@ -0,0 +1,175 @@ + + * @copyright Copyright (c) 2013 - 2020, Alex Tselegidis + * @license http://opensource.org/licenses/GPL-3.0 - GPLv3 + * @link http://easyappointments.org + * @since v1.4.3 + * ---------------------------------------------------------------------------- */ + +/** + * Class Ics_calendar + * + * This class replaces the Jsvrcek\ICS\Utility\Provider so that it becomes a PHP 8.1 compatible Iterator class. + * + * Since the method signatures changed in PHP 8.1, the ReturnTypeWillChange attribute allows us to keep compatibility + * between different PHP versions. + */ +class Ics_provider implements \Iterator { + /** + * @var \Closure + */ + private $provider; + + /** + * @var array + */ + public $data = []; + + /** + * @var array + */ + public $manuallyAddedData = []; + + /** + * @var integer + */ + private $key; + + /** + * @var mixed + */ + private $first; + + /** + * @param \Closure $provider An optional closure for adding items in batches during iteration. The closure will be + * called each time the end of the internal data array is reached during iteration, and the current data + * array key value will be passed as an argument. The closure should return an array containing the next + * batch of items. + */ + public function __construct(\Closure $provider = NULL) + { + $this->provider = $provider; + } + + /** + * for manually adding items, rather than using a provider closure to add items in batches during iteration + * Cannot be used in conjunction with a provider closure! + * + * @param mixed $item + * @return void + */ + #[ReturnTypeWillChange] + public function add($item) + { + $this->manuallyAddedData[] = $item; + } + + /** + * @return false|mixed + * @see Iterator::current() + */ + #[ReturnTypeWillChange] + public function current() + { + return current($this->data); + } + + /** + * @return float|int|null + * @see Iterator::key() + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->key; + } + + /** + * @return void + * @see Iterator::next() + */ + #[ReturnTypeWillChange] + public function next() + { + array_shift($this->data); + $this->key++; + } + + /** + * @return void + * @see Iterator::rewind() + */ + #[ReturnTypeWillChange] + public function rewind() + { + $this->data = []; + $this->key = 0; + } + + /** + * get next batch from provider if data array is at the end + * + * @return bool + * @see Iterator::valid() + */ + #[ReturnTypeWillChange] + public function valid() + { + if (count($this->data) < 1) + { + if ($this->provider instanceof \Closure) + { + $this->data = $this->provider->__invoke($this->key); + if (isset($this->data[0])) + { + $this->first = $this->data[0]; + } + } + else + { + $this->data = $this->manuallyAddedData; + $this->manuallyAddedData = []; + } + } + + return count($this->data) > 0; + } + + /** + * Returns first event + * + * @return false|mixed + */ + #[ReturnTypeWillChange] + public function first() + { + if (isset($this->first)) + { + return $this->first; + } + + if ($this->provider instanceof \Closure) + { + if ($this->valid()) + { + return $this->first; + } + else + { + return FALSE; + } + } + + if ( ! isset($this->manuallyAddedData[0])) + { + return FALSE; + } + + return $this->manuallyAddedData[0]; + } +} diff --git a/composer.json b/composer.json index 761927eb..9faad569 100644 --- a/composer.json +++ b/composer.json @@ -31,12 +31,6 @@ "EA\\Engine\\": "engine/" } }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/alextselegidis/CodeIgniter.git" - } - ], "require": { "php": ">=7.3", "ext-curl": "*", diff --git a/composer.lock b/composer.lock index 75ca9d88..b3c6c21b 100644 --- a/composer.lock +++ b/composer.lock @@ -65,16 +65,16 @@ }, { "name": "google/apiclient", - "version": "v2.10.1", + "version": "v2.12.1", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client.git", - "reference": "11871e94006ce7a419bb6124d51b6f9ace3f679b" + "reference": "1530583a711f4414407112c4068464bcbace1c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/11871e94006ce7a419bb6124d51b6f9ace3f679b", - "reference": "11871e94006ce7a419bb6124d51b6f9ace3f679b", + "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/1530583a711f4414407112c4068464bcbace1c71", + "reference": "1530583a711f4414407112c4068464bcbace1c71", "shasum": "" }, "require": { @@ -82,8 +82,8 @@ "google/apiclient-services": "~0.200", "google/auth": "^1.10", "guzzlehttp/guzzle": "~5.3.3||~6.0||~7.0", - "guzzlehttp/psr7": "^1.2", - "monolog/monolog": "^1.17|^2.0", + "guzzlehttp/psr7": "^1.7||^2.0.0", + "monolog/monolog": "^1.17||^2.0", "php": "^5.6|^7.0|^8.0", "phpseclib/phpseclib": "~2.0||^3.0.2" }, @@ -92,10 +92,12 @@ "composer/composer": "^1.10.22", "dealerdirect/phpcodesniffer-composer-installer": "^0.7", "phpcompatibility/php-compatibility": "^9.2", - "phpunit/phpunit": "^5.7||^8.5.13", + "phpspec/prophecy-phpunit": "^1.1||^2.0", + "phpunit/phpunit": "^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0", "squizlabs/php_codesniffer": "~2.3", "symfony/css-selector": "~2.1", - "symfony/dom-crawler": "~2.1" + "symfony/dom-crawler": "~2.1", + "yoast/phpunit-polyfills": "^1.0" }, "suggest": { "cache/filesystem-adapter": "For caching certs and tokens (using Google\\Client::setCache)" @@ -103,16 +105,16 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-main": "2.x-dev" } }, "autoload": { - "psr-4": { - "Google\\": "src/" - }, "files": [ "src/aliases.php" ], + "psr-4": { + "Google\\": "src/" + }, "classmap": [ "src/aliases.php" ] @@ -128,9 +130,9 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client/issues", - "source": "https://github.com/googleapis/google-api-php-client/tree/v2.10.1" + "source": "https://github.com/googleapis/google-api-php-client/tree/v2.12.1" }, - "time": "2021-06-25T14:25:44+00:00" + "time": "2021-12-02T03:34:25+00:00" }, { "name": "google/apiclient-services", @@ -525,16 +527,16 @@ }, { "name": "jsvrcek/ics", - "version": "0.8", + "version": "0.8.1", "source": { "type": "git", "url": "https://github.com/jasvrcek/ICS.git", - "reference": "fce6d1f1af977b0650bfa630f2252d2b8ec1f0dc" + "reference": "161ecc642c573785e305477aebdcd491cb9f9526" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jasvrcek/ICS/zipball/fce6d1f1af977b0650bfa630f2252d2b8ec1f0dc", - "reference": "fce6d1f1af977b0650bfa630f2252d2b8ec1f0dc", + "url": "https://api.github.com/repos/jasvrcek/ICS/zipball/161ecc642c573785e305477aebdcd491cb9f9526", + "reference": "161ecc642c573785e305477aebdcd491cb9f9526", "shasum": "" }, "require": { @@ -571,9 +573,9 @@ ], "support": { "issues": "https://github.com/jasvrcek/ICS/issues", - "source": "https://github.com/jasvrcek/ICS/tree/0.8" + "source": "https://github.com/jasvrcek/ICS/tree/0.8.1" }, - "time": "2021-01-12T15:30:40+00:00" + "time": "2022-02-02T16:40:24+00:00" }, { "name": "monolog/monolog", diff --git a/system/libraries/Session/drivers/Session_files_driver.php b/system/libraries/Session/drivers/Session_files_driver.php index 49bf5b78..ecbf6093 100644 --- a/system/libraries/Session/drivers/Session_files_driver.php +++ b/system/libraries/Session/drivers/Session_files_driver.php @@ -129,6 +129,7 @@ class CI_Session_files_driver extends CI_Session_driver implements SessionHandle * @param string $name Session cookie name * @return bool */ + #[ReturnTypeWillChange] public function open($save_path, $name) { if ( ! is_dir($save_path)) @@ -165,7 +166,8 @@ class CI_Session_files_driver extends CI_Session_driver implements SessionHandle * @param string $session_id Session ID * @return string Serialized session data */ - public function read($session_id) + #[ReturnTypeWillChange] + public function read($session_id) { // This might seem weird, but PHP 5.6 introduces session_reset(), // which re-reads session data @@ -238,7 +240,8 @@ class CI_Session_files_driver extends CI_Session_driver implements SessionHandle * @param string $session_data Serialized session data * @return bool */ - public function write($session_id, $session_data) + #[ReturnTypeWillChange] + public function write($session_id, $session_data) { // If the two IDs don't match, we have a session_regenerate_id() call // and we need to close the old handle and open a new one @@ -295,7 +298,8 @@ class CI_Session_files_driver extends CI_Session_driver implements SessionHandle * * @return bool */ - public function close() + #[ReturnTypeWillChange] + public function close() { if (is_resource($this->_file_handle)) { @@ -318,7 +322,8 @@ class CI_Session_files_driver extends CI_Session_driver implements SessionHandle * @param string $session_id Session ID * @return bool */ - public function destroy($session_id) + #[ReturnTypeWillChange] + public function destroy($session_id) { if ($this->close() === $this->_success) { @@ -359,7 +364,8 @@ class CI_Session_files_driver extends CI_Session_driver implements SessionHandle * @param int $maxlifetime Maximum lifetime of sessions * @return bool */ - public function gc($maxlifetime) + #[ReturnTypeWillChange] + public function gc($maxlifetime) { if ( ! is_dir($this->_config['save_path']) OR ($directory = opendir($this->_config['save_path'])) === FALSE) {