mirror of
				https://github.com/dwaxweiler/connector-mobilizon
				synced 2025-06-05 21:59:25 +02:00 
			
		
		
		
	move requests to backend (#18)
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -3,3 +3,6 @@ build/ | ||||
| coverage/ | ||||
| node_modules/ | ||||
| vendor/ | ||||
|  | ||||
| .phpunit.cache | ||||
| .phpunit.result.cache | ||||
|   | ||||
| @@ -47,3 +47,4 @@ The current changelog can be found under [source/changelog.txt](source/changelog | ||||
| - Update PHP dependencies: `composer update` | ||||
| - Check for direct PHP dependency updates: `composer outdated --direct` | ||||
| - Format code with prettier: `npm run format` | ||||
| - Generate `vendor/autoload.php` file after creating new class: `composer dump-autoload` | ||||
|   | ||||
| @@ -1 +1,10 @@ | ||||
| {} | ||||
| { | ||||
|   "autoload": { | ||||
|     "psr-4": { | ||||
|       "MobilizonConnector\\": "source/includes/" | ||||
|     } | ||||
|   }, | ||||
|   "require-dev": { | ||||
|     "phpunit/phpunit": "^9.6" | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										1738
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1738
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -11,9 +11,8 @@ function injectMetadata() { | ||||
|   return src( | ||||
|     [ | ||||
|       FOLDER_BUILD + '/front/block-events-loader.js', | ||||
|       FOLDER_BUILD + '/front/events-loader.js', | ||||
|       FOLDER_BUILD + '/' + PACKAGE.name + '.php', | ||||
|       FOLDER_BUILD + '/includes/constants.php', | ||||
|       FOLDER_BUILD + '/includes/Constants.php', | ||||
|       FOLDER_BUILD + '/readme.txt', | ||||
|     ], | ||||
|     { base: './' }, | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
|     "eslint": "npx eslint source/**/*.js", | ||||
|     "format": "npx prettier --write .", | ||||
|     "prepare": "husky install", | ||||
|     "test": "ava" | ||||
|     "test": "ava && ./vendor/bin/phpunit" | ||||
|   }, | ||||
|   "author": { | ||||
|     "name": "Daniel Waxweiler", | ||||
|   | ||||
							
								
								
									
										27
									
								
								phpunit.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								phpunit.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd" | ||||
|          bootstrap="vendor/autoload.php" | ||||
|          cacheResultFile=".phpunit.cache/test-results" | ||||
|          executionOrder="depends,defects" | ||||
|          forceCoversAnnotation="false" | ||||
|          beStrictAboutCoversAnnotation="true" | ||||
|          beStrictAboutOutputDuringTests="true" | ||||
|          beStrictAboutTodoAnnotatedTests="true" | ||||
|          convertDeprecationsToExceptions="true" | ||||
|          failOnRisky="true" | ||||
|          failOnWarning="true" | ||||
|          verbose="true"> | ||||
|     <testsuites> | ||||
|         <testsuite name="default"> | ||||
|             <directory>tests</directory> | ||||
|         </testsuite> | ||||
|     </testsuites> | ||||
|  | ||||
|     <coverage cacheDirectory=".phpunit.cache/code-coverage" | ||||
|               processUncoveredFiles="true"> | ||||
|         <include> | ||||
|             <directory suffix=".php">source/includes</directory> | ||||
|         </include> | ||||
|     </coverage> | ||||
| </phpunit> | ||||
| @@ -1,9 +1,12 @@ | ||||
| ### [Unreleased] | ||||
| #### Added | ||||
| - Display name of group when it cannot be found | ||||
| #### Changed | ||||
| - Let backend do requests to API of Mobilizon instance for increased privacy | ||||
| #### Deprecated | ||||
| #### Removed | ||||
| #### Fixed | ||||
| - Fix displaying more than one block in the editor | ||||
| #### Security | ||||
|  | ||||
| ### [0.11.5] | ||||
|   | ||||
| @@ -10,11 +10,18 @@ | ||||
|  * License:           <wordpress-license> | ||||
|  */ | ||||
|  | ||||
| require_once __DIR__ . '/includes/constants.php'; | ||||
| require_once __DIR__ . '/includes/settings.php'; | ||||
| require_once __DIR__ . '/includes/events-list-block.php'; | ||||
| require_once __DIR__ . '/includes/events-list-shortcut.php'; | ||||
| require_once __DIR__ . '/includes/events-list-widget.php'; | ||||
| require_once __DIR__ . '/includes/exceptions/GeneralException.php'; | ||||
| require_once __DIR__ . '/includes/exceptions/GroupNotFoundException.php'; | ||||
| require_once __DIR__ . '/includes/Constants.php'; | ||||
| require_once __DIR__ . '/includes/Api.php'; | ||||
| require_once __DIR__ . '/includes/EventsCache.php'; | ||||
| require_once __DIR__ . '/includes/Settings.php'; | ||||
| require_once __DIR__ . '/includes/DateTimeWrapper.php'; | ||||
| require_once __DIR__ . '/includes/Formatter.php'; | ||||
| require_once __DIR__ . '/includes/GraphQlClient.php'; | ||||
| require_once __DIR__ . '/includes/EventsListBlock.php'; | ||||
| require_once __DIR__ . '/includes/EventsListShortcut.php'; | ||||
| require_once __DIR__ . '/includes/EventsListWidget.php'; | ||||
|  | ||||
| // Exit if this file is called directly. | ||||
| if (!defined('ABSPATH')) { | ||||
| @@ -24,11 +31,11 @@ if (!defined('ABSPATH')) { | ||||
| final class Mobilizon_Connector { | ||||
|  | ||||
|   private function __construct() { | ||||
|     add_action('init', [$this, 'register_api']); | ||||
|     add_action('init', [$this, 'register_blocks']); | ||||
|     add_action('init', [$this, 'register_settings'], 1); // required for register_blocks | ||||
|     add_action('init', [$this, 'register_shortcut']); | ||||
|     add_action('widgets_init', [$this, 'register_widget']); | ||||
|     add_action('wp_enqueue_scripts', [$this, 'register_scripts']); | ||||
|     register_activation_hook(__FILE__, [$this, 'enable_activation']); | ||||
|   } | ||||
|  | ||||
| @@ -49,12 +56,15 @@ final class Mobilizon_Connector { | ||||
|     $settings = array( | ||||
|       'isShortOffsetNameShown' => MobilizonConnector\Settings::isShortOffsetNameShown(), | ||||
|       'locale' => str_replace('_', '-', get_locale()), | ||||
|       'timeZone' => wp_timezone_string(), | ||||
|       'url' => MobilizonConnector\Settings::getUrl() | ||||
|       'timeZone' => wp_timezone_string() | ||||
|     ); | ||||
|     wp_add_inline_script($scriptName, 'var MOBILIZON_CONNECTOR = ' . json_encode($settings), 'before'); | ||||
|   } | ||||
|  | ||||
|   public function register_api() { | ||||
|     MobilizonConnector\Api::init(); | ||||
|   } | ||||
|  | ||||
|   public function register_blocks() { | ||||
|     $scriptName = MobilizonConnector\EventsListBlock::initAndReturnScriptName(); | ||||
|     $this->load_settings_globally_before_script($scriptName); | ||||
| @@ -64,12 +74,6 @@ final class Mobilizon_Connector { | ||||
|     MobilizonConnector\Settings::init(); | ||||
|   } | ||||
|  | ||||
|   public function register_scripts() { | ||||
|     $name = MobilizonConnector\NAME . '-js'; | ||||
|     wp_enqueue_script($name, plugins_url('front/events-loader.js', __FILE__ )); | ||||
|     $this->load_settings_globally_before_script($name); | ||||
|   } | ||||
|  | ||||
|   public function register_shortcut() { | ||||
|     MobilizonConnector\EventsListShortcut::init(); | ||||
|   } | ||||
|   | ||||
| @@ -1,75 +1,101 @@ | ||||
| /* eslint-disable @wordpress/i18n-ellipsis */ | ||||
| import { loadEventList } from '../../events-loader.js' | ||||
| import { | ||||
|   clearEventsList, | ||||
|   displayErrorMessage, | ||||
|   displayEvents, | ||||
|   hideErrorMessages, | ||||
|   showLoadingIndicator, | ||||
| } from '../../events-displayer.js' | ||||
|  | ||||
| const { InspectorControls, useBlockProps } = wp.blockEditor | ||||
| const { PanelBody } = wp.components | ||||
| const { Panel, PanelBody } = wp.components | ||||
| const { useEffect } = wp.element | ||||
| const { __ } = wp.i18n | ||||
|  | ||||
| const NAME = '<wordpress-name>' | ||||
|  | ||||
| let timer | ||||
|  | ||||
| export default ({ attributes, setAttributes }) => { | ||||
|   let timer | ||||
|   const blockProps = useBlockProps({ | ||||
|     className: NAME + '_events-list', | ||||
|     'data-maximum': attributes.eventsCount, | ||||
|     'data-group-name': attributes.groupName, | ||||
|   }) | ||||
|   function reloadEventList() { | ||||
|   function reloadEventList(eventsCount, groupName) { | ||||
|     if (timer) { | ||||
|       clearTimeout(timer) | ||||
|     } | ||||
|     timer = setTimeout(() => { | ||||
|     timer = setTimeout(async () => { | ||||
|       const container = document.getElementById(blockProps.id) | ||||
|       if (container) { | ||||
|         loadEventList(container) | ||||
|         hideErrorMessages(container) | ||||
|         clearEventsList(container) | ||||
|         showLoadingIndicator(container) | ||||
|         let url = `/wp-json/connector-mobilizon/v1/events?eventsCount=${eventsCount}` | ||||
|         if (groupName) { | ||||
|           url += `&groupName=${groupName}` | ||||
|         } | ||||
|         await fetch(url) | ||||
|           .then((response) => response.text()) | ||||
|           .then((data) => { | ||||
|             const events = JSON.parse(data) | ||||
|             displayEvents({ | ||||
|               events, | ||||
|               document, | ||||
|               container, | ||||
|               maxEventsCount: eventsCount, | ||||
|             }) | ||||
|           }) | ||||
|           .catch((data) => { | ||||
|             displayErrorMessage({ data, container }) | ||||
|           }) | ||||
|       } | ||||
|     }, 500) | ||||
|   } | ||||
|   useEffect(() => { | ||||
|     reloadEventList() | ||||
|     reloadEventList(attributes.eventsCount, attributes.groupName) | ||||
|   }, []) | ||||
|   function updateEventsCount(event) { | ||||
|     let newValue = Number(event.target.value) | ||||
|     if (newValue < 1) newValue = 1 | ||||
|     setAttributes({ eventsCount: newValue }) | ||||
|     reloadEventList() | ||||
|     reloadEventList(newValue, attributes.groupName) | ||||
|   } | ||||
|   function updateGroupName(event) { | ||||
|     setAttributes({ groupName: event.target.value }) | ||||
|     reloadEventList() | ||||
|     const newValue = event.target.value | ||||
|     setAttributes({ groupName: newValue }) | ||||
|     reloadEventList(attributes.eventsCount, newValue) | ||||
|   } | ||||
|   return [ | ||||
|     <InspectorControls> | ||||
|       <PanelBody title={__('Events List Settings', '<wordpress-name>')}> | ||||
|         <label | ||||
|           className="components-base-control__label" | ||||
|           htmlFor={NAME + '_events-count'} | ||||
|         > | ||||
|           {__('Number of events to show', '<wordpress-name>')} | ||||
|         </label> | ||||
|         <input | ||||
|           className="components-text-control__input" | ||||
|           type="number" | ||||
|           value={attributes.eventsCount} | ||||
|           onChange={updateEventsCount} | ||||
|           id={NAME + '_events-count'} | ||||
|         /> | ||||
|         <label | ||||
|           className="components-base-control__label" | ||||
|           htmlFor={NAME + '_group-name'} | ||||
|         > | ||||
|           {__('Group name (optional)', '<wordpress-name>')} | ||||
|         </label> | ||||
|         <input | ||||
|           className="components-text-control__input" | ||||
|           type="text" | ||||
|           value={attributes.groupName} | ||||
|           onChange={updateGroupName} | ||||
|           id={NAME + '_group-name'} | ||||
|         /> | ||||
|       </PanelBody> | ||||
|       <Panel> | ||||
|         <PanelBody title={__('Events List Settings', '<wordpress-name>')}> | ||||
|           <label | ||||
|             className="components-base-control__label" | ||||
|             htmlFor={NAME + '_events-count'} | ||||
|           > | ||||
|             {__('Number of events to show', '<wordpress-name>')} | ||||
|           </label> | ||||
|           <input | ||||
|             className="components-text-control__input" | ||||
|             type="number" | ||||
|             value={attributes.eventsCount} | ||||
|             onChange={updateEventsCount} | ||||
|             id={NAME + '_events-count'} | ||||
|           /> | ||||
|           <label | ||||
|             className="components-base-control__label" | ||||
|             htmlFor={NAME + '_group-name'} | ||||
|           > | ||||
|             {__('Group name (optional)', '<wordpress-name>')} | ||||
|           </label> | ||||
|           <input | ||||
|             className="components-text-control__input" | ||||
|             type="text" | ||||
|             value={attributes.groupName} | ||||
|             onChange={updateGroupName} | ||||
|             id={NAME + '_group-name'} | ||||
|           /> | ||||
|         </PanelBody> | ||||
|       </Panel> | ||||
|     </InspectorControls>, | ||||
|     <div {...blockProps}> | ||||
|       <div className="general-error" style={{ display: 'none' }}> | ||||
|   | ||||
| @@ -18,7 +18,6 @@ test.before(() => { | ||||
|  | ||||
| test.beforeEach((t) => { | ||||
|   t.context.container = document.createElement('div') | ||||
|   t.context.container.setAttribute('data-maximum', '2') | ||||
|  | ||||
|   const errorMessageGeneral = document.createElement('div') | ||||
|   errorMessageGeneral.setAttribute('class', 'general-error') | ||||
| @@ -40,24 +39,20 @@ test.beforeEach((t) => { | ||||
| }) | ||||
|  | ||||
| test('#displayEvents one event', (t) => { | ||||
|   const data = { | ||||
|     events: { | ||||
|       elements: [ | ||||
|         { | ||||
|           title: 'a', | ||||
|           url: 'b', | ||||
|           beginsOn: '2021-04-15T10:30:00Z', | ||||
|           endsOn: '2021-04-15T15:30:00Z', | ||||
|           physicalAddress: { | ||||
|             description: 'c', | ||||
|             locality: 'd', | ||||
|           }, | ||||
|         }, | ||||
|       ], | ||||
|   const events = [ | ||||
|     { | ||||
|       title: 'a', | ||||
|       url: 'b', | ||||
|       beginsOn: '2021-04-15T10:30:00Z', | ||||
|       endsOn: '2021-04-15T15:30:00Z', | ||||
|       physicalAddress: { | ||||
|         description: 'c', | ||||
|         locality: 'd', | ||||
|       }, | ||||
|     }, | ||||
|   } | ||||
|   ] | ||||
|   const container = t.context.container | ||||
|   displayEvents({ data, document, container }) | ||||
|   displayEvents({ events, document, container, maxEventsCount: 2 }) | ||||
|   const list = container.querySelector('ul') | ||||
|   t.is(list.children[0].childNodes[0].tagName, 'A') | ||||
|   t.is(list.children[0].childNodes[0].getAttribute('href'), 'b') | ||||
|   | ||||
| @@ -6,18 +6,14 @@ export function clearEventsList(container) { | ||||
|   list.replaceChildren() | ||||
| } | ||||
|  | ||||
| export function displayEvents({ data, document, container }) { | ||||
| export function displayEvents({ events, document, container, maxEventsCount }) { | ||||
|   hideLoadingIndicator(container) | ||||
|  | ||||
|   const isShortOffsetNameShown = | ||||
|     window.MOBILIZON_CONNECTOR.isShortOffsetNameShown | ||||
|   const locale = window.MOBILIZON_CONNECTOR.locale | ||||
|   const maxEventsCount = container.getAttribute('data-maximum') | ||||
|   const timeZone = window.MOBILIZON_CONNECTOR.timeZone | ||||
|  | ||||
|   const events = data.events | ||||
|     ? data.events.elements | ||||
|     : data.group.organizedEvents.elements | ||||
|   const eventsCount = Math.min(maxEventsCount, events.length) | ||||
|   const list = container.querySelector('ul') | ||||
|   for (let i = 0; i < eventsCount; i++) { | ||||
|   | ||||
| @@ -1,38 +0,0 @@ | ||||
| import { | ||||
|   clearEventsList, | ||||
|   displayEvents, | ||||
|   displayErrorMessage, | ||||
|   hideErrorMessages, | ||||
|   showLoadingIndicator, | ||||
| } from './events-displayer.js' | ||||
| import * as GraphqlWrapper from './graphql-wrapper.js' | ||||
|  | ||||
| const NAME = '<wordpress-name>' | ||||
| const URL_SUFFIX = '/api' | ||||
|  | ||||
| document.addEventListener('DOMContentLoaded', loadEventLists) | ||||
|  | ||||
| function loadEventLists() { | ||||
|   const eventLists = document.getElementsByClassName(NAME + '_events-list') | ||||
|   for (const list of eventLists) { | ||||
|     loadEventList(list) | ||||
|   } | ||||
| } | ||||
|  | ||||
| export function loadEventList(container) { | ||||
|   const url = MOBILIZON_CONNECTOR.url + URL_SUFFIX | ||||
|   const limit = parseInt(container.getAttribute('data-maximum')) | ||||
|   const groupName = container.getAttribute('data-group-name') | ||||
|   hideErrorMessages(container) | ||||
|   clearEventsList(container) | ||||
|   showLoadingIndicator(container) | ||||
|   if (groupName) { | ||||
|     GraphqlWrapper.getUpcomingEventsByGroupName({ url, limit, groupName }) | ||||
|       .then((data) => displayEvents({ data, document, container })) | ||||
|       .catch((data) => displayErrorMessage({ data, container })) | ||||
|   } else { | ||||
|     GraphqlWrapper.getUpcomingEvents({ url, limit }) | ||||
|       .then((data) => displayEvents({ data, document, container })) | ||||
|       .catch((data) => displayErrorMessage({ data, container })) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										53
									
								
								source/includes/Api.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								source/includes/Api.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| class Api { | ||||
|   public static function init() { | ||||
|     add_action('rest_api_init', 'MobilizonConnector\Api::init_api'); | ||||
|   } | ||||
|  | ||||
|   public static function init_api() { | ||||
|     register_rest_route( | ||||
|       NAME . '/v1', | ||||
|       '/events', | ||||
|       [ | ||||
|         'methods' => 'GET', | ||||
|         'callback' => 'MobilizonConnector\Api::get_events', | ||||
|         'args' => [ | ||||
|           'eventsCount' => [ | ||||
|             'required' => true, | ||||
|             'validate_callback' => function($param, $request, $key) { | ||||
|               return is_numeric($param) && $param > 0; | ||||
|             } | ||||
|           ], | ||||
|           'groupName' => [ | ||||
|             'validate_callback' => function($param, $request, $key) { | ||||
|               return !is_numeric($param); | ||||
|             } | ||||
|           ] | ||||
|         ], | ||||
|         'permission_callback' => '__return_true', | ||||
|       ] | ||||
|       ); | ||||
|   } | ||||
|  | ||||
|   public static function get_events($request) { | ||||
|     $eventsCount = $request['eventsCount']; | ||||
|     $groupName = isset($request['groupName']) ? $request['groupName'] : ''; | ||||
|  | ||||
|     $url = Settings::getUrl(); | ||||
|  | ||||
|     try { | ||||
|       if ($groupName) { | ||||
|         $events = GraphQlClient::get_upcoming_events_by_group_name($url, (int) $eventsCount, $groupName); | ||||
|       } else { | ||||
|         $events = GraphQlClient::get_upcoming_events($url, (int) $eventsCount); | ||||
|       } | ||||
|       return $events; | ||||
|     } catch (GeneralException $e) { | ||||
|       return new \WP_Error('events_not_loading', 'The events could not be loaded!', array('status' => 500)); | ||||
|     } catch (GroupNotFoundException $e) { | ||||
|       return new \WP_Error('group_not_found', sprintf('The group "%s" could not be found!', $groupName), array('status' => 404)); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1,11 +1,6 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
| 
 | ||||
| // Exit if this file is called directly.
 | ||||
| if (!defined('ABSPATH')) { | ||||
|   exit; | ||||
| } | ||||
| 
 | ||||
| const DEFAULT_EVENTS_COUNT = 5; | ||||
| const NAME = '<wordpress-name>'; | ||||
| const NICE_NAME = '<wordpress-nice-name>'; | ||||
							
								
								
									
										34
									
								
								source/includes/DateTimeWrapper.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								source/includes/DateTimeWrapper.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| final class DateTimeWrapper { | ||||
|   private $dateTime; | ||||
|   private $locale; | ||||
|   private $timeZone; | ||||
|  | ||||
|   public function __construct(string $text, string $locale = 'en-GB', string $timeZone = 'utc') { | ||||
|     if (!$locale) { | ||||
|       $locale = 'en-GB'; | ||||
|     } | ||||
|     if (!$timeZone) { | ||||
|       $timeZone = 'utc'; | ||||
|     } | ||||
|     $this->dateTime = new \DateTime($text); | ||||
|     $this->locale = $locale; | ||||
|     $this->timeZone = new \DateTimeZone($timeZone); | ||||
|   } | ||||
|  | ||||
|   public function get24Time(): string { | ||||
|     $formatter = \IntlDateFormatter::create($this->locale, \IntlDateFormatter::NONE, \IntlDateFormatter::SHORT, $this->timeZone); | ||||
|     return $formatter->format($this->dateTime); | ||||
|   } | ||||
|  | ||||
|   public function getShortDate(): string { | ||||
|     $formatter = \IntlDateFormatter::create($this->locale, \IntlDateFormatter::SHORT, \IntlDateFormatter::NONE, $this->timeZone); | ||||
|     return $formatter->format($this->dateTime); | ||||
|   } | ||||
|  | ||||
|   public function getTimeZoneName(): string { | ||||
|     return $this->timeZone->getName(); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										19
									
								
								source/includes/EventsCache.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								source/includes/EventsCache.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| final class EventsCache { | ||||
|  | ||||
|   private static $MAX_AGE_IN_S = 120; | ||||
|  | ||||
|   public static function set(array $parameters, mixed $data): void { | ||||
|     // md5 is used as key must be 172 characters or fewer in length. | ||||
|     $key = md5(json_encode($parameters)); | ||||
|     set_transient($key, $data, self::$MAX_AGE_IN_S); | ||||
|   } | ||||
|  | ||||
|   public static function get(array $parameters): mixed { | ||||
|     $key = md5(json_encode($parameters)); | ||||
|     $data = get_transient($key); | ||||
|     return $data; | ||||
|   } | ||||
| } | ||||
| @@ -1,11 +1,6 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
| 
 | ||||
| // Exit if this file is called directly.
 | ||||
| if (!defined('ABSPATH')) { | ||||
|   exit; | ||||
| } | ||||
| 
 | ||||
| class EventsListBlock { | ||||
| 
 | ||||
|   public static function initAndReturnScriptName(): string { | ||||
| @@ -41,12 +36,29 @@ class EventsListBlock { | ||||
|   } | ||||
| 
 | ||||
|   public static function render($block_attributes, $content) { | ||||
|     $classNamePrefix = NAME; | ||||
|     $url = Settings::getUrl(); | ||||
|     $eventsCount = $block_attributes['eventsCount']; | ||||
|     $groupName = isset($block_attributes['groupName']) ? $block_attributes['groupName'] : ''; | ||||
| 
 | ||||
|     ob_start(); | ||||
|     require dirname(__DIR__) . '/view/events-list.php'; | ||||
|     try { | ||||
|       if ($groupName) { | ||||
|         $events = GraphQlClient::get_upcoming_events_by_group_name($url, (int) $eventsCount, $groupName); | ||||
|       } else { | ||||
|         $events = GraphQlClient::get_upcoming_events($url, (int) $eventsCount); | ||||
|       } | ||||
| 
 | ||||
|       $classNamePrefix = NAME; | ||||
|       $locale = get_locale(); | ||||
|       $isShortOffsetNameShown = Settings::isShortOffsetNameShown(); | ||||
|       $timeZone = wp_timezone_string(); | ||||
| 
 | ||||
|       require dirname(__DIR__) . '/view/events-list.php'; | ||||
|     } catch (GeneralException $e) { | ||||
|       require dirname(__DIR__) . '/view/events-list-not-loaded.php'; | ||||
|     } catch (GroupNotFoundException $e) { | ||||
|       require dirname(__DIR__) . '/view/events-list-group-not-found.php'; | ||||
|     } | ||||
|     $output = ob_get_clean(); | ||||
|     return $output; | ||||
|   } | ||||
| @@ -1,11 +1,6 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
| 
 | ||||
| // Exit if this file is called directly.
 | ||||
| if (!defined('ABSPATH')) { | ||||
|   exit; | ||||
| } | ||||
| 
 | ||||
| class EventsListShortcut { | ||||
|    | ||||
|   public static function init() { | ||||
| @@ -24,12 +19,29 @@ class EventsListShortcut { | ||||
|       ), $atts | ||||
|     ); | ||||
| 
 | ||||
|     $classNamePrefix = NAME; | ||||
|     $url = Settings::getUrl(); | ||||
|     $eventsCount = $atts_with_overriden_defaults['events-count']; | ||||
|     $groupName = $atts_with_overriden_defaults['group-name']; | ||||
| 
 | ||||
|     ob_start(); | ||||
|     require dirname(__DIR__) . '/view/events-list.php'; | ||||
|     try { | ||||
|       if ($groupName) { | ||||
|         $events = GraphQlClient::get_upcoming_events_by_group_name($url, (int) $eventsCount, $groupName); | ||||
|       } else { | ||||
|         $events = GraphQlClient::get_upcoming_events($url, (int) $eventsCount); | ||||
|       } | ||||
| 
 | ||||
|       $classNamePrefix = NAME; | ||||
|       $locale = get_locale(); | ||||
|       $isShortOffsetNameShown = Settings::isShortOffsetNameShown(); | ||||
|       $timeZone = wp_timezone_string(); | ||||
| 
 | ||||
|       require dirname(__DIR__) . '/view/events-list.php'; | ||||
|     } catch (GeneralException $e) { | ||||
|       require dirname(__DIR__) . '/view/events-list-not-loaded.php'; | ||||
|     } catch (GroupNotFoundException $e) { | ||||
|       require dirname(__DIR__) . '/view/events-list-group-not-found.php'; | ||||
|     } | ||||
|     $output = ob_get_clean(); | ||||
|     return $output; | ||||
|   } | ||||
| @@ -1,11 +1,6 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
| 
 | ||||
| // Exit if this file is called directly.
 | ||||
| if (!defined('ABSPATH')) { | ||||
|   exit; | ||||
| } | ||||
| 
 | ||||
| class EventsListWidget extends \WP_Widget { | ||||
| 
 | ||||
|   public function __construct() { | ||||
| @@ -25,11 +20,28 @@ class EventsListWidget extends \WP_Widget { | ||||
|       echo $args['before_title'].apply_filters('widget_title', $options['title']).$args['after_title']; | ||||
|     } | ||||
| 
 | ||||
|     $classNamePrefix = NAME; | ||||
|     $url = Settings::getUrl(); | ||||
|     $eventsCount = $options['eventsCount']; | ||||
|     $groupName = isset($options['groupName']) ? $options['groupName'] : ''; | ||||
| 
 | ||||
|     require dirname(__DIR__) . '/view/events-list.php'; | ||||
|     try { | ||||
|       if ($groupName) { | ||||
|         $events = GraphQlClient::get_upcoming_events_by_group_name($url, (int) $eventsCount, $groupName); | ||||
|       } else { | ||||
|         $events = GraphQlClient::get_upcoming_events($url, (int) $eventsCount); | ||||
|       } | ||||
| 
 | ||||
|       $classNamePrefix = NAME; | ||||
|       $locale = get_locale(); | ||||
|       $isShortOffsetNameShown = Settings::isShortOffsetNameShown(); | ||||
|       $timeZone = wp_timezone_string(); | ||||
|    | ||||
|       require dirname(__DIR__) . '/view/events-list.php'; | ||||
|     } catch (GeneralException $e) { | ||||
|       require dirname(__DIR__) . '/view/events-list-not-loaded.php'; | ||||
|     } catch (GroupNotFoundException $e) { | ||||
|       require dirname(__DIR__) . '/view/events-list-group-not-found.php'; | ||||
|     } | ||||
| 
 | ||||
|     echo $args['after_widget']; | ||||
|   } | ||||
							
								
								
									
										42
									
								
								source/includes/Formatter.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								source/includes/Formatter.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| final class Formatter | ||||
| { | ||||
|   public static function format_date(string $locale, string $timeZone, string $start, ?string $end, bool $isShortOffsetNameShown): string { | ||||
|     $startDateTime = new DateTimeWrapper($start, $locale, $timeZone); | ||||
|     $dateText = $startDateTime->getShortDate(); | ||||
|     $dateText .= ' ' . $startDateTime->get24Time(); | ||||
|     if (!$end && $isShortOffsetNameShown) { | ||||
|       $dateText .= ' (' . $startDateTime->getTimeZoneName() . ')'; | ||||
|     } | ||||
|     if ($end) { | ||||
|       $endDateTime = new DateTimeWrapper($end, $locale, $timeZone); | ||||
|       if ($startDateTime->getShortDate() != $endDateTime->getShortDate()) { | ||||
|         $dateText .= ' - '; | ||||
|         $dateText .= $endDateTime->getShortDate() . ' '; | ||||
|       } else { | ||||
|         $dateText .= ' - '; | ||||
|       } | ||||
|       $dateText .= $endDateTime->get24Time(); | ||||
|       if ($isShortOffsetNameShown) { | ||||
|         $dateText .= ' (' . $endDateTime->getTimeZoneName() . ')'; | ||||
|       } | ||||
|     } | ||||
|     return $dateText; | ||||
|   } | ||||
|  | ||||
|   public static function format_location(string $description, string $locality): string { | ||||
|     $location = ''; | ||||
|     if ($description && trim($description)) { | ||||
|       $location .= trim($description); | ||||
|     } | ||||
|     if ($location && $locality) { | ||||
|       $location .= ', '; | ||||
|     } | ||||
|     if ($locality) { | ||||
|       $location .= $locality; | ||||
|     } | ||||
|     return $location; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										113
									
								
								source/includes/GraphQlClient.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								source/includes/GraphQlClient.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| final class GraphQlClient { | ||||
|  | ||||
|   public static function query(string $endpoint, string $query, array $variables = [], ?string $token = null): array | ||||
|   { | ||||
|     $headers = ['Content-Type: application/json']; | ||||
|     if ($token !== null) { | ||||
|         $headers[] = "Authorization: bearer $token"; | ||||
|     } | ||||
|  | ||||
|     $context = stream_context_create([ | ||||
|         'http' => [ | ||||
|             'method' => 'POST', | ||||
|             'header' => $headers, | ||||
|             'content' => json_encode(['query' => $query, 'variables' => $variables]), | ||||
|         ] | ||||
|       ]); | ||||
|     $data = @file_get_contents($endpoint, false, $context); | ||||
|  | ||||
|     if ($data === false) { | ||||
|       $error = error_get_last(); | ||||
|       throw new \ErrorException($error['message'], $error['type']); | ||||
|     } | ||||
|  | ||||
|     return json_decode($data, true); | ||||
|   } | ||||
|  | ||||
|   public static function get_upcoming_events(string $url, int $limit): array { | ||||
|     $query = <<<'GRAPHQL' | ||||
|       query ($limit: Int) { | ||||
|         events(limit: $limit) { | ||||
|           elements { | ||||
|             id, | ||||
|             title, | ||||
|             url, | ||||
|             beginsOn, | ||||
|             endsOn, | ||||
|             physicalAddress { | ||||
|               description, | ||||
|               locality | ||||
|             } | ||||
|           }, | ||||
|           total | ||||
|         } | ||||
|       } | ||||
|       GRAPHQL; | ||||
|  | ||||
|     $cachedEvents = EventsCache::get(['url' => $url, 'query' => $query, 'limit' => $limit]); | ||||
|     if ($cachedEvents !== false) { | ||||
|       return $cachedEvents; | ||||
|     } | ||||
|  | ||||
|     $endpoint = $url . '/api'; | ||||
|     $data = self::query($endpoint, $query, ['limit' => $limit]); | ||||
|     self::checkData($data); | ||||
|  | ||||
|     $events = $data['data']['events']['elements']; | ||||
|     EventsCache::set(['url' => $url, 'query' => $query, 'limit' => $limit], $events); | ||||
|     return $events; | ||||
|   } | ||||
|  | ||||
|   public static function get_upcoming_events_by_group_name(string $url, int $limit, string $groupName): array { | ||||
|     $query = <<<'GRAPHQL' | ||||
|       query ($afterDatetime: DateTime, $groupName: String!, $limit: Int) { | ||||
|         group(preferredUsername: $groupName) { | ||||
|           organizedEvents(afterDatetime: $afterDatetime, limit: $limit) { | ||||
|             elements { | ||||
|               id, | ||||
|               title, | ||||
|               url, | ||||
|               beginsOn, | ||||
|               endsOn, | ||||
|               physicalAddress { | ||||
|                 description, | ||||
|                 locality | ||||
|               } | ||||
|             }, | ||||
|             total | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       GRAPHQL; | ||||
|  | ||||
|     $afterDatetime = date(\DateTime::ISO8601); | ||||
|  | ||||
|     $cachedEvents = EventsCache::get(['url' => $url, 'query' => $query, 'afterDatetime' => $afterDatetime, 'groupName' => $groupName, 'limit' => $limit]); | ||||
|     if ($cachedEvents !== false) { | ||||
|       return $cachedEvents; | ||||
|     } | ||||
|  | ||||
|     $endpoint = $url . '/api'; | ||||
|     $data = self::query($endpoint, $query, ['afterDatetime' => $afterDatetime, 'groupName' => $groupName, 'limit' => $limit]); | ||||
|     self::checkData($data); | ||||
|  | ||||
|     $events = $data['data']['group']['organizedEvents']['elements']; | ||||
|     EventsCache::set(['url' => $url, 'query' => $query, 'afterDatetime' => $afterDatetime, 'groupName' => $groupName, 'limit' => $limit], $events); | ||||
|     return $events; | ||||
|   } | ||||
|  | ||||
|   private static function checkData($data) { | ||||
|     if (isset($data['errors'])) { | ||||
|       if (count($data['errors']) > 0 && | ||||
|           isset($data['errors'][0]['code']) && | ||||
|           $data['errors'][0]['code'] === 'group_not_found') { | ||||
|         throw new GroupNotFoundException(serialize($data['errors'][0])); | ||||
|       } else { | ||||
|         throw new GeneralException(serialize($data['errors'])); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1,11 +1,6 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
| 
 | ||||
| // Exit if this file is called directly.
 | ||||
| if (!defined('ABSPATH')) { | ||||
|   exit; | ||||
| } | ||||
| 
 | ||||
| class Settings { | ||||
| 
 | ||||
|   private static $DEFAULT_OPTION_URL = 'https://mobilizon.fr'; | ||||
							
								
								
									
										12
									
								
								source/includes/exceptions/GeneralException.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								source/includes/exceptions/GeneralException.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| class GeneralException extends \Exception { | ||||
|   public function __construct($message, $code = 0, Throwable $previous = null) { | ||||
|     parent::__construct($message, $code, $previous); | ||||
|   } | ||||
|  | ||||
|   public function __toString() { | ||||
|     return __CLASS__ . ": [{$this->code}]: {$this->message}\n"; | ||||
| } | ||||
| } | ||||
							
								
								
									
										12
									
								
								source/includes/exceptions/GroupNotFoundException.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								source/includes/exceptions/GroupNotFoundException.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| class GroupNotFoundException extends \Exception { | ||||
|   public function __construct($message, $code = 0, Throwable $previous = null) { | ||||
|     parent::__construct($message, $code, $previous); | ||||
|   } | ||||
|  | ||||
|   public function __toString() { | ||||
|     return __CLASS__ . ": [{$this->code}]: {$this->message}\n"; | ||||
| } | ||||
| } | ||||
| @@ -17,7 +17,7 @@ License: <wordpress-license> | ||||
| Features | ||||
| - Display events as Gutenberg block, as widget and as shortcut | ||||
| - Display events' title, date, and location, if available | ||||
| - Cache requests' responses for 2 minutes in the browser's `sessionStorage` | ||||
| - Cache requests' responses for 2 minutes in the database | ||||
| - Configure number of events to show per block, per widget and per shortcut | ||||
| - Optionally filter events by a specific group per block, per widget and per shortcut | ||||
| - Set the URL of the Mobilizon instance in the settings | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <?php | ||||
| require_once __DIR__ . '/includes/constants.php'; | ||||
| require_once __DIR__ . '/includes/settings.php'; | ||||
| require_once __DIR__ . '/includes/Constants.php'; | ||||
| require_once __DIR__ . '/includes/Settings.php'; | ||||
|  | ||||
| // If uninstall.php is not called by WordPress, exit. | ||||
| if (!defined('WP_UNINSTALL_PLUGIN')) { | ||||
|   | ||||
							
								
								
									
										11
									
								
								source/view/events-list-group-not-found.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								source/view/events-list-group-not-found.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| // Exit if this file is called directly. | ||||
| if (!defined('ABSPATH')) { | ||||
|   exit; | ||||
| } | ||||
| ?> | ||||
| <div class="<?php echo esc_attr($classNamePrefix); ?>_events-list"> | ||||
|   <?php echo esc_html(sprintf(__('The group "%s" could not be found!', 'connector-mobilizon'), $groupName)); ?> | ||||
| </div> | ||||
							
								
								
									
										11
									
								
								source/view/events-list-not-loaded.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								source/view/events-list-not-loaded.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| // Exit if this file is called directly. | ||||
| if (!defined('ABSPATH')) { | ||||
|   exit; | ||||
| } | ||||
| ?> | ||||
| <div class="<?php echo esc_attr($classNamePrefix); ?>_events-list"> | ||||
|   <?php esc_html_e('The events could not be loaded!', 'connector-mobilizon'); ?> | ||||
| </div> | ||||
| @@ -1,4 +1,6 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| // Exit if this file is called directly. | ||||
| if (!defined('ABSPATH')) { | ||||
|   exit; | ||||
|   | ||||
| @@ -1,14 +1,23 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| // Exit if this file is called directly. | ||||
| if (!defined('ABSPATH')) { | ||||
|   exit; | ||||
| } | ||||
| ?> | ||||
| <div class="<?php echo esc_attr($classNamePrefix); ?>_events-list" | ||||
|   data-maximum="<?php echo esc_attr($eventsCount); ?>" | ||||
|   data-group-name="<?php echo esc_attr($groupName); ?>"> | ||||
|   <div class="general-error" style="display: none;"><?php esc_html_e('The events could not be loaded!', 'connector-mobilizon'); ?></div> | ||||
|   <div class="group-not-found" style="display: none;"><?php esc_html_e('The group could not be found!', 'connector-mobilizon'); ?></div> | ||||
|   <div class="loading-indicator" style="display: none;"><?php esc_html_e('Loading...', 'connector-mobilizon'); ?></div> | ||||
|   <ul style="list-style-type: none; padding-left: 0;"></ul> | ||||
| <div class="<?php echo esc_attr($classNamePrefix); ?>_events-list"> | ||||
|   <ul style="list-style-type: none; padding-left: 0;"> | ||||
|     <?php foreach($events as $event) { ?> | ||||
|     <li> | ||||
|       <a href="<?php echo esc_attr($event['url']); ?>"><?php echo esc_html_e($event['title']); ?></a> | ||||
|       <br> | ||||
|       <?php echo esc_html_e(Formatter::format_date($locale, $timeZone, $event['beginsOn'], $event['endsOn'], $isShortOffsetNameShown)); ?> | ||||
|       <?php if (isset($event['physicalAddress'])) { ?> | ||||
|       <br> | ||||
|       <?php echo esc_html_e(Formatter::format_location($event['physicalAddress']['description'], $event['physicalAddress']['locality'])) ?> | ||||
|       <?php } ?> | ||||
|     </li> | ||||
|     <?php } ?> | ||||
|   </ul> | ||||
| </div> | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| // Exit if this file is called directly. | ||||
| if (!defined('ABSPATH')) { | ||||
|   exit; | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| // Exit if this file is called directly. | ||||
| if (!defined('ABSPATH')) { | ||||
|   exit; | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| <?php | ||||
| namespace MobilizonConnector; | ||||
|  | ||||
| // Exit if this file is called directly. | ||||
| if (!defined('ABSPATH')) { | ||||
|   exit; | ||||
|   | ||||
							
								
								
									
										47
									
								
								tests/DateTimeWrapperTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								tests/DateTimeWrapperTest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| <?php | ||||
| declare(strict_types=1); | ||||
|  | ||||
| use MobilizonConnector\DateTimeWrapper; | ||||
| use PHPUnit\Framework\TestCase; | ||||
|  | ||||
| final class DateTimeWrapperTest extends TestCase { | ||||
|   public function testCanGet24TimeForUsualTime(): void { | ||||
|     $d = new DateTimeWrapper('2020-12-24T16:45:00Z'); | ||||
|     $this->assertSame('16:45', $d->get24Time()); | ||||
|   } | ||||
|  | ||||
|   public function testCanGetShortDateForUsualDate(): void { | ||||
|     $d = new DateTimeWrapper('2020-12-24T16:45:00Z'); | ||||
|     $this->assertSame('24/12/2020', $d->getShortDate()); | ||||
|   } | ||||
|  | ||||
|   public function testCanGetShortDateForUsualDateWithLocaleWithUnderscore(): void { | ||||
|     $d = new DateTimeWrapper('2020-12-24T16:45:00Z'); | ||||
|     $this->assertSame('24/12/2020', $d->getShortDate(), 'en_GB'); | ||||
|   } | ||||
|  | ||||
|   public function testCanGetShortDateForUsualDateWithTimezoneString(): void { | ||||
|     $d = new DateTimeWrapper('2020-12-24T16:45:00Z', 'en-GB', 'Europe/Rome'); | ||||
|     $this->assertSame('24/12/2020', $d->getShortDate()); | ||||
|   } | ||||
|  | ||||
|   public function testCanGetShortDateForUsualDateWithNamedOffset(): void { | ||||
|     $d = new DateTimeWrapper('2020-12-24T16:45:00Z', 'en-GB', 'UTC'); | ||||
|     $this->assertSame('24/12/2020', $d->getShortDate()); | ||||
|   } | ||||
|  | ||||
|   public function testCanGetShortDateForUsualDateWithOffset(): void { | ||||
|     $d = new DateTimeWrapper('2020-12-24T16:45:00Z', 'en-GB', '+02:00'); | ||||
|     $this->assertSame('24/12/2020', $d->getShortDate()); | ||||
|   } | ||||
|  | ||||
|   public function testCanGetShortDateForUsualDateWithEmptyTimezone(): void { | ||||
|     $d = new DateTimeWrapper('2020-12-24T16:45:00Z', 'en-GB', ''); | ||||
|     $this->assertSame('24/12/2020', $d->getShortDate()); | ||||
|   } | ||||
|  | ||||
|   public function testCanGetShortOffsetNameForUsualTime(): void { | ||||
|     $d = new DateTimeWrapper('2020-12-24T16:45:00Z'); | ||||
|     $this->assertSame('UTC', $d->getTimeZoneName()); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										52
									
								
								tests/FormatterTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								tests/FormatterTest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| <?php | ||||
| declare(strict_types=1); | ||||
|  | ||||
| use MobilizonConnector\Formatter; | ||||
| use PHPUnit\Framework\TestCase; | ||||
|  | ||||
| final class FormatterTest extends PHPUnit\Framework\TestCase | ||||
| { | ||||
|   public function testCanDateFormatOneDate(): void { | ||||
|     $this->assertSame('15/04/2021 10:30 - 15:30', Formatter::format_date('en-GB', 'UTC', '2021-04-15T10:30:00Z', '2021-04-15T15:30:00Z', false)); | ||||
|   } | ||||
|  | ||||
|   public function testCanDateFormatOneDateWithOffset(): void { | ||||
|     $this->assertSame('15/04/2021 10:30 - 15:30 (UTC)', Formatter::format_date('en-GB', 'UTC', '2021-04-15T10:30:00Z', '2021-04-15T15:30:00Z', true)); | ||||
|   } | ||||
|  | ||||
|   public function testCanDateFormatOneDateWithTimeZoneOffset(): void { | ||||
|     $this->assertSame('15/04/2021 11:30 - 16:30', Formatter::format_date('en-GB', '+01:00', '2021-04-15T10:30:00Z', '2021-04-15T15:30:00Z', false)); | ||||
|   } | ||||
|  | ||||
|   public function testCanDateFormatTwoDates(): void { | ||||
|     $this->assertSame('15/04/2021 10:30 - 16/04/2021 15:30', Formatter::format_date('en-GB', 'UTC', '2021-04-15T10:30:00Z', '2021-04-16T15:30:00Z', false)); | ||||
|   } | ||||
|  | ||||
|   public function testCanDateFormatTwoDatesWithOffset(): void { | ||||
|     $this->assertSame('15/04/2021 10:30 - 16/04/2021 15:30 (UTC)', Formatter::format_date('en-GB', 'UTC', '2021-04-15T10:30:00Z', '2021-04-16T15:30:00Z', true)); | ||||
|   } | ||||
|  | ||||
|   public function testCanDateFormatWhenSecondDateIsNull(): void { | ||||
|     $this->assertSame('15/04/2021 10:30', Formatter::format_date('en-GB', 'UTC', '2021-04-15T10:30:00Z', null, false)); | ||||
|   } | ||||
|  | ||||
|   public function testCanDateFormatWhenSecondDateIsNullWithOffset(): void { | ||||
|     $this->assertSame('15/04/2021 10:30 (UTC)', Formatter::format_date('en-GB', 'UTC', '2021-04-15T10:30:00Z', null, true)); | ||||
|   } | ||||
|  | ||||
|   public function testCanLocationFormatBothParameters(): void { | ||||
|     $this->assertSame('a, b', Formatter::format_location('a', 'b')); | ||||
|   } | ||||
|  | ||||
|   public function testLocationFormatDescriptionOnly(): void { | ||||
|     $this->assertSame('a', Formatter::format_location('a', '')); | ||||
|   } | ||||
|  | ||||
|   public function testLocationFormatDescriptionWithSpaceOnly(): void { | ||||
|     $this->assertSame('', Formatter::format_location(' ', '')); | ||||
|   } | ||||
|  | ||||
|   public function testLocationFormatLocalityOnly(): void { | ||||
|     $this->assertSame('a', Formatter::format_location('', 'a')); | ||||
|   } | ||||
| } | ||||
| @@ -8,7 +8,6 @@ const FOLDER_SOURCE = './source' | ||||
| module.exports = { | ||||
|   entry: { | ||||
|     'block-events-loader': FOLDER_SOURCE + '/front/block-events-loader.js', | ||||
|     'events-loader': FOLDER_SOURCE + '/front/events-loader.js', | ||||
|   }, | ||||
|   output: { | ||||
|     filename: '[name].js', | ||||
|   | ||||
		Reference in New Issue
	
	Block a user