WIP New v2 plugin with widget and shortcode, and more WP friendly markup.

This commit is contained in:
ekes 2019-04-27 18:36:32 +02:00
parent 5fd32fb63b
commit 649f84ad8c
557 changed files with 648 additions and 98567 deletions

View file

@ -0,0 +1,73 @@
<?php
class Squat_Radar_Connector {
const BASE_URL = 'https://radar.squat.net';
const API_EVENTS = '/api/1.2/search/events.json';
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);
}
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;
}
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) ) {
$query['fields'] = urlencode(implode(',', $fields));
}
if ( ! empty($language) ) {
$query['language'] = urlencode($language);
}
if ( ! empty($limit) ) {
$query['limit'] = urlencode($limit);
}
return $query;
}
function events( $facets, $fields = [], $language = NULL, $limit = 10, $expiration = 10800, $reset = FALSE ) {
$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 { }

View file

@ -0,0 +1,82 @@
<?php
class Squat_Radar_Formatter {
static public function format_event($event, $fields) {
foreach ($fields as $field) {
$value = self::getValue($event, explode(':', $field));
}
}
/**
* Retrieves a value from a nested array with variable depth.
*
* This helper function should be used when the depth of the array element
* being retrieved may vary (that is, the number of parent keys is variable).
* It is primarily used for form structures and renderable arrays.
*
* Without this helper function the only way to get a nested array value with
* variable depth in one line would be using eval(), which should be avoided:
* @code
* // Do not do this! Avoid eval().
* // May also throw a PHP notice, if the variable array keys do not exist.
* eval('$value = $array[\'' . implode("']['", $parents) . "'];");
* @endcode
*
* Instead, use this helper function:
* @code
* $value = NestedArray::getValue($form, $parents);
* @endcode
*
* A return value of NULL is ambiguous, and can mean either that the requested
* key does not exist, or that the actual value is NULL. If it is required to
* know whether the nested array key actually exists, pass a third argument
* that is altered by reference:
* @code
* $key_exists = NULL;
* $value = NestedArray::getValue($form, $parents, $key_exists);
* if ($key_exists) {
* // Do something with $value.
* }
* @endcode
*
* However if the number of array parent keys is static, the value should
* always be retrieved directly rather than calling this function.
* For instance:
* @code
* $value = $form['signature_settings']['signature'];
* @endcode
*
* @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.
*
* @author drupal.org contributors
*/
public static function &getValue(array &$array, array $parents, &$key_exists = NULL) {
$ref =& $array;
foreach ($parents as $parent) {
if (is_array($ref) && array_key_exists($parent, $ref)) {
$ref =& $ref[$parent];
}
else {
$key_exists = FALSE;
$null = NULL;
return $null;
}
}
$key_exists = TRUE;
return $ref;
}
}

View file

@ -0,0 +1,90 @@
<?php
/**
* Manage the Squat Radar plugin.
*
* @package Squat_Radar
*/
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', 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') );
Squat_Radar_Formatter::register();
}
/**
* Load translation files
*
* @since 0.2.4
*/
function i18n() {
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');
}
return ob_get_clean();
}
function add_sidebar() {
register_sidebar(array(
'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' ),
'id' => 'squat_radar_widget_shortcode',
'before_widget' => '<div class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
));
}
}

View file

@ -0,0 +1,284 @@
<?php
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_style', [__CLASS__, 'widget_style'] );
}
static public function widget_style() {
wp_register_style( 'squat-radar-widget', SQUAT_RADAR_URL . '/assets/squat-radar.css' );
}
static public function widget_script() {
wp_register_script( 'squat-radar-widget', SQUAT_RADAR_URL . '/assets/squat-radar.js', ['jquery'] );
}
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 '<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'];
}
public static function ajax_callback() {
if ( ! array_key_exists('instance', $_POST) ) {
wp_die();
}
$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 );
}
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);
}
$html = '';
foreach ($data['result'] as $id => $event) {
$html .= Squat_Radar_Formatter::format_event($event);
}
$data['html'] = $html;
wp_send_json($data);
$print.="<li class='squat-li'>";
if ($r->title) {
$print.="<h3 class='squat-h3'>";
$print.=strip_tags($r->title);
$print.="</h3>";
}
if ($r->date_time[0]) {
$print.="<span class='squat-tijd'>";
$print.=verwerk_datum($r->date_time[0]);
$print.="</span>";
}
if ($r->body and $r->body->value) {
$print.="<p class='squat-p'>".limit_text(strip_tags($r->body->value), 40) . "</p>";
}
if ($r->title and $r->date_time[0]) {
$radar_link = "https://radar.squat.net/en/node/$r_id";
if ( $squat_lang == "nl") {
$nog_meer = "zie alles";
$meer = "meer";
} else {
$nog_meer = "see all";
$meer = "more";
}
$print.= "<a class='squat-link button' target='_blank' href='#' data-link='$radar_link' data-nog-meer='$nog_meer'>$meer</a>";
}
$print.="</li>";
}
public function form( $instance ) {
//
// Introduction.
//
echo "<p>";
echo __('If you want to display this single widget in a page or post use the Shortcode:') . '&nbsp;';
if ( $this->number == '__i__' ) {
echo '<em>' . __( 'Shortcode will be available once you have saved this the first time') . '</em>';
}
else {
esc_attr_e( '[squat_radar_widget id="' . $this->number . '"]' );
}
echo "</p>";
//
// 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( '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', 'squat-radar') . '</div>';
}
if ( ! $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>';
}
// 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( '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 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') . '</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>';
echo '</fieldset>';
}
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'] );
}
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' => []];
}
if ( ! empty($new_instance['fields']) ) {
$matches = [];
preg_match_all('/([a-zA-Z:]+)/', $new_instance['fields'], $matches);
$options['fields'] = $matches[0];
}
else {
$options['fields'] = [];
}
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;
}
return $options;
}
}