mirror of
https://0xacab.org/radar/radar-wp.git
synced 2025-04-20 14:56:31 +02:00
WIP New v2 plugin with widget and shortcode, and more WP friendly markup.
This commit is contained in:
parent
5fd32fb63b
commit
649f84ad8c
557 changed files with 648 additions and 98567 deletions
73
includes/squat-radar-connector.php
Normal file
73
includes/squat-radar-connector.php
Normal 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 { }
|
82
includes/squat-radar-formatter.php
Normal file
82
includes/squat-radar-formatter.php
Normal 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;
|
||||
}
|
||||
}
|
90
includes/squat-radar-instance.php
Normal file
90
includes/squat-radar-instance.php
Normal 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>',
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
}
|
284
includes/squat-radar-widget.php
Normal file
284
includes/squat-radar-widget.php
Normal 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:') . ' ';
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue