From a6f657092d6fcb021a5dbce0c2ec33821e563fc7 Mon Sep 17 00:00:00 2001 From: ekes Date: Mon, 29 Apr 2019 20:07:19 +0200 Subject: [PATCH] WIP suggested fields, additional theming, cron, shortcodes. --- assets/squat-radar.css | 55 ++++---- includes/squat-radar-connector.php | 5 +- includes/squat-radar-formatter.php | 88 +++++++----- includes/squat-radar-instance.php | 33 ++--- includes/squat-radar-widget.php | 218 +++++++++++++++++++++++++---- 5 files changed, 284 insertions(+), 115 deletions(-) diff --git a/assets/squat-radar.css b/assets/squat-radar.css index 92e60cf..699652c 100644 --- a/assets/squat-radar.css +++ b/assets/squat-radar.css @@ -1,48 +1,55 @@ -.Squat_radar li.squat-li { +.squat-radar.radar-event { margin-bottom: 1em; padding-bottom: 1em; border-bottom: 1px solid #ddd; text-align:left; } - .Squat_radar li.squat-li::after { - content: ''; - display: block; - float: none; - clear: both; - } - .Squat_radar li.squat-li:last-child { - border-bottom: 0; - padding-bottom: 0; - margin-bottom: 0; - } +.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 h3.squat-h3 { +.squat-radar .squat-radar-title { line-height: 1; font-size: 1.4em; margin-bottom: 0.2em; } -.Squat_radar p.squat-p { - display: none; - text-align:left; - margin-bottom: 0.6em; -} - -.Squat_radar span.squat-tijd { +.squat-radar .squat-radar-datetime { font-weight: bold; -/* display: block;*/ } -.Squat_radar a.squat-link { +.squat-radar .squat-radar-datetime::before { + content: ''; + display: block; + float: none; + clear: both; +} + +.squat-radar .squat-radar-list li { + display: inline; +} + +.squat-radar a.squat-radar-url-more { padding: .4180469716em 1.11575em; margin-left: 10px; /* margin-bottom: 10px;*/ float: right; } -.Squat_radar a.squat-link:hover { +.squat-radar a.squat-radar-url-more:hover { color: white !important; } -.Squat_radar a.squat-link:focus { +.squat_radar a.squat-radar-url-more:focus { outline: 0; } diff --git a/includes/squat-radar-connector.php b/includes/squat-radar-connector.php index 54e5510..f8ff6bc 100644 --- a/includes/squat-radar-connector.php +++ b/includes/squat-radar-connector.php @@ -45,7 +45,8 @@ class Squat_Radar_Connector { $query[] = ['facets[' . urlencode($key) . '][]' => urlencode($value)]; } if ( ! empty($fields) ) { - $query['fields'] = urlencode(implode(',', $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); @@ -57,7 +58,7 @@ class Squat_Radar_Connector { } function events( $facets, $fields = [], $language = NULL, $limit = 10, $expiration = 10800, $reset = FALSE ) { - $fields = array_merge($fields, ['uuid', 'url']); + $fields = array_merge($fields, ['uuid', 'title', 'url', 'event_status']); $transient_key = 'squat_radar_events_' . sha1(implode($facets) . implode($fields) . $language . $limit); if (! $reset && $data = get_transient( $transient_key )) { return $data; diff --git a/includes/squat-radar-formatter.php b/includes/squat-radar-formatter.php index 43e87c1..055ea01 100644 --- a/includes/squat-radar-formatter.php +++ b/includes/squat-radar-formatter.php @@ -9,16 +9,21 @@ class Squat_Radar_Formatter { // 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); - // field 'url' was turned into a more link, example of an override with more specificity. + // Field 'url' was already turned into 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. These filters just add additional wrapper markup. + // $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); } @@ -26,7 +31,8 @@ class Squat_Radar_Formatter { $context['event'] = $event; $output = []; - $output[] = '
'; + $event_status = self::getValue( $event, ['event_status'] ); + $output[] = '
'; foreach ($fields as $field) { $field_tree = explode(':', $field); $value = self::getValue($event, $field_tree); @@ -70,10 +76,11 @@ class Squat_Radar_Formatter { // 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 = ''; - $output .= self::field_date_format($value['time_start'], 'start'); + $output = ''; + $output .= self::field_date_format( $value['time_start'], 'start' ); if ($value['time_start'] != $value['time_end']) { - $output .= self::field_date_format($value['time_end'], '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 .= ''; return $output; @@ -97,15 +104,21 @@ class Squat_Radar_Formatter { return $value; } - private static function field_date_format($time, $start_end) { + 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 = ''; - $output .= ''; - $output .= date_i18n($date_format, strtotime($time)); - $output .= ' '; + if ( ! $time_only ) { + $output .= ''; + $output .= date_i18n($date_format, strtotime($time)); + $output .= ' '; + } + $output .= ''; $output .= date_i18n($time_format, strtotime($time)); $output .= ''; @@ -142,11 +155,11 @@ class Squat_Radar_Formatter { case 'map': $output = []; foreach ($value as $map) { - if ( is_array($value) && $value['lat'] !== NULL && $value['lon'] !== NULL ) { + if ( is_array($map) && $map['lat'] !== NULL && $map['lon'] !== NULL ) { $this_output = ''; - $lat = $value['lat']; - $lon = $value['lon']; - $this_output .= ""; + $lat = $map['lat']; + $lon = $map['lon']; + $this_output .= ""; $this_output .= __('[Map]', 'squat-radar'); $this_output .= ''; $output[] = $this_output; @@ -157,19 +170,19 @@ class Squat_Radar_Formatter { case 'address': $output = []; foreach ($value as $address) { - if ( is_array($value) ) { + if ( is_array($address) ) { $this_address = []; foreach (['name_line', 'thoroughfare', 'locality', 'postal_code', 'country'] as $field_name) { - if (! empty($value[$field_name])) { + if (! empty($address[$field_name])) { $this_line = ''; - $this_line .= sanitize_text_field($value[$field_name]); + $this_line .= sanitize_text_field($address[$field_name]); $this_line .= ''; $this_address[] = $this_line; } } $this_output = ''; - $this_output .= implode(' ,', $this_address); + $this_output .= implode(', ', $this_address); $this_output .= ''; $output[] = $this_output; } @@ -184,27 +197,28 @@ class Squat_Radar_Formatter { * Item Radar links implementation of 'squat_radar_field_html' filter. */ function field_link_html($value, $original, $field, $context) { - if ($field[0] == 'title' && ! empty($context['event']['url'])) { + if ( ($field[0] == 'title' || $field[0] == 'title_field') && ! empty($context['event']['url'])) { return '' . sanitize_text_field( $value ) . ''; } - + if ($field[0] == 'url' && count($field) == 1) { return '' . __('moreā€¦', 'squat-radar') . ''; } - - if ($field[0] == 'url') { - array_shift($field); - $field_tree = array_reverse($field); - $sibling_fields = self::getValue($event, $field_tree); + elseif ($field[0] == 'url') { $title = esc_url($value); - $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'; + 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 '' . $title . ''; } @@ -223,11 +237,11 @@ class Squat_Radar_Formatter { * image:file:url */ function field_image_html($value, $original, $field, $context) { - if ($field[0] == 'url' && $field[1] == 'file' && $field[2] == 'image') { - return ''; - } + if ($field[0] == 'url' && $field[1] == 'file' && $field[2] == 'image') { + return ''; + } - return $value; + return $value; } /** diff --git a/includes/squat-radar-instance.php b/includes/squat-radar-instance.php index 17f9ac6..db62124 100644 --- a/includes/squat-radar-instance.php +++ b/includes/squat-radar-instance.php @@ -29,11 +29,10 @@ class Squat_Radar_Instance { include SQUAT_RADAR_DIR . 'includes/squat-radar-connector.php'; include SQUAT_RADAR_DIR . 'includes/squat-radar-formatter.php'; - add_shortcode( 'squat_radar_sidebar', array($this, 'print_sidebar') ); - // add_shortcode( 'squat_radar_widget', array( $this, 'shortcode' ) ); - add_action( 'plugins_loaded', array( $this, 'i18n' ), 5 ); - add_action( 'widgets_init', array( $this, 'add_sidebar' ), 20 ); - add_action( 'widgets_init', array('Squat_Radar_Widget', 'register_widget') ); + 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(); } @@ -47,25 +46,11 @@ class Squat_Radar_Instance { load_plugin_textdomain( 'squat-radar', false, '/languages' ); } - /** - * output a widget using 'widget' shortcode. - * - * Requires the widget ID. - * You can overwrite widget args: before_widget, before_title, after_title, after_widget - * - * @example [widget id="text-1"] - * @since 0.1 - */ - public function shortcode( $atts, $content = null ) { - $atts['echo'] = false; - return $this->do_widget( $atts ); - } - function print_sidebar() { ob_start(); - if (is_active_sidebar('squat_widget_gebied')) { - dynamic_sidebar('squat_widget_gebied'); + if (is_active_sidebar('squat_radar_widget_shortcode')) { + dynamic_sidebar('squat_radar_widget_shortcode'); } return ob_get_clean(); @@ -75,15 +60,15 @@ class Squat_Radar_Instance { function add_sidebar() { - register_sidebar(array( + 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] shortcode.', 'squat-radar' ), + 'description'=> __( 'This widget area is not by default displayed on frontend. It can be displayed with all its widgets with the [squat_radar] shortcode; or used to hold active widgets displayed with their own [squat_radar_widget id="X"] shortcode, see instructions on widget configuration for the id.', 'squat-radar' ), 'id' => 'squat_radar_widget_shortcode', 'before_widget' => '
', 'after_widget' => '
', 'before_title' => '

', 'after_title' => '

', - )); + ]); } diff --git a/includes/squat-radar-widget.php b/includes/squat-radar-widget.php index 6d8657b..e41a8bc 100644 --- a/includes/squat-radar-widget.php +++ b/includes/squat-radar-widget.php @@ -23,6 +23,11 @@ class Squat_Radar_Widget extends WP_Widget { add_action( 'wp_ajax_nopriv_squat_radar_events', [__CLASS__, 'ajax_callback'] ); add_action( 'wp_enqueue_scripts', [__CLASS__, 'widget_script'] ); add_action( 'wp_enqueue_style', [__CLASS__, 'widget_style'] ); + + add_shortcode( 'squat_radar_widget', [__CLASS__, 'shortcode' ] ); + + add_action( 'squat_radar_widget_cache_cron', [__CLASS__, 'cache_cron'] ); + add_option( 'squat_radar_widget_cron_run', []); } static public function widget_style() { @@ -33,23 +38,98 @@ class Squat_Radar_Widget extends WP_Widget { wp_register_script( 'squat-radar-widget', SQUAT_RADAR_URL . '/assets/squat-radar.js', ['jquery'] ); } + /** + * Output a widget using 'squat_radar_widget' shortcode. + * + * Requires the widget ID. + * + * @example [squat_radar_widget id="1"] + */ + static public function shortcode( $attributes, $content = '' ) { + $defaults = [ + 'id' => '__i__', + ]; + $attributes = shortcode_atts($defaults, $attributes, 'squat_radar_widget'); + + // Return early if ID is unknown. + $option = get_option( 'widget_squat_radar' ); + if (! isset( $option[$attributes['id']] )) { + if ( current_user_can( 'administrator' ) ) { + $content = '' . __('Squat Radar Widget shortcode ID not recognised. Check the suggestion at the top of the widget in the adminstration interface.', 'squat-radar') . ''; + } + return $content; + } + + $instance = $option[$attributes['id']]; + // render the widget + ob_start(); + // To allow overriding the args here? For before after etc. + the_widget( __CLASS__, $instance); + $content = ob_get_clean(); + + return $content; + } + + 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); + } + + protected static function cache_refresh($instance) { + $connector = new Squat_Radar_Connector(); + + // @todo Languages... + try { + $data = $connector->events($instance['url']['keys']['facets'], $instance['fields'], $language, $instance['limit'], 0, TRUE ); + } + catch ( Squat_Radar_Connector_Exception $e ) { + return FALSE; + } + + return TRUE; + } + + public function widget( $args, $instance ) { wp_enqueue_style( 'squat-radar-widget' ); - wp_enqueue_script( 'squat-radar-widget'); - wp_localize_script( 'squat-radar-widget', 'squat_radar_widget', [ 'ajaxurl' => admin_url( 'admin-ajax.php' ) ] ); - // Seems non-trivial to send (potentially) multiple values for different widget instances appending an array/hash. $widget_id = 'squat_radar_widget_' . $this->number; - wp_localize_script( 'squat-radar-widget', $widget_id, $instance ); - + echo $args['before_widget']; if ( ! empty( $instance['title'] ) ) { echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title']; } - echo ''; + 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 ''; + } + } + 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, $instance ); + + echo ''; + } echo $args['after_widget']; } @@ -59,11 +139,10 @@ class Squat_Radar_Widget extends WP_Widget { wp_die(); } + $data = []; $instance = $_POST['instance']; - $language = defined('ICL_LANGUAGE_CODE') ? ICL_LANGUAGE_CODE : $instance['url']['keys']['language']; - $connector = new Squat_Radar_Connector(); try { - $data = $connector->events($instance['url']['keys']['facets'], $instance['fields'], $language, $instance['limit'], $instance['cache_expire'], TRUE ); + $data['html'] = self::instance_events_html($instance); } catch ( Squat_Radar_Connector_Exception $e ) { $data = ['is_error' => TRUE]; @@ -71,20 +150,26 @@ class Squat_Radar_Widget extends WP_Widget { $data['error']['code'] = $e->getCode(); $data['error']['message'] = $e->getMessage(); } - wp_send_json($data); } - $html = ''; - foreach ($data['result'] as $id => $event) { - $output = apply_filters( 'squat_radar_format_event', $event, $instance['fields'], ['instance' => $instance] ); - $html .= implode($output); - } - $data['html'] = $html; - 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'], TRUE ); + $html = ''; + foreach ($data['result'] as $id => $event) { + $output = apply_filters( 'squat_radar_format_event', $event, $instance['fields'], ['instance' => $instance] ); + $html .= implode(' ', $output); + } + + return $html; + } + public function form( $instance ) { + // // Introduction. // @@ -159,6 +244,25 @@ class Squat_Radar_Widget extends WP_Widget { } + echo '
'; + echo '
'; + echo '' . __('Fields', 'squat-radar') . ''; + echo '

'; + 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 ""; + echo "
"; + } + echo '

'; + echo '
'; + // ADVANCED echo '
'; echo '
'; @@ -168,14 +272,14 @@ class Squat_Radar_Widget extends WP_Widget { // $field_id = esc_attr( $this->get_field_id( 'fields' ) ); $field_name = esc_attr( $this->get_field_name( 'fields' ) ); - $field_label = esc_attr( 'Fields:', 'squat-radar' ); - $field_value = empty( $instance['fields'] ) ? '' : esc_attr( implode( ' ,', $instance['fields'] ) ); + $field_label = esc_attr( 'Additional fields:', 'squat-radar' ); + $field_value = empty( $instance['fields'] ) ? '' : esc_attr( implode( ', ', $instance['fields'] ) ); $field_class = 'widefat'; echo "

"; echo ""; echo ""; echo "

"; - echo '
' . __('A list of fields to display. Presently these are API names, hence advanced field. Examples: title, body, topic, category, date_time, image, flyer, tags, offline:map') . '
'; + echo '
' . __('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. Can also be used instead of checkboxes to define the order fields are displayed in.') . '
'; // // Cache expiry. @@ -194,6 +298,15 @@ class Squat_Radar_Widget extends WP_Widget { echo ""; echo "

"; echo '
' . __('Length of time the cache of events will be kept. Longer faster, but updated less often.') . '
'; + + $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 ""; + echo "
"; + echo '
' . __('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.') . '
'; echo '
'; @@ -202,7 +315,6 @@ class Squat_Radar_Widget extends WP_Widget { public function update( $new_instance, $old_instance ) { $options = []; - $options['debug'] = $new_instance; if ( ! empty( $new_instance['title'] ) ) { $options['title'] = sanitize_text_field( $new_instance['title'] ); @@ -223,13 +335,17 @@ class Squat_Radar_Widget extends WP_Widget { $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'] = $matches[0]; - } - else { - $options['fields'] = []; + $options['fields'] += array_combine($matches[0], $matches[0]); } if ( ! empty( $new_instance['limit'] ) ) { @@ -243,8 +359,54 @@ class Squat_Radar_Widget extends WP_Widget { $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; + 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' ), + '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; + } + +}