<?php
/**
 * Liveticker: Plugin main class.
 *
 * This file contains the plugin's base class.
 *
 * @package SCLiveticker
 */

namespace SCLiveticker;

use WP_Query;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}


/**
 * Liveticker.
 */
class SCLiveticker {
	/**
	 * Options tag.
	 *
	 * @var string OPTIONS
	 */
	const VERSION = '1.2.2';

	/**
	 * Options tag.
	 *
	 * @var string OPTIONS
	 */
	const OPTION = 'stklcode-liveticker';

	/**
	 * Plugin options.
	 *
	 * @var array $options
	 */
	protected static $options;

	/**
	 * Marker if shortcode is present.
	 *
	 * @var boolean $shortcode_present
	 */
	protected static $shortcode_present = false;


	/**
	 * Marker if widget is present.
	 *
	 * @var boolean $shortcode_present
	 */
	protected static $widget_present = false;

	/**
	 * Plugin initialization.
	 *
	 * @return void
	 */
	public static function init(): void {
		// Skip on autosave.
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}

		// Load plugin options.
		self::update_options();

		// Add filter for REST API queries.
		add_filter( 'rest_api_init', array( 'SCLiveticker\\Api', 'init' ) );
		add_filter( 'rest_scliveticker_tick_query', array( 'SCLiveticker\\Api', 'tick_query_filter' ), 10, 2 );

		// Skip on AJAX if not enabled.
		if ( ( ! isset( self::$options['enable_ajax'] ) || 1 !== self::$options['enable_ajax'] ) && ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
			return;
		}

		// Load Textdomain.
		load_plugin_textdomain( 'stklcode-liveticker' );

		// Allow shortcodes in widgets.
		add_filter( 'widget_text', 'do_shortcode' );

		// Add shortcode.
		add_shortcode( 'liveticker', array( __CLASS__, 'shortcode_ticker_show' ) );

		// Enqueue styles and JavaScript.
		add_action( 'wp_footer', array( __CLASS__, 'enqueue_resources' ) );

		// Add AJAX hook if configured.
		if ( 1 === self::$options['enable_ajax'] ) {
			add_action( 'wp_ajax_sclt_update-ticks', array( __CLASS__, 'ajax_update' ) );
			add_action( 'wp_ajax_nopriv_sclt_update-ticks', array( __CLASS__, 'ajax_update' ) );
		}

		// Admin only actions.
		if ( is_admin() ) {
			// Add dashboard "right now" functionality.
			add_action( 'right_now_content_table_end', array( 'SCLiveticker\\Admin', 'dashboard_right_now' ) );

			// Settings.
			add_action( 'admin_init', array( 'SCLiveticker\\Settings', 'register_settings' ) );
			add_action( 'admin_menu', array( 'SCLiveticker\\Admin', 'register_settings_page' ) );
		}
	}

	/**
	 * Register tick post type.
	 *
	 * @return void
	 */
	public static function register_types(): void {
		// Add new taxonomy, make it hierarchical (like categories).
		$labels = array(
			'name'              => _x( 'Ticker', 'taxonomy general name', 'stklcode-liveticker' ),
			'singular_name'     => _x( 'Ticker', 'taxonomy singular name', 'stklcode-liveticker' ),
			'search_items'      => __( 'Search Tickers', 'stklcode-liveticker' ),
			'all_items'         => __( 'All Tickers', 'stklcode-liveticker' ),
			'parent_item'       => __( 'Parent Ticker', 'stklcode-liveticker' ),
			'parent_item_colon' => __( 'Parent Ticker:', 'stklcode-liveticker' ),
			'edit_item'         => __( 'Edit Ticker', 'stklcode-liveticker' ),
			'update_item'       => __( 'Update Ticker', 'stklcode-liveticker' ),
			'add_new_item'      => __( 'Add New Ticker', 'stklcode-liveticker' ),
			'new_item_name'     => __( 'New Ticker', 'stklcode-liveticker' ),
			'menu_name'         => __( 'Ticker', 'stklcode-liveticker' ),
		);

		register_taxonomy(
			'scliveticker_ticker',
			array( 'scliveticker_tick' ),
			array(
				'hierarchical'      => true,
				'labels'            => $labels,
				'show_ui'           => true,
				'show_admin_column' => true,
				'query_var'         => true,
				'show_in_rest'      => true,
			)
		);

		// Post type arguments.
		$args = array(
			'labels'             => array(
				'name'               => __( 'Ticks', 'stklcode-liveticker' ),
				'singular_name'      => __( 'Tick', 'stklcode-liveticker' ),
				'add_new'            => __( 'Add New', 'stklcode-liveticker' ),
				'add_new_item'       => __( 'Add New Tick', 'stklcode-liveticker' ),
				'edit_item'          => __( 'Edit Tick', 'stklcode-liveticker' ),
				'new_item'           => __( 'New Tick', 'stklcode-liveticker' ),
				'all_items'          => __( 'All Ticks', 'stklcode-liveticker' ),
				'view_item'          => __( 'View Tick', 'stklcode-liveticker' ),
				'search_items'       => __( 'Search Ticks', 'stklcode-liveticker' ),
				'not_found'          => __( 'No Ticks found', 'stklcode-liveticker' ),
				'not_found_in_trash' => __( 'No Ticks found in Trash', 'stklcode-liveticker' ),
				'parent_item_colon'  => '',
				'menu_name'          => __( 'Liveticker', 'stklcode-liveticker' ),
			),
			'public'             => false,
			'publicly_queryable' => true,
			'show_ui'            => true,
			'show_in_menu'       => true,
			'menu_icon'          => 'dashicons-rss',
			'capability_type'    => 'post',
			'supports'           => array( 'title', 'editor', 'author' ),
			'taxonomies'         => array( 'scliveticker_ticker' ),
			'has_archive'        => true,
			'show_in_rest'       => true,
		);

		register_post_type( 'scliveticker_tick', $args );
	}

	/**
	 * Output Liveticker
	 *
	 * @param array $atts Shortcode attributes.
	 *
	 * @return string
	 */
	public static function shortcode_ticker_show( array $atts ): string {
		// Indicate presence of shortcode (to enqueue styles/scripts later).
		self::$shortcode_present = true;

		// Initialize output.
		$output = '';

		// Check if first attribute is filled.
		if ( ! empty( $atts['ticker'] ) ) {
			$ticker = sanitize_text_field( $atts['ticker'] );

			// Set limit to infinite, if not set explicitly.
			if ( ! isset( $atts['limit'] ) ) {
				$atts['limit'] = - 1;
			}
			$limit = intval( $atts['limit'] );

			// Determine if feed link should be shown.
			if ( isset( $atts['feed'] ) ) {
				$show_feed = 'true' === strtolower( $atts['feed'] ) || '1' === $atts['feed'];
			} else {
				$show_feed = 1 === self::$options['show_feed'];
			}
			$args = array(
				'post_type'      => 'scliveticker_tick',
				'posts_per_page' => $limit,
				'tax_query'      => array(
					array(
						'taxonomy' => 'scliveticker_ticker',
						'field'    => 'slug',
						'terms'    => $ticker,
					),
				),
			);

			$wp_query = new WP_Query( $args );

			$ticks = '';
			$last  = null;
			while ( $wp_query->have_posts() ) {
				$wp_query->the_post();
				$ticks .= self::tick_html( get_the_time( 'd.m.Y H:i' ), get_the_title(), get_the_content(), get_the_ID() );
				if ( is_null( $last ) ) {
					$last = get_post_modified_time( 'Y-m-d\TH:i:s', true );
				}
			}

			$output = '<div class="wp-block-scliveticker-ticker';
			if ( 1 === self::$options['enable_ajax'] ) {
				$output .= ' sclt-ajax" '
							. 'data-sclt-ticker="' . $ticker . '" '
							. 'data-sclt-limit="' . $limit . '" '
							. 'data-sclt-last="' . $last;
			}
			$output .= '"><ul>' . $ticks . '</ul></div>';

			// Show RSS feed link, if configured.
			if ( $show_feed ) {
				$feed_link = get_post_type_archive_feed_link( 'scliveticker_tick' ) . '';
				if ( false === strpos( $feed_link, '&' ) ) {
					$feed_link .= '?scliveticker_ticker=' . $ticker;
				} else {
					$feed_link .= '&scliveticker_ticker=' . $ticker;
				}
				$output .= '<a href="' . esc_attr( $feed_link ) . '">Feed</a>';
			}
		}

		return $output;
	}

	/**
	 * Register frontend JS.
	 *
	 * @return void
	 * @since 1.1 Combined former methods "enqueue_styles" and "enqueue_scripts".
	 */
	public static function enqueue_resources(): void {
		// Only add if shortcode is present.
		if ( self::$shortcode_present || self::$widget_present || has_block( 'scliveticker/ticker' ) ) {
			wp_enqueue_script(
				'scliveticker-js',
				SCLIVETICKER_BASE . 'scripts/liveticker.min.js',
				array(),
				self::VERSION,
				true
			);

			// Add endpoint to script.
			wp_localize_script(
				'scliveticker-js',
				'scliveticker',
				array(
					'ajax_url'        => admin_url( 'admin-ajax.php' ),
					'nonce'           => wp_create_nonce( 'scliveticker_update-ticks' ),
					'api'             => rest_url(),
					'embedded_script' => boolval( self::$options['embedded_script'] ),
					'poll_interval'   => self::$options['poll_interval'] * 1000,
				)
			);

			// Enqueue CSS if enabled.
			if ( 1 === self::$options['enable_css'] ) {
				wp_enqueue_style(
					'sclt-css',
					SCLIVETICKER_BASE . 'styles/liveticker.min.css',
					'',
					self::VERSION
				);
			}
		}
	}

	/**
	 * Process Ajax upload file
	 *
	 * @return void
	 */
	public static function ajax_update(): void {
		// Verify AJAX nonce.
		check_ajax_referer( 'scliveticker_update-ticks' );

		// Extract update requests.
		if ( isset( $_POST['update'] ) && is_array( $_POST['update'] ) ) {  // Input var okay.
			$res = array();
			// @codingStandardsIgnoreLine Sanitization of array handled on field level.
			foreach ( wp_unslash( $_POST['update'] ) as $update_req ) {
				if ( is_array( $update_req ) && ( isset( $update_req['s'] ) || isset( $update_req['w'] ) ) ) {
					if ( isset( $update_req['s'] ) ) {
						$is_widget = false;
						$slug      = sanitize_text_field( $update_req['s'] );
					} elseif ( isset( $update_req['w'] ) ) {
						$is_widget = true;
						$slug      = sanitize_text_field( $update_req['w'] );
					} else {
						// Should never occur, but for completeness' sake...
						break;
					}

					$limit     = ( isset( $update_req['l'] ) ) ? intval( $update_req['l'] ) : - 1;
					$last_poll = explode(
						',',
						gmdate(
							'Y,m,d,H,i,s',
							( isset( $update_req['t'] ) ) ? intval( $update_req['t'] ) : 0
						)
					);

					// Query new ticks from DB.
					$query_args = array(
						'post_type'      => 'scliveticker_tick',
						'posts_per_page' => $limit,
						'tax_query'      => array(
							array(
								'taxonomy' => 'scliveticker_ticker',
								'field'    => 'slug',
								'terms'    => $slug,
							),
						),
						'date_query'     => array(
							'column' => 'post_date_gmt',
							'after'  => array(
								'year'   => intval( $last_poll[0] ),
								'month'  => intval( $last_poll[1] ),
								'day'    => intval( $last_poll[2] ),
								'hour'   => intval( $last_poll[3] ),
								'minute' => intval( $last_poll[4] ),
								'second' => intval( $last_poll[5] ),
							),
						),
					);

					$query = new WP_Query( $query_args );

					$out = '';
					while ( $query->have_posts() ) {
						$query->the_post();
						if ( $is_widget ) {
							$out .= self::tick_html_widget( get_the_time( 'd.m.Y H:i' ), get_the_title(), false, get_the_ID() );
						} else {
							$out .= self::tick_html( get_the_time( 'd.m.Y H:i' ), get_the_title(), get_the_content(), get_the_ID() );
						}
					}

					if ( $is_widget ) {
						$res[] = array(
							'w' => $slug,
							'h' => $out,
							't' => time(),
						);
					} else {
						$res[] = array(
							's' => $slug,
							'h' => $out,
							't' => time(),
						);
					}
				}
			}
			// Echo JSON encoded result set.
			echo wp_json_encode( $res );
		}

		exit;
	}

	/**
	 * Mark that Widget is present.
	 *
	 * @return void
	 */
	public static function mark_widget_present(): void {
		self::$widget_present = true;
	}

	/**
	 * Update options.
	 *
	 * @return void
	 *
	 * @since 1.0.0
	 * @since 1.3.0 removed unused parameter
	 */
	protected static function update_options(): void {
		self::$options = wp_parse_args(
			get_option( self::OPTION ),
			self::default_options()
		);
	}

	/**
	 * Create default plugin configuration.
	 *
	 * @return array The options array.
	 */
	protected static function default_options(): array {
		return array(
			'enable_ajax'      => 1,
			'poll_interval'    => 60,
			'enable_css'       => 1,
			'show_feed'        => 0,
			'enable_shortcode' => 0,
			'embedded_script'  => 0,
			'reset_settings'   => 0,
		);
	}

	/**
	 * Generate HTML code for a tick element.
	 *
	 * @param string  $time    Tick time (readable).
	 * @param string  $title   Tick title.
	 * @param string  $content Tick content.
	 * @param integer $id      Tick ID.
	 *
	 * @return string HTML code of tick.
	 */
	private static function tick_html( string $time, string $title, string $content, int $id ): string {
		if ( self::$options['enable_shortcode'] ) {
			$content = do_shortcode( $content );
		}

		return '<li class="sclt-tick" data-sclt-tick-id="' . esc_attr( $id ) . '">'
			. '<span class="sclt-tick-time">' . esc_html( $time ) . '</span>'
			. '<span class="sclt-tick-title">' . esc_html( $title ) . '</span>'
			. '<div class="sclt-tick-content">' . $content . '</div></li>';
	}

	/**
	 * Generate HTML code for a tick element in widget.
	 *
	 * @param string  $time      Tick time (readable).
	 * @param string  $title     Tick title.
	 * @param boolean $highlight Highlight element.
	 * @param integer $id        Tick ID.
	 *
	 * @return string HTML code of widget tick.
	 */
	public static function tick_html_widget( string $time, string $title, bool $highlight, int $id = 0 ): string {
		$out = '<li';
		if ( $highlight ) {
			$out .= ' class="sclt-widget-new"';
		}
		if ( $id > 0 ) {
			$out .= ' data-sclt-tick-id="' . esc_attr( $id ) . '"';
		}
		return $out . '>'
			. '<span class="sclt-widget-time">' . esc_html( $time ) . '</span>'
			. '<span class="sclt-widget-title">' . $title . '</span>'
			. '</li>';
	}
}