diff --git a/wp-content/plugins/squat-radar-calendar-integration/assets/squat-radar.css b/wp-content/plugins/squat-radar-calendar-integration/assets/squat-radar.css new file mode 100644 index 0000000000000000000000000000000000000000..bc17894fdb08843e84f5bd0e0dcf3e75ce5142b7 --- /dev/null +++ b/wp-content/plugins/squat-radar-calendar-integration/assets/squat-radar.css @@ -0,0 +1,72 @@ +.squat-radar.radar-event { + margin-bottom: 1em; + padding-bottom: 1em; + border-bottom: 1px solid #ddd; + text-align:left; +} +.squat-radar.radar-event::after { + content: ''; + display: block; + float: none; + clear: both; +} +.squat-radar.rader-event:last-child { + border-bottom: 0; + padding-bottom: 0; + margin-bottom: 0; +} +.squat-radar.radar-event-cancelled { + text-decoration: line-through; +} + +.squat-radar .squat-radar-title { + line-height: 1; + font-size: 1.4em; + margin-bottom: 0.2em; +} + +.squat-radar .squat-radar-datetime { + font-weight: bold; +} + +.squat-radar .squat-radar-datetime.squat-radar-datetime-start::before { + content: ''; + display: block; + float: none; + clear: both; +} + +.squat-radar .squat-radar-event-start-end::after, +.squat-radar .squat-radar-event-start { + content: ''; + display: block; + float: none; + clear: both; +} + +.squat-radar .squat-radar-list li { + display: inline; + list-style: none; +} + +.squat-radar .squat-radar-list li:after { + content: ", "; +} + +.squat-radar .squat-radar-list li:last-child:after { + content: ""; +} + +.squat-radar a.squat-radar-url-more { + padding: .4180469716em 1.11575em; + margin-left: 10px; +/* margin-bottom: 10px;*/ + float: right; +} +.squat-radar a.squat-radar-url-more:hover { + color: white !important; +} + +.squat_radar a.squat-radar-url-more:focus { + outline: 0; +} diff --git a/wp-content/plugins/squat-radar-calendar-integration/assets/squat-radar.js b/wp-content/plugins/squat-radar-calendar-integration/assets/squat-radar.js new file mode 100644 index 0000000000000000000000000000000000000000..00394f97e6a1067bef2061aa08bfa55a1fcbb40f --- /dev/null +++ b/wp-content/plugins/squat-radar-calendar-integration/assets/squat-radar.js @@ -0,0 +1,41 @@ +jQuery(function($){ + + $(".squat-radar-widget.squat-radar-ajax").each(function(index, widget) { + $.ajax({ + url: squat_radar_widget.ajaxurl, + context: document.body, + type: 'POST', + data: { + action: "squat_radar_events", + instance: window[widget.id], + }, + success: function(result){ + + if (result.is_error) { + if (result.error) { + $(widget).append( + '<p>Error: ' + result.error.code + ' - ' + result.error.message + '</p>' + ); + } + } else { + $(widget).empty(); + $(widget).append(result.html); +// $(".squat-li a").on('click', function(e){ + +// if (this.hasAttribute("data-link")) { +// e.preventDefault(); +// $(this).parent().find('p').show(); +// this.textContent = this.getAttribute('data-nog-meer'); +// this.setAttribute('href', this.getAttribute('data-link')); +// this.removeAttribute('data-link'); +// } +// +// }); + } + }, + error: function(res){ + console.log('AJAX error', res); + } + }); + }); +}); diff --git a/wp-content/plugins/squat-radar-calendar-integration/composer.json b/wp-content/plugins/squat-radar-calendar-integration/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..5963699369e8b6f880c69ce4cb5ddb624be2f207 --- /dev/null +++ b/wp-content/plugins/squat-radar-calendar-integration/composer.json @@ -0,0 +1,14 @@ +{ + "name": "radar-wp/squat-radar", + "description": "Wordpress Calendar widget for https://radar.squat.net/ events", + "type": "wordpress-plugin", + "homepage": "https://radar.squat.net", + "license": "GPL-2.0+", + "authors": [ { + "name": "Radar contributors", + "homepage": "https://0xacab.org/radar" + } ], + "require": { + "php": ">=5.4.0" + } +} diff --git a/wp-content/plugins/squat-radar-calendar-integration/includes/squat-radar-connector.php b/wp-content/plugins/squat-radar-calendar-integration/includes/squat-radar-connector.php new file mode 100644 index 0000000000000000000000000000000000000000..50479d6fbe63d24289588cd249e0aa22f4ab6a6a --- /dev/null +++ b/wp-content/plugins/squat-radar-calendar-integration/includes/squat-radar-connector.php @@ -0,0 +1,142 @@ +<?php +/** + * Squat Radar Connector. + * + * Fetch data from Radar API. + * + * @package squat-radar + * @since 2.0.0 + */ + +// Exit if accessed directly. +defined( 'ABSPATH' ) || exit; + +class Squat_Radar_Connector { + + const BASE_URL = 'https://radar.squat.net'; + const API_EVENTS = '/api/1.2/search/events.json'; + + /** + * Retrieve array of events from API based on a query. + * + * @see self::encode_api_query() for $query values. + * + * @param array $query + * Key value pairs for the API query. + * + * @return array + * Array of events. + * + * @throws Squat_Radar_Connector_Exception + * When events are not returned, but a timeout or API error. + */ + function get_events( $query ) { + + $url = self::BASE_URL . self::API_EVENTS . '?' . build_query( $query ); + $response = wp_remote_get( $url ); + if ( is_wp_error( $response ) ) { + throw new Squat_Radar_Connector_Exception( $response->get_error_message() ); + } + $code = wp_remote_retrieve_response_code( $response ); + if ( $code != 200) { + throw new Squat_Radar_Connector_Exception( wp_remote_retrieve_body( $response ), $code ); + } + return json_decode( wp_remote_retrieve_body( $response ), true); + + } + + /** + * Turn a Radar frontend Search URL into facets key value and language values. + * + * @param string $url + * The https://radar.squat.net/events filtered URL. + * + * @return array + * [ 'language' => language code, 'facets' => [key => value] ]. + */ + function decode_search_url( $url ) { + $matches = []; + $result = []; + // Urldecode not required here because of the regex match. + // Radar paramaters here are transcoded so will match. + if (preg_match('|//radar.squat.net/([a-z]{2})/events/([a-zA-Z0-9\-/]*)|', $url, $matches)) { + $result['language'] = $matches[1]; + foreach (array_chunk(explode('/', $matches[2]), 2) as $key_value_pair) { + $result['facets'][$key_value_pair[0]] = $key_value_pair[1]; + } + } + + return $result; + } + + /** + * Encode a query key value from facets, fields, language, limit. + * + * @param array $facets + * Optional. Facet key => filter value array. + * @param array $fields + * Optional. Index array of API field names to retrieve. + * @param string $language + * Optional. Language code. + * @param int $limit + * Optional. Maximum number to items to return. + * + * @return array + * Array for use in self::get_events(). + */ + function encode_api_query( $facets = [], $fields = [], $language = '', $limit = 10 ) { + $query = []; + + // Urlencode should do nothing here @see comment in decode_search_url. + // If someone has snuck something in it will however help. + foreach ( $facets as $key => $value ) { + $query['facets[' . urlencode($key) . ']'][] = urlencode($value); + } + if ( ! empty($fields) ) { + // {raw}urlencode is encoding : and , both of which are valid pchar. + $query['fields'] = preg_replace('/[^a-z_:,]/', '', implode(',', $fields)); + } + if ( ! empty($language) ) { + $query['language'] = urlencode($language); + } + if ( ! empty($limit) ) { + $query['limit'] = urlencode($limit); + } + return $query; + } + + /** + * Return events meeting argument criteria. Either from cache, or retrieved from API. + * + * @param array $facets + * Facet name key => filter value. + * @param array $fields + * Optional. Array of key names. + * @param string $language + * Optional. Language code. + * @param int $limit + * Maximum number of items to return. + * @param int $expiration + * Seconds to cache results. 0 never expires. + * @param bool $reset + * Force a cache reset. + * + * @return array + * Array of event arrays, values keyed by field name. + */ + function events( $facets, $fields = [], $language = NULL, $limit = 10, $expiration = 10800, $reset = FALSE ) { + // Fields we often want to get data out of but not necessarily are chosen to be shown. + $fields = array_merge($fields, ['uuid', 'title', 'body:value', 'url', 'event_status']); + $transient_key = 'squat_radar_events_' . sha1(implode($facets) . implode($fields) . $language . $limit); + if (! $reset && $data = get_transient( $transient_key )) { + return $data; + } + $query = $this->encode_api_query( $facets, $fields, $language, $limit ); + $events = $this->get_events($query); + + set_transient( $transient_key, $events, $expiration ); + return $events; + } +} + +class Squat_Radar_Connector_Exception extends Exception { } diff --git a/wp-content/plugins/squat-radar-calendar-integration/includes/squat-radar-formatter.php b/wp-content/plugins/squat-radar-calendar-integration/includes/squat-radar-formatter.php new file mode 100644 index 0000000000000000000000000000000000000000..8481f498f7f4a27208363edf80b35e5e4dc87513 --- /dev/null +++ b/wp-content/plugins/squat-radar-calendar-integration/includes/squat-radar-formatter.php @@ -0,0 +1,442 @@ +<?php +/** + * Squat Radar Events Formatter. + * + * Provides filters to format the output of Squat Radar Events. + * + * 'squat_radar_format_event' allows the whole event to be formatted. + * Default filter Squat_Radar_Formatter::format_event(). + * + * 'squat_radar_field_html' formats individual fields. + * Basic implementation Squat_Radar_Formatter::field_html(). + * + * @package squat-radar + * @since 2.0.0 + */ + +// Exit if accessed directly. +defined( 'ABSPATH' ) || exit; + +class Squat_Radar_Formatter { + + /** + * Register filters with Wordpress. + */ + static public function register() { + // Filter to go through fields and then call filters to turn these into HTML. + add_filter('squat_radar_format_event', [__CLASS__, 'format_event'], 10, 3); + + // Filters to turn each individual field into HTML. + // + // $value is the data from the field and can be an array or string. + // + // These filters extract data from arrays based on the field structure. + // If you make a change it is a requirement to sanitize + // anything that will be output. + add_filter('squat_radar_field_html', [__CLASS__, 'field_date_html'], 5, 4); + add_filter('squat_radar_field_html', [__CLASS__, 'field_location_html'], 5, 4); + add_filter('squat_radar_field_html', [__CLASS__, 'field_link_html'], 5, 4); + add_filter('squat_radar_field_html', [__CLASS__, 'field_summary_html'], 5, 4); + // Field 'url' was already turned into a <a> link, by field_link_html. + // The field_image_html is an example of an override with more specificity. + add_filter('squat_radar_field_html', [__CLASS__, 'field_image_html'], 7, 4); + // If $value is an array it is flattened into a string here. + // If $value != $original it will _not_ be sanitized, assumption is that it has been already. + add_filter('squat_radar_field_html', [__CLASS__, 'field_html'], 10, 4); + // $value is always a string from this point. + // These filters just add additional wrapper markup. + add_filter('squat_radar_field_html', [__CLASS__, 'field_title_html'], 15, 4); + } + + /** + * Implementation of 'squat_radar_format_event'. + * + * Formats an API event array into HTML. + * + * @param array $event + * The event array from the API. Nested field names with values. + * @param array $fields + * The field names required for display. Colons used to denote nesting. + * @param array $context + * + * @return string + * HTML. + */ + static public function format_event($event, $fields, $context) { + + $context['event'] = $event; + $output = []; + $event_status = self::getValue( $event, ['event_status'] ); + $output[] = '<div class="squat-radar radar-event radar-event-' . $event_status . '">'; + foreach ($fields as $field) { + $field_tree = explode(':', $field); + $value = self::getValue($event, $field_tree); + $field_tree = array_reverse($field_tree); + $output[] = apply_filters('squat_radar_field_html', $value, $value, $field_tree, $context); + } + $output[] = '</div>'; + return $output; + + } + + /** + * Basic implementation of 'squat_radar_field_html' filter. + * + * Put the output into HTML. + * + * @param array|string $value + * The field value being manipulated to become HTML to be displayed. + * @param array|string $original + * The original value of the field before any changes by filters. + * @param array $field + * The field tree. $field[0] being the name of the present field. $field[1] + * being any parent etc. + * @param array $context + * + * @return string + * Flattend array with additional default classes. + */ + static public function field_html($value, $original, $field, $context) { + if ($value != $original) { + return $value; + } + + if (is_array($value)) { + if ( ! empty($value['value']) ) { + $value = $value['value']; + } + elseif ( ! empty($value['title']) ) { + $value = $value['title']; + } + elseif ( ! empty($value['name']) ) { + $value = $value['name']; + } + elseif ( ! empty($value[0]['value']) ) { + foreach ($value as $row) { + $values[] = $row['value']; + } + $value = $values; + } + elseif ( ! empty($value[0]['title']) ) { + foreach ($value as $row) { + $titles[] = $row['title']; + } + $value = $titles; + } + elseif ( ! empty($value[0]['name']) ) { + foreach ($value as $row) { + $names[] = $row['name']; + } + $value = $names; + } + } + + if (is_array($value)) { + $output = '<ul class="squat-radar-' . sanitize_html_class($field[0]) . ' squat-radar-list">'; + foreach ($value as $row) { + $output .= '<li class="squat-radar-item-' . sanitize_html_class($field[0]) . '">' . sanitize_text_field( $row ) . '</li>'; + } + $output .= '</ul>'; + + return $output; + } + else { + $value = '<span class="squat-radar-' . sanitize_html_class($field[0]) . '">' . wp_kses_post( $value ) . '</span>'; + } + + return $value; + } + + /** + * Date field formatting implementation of 'squat_radar_field_html' filter. + */ + static public function field_date_html($value, $original, $field, $context) { + + switch ($field[0]) { + case 'created': + case 'updated': + $output = ''; + if ($value) { + $output = date_i18n( get_option( 'date_format' ), $value ); + $placeholder = ($field[0] == 'created') ? __('Created: %s', 'squat-radar') : __('Updated: %s', 'squat-radar'); + $output = '<span class="squat-radar-meta-data squat-radar-' . $field[0] . '">' . sprintf($placeholder, $output) . '</span>'; + } + return $output; + + // "date_time": [ + // { + // "value": "1556442000", + // "value2": "1556442000", + // "duration": 0, + // "time_start": "2019-04-28T11:00:00+02:00", + // "time_end": "2019-04-28T11:00:00+02:00", + // "rrule": null + // } + // ], + case 'date_time': + + $output = ''; + // There can only be one date. With repeat etc. but just one. + // Repeating events will appear as a new item for each repeat in the feed. + $value = $value[0]; + $output = '<span class="squat-radar-event-start-end">'; + $output .= self::field_date_format( $value['time_start'], 'start' ); + if ($value['time_start'] != $value['time_end']) { + $time_only = ( substr($value['time_start'], 0, 10) == substr($value['time_end'], 0, 10) ); + $output .= ' - ' . self::field_date_format( $value['time_end'], 'end', $time_only ); + } + $output .= '</span>'; + return $output; + + case 'time_start': + $value = $value[0]; + $output = '<span class="squat-radar-event-start">'; + $output .= self::field_date_format($value, 'start'); + $output .= '</span>'; + return $output; + + case 'time_end': + $value = $value[0]; + $output = '<span class="squat-radar-event-end">'; + $output .= self::field_date_format($value, 'end'); + $output .= '</span>'; + return $output; + + } + + return $value; + } + + private static function field_date_format($time, $start_end, $time_only = FALSE) { + + $date_format = get_option('squat_radar_date_format', 'j M Y'); + $time_format = get_option('squat_radar_time_format', 'H:i'); + + // Remove offset to stop time being converted to UTC. + $time = substr($time, 0, -6); + + $output = '<span class="squat-radar-datetime squat-radar-datetime-' . $start_end .'">'; + if ( ! $time_only ) { + $output .= '<span class="squat-radar-date">'; + $output .= date_i18n($date_format, strtotime($time)); + $output .= '</span> '; + } + $output .= '<span class="squat-radar-time">'; + $output .= date_i18n($time_format, strtotime($time)); + $output .= '</span></span>'; + + return $output; + } + + /** + * Location field implementation of 'squat_radar_field_html' filter. + * + * "offline": [ + * { + * "uri": "https://radar.squat.net/api/1.2/location/b5786379-da49-4026-8c4e-bcc1a1563284", + * "id": "b5786379-da49-4026-8c4e-bcc1a1563284", + * "resource": "location", + * "title": "Yorck-Kino Yorckstr. 86 Berlin Deutschland", + * "map": { + * "geom": "POINT (13.3853499 52.4930248)", + * "geo_type": "point", + * "lat": "52.493024800000", + * "lon": "13.385349900000", + * "left": "13.385349900000", + * "top": "52.493024800000", + * "right": "13.385349900000", + * "bottom": "52.493024800000", + * "srid": null, + * "latlon": "52.493024800000,13.385349900000", + * "schemaorg_shape": "" + * } + * } + * ] + */ + static public function field_location_html($value, $original, $field, $context) { + switch ($field[0]) { + case 'map': + $output = []; + foreach ($value as $map) { + if ( is_array($map) && ! empty($map['lat']) && $map['lat'] !== NULL && $map['lon'] !== NULL ) { + $this_output = '<span class="squat-radar-location squat-radar-location-map-link">'; + $lat = $map['lat']; + $lon = $map['lon']; + $this_output .= "<a href=\"https://www.openstreetmap.org/?mlat=$lat&mlon=$lon#map=14/$lat/$lon\" target=\"_blank\">"; + $this_output .= __('[Map]', 'squat-radar'); + $this_output .= '</a></span>'; + $output[] = $this_output; + } + } + return implode(', ', $output); + + case 'address': + $output = []; + foreach ($value as $address) { + if ( is_array($address) ) { + $this_address = []; + foreach (['name_line', 'thoroughfare', 'locality', 'postal_code', 'country'] as $field_name) { + if (! empty($address[$field_name])) { + $this_line = '<span class="squat-radar-location-' . $field_name . '">'; + $this_line .= sanitize_text_field($address[$field_name]); + $this_line .= '</span>'; + $this_address[] = $this_line; + } + } + + $this_output = '<span class="squat-radar-location squat-radar-location-address">'; + $this_output .= implode(', ', $this_address); + $this_output .= '</span>'; + $output[] = $this_output; + } + } + return implode('; ', $output); + } + + return $value; + } + + /** + * Item Radar links implementation of 'squat_radar_field_html' filter. + */ + static public function field_link_html($value, $original, $field, $context) { + if ( ($field[0] == 'title' || $field[0] == 'title_field') && ! empty($context['event']['url'])) { + return '<a href="' . esc_url($context['event']['url']) . '" class="squat-radar-url squat-radar-url-title">' . sanitize_text_field( $value ) . '</a>'; + } + + if ($field[0] == 'url' && count($field) == 1) { + return '<a href="' . esc_url_raw($value) . '" class="squat-radar-url squat-radar-url-more">' . __('moreā¦', 'squat-radar') . '</a>'; + } + elseif ($field[0] == 'url') { + $title = esc_url($value); + array_shift($field); + if (is_array($field)) { + $field_tree = array_reverse($field); + $sibling_fields = self::getValue($context['event'], $field_tree); + $class = 'squat-radar-url-link'; + if (! empty($sibling_fields['title']) ) { + $title = sanitize_text_field( $sibling_fields['title']); + $class = 'squat-radar-url-title'; + } + elseif ( ! empty($sibling_fields['name']) ) { + $title = sanitize_text_field( $sibling_fields['name']); + $class = 'squat-radar-url-name'; + } + } + return '<a href="' . esc_url_raw($value) . '" class="squat-radar-url ' . $class . '">' . $title . '</a>'; + } + + if ($field[0] == 'link') { + return '<a href="' . esc_url_raw($value['url']) . '" class="squat-radar-url squat-radar-url-link">' . esc_url($value['url']) . '</a>'; + } + + return $value; + } + + /** + * Item Radar summary implementation of 'squat_radar_field_html' filter. + */ + static public function field_summary_html($value, $original, $field, $context) { + if ( $field[0] == 'summary' ) { + // Summary is only populated if there is an explict summary. + $value = trim($value); + if ( empty( $value ) ) { + array_shift($field); + if (is_array($field)) { + $field_tree = array_reverse($field); + $sibling_fields = self::getValue($context['event'], $field_tree); + if (! empty( $sibling_fields['value'] ) ) { + $value = wp_trim_words( $sibling_fields['value'], 30 ); + } + } + } + + if ( ! empty($value) ) { + $value = '<span class="squat-radar-body-summary">' . wp_kses_post( $value ) . '</span>'; + } + } + + return $value; + } + + /** + * Format image implementation of 'squat_radar_field_html' filter. + * + * Intentionally run after field_link_html. Showing how to override an existing filter. + * image:file:url + */ + static public function field_image_html($value, $original, $field, $context) { + if ( isset($field[0]) && $field[0] == 'url' && + isset($field[1]) && $field[1] == 'file' && + isset($field[2]) && $field[2] == 'image' + ) { + return '<img src="'. esc_url_raw($original) .'" class="squat-radar-image" \>'; + } + + return $value; + } + + + /** + * Title field HTML implementation of 'squat_radar_field_html' filter. + */ + static public function field_title_html($value, $original, $field, $context) { + if (($field[0] == 'title' || $field[0] == 'title_field') && count($field) == 1) { + $value = '<h3 class="squat-radar-title">' . $value . '</h3>'; + } + + return $value; + } + + /** + * Retrieves a value from a nested array with variable depth. + * + * Handles on level of multiple[] values on a key. + * It will work for deeper multiples, but return the top match. + * + * @param array $array + * The array from which to get the value. + * @param array $parents + * An array of parent keys of the value, starting with the outermost key. + * @param bool $key_exists + * (optional) If given, an already defined variable that is altered by + * reference. + * + * @return mixed + * The requested nested value. Possibly NULL if the value is NULL or not all + * nested parent keys exist. $key_exists is altered by reference and is a + * Boolean that indicates whether all nested parent keys exist (TRUE) or not + * (FALSE). This allows to distinguish between the two possibilities when + * NULL is returned. + */ + public static function &getValue(array &$array, array $parents, &$key_exists = NULL) { + $ref =& $array; + while ($parent = array_shift($parents)) { + if (is_array($ref) && array_key_exists($parent, $ref)) { + $ref =& $ref[$parent]; + } + elseif (is_array($ref) && isset($ref[0])) { + $multiple = []; + array_unshift($parents, $parent); + foreach ($ref as &$value) { + $multiple[] = self::getValue($value, $parents, $key_exists); + } + if (!empty($multiple) ) { + return $multiple; + } + else { + $key_exists = FALSE; + $null = NULL; + return $null; + } + } + else { + $key_exists = FALSE; + $null = NULL; + return $null; + } + } + $key_exists = TRUE; + return $ref; + } +} diff --git a/wp-content/plugins/squat-radar-calendar-integration/includes/squat-radar-instance.php b/wp-content/plugins/squat-radar-calendar-integration/includes/squat-radar-instance.php new file mode 100644 index 0000000000000000000000000000000000000000..f37cc63aaff9de194c41989705cbdffa367089b1 --- /dev/null +++ b/wp-content/plugins/squat-radar-calendar-integration/includes/squat-radar-instance.php @@ -0,0 +1,78 @@ +<?php +/** + * Manage the Squat Radar plugin. + * + * @package squat-radar + * @since 2.0.0 + */ + +// Exit if accessed directly. +defined( 'ABSPATH' ) || exit; + +/** + * Singleton for managing Squat Radar. + */ +class Squat_Radar_Instance { + + private static $instance = null; + + /** + * Creates or returns an instance of this class. + * + * @return A single instance of this class. + */ + public static function get_instance() { + return null == self::$instance ? self::$instance = new self : self::$instance; + } + + private function __construct() { + include SQUAT_RADAR_DIR . 'includes/squat-radar-widget.php'; + include SQUAT_RADAR_DIR . 'includes/squat-radar-connector.php'; + include SQUAT_RADAR_DIR . 'includes/squat-radar-formatter.php'; + + add_shortcode( 'squat_radar_sidebar', [$this, 'print_sidebar'] ); + add_action( 'plugins_loaded', [$this, 'i18n'], 5 ); + add_action( 'widgets_init', [ $this, 'add_sidebar'], 20 ); + add_action( 'widgets_init', ['Squat_Radar_Widget', 'register_widget'] ); + + Squat_Radar_Formatter::register(); + } + + /** + * Load translation files. + */ + function i18n() { + load_plugin_textdomain( 'squat-radar', false, '/languages' ); + } + + /** + * Shortcode callback to print the dynamic sidebar. + */ + function print_sidebar() { + ob_start(); + + if (is_active_sidebar('squat_radar_widget_shortcode')) { + dynamic_sidebar('squat_radar_widget_shortcode'); + } + + return ob_get_clean(); + } + + /** + * Action callback to add the dynamic sidebar. + */ + function add_sidebar() { + + register_sidebar([ + 'name' => __( 'Squat Radar Shortcodes'), + 'description'=> __( 'This widget area is not by default displayed on frontend. It can be displayed with all its widgets with the [squat_radar_sidebar] shortcode.', 'squat-radar' ), + 'id' => 'squat_radar_widget_shortcode', + 'before_widget' => '<div class="widget %2$s">', + 'after_widget' => '</div>', + 'before_title' => '<h3 class="widget-title">', + 'after_title' => '</h3>', + ]); + + } + +} diff --git a/wp-content/plugins/squat-radar-calendar-integration/includes/squat-radar-widget.php b/wp-content/plugins/squat-radar-calendar-integration/includes/squat-radar-widget.php new file mode 100644 index 0000000000000000000000000000000000000000..85ce7055e25db344856028ff9c9ed51ce7e826ba --- /dev/null +++ b/wp-content/plugins/squat-radar-calendar-integration/includes/squat-radar-widget.php @@ -0,0 +1,428 @@ +<?php +/** + * Squat Radar Events Widget. + * + * @package squat-radar + * @since 2.0.0 + */ + +// Exit if accessed directly. +defined( 'ABSPATH' ) || exit; + +class Squat_Radar_Widget extends WP_Widget { + + public function __construct() { + $widget_ops = array( + 'classname' => 'squat-radar-widget', + 'description' => 'Radar Events List', + ); + + $this->connector = new Squat_Radar_Connector(); + + parent::__construct( 'Squat_Radar', 'Squat Radar Events', $widget_ops ); + } + + /** + * Register the widget + */ + public static function register_widget() { + register_widget( __CLASS__ ); + add_action( 'wp_ajax_squat_radar_events', [__CLASS__, 'ajax_callback'] ); + add_action( 'wp_ajax_nopriv_squat_radar_events', [__CLASS__, 'ajax_callback'] ); + add_action( 'wp_enqueue_scripts', [__CLASS__, 'widget_script'] ); + add_action( 'wp_enqueue_scripts', [__CLASS__, 'widget_style'] ); + + add_action( 'squat_radar_widget_cache_cron', [__CLASS__, 'cache_cron'] ); + add_option( 'squat_radar_widget_cron_run', []); + } + + /** + * Enqueue scripts callback, add CSS. + */ + static public function widget_style() { + wp_register_style( 'squat-radar-widget', SQUAT_RADAR_URL . 'assets/squat-radar.css' ); + wp_enqueue_style( 'squat-radar-widget' ); + } + + /** + * Enqueue scripts callback, add JS. + */ + static public function widget_script() { + wp_register_script( 'squat-radar-widget', SQUAT_RADAR_URL . 'assets/squat-radar.js', ['jquery'] ); + } + + /** + * Cron action. + * + * Uses an option to keep track of when run, and updates any (experimental) widgets that update using a cron period instead of ajax. + */ + public static function cache_cron() { + $now = time(); + $last_run = get_option('squat_radar_widget_cron_run', []); + foreach (self::cron_instances() as $number => $instance) { + if (! isset($last_run[$number]) || $last_run[$number] + $instance['cache_expire'] < $now ) { + if (self::cache_refresh($instance)) { + $last_run[$number] = $now; + } + } + } + set_option('squat_radar_widget_cron_run', $last_run); + } + + /** + * Refresh an individual widget instance for cache_cron(). + */ + protected static function cache_refresh($instance) { + $connector = new Squat_Radar_Connector(); + + $languages = apply_filters( 'wpml_active_languages', NULL); + $languages = array_keys($languages); + $languages = array_merge($instance['url']['keys']['language'], (array) $languages); + foreach ($languages as $language) { + try { + // Force update. Don't set expire. + $data = $connector->events($instance['url']['keys']['facets'], $instance['fields'], $language, $instance['limit'], 0, TRUE ); + } + catch ( Squat_Radar_Connector_Exception $e ) { + return FALSE; + } + } + + return TRUE; + } + + /** + * Implementation of WP_Widget::widget(). + * + * Outputs the events for the correct instance of the widget. + */ + public function widget( $args, $instance ) { + $widget_id = 'squat_radar_widget_' . $this->number; + + echo $args['before_widget']; + + if ( ! empty( $instance['title'] ) ) { + echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title']; + } + + if ( ! empty($instance['use_cron']) ) { + try { + echo self::instance_events_html($instance); + } + catch ( Squat_Radar_Connector_Exception $e ) { + if ( current_user_can( 'administrator' ) ) { + echo $e->getCode() . ': ' . $e->getMessage(); + } + echo '<div id="' . $widget_id . '" class="squat-radar-widget"><a href="' . esc_url_raw( $instance['url']['value'] ) . '">' + . esc_url( $instance['url']['value'] ) + . '</a></div>'; + } + } + else { + wp_enqueue_script( 'squat-radar-widget'); + wp_localize_script( 'squat-radar-widget', 'squat_radar_widget', [ 'ajaxurl' => admin_url( 'admin-ajax.php' ) ] ); + wp_localize_script( 'squat-radar-widget', $widget_id, ['number' => $this->number] ); + + echo '<div id="' . $widget_id . '" class="squat-radar-widget squat-radar-ajax"><a href="' . esc_url_raw( $instance['url']['value'] ) . '">' + . esc_url( $instance['url']['value'] ) + . '</a></div>'; + } + + echo $args['after_widget']; + } + + /** + * Action callback for AJAX widget display. + */ + public static function ajax_callback() { + if ( ! array_key_exists('instance', $_POST) ) { + wp_die(); + } + + $data = []; + + // Load instance configuration from ID. + $instance_number = (int) $_POST['instance']['number']; + $widget_options_all = get_option('widget_squat_radar'); + if ( ! isset($widget_options_all[$instance_number]) ) { + wp_die(); + } + + try { + $data['html'] = self::instance_events_html($widget_options_all[$instance_number]); + } + catch ( Squat_Radar_Connector_Exception $e ) { + $data = ['is_error' => TRUE]; + if ( current_user_can( 'administrator' ) ) { + $data['error']['code'] = $e->getCode(); + $data['error']['message'] = $e->getMessage(); + } + } + + wp_send_json($data); + } + + public static function instance_events_html($instance) { + $language = defined('ICL_LANGUAGE_CODE') ? ICL_LANGUAGE_CODE : $instance['url']['keys']['language']; + $connector = new Squat_Radar_Connector(); + $data = $connector->events($instance['url']['keys']['facets'], $instance['fields'], $language, $instance['limit'], $instance['cache_expire']); + $html = ''; + foreach ($data['result'] as $id => $event) { + $output = apply_filters( 'squat_radar_format_event', $event, $instance['fields'], ['instance' => $instance] ); + $html .= implode(' ', $output); + } + + return $html; + } + + /** + * Implementation of WP_Widget::form(). + * + * Widget options. + */ + public function form( $instance ) { + + // + // Title. + // + $field_id = esc_attr( $this->get_field_id( 'title' ) ); + $field_name = esc_attr( $this->get_field_name( 'title' ) ); + $field_label = esc_attr( 'Title:', 'squat-radar' ); + $field_value = empty( $instance['title'] ) ? '' : esc_attr( $instance['title'] ); + $field_class = 'widefat'; + echo "<p>"; + echo "<label for=\"$field_id\">$field_label</label>"; + echo "<input class=\"$field_class\" id=\"$field_id\" name=\"$field_name\" type=\"text\" value=\"$field_value\">"; + echo "</p>"; + + // + // Limit + // + $field_id = esc_attr( $this->get_field_id( 'limit' ) ); + $field_name = esc_attr( $this->get_field_name( 'limit' ) ); + $field_label = esc_attr( 'Max number of events to display:', 'squat-radar' ); + $field_value = empty( $instance['limit'] ) ? '10' : (int) $instance['limit']; + $field_class = 'tiny-text'; + echo "<p>"; + echo "<label for=\"$field_id\">$field_label</label>"; + echo "<input class=\"$field_class\" id=\"$field_id\" name=\"$field_name\" type=\"number\" step=\"1\" min=\"1\" value=\"$field_value\" size=\"3\">"; + echo "</p>"; + + + // + // URL. + // + $field_error = ! empty( $instance['url']['error'] ); + $field_id = esc_attr( $this->get_field_id( 'url' ) ); + $field_name = esc_attr( $this->get_field_name( 'url' ) ); + $field_label = esc_attr( 'Event Search URL:', 'squat-radar' ); + $field_value = empty( $instance['url']['value'] ) ? '' : esc_attr( $instance['url']['value'] ); + $field_class = 'widefat' . $field_error ? ' error' : ''; + echo "<p>"; + echo "<label for=\"$field_id\">$field_label</label>"; + echo "<input class=\"$field_class\" id=\"$field_id\" name=\"$field_name\" type=\"text\" value=\"$field_value\">"; + echo "</p>"; + if ( $field_error ) { + echo '<div class="description error">' . __('The URL was not recognised as a Radar Events search result. It needs to include the domain and the rest of the /events/search/path like: https://radar.squat.net/en/events/city/City_Name/group/123 Start from <a href="https://radar.squat.net/en/events" target="_blank">https://radar.squat.net/en/events</a> and use the filters in the right hand colunm there before copying the URL from your browser address bar.', 'squat-radar') . '</div>'; + } + else { + echo '<div class="description">' . __('Go to <a href="https://radar.squat.net/en/events" target="_blank">https://radar.squat.net/en/events</a> and filter for the events you want to show. Then copy the URL from your address bar into here. It will look similar to: https://radar.squat.net/en/events/city/City_Name/group/123 for example the URL to show all international callouts is https://radar.squat.net/en/events/callout/international-callout', 'squat-radar') . '</div>'; + } + + if ( empty($instance['url']['error']) && ! empty( $instance['url']['keys'] ) ) { + echo '<hr>'; + echo '<p>' . __('Currently selecting events:', 'squat-radar') . '</p>'; + echo '<dl>'; + echo '<dt>' . __('Default language', 'squat-radar') . '</dt>'; + echo '<dd>' . esc_html($instance['url']['keys']['language']) . '</dd>'; + foreach ($instance['url']['keys']['facets'] as $key => $value) { + echo '<dt>' . esc_html($key) . '</dt>'; + echo '<dd>' . esc_html($value) . '</dd>'; + } + echo '</dl>'; + + } + + echo '<hr>'; + echo '<fieldset>'; + echo '<legend>' . __('Fields', 'squat-radar') . '</legend>'; + echo '<p>'; + // Some sensible checkbox defaults. + if ( empty($instance['fields']) ) { + $instance['fields'] = [ + 'title_field' => '', + 'date_time:time_start' => '', + 'body:summary' => '', + 'category' => '', + 'offline:address' => '', + 'offline:map' => '', + 'url' => '', + ]; + } + foreach ($this->preset_fields() as $api_field_name => $field_label) { + $field_id = esc_attr( $this->get_field_id( 'field-' . $api_field_name ) ); + $field_name = esc_attr( $this->get_field_name( 'field-' . $api_field_name ) ); + $field_label = esc_attr( $field_label ); + $checked = ''; + if ( isset($instance['fields'][$api_field_name]) ) { + unset($instance['fields'][$api_field_name]); + $checked = ' checked="checked"'; + } + echo "<input type=\"checkbox\" class=\"checkbox\" id=\"$field_id\" name=\"$field_name\"$checked />"; + echo "<label for=\"$field_id\">$field_label</label><br />"; + } + echo '</p>'; + echo '</fieldset>'; + + // ADVANCED + echo '<hr>'; + echo '<fieldset>'; + echo '<legend>' . __('Advanced settings', 'squat-radar') . '</legend>'; + // + // Fields. + // + $field_id = esc_attr( $this->get_field_id( 'fields' ) ); + $field_name = esc_attr( $this->get_field_name( 'fields' ) ); + $field_label = esc_attr( 'Additional fields:', 'squat-radar' ); + $field_value = empty( $instance['fields'] ) ? '' : esc_attr( implode( ', ', $instance['fields'] ) ); + $field_class = 'widefat'; + echo "<p>"; + echo "<label for=\"$field_id\">$field_label</label>"; + echo "<input class=\"$field_class\" id=\"$field_id\" name=\"$field_name\" type=\"text\" value=\"$field_value\">"; + echo "</p>"; + echo '<div class="description">' . __('A comma seperated list of field API names. Examples: phone, price, flyer, offline:address:thoroughfare. Some fields might need an additonal filter to format them properly.') . '</div>'; + + // + // Cache expiry. + // + $field_id = esc_attr( $this->get_field_id( 'cache_expire' ) ); + $field_name = esc_attr( $this->get_field_name( 'cache_expire' ) ); + $field_label = esc_attr( 'Cache length:', 'squat-radar' ); + $field_value = empty( $instance['cache_expire'] ) ? 10800 : (int) $instance['cache_expire']; + $field_class = 'widefat'; + echo "<p>"; + echo "<label for=\"$field_id\">$field_label</label>"; + echo "<select class=\"$field_class\" id=\"$field_id\" name=\"$field_name\">"; + echo '<option value="3600"' . selected( $field_value, 3600 ) . '>' . __('1 hour') . '</option>'; + echo '<option value="10800"' . selected( $field_value, 10800 ) . '>' . __('3 hours') . '</option>'; + echo '<option value="43200"' . selected( $field_value, 43200 ) . '>' . __('12 hours') . '</option>'; + echo "</select>"; + echo "</p>"; + echo '<div class="description">' . __('Length of time the cache of events will be kept. Longer faster, but updated less often.') . '</div>'; + + $field_id = esc_attr( $this->get_field_id( 'use_cron' ) ); + $field_name = esc_attr( $this->get_field_name( 'use_cron' ) ); + $field_label = esc_attr__( 'Use cron' ); + $use_cron = isset($instance['use_cron']) ? (bool) $instance['use_cron'] : false; + $checked = checked( $use_cron, TRUE, FALSE ); + echo "<input type=\"checkbox\" class=\"checkbox\" id=\"$field_id\" name=\"$field_name\"$checked />"; + echo "<label for=\"$field_id\">$field_label</label><br />"; + echo '<div class="description">' . __('Experimental. Do not use AJAX, but always display the cached version of the events. Update the cache after the expiry length using cron. Works best if you have a regular external cronjob running.') . '</div>'; + + echo '</fieldset>'; + + } + + /** + * Implementation of WP_Widget::update(). + * + * Save widget options. + */ + public function update( $new_instance, $old_instance ) { + $options = []; + + if ( ! empty( $new_instance['title'] ) ) { + $options['title'] = sanitize_text_field( $new_instance['title'] ); + } + else { + $options['title'] = ''; + } + + if ( ! empty($new_instance['url']) ) { + $keys = $this->connector->decode_search_url($new_instance['url']); + $options['url']['keys'] = $keys; + $options['url']['value'] = $new_instance['url']; + if (empty($keys)) { + $options['url']['error'] = 'URL not recognised'; + } + } + else { + $options['url'] = ['value' => '', 'keys' => []]; + } + + $options['fields'] = []; + foreach ($this->preset_fields() as $field_name => $field_label) { + if ( ! empty($new_instance['field-' . $field_name]) ) { + $options['fields'][$field_name] = $field_name; + } + } + + if ( ! empty($new_instance['fields']) ) { + $matches = []; + preg_match_all('/([a-zA-Z_:]+)/', $new_instance['fields'], $matches); + $options['fields'] += array_combine($matches[0], $matches[0]); + } + + if ( ! empty( $new_instance['limit'] ) ) { + $options['limit'] = (int) $new_instance['limit']; + } + + if ( ! empty( $new_instance['cache_expire'] ) ) { + $options['cache_expire'] = (int) $new_instance['cache_expire']; + } + else { + $options['cache_expire'] = 10800; + } + + if ( empty( $new_instance['use_cron'] )) { + $options['use_cron'] = FALSE; + $cron_instances = self::cron_instances(); + unset($cron_instances[$this->number]); + if ( empty($cron_instances) && ($timestamp = wp_next_scheduled( 'squat_radar_widget_cache_cron' ) )) { + wp_unschedule_event( $timestamp, 'squat_radar_widget_cache_cron' ); + } + } + else { + $options['use_cron'] = TRUE; + self::cache_refresh($options); + if ( ! wp_next_scheduled( 'squat_radar_widget_cache_cron' ) ) { + wp_schedule_event( time() + $options['cache_expire'], 'hourly', 'squat_radar_widget_cache_cron'); + } + } + + return $options; + } + + public function preset_fields() { + return [ + 'title_field' => __( 'Title' ), + 'event_status' => __( 'Event status (proposed, or cancelled)' ), + 'date_time' => __( 'Date and Time (start and optional end)' ), + 'date_time:time_start' => __( 'Date and Time (start only)' ), + 'body' => __( 'Body' ), + 'body:summary' => __( 'Body (teaser, summary)' ), + 'category' => __( 'Categories' ), + 'topic' => __( 'Tags' ), + 'offline:address' => __( 'Address' ), + 'offline:map' => __( 'Map (link)' ), + 'og_group_ref' => __( 'Groups' ), + 'price_category' => __( 'Price category' ), + 'image:file:url' => __( 'Image' ), + 'link' => __( 'Event URL (entered not Radar)' ), + 'url' => __( 'More link (to event on Radar)' ), + ]; + } + + public static function cron_instances() { + $cron_instances = []; + $instances = get_option( 'widget_squat_radar' ); + foreach ($instances as $number => $instance) { + if (! empty($instance['use_cron']) ) { + $cron_instances[$number] = $instance; + } + } + + return $cron_instances; + } + +} diff --git a/wp-content/plugins/squat-radar-calendar-integration/readme.txt b/wp-content/plugins/squat-radar-calendar-integration/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..a7ded67042c5a954ab7cb94d5246cea499e2e7e5 --- /dev/null +++ b/wp-content/plugins/squat-radar-calendar-integration/readme.txt @@ -0,0 +1,27 @@ +=== squat-radar === +Tags: calendar,events +Requires at least: 4.0 +Tested up to: 5.2 +Requires PHP: 5.4.0 +Stable tag: 2.0.5 +License: GPLv2 or later +License URI: https://www.gnu.org/licenses/gpl-2.0.html + +Provides integration with https://radar.squat.net/ to display events on your Wordpress website. + +== Installation == +Enable the plugin as normal for your system. + +Once activated: +1. Visit the Widgets page. Here you will now see 'Squat Radar Events' widget, and the 'Squat Radar Shortcode' sidebar. +2. Add the widget to either the sidebar you want, or if you want to use the shortcode in content, the 'Squat Radar Shortcode' sidebar. +3. To configure the sidebar go to https://radar.squat.net/events and filter for the events you want to show. Maybe your city and group, or a category etc. +4. Once you have the events filter you want copy the address from your address bar into the widget. +5. Select which fields you would like to show. + +If you put the widget in a displayed sidebar that's it. The filtered list of upcoming events will now show up. +If you used the Shortcode sidebar, add [squat_radar_sidebar] to the content where you want the events to display. +If you put the shortcode in a page called 'Events', make sure you've disabled any plugins which would suppress the contents of that page, for example 'The Events Calendar'. + +For extra-easy instructions with screenshots, look here: +https://network23.org/blog/2019/10/18/radar-events-plugin/ diff --git a/wp-content/plugins/squat-radar-calendar-integration/squat-radar.php b/wp-content/plugins/squat-radar-calendar-integration/squat-radar.php new file mode 100644 index 0000000000000000000000000000000000000000..479bcb9cb0dba8fd3a0702167a023f44f7a62644 --- /dev/null +++ b/wp-content/plugins/squat-radar-calendar-integration/squat-radar.php @@ -0,0 +1,30 @@ +<?php + +/** + * Squat Radar bootstrap file + * + * @link https://radar.squat.net/ + * @since 2.0.0 + * @package Squat_Radar + * + * @wordpress-plugin + * Plugin Name: Squat Radar calendar integration + * Plugin URI: https://0xacab.org/radar/radar-wp + * Description: Provides widget, and shortcode, integration for displaying events from https://radar.squat.net/ agenda. + * Version: 2.0.5 + * Author: Radar contributors + * License: GPL-2.0+ + * License URI: http://www.gnu.org/licenses/gpl-2.0.txt + * Text Domain: squat-radar + * Domain Path: /languages + */ + +if ( ! defined( 'ABSPATH' ) ) { + die; +} + +define( 'SQUAT_RADAR_URL', plugin_dir_url( __FILE__ ) ); +define( 'SQUAT_RADAR_DIR', trailingslashit( plugin_dir_path( __FILE__ ) ) ); +include SQUAT_RADAR_DIR . 'includes/squat-radar-instance.php'; + +Squat_Radar_Instance::get_instance();