<?php
/**
 * Dynamic Content.
 *
 * Lets any text/textarea field on any element resolve a dynamic token such as
 * {{cafw:post_title}} to live data. Tokens are inserted from the editor and
 * resolved generically through the shortcode_atts filter, so every element
 * (ours, native and third-party) is supported with no per-element code.
 *
 * @package CraftAddonsForWPBakery
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class CAFW_Dynamic
 */
class CAFW_Dynamic {

	const MODULE = 'dynamic-content';

	/**
	 * @return void
	 */
	public static function init() {
		add_filter( 'the_content', array( __CLASS__, 'resolve_string' ), 9 );
		add_action( 'vc_after_init', array( __CLASS__, 'register_resolvers' ) );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_editor' ) );
		add_action( 'vc_frontend_editor_enqueue_js_css', array( __CLASS__, 'enqueue_editor_front' ) );
	}

	/**
	 * The source catalog, grouped. Filterable so Pro (or a site) can add more.
	 *
	 * @return array<int,array{label:string,items:array}>
	 */
	public static function sources() {
		$groups = array(
			array(
				'label' => __( 'Post', 'craft-addons-for-wpbakery' ),
				'items' => array(
					'post_title'   => array( 'label' => __( 'Post title', 'craft-addons-for-wpbakery' ) ),
					'post_excerpt' => array( 'label' => __( 'Post excerpt', 'craft-addons-for-wpbakery' ) ),
					'post_date'    => array(
						'label'       => __( 'Post date', 'craft-addons-for-wpbakery' ),
						'arg_label'   => __( 'Format (optional)', 'craft-addons-for-wpbakery' ),
						'placeholder' => 'F j, Y',
					),
					'post_url'     => array( 'label' => __( 'Post URL', 'craft-addons-for-wpbakery' ) ),
					'post_id'      => array( 'label' => __( 'Post ID', 'craft-addons-for-wpbakery' ) ),
					'author_name'  => array( 'label' => __( 'Author name', 'craft-addons-for-wpbakery' ) ),
				),
			),
		);

		foreach ( self::pro_sources() as $group ) {
			$groups[] = $group;
		}

		return apply_filters( 'cafw_dynamic_sources', $groups );
	}

	/**
	 * The Pro source groups. Listed (locked) in the free build as an upsell;
	 * Pro unlocks them and resolves their tokens via CAFW_Pro_Dynamic.
	 *
	 * @return array<int,array{label:string,pro:bool,items:array}>
	 */
	public static function pro_sources() {
		$groups = array(
			array(
				'label' => __( 'Custom fields', 'craft-addons-for-wpbakery' ),
				'pro'   => true,
				'items' => array(
					'post_meta' => array(
						'label'       => __( 'Custom field (post meta)', 'craft-addons-for-wpbakery' ),
						'arg_label'   => __( 'Meta key', 'craft-addons-for-wpbakery' ),
						'placeholder' => 'my_field_key',
						'arg_require' => true,
					),
				),
			),
			array(
				'label' => __( 'Site & request', 'craft-addons-for-wpbakery' ),
				'pro'   => true,
				'items' => array(
					'site_title'   => array( 'label' => __( 'Site title', 'craft-addons-for-wpbakery' ) ),
					'site_tagline' => array( 'label' => __( 'Site tagline', 'craft-addons-for-wpbakery' ) ),
					'current_date' => array(
						'label'       => __( 'Current date', 'craft-addons-for-wpbakery' ),
						'arg_label'   => __( 'Format (optional)', 'craft-addons-for-wpbakery' ),
						'placeholder' => 'F j, Y',
					),
					'user_name'    => array( 'label' => __( 'Logged-in user name', 'craft-addons-for-wpbakery' ) ),
					'url_param'    => array(
						'label'       => __( 'URL parameter', 'craft-addons-for-wpbakery' ),
						'arg_label'   => __( 'Parameter name', 'craft-addons-for-wpbakery' ),
						'placeholder' => 'utm_source',
						'arg_require' => true,
					),
				),
			),
		);

		if ( function_exists( 'get_field' ) ) {
			$groups[] = array(
				'label' => __( 'ACF', 'craft-addons-for-wpbakery' ),
				'pro'   => true,
				'items' => array(
					'acf' => array(
						'label'       => __( 'ACF field', 'craft-addons-for-wpbakery' ),
						'arg_label'   => __( 'Field name', 'craft-addons-for-wpbakery' ),
						'placeholder' => 'my_field',
						'arg_require' => true,
					),
				),
			);
		}

		return $groups;
	}

	/**
	 * Resolve a single token to its value.
	 *
	 * @param string $key Source key.
	 * @param string $arg Optional argument.
	 * @return string
	 */
	public static function resolve_token( $key, $arg = '' ) {
		$post_id = get_the_ID();
		$value   = '';

		switch ( $key ) {
			case 'post_title':
				$value = $post_id ? get_the_title( $post_id ) : '';
				break;
			case 'post_excerpt':
				$value = $post_id ? get_the_excerpt( $post_id ) : '';
				break;
			case 'post_date':
				$value = $post_id ? get_the_date( $arg, $post_id ) : '';
				break;
			case 'post_url':
				$value = $post_id ? (string) get_permalink( $post_id ) : '';
				break;
			case 'post_id':
				$value = $post_id ? (string) $post_id : '';
				break;
			case 'author_name':
				$author = $post_id ? (int) get_post_field( 'post_author', $post_id ) : 0;
				$value  = $author ? get_the_author_meta( 'display_name', $author ) : '';
				break;
		}

		return (string) apply_filters( 'cafw_dynamic_resolve', $value, $key, $arg, $post_id );
	}

	/**
	 * Replace every {{cafw:...}} token in a string.
	 *
	 * @param string $value Raw value.
	 * @return string
	 */
	public static function resolve_string( $value ) {
		if ( ! is_string( $value ) || false === strpos( $value, '{{cafw:' ) ) {
			return $value;
		}

		return preg_replace_callback(
			'/\{\{cafw:([a-z0-9_]+)(?:\|([^}]*))?\}\}/i',
			static function ( $m ) {
				return CAFW_Dynamic::resolve_token( $m[1], isset( $m[2] ) ? $m[2] : '' );
			},
			$value
		);
	}

	/**
	 * Resolve tokens in a shortcode's resolved attributes.
	 *
	 * @param array $out Attributes after shortcode_atts().
	 * @return array
	 */
	public static function resolve_atts( $out ) {
		if ( ! is_array( $out ) ) {
			return $out;
		}
		foreach ( $out as $key => $value ) {
			if ( is_string( $value ) ) {
				$out[ $key ] = self::resolve_string( $value );
			}
		}
		return $out;
	}

	/**
	 * Hook the resolver onto every mapped shortcode.
	 *
	 * @return void
	 */
	public static function register_resolvers() {
		if ( ! class_exists( 'WPBMap' ) ) {
			return;
		}
		$shortcodes = WPBMap::getAllShortCodes();
		if ( ! is_array( $shortcodes ) ) {
			return;
		}
		foreach ( array_keys( $shortcodes ) as $base ) {
			add_filter( 'shortcode_atts_' . $base, array( __CLASS__, 'resolve_atts' ), 20 );
		}
	}

	/**
	 * @param string $hook Current admin page hook.
	 * @return void
	 */
	public static function enqueue_editor( $hook ) {
		if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) {
			return;
		}
		self::enqueue_editor_front();
	}

	/**
	 * @return void
	 */
	public static function enqueue_editor_front() {
		wp_enqueue_style(
			'cafw-dynamic',
			CAFW_URL . 'admin/css/cafw-dynamic.css',
			array(),
			CAFW_Plugin::asset_ver( 'admin/css/cafw-dynamic.css' )
		);
		wp_enqueue_script(
			'cafw-dynamic',
			CAFW_URL . 'admin/js/cafw-dynamic.js',
			array(),
			CAFW_Plugin::asset_ver( 'admin/js/cafw-dynamic.js' ),
			true
		);
		wp_localize_script(
			'cafw-dynamic',
			'cafwDynamic',
			array(
				'sources'    => self::sources(),
				'upgradeUrl' => function_exists( 'cafw_fs' ) ? cafw_fs()->get_upgrade_url() : '',
				'i18n'       => array(
					'button'  => __( 'Insert dynamic content', 'craft-addons-for-wpbakery' ),
					'title'   => __( 'Dynamic content', 'craft-addons-for-wpbakery' ),
					'insert'  => __( 'Insert', 'craft-addons-for-wpbakery' ),
					'dynamic' => __( 'Dynamic', 'craft-addons-for-wpbakery' ),
					'pro'     => __( 'Pro', 'craft-addons-for-wpbakery' ),
				),
			)
		);
	}
}
