<?php
/**
 * File for Hustle_Module_Page_Abstract class.
 *
 * @package Hustle
 * @since 4.0.1
 */

/**
 * Class Hustle_Module_Page.
 * Base model for the listing pages and their wizards.
 *
 * @since 4.0.1
 */
abstract class Hustle_Module_Page_Abstract extends Hustle_Admin_Page_Abstract {

	/**
	 * Edit page slug defined by WordPress when registering the page.
	 *
	 * @since 4.3.1
	 * @var string
	 */
	private $page_edit_slug;

	/**
	 * Wizard page slug assigned by us.
	 *
	 * @since 4.0.1
	 * @var string
	 */
	protected $page_edit;

	/**
	 * Wizard page title.
	 *
	 * @since 4.0.1
	 * @var string
	 */
	protected $page_edit_title;

	/**
	 * Capability required for the wizard page to be available.
	 *
	 * @since 4.0.1
	 * @var string
	 */
	protected $page_edit_capability;

	/**
	 * Path to the wizard's template page relative to the 'views' folder.
	 *
	 * @since 4.0.1
	 * @var string
	 */
	protected $page_edit_template_path;

	/**
	 * Current module. Only set on wizards when the module exists.
	 *
	 * @since 4.0.3
	 * @var integer
	 */
	protected $module = false;

	/**
	 * Count of the active module of the current type.
	 *
	 * @since 4.2.0
	 * @var integer
	 */
	private $module_count_type;

	/**
	 * Module type this page belongs to.
	 *
	 * @since 4.2.0
	 * @var string
	 */
	public $module_type;

	/**
	 * Established the properties for the page.
	 *
	 * @since 4.0.1
	 */
	protected function init() {

		$this->set_page_properties();

		$this->page_menu_title = $this->page_title;

		$this->page = Hustle_Data::get_listing_page_by_module_type( $this->module_type );

		$this->page_capability = 'hustle_edit_module';

		$this->page_edit = Hustle_Data::get_wizard_page_by_module_type( $this->module_type );

		$this->page_edit_capability = 'hustle_edit_module';

		/* translators: module's type */
		$this->page_edit_title = sprintf( esc_html__( 'New %s', 'hustle' ), Opt_In_Utils::get_module_type_display_name( $this->module_type ) );

		add_filter( 'submenu_file', array( $this, 'admin_submenu_file' ), 10, 2 );

		add_action( 'admin_head', array( $this, 'hide_unwanted_submenus' ) );

		// Admin-menu-editor compatibility.
		add_action( 'admin_menu_editor-menu_replaced', array( $this, 'hide_unwanted_submenus' ) );

		// Actions to perform when the current page is the listing or the wizard page.
		if ( ! empty( $this->current_page ) && ( $this->current_page === $this->page || $this->current_page === $this->page_edit ) ) {
			$this->on_listing_and_wizard_actions();
		}
	}

	/**
	 * Perform actions during the 'load-{page}' hook.
	 *
	 * @since 7.8.11
	 */
	public function current_page_loaded() {
		Hustle_Background_Conversion_Log::get_instance()->process_task();
		parent::current_page_loaded();
	}

	/**
	 * Set up the page's own properties
	 * Like the current module type, page title, path to the listing page and wizard page template.
	 *
	 * @since 4.0.2
	 */
	abstract protected function set_page_properties();

	/**
	 * Actions to be performed on Dashboard page.
	 *
	 * @since 4.0.4
	 */
	protected function on_listing_and_wizard_actions() {

		if ( $this->page_edit === $this->current_page ) {
			$this->on_wizard_only_actions();
		} else {
			$this->on_listing_only_actions();
		}
	}

	/**
	 * Actions to run on listing pages only
	 *
	 * @since 4.2.0
	 */
	private function on_listing_only_actions() {
		add_filter( 'removable_query_args', array( $this, 'maybe_remove_paged' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'register_listing_scripts' ), 99 );
	}

	/**
	 * Remove paged get attribute if there isn't a module and it's not the first page
	 *
	 * @since 4.0.0
	 * @param array $removable_query_args URL query args to be removed.
	 * @return array
	 */
	public function maybe_remove_paged( $removable_query_args ) {
		$paged       = filter_input( INPUT_GET, 'paged', FILTER_VALIDATE_INT );
		$module_type = $this->module_type;

		if ( $paged && 1 !== $paged && $module_type ) {
			$args             = array(
				'module_type' => $module_type,
				'page'        => $paged,
			);
			$entries_per_page = Hustle_Settings_Admin::get_per_page( 'module' );
			$modules          = Hustle_Module_Collection::instance()->get_all( null, $args, $entries_per_page );
			if ( empty( $modules ) ) {
				$_SERVER['REQUEST_URI'] = remove_query_arg( 'paged' );
				$removable_query_args[] = 'paged';
				unset( $_GET['paged'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			}
		}

		if ( $module_type ) {
			self::maybe_view_stats( $module_type );
		}

		return $removable_query_args;
	}

	/**
	 * Change pagination page to the relevant one for View Stats links
	 *
	 * @param string $module_type Module type.
	 */
	private static function maybe_view_stats( $module_type ) {
		$module_id = filter_input( INPUT_GET, 'view-stats', FILTER_VALIDATE_INT );
		if ( ! $module_id ) {
			return;
		}
		$module_id        = (string) $module_id;
		$args             = array(
			'module_type' => $module_type,
			'fields'      => 'ids',
		);
		$entries_per_page = Hustle_Settings_Admin::get_per_page( 'module' );
		$modules          = Hustle_Module_Collection::instance()->get_all( null, $args );
		$i                = array_search( $module_id, $modules, true );
		if ( false === $i ) {
			return;
		}

		$paged = ceil( ( $i + 1 ) / $entries_per_page );
		if ( 1 < $paged ) {
			$_GET['paged'] = $paged;
		}
	}

	/**
	 * Actions to run on wizard pages only
	 *
	 * @since 4.0.3
	 */
	private function on_wizard_only_actions() {

		// Set the current module on Wizards, abort if invalid.
		$module_id = filter_input( INPUT_GET, 'id', FILTER_VALIDATE_INT );
		$module    = Hustle_Module_Collection::instance()->return_model_from_id( $module_id );

		if ( is_wp_error( $module ) ) {
			// Redirect asap.
			add_action( 'admin_init', array( $this, 'redirect_module_not_found' ) );
			return;
		}

		$this->module = $module;

		// Scripts for all wizards.
		add_action( 'admin_enqueue_scripts', array( $this, 'register_wizard_scripts' ) );

		if ( Hustle_Module_Model::SOCIAL_SHARING_MODULE !== $this->module_type ) {

			// Scripts for non ssharing wizards.
			add_action( 'admin_enqueue_scripts', array( $this, 'register_non_sshare_wizard_scripts' ) );

			// Allow rich editor in.
			add_filter( 'user_can_richedit', '__return_true' );

			// Set up all the filters and buttons for tinymce editors.
			$this->set_up_tinymce();

			// Add hustle's button to tinymce editor.
			add_filter( 'mce_buttons', array( $this, 'register_tinymce_buttons' ) );

			add_filter( 'mce_external_plugins', array( $this, 'add_hustle_tinymce_button_and_remove_externals' ) );
		}
	}

	/**
	 * Scripts used in all wizards
	 * They used to be enqueued by Hustle_Module_Admin.
	 *
	 * @since 4.2.0
	 */
	public function register_wizard_scripts() {

		wp_enqueue_script( 'jquery-ui-sortable' );

		wp_enqueue_script( 'fast_wistia', '//fast.wistia.com/assets/external/E-v1.js', array(), '1', true );

		self::add_color_picker();
	}

	/**
	 * Scripts used in all listings.
	 * They used to be enqueued by Hustle_Module_Admin.
	 *
	 * @since 4.2.0
	 */
	public function register_listing_scripts() {

		wp_enqueue_script(
			'chartjs',
			Opt_In::$plugin_url . 'assets/js/vendor/chartjs/chart.min.js',
			array(),
			'2.7.2',
			true
		);
	}

	/**
	 * Register the scripts used in wizards, but not in the Ssharing one
	 * Scripts used to be enqueued by Hustle_Module_Admin.
	 *
	 * @since 4.2.0
	 */
	public function register_non_sshare_wizard_scripts() {

		wp_enqueue_script( 'thickbox' );
		wp_enqueue_media();
		wp_enqueue_script( 'media-upload' );

		Opt_In_Utils::maybe_add_scripts_for_ie();

		// Datepicker and timpicker for automated email in optins.
		wp_enqueue_script( 'jquery-ui-datepicker' );
		wp_enqueue_script(
			'jquery-ui-timepicker',
			Opt_In::$plugin_url . 'assets/js/vendor/jquery.timepicker.min.js',
			array( 'jquery' ),
			'1.3.5',
			true
		);

		wp_enqueue_style(
			'jquery-ui-timepicker',
			Opt_In::$plugin_url . 'assets/css/jquery.timepicker.min.css',
			array(),
			'1.3.5'
		);

		// Register moment.js and its timezone extension.
		// Used for schedule, to calculate time with timezones on client side.
		wp_enqueue_script(
			'hustle-moment',
			Opt_In::$plugin_url . 'assets/js/vendor/moment.min.js',
			array( 'jquery' ),
			Opt_In::VERSION,
			true
		);

		wp_enqueue_script(
			'hustle-moment-timezone',
			Opt_In::$plugin_url . 'assets/js/vendor/moment-timezone-with-data.min.js',
			array( 'hustle-moment' ),
			Opt_In::VERSION,
			true
		);
	}

	/**
	 * Removing all MCE external plugins which often break our pages and add Hustle's button
	 *
	 * @since 3.0.8
	 * @param array $external_plugins External plugins.
	 * @return array
	 */
	public function add_hustle_tinymce_button_and_remove_externals( $external_plugins ) {
		remove_all_filters( 'mce_external_plugins' );

		$external_plugins           = array();
		$external_plugins['hustle'] = Opt_In::$plugin_url . 'assets/js/vendor/tiny-mce-button.js';
		add_action( 'admin_footer', array( $this, 'add_tinymce_variables' ) );

		return $external_plugins;
	}

	/**
	 * Add the current fields to the editor's selector
	 *
	 * @since 3.0.8
	 */
	public function add_tinymce_variables() {

		$var_button   = array();
		$saved_fields = $this->module->get_form_fields();
		$var_button   = array();

		if ( is_array( $saved_fields ) && ! empty( $saved_fields ) ) {
			$fields         = array();
			$ignored_fields = Hustle_Entry_Model::ignored_fields();

			foreach ( $saved_fields as $field_name => $data ) {
				if ( ! in_array( $data['type'], $ignored_fields, true ) ) {
					$fields[ $field_name ] = esc_html( $data['label'] );
				}
			}

			// Add Unsubscribe Link.
			$fields['hustle_unsubscribe_link'] = esc_html__( 'Unsubscribe Link', 'hustle' );

			$available_editors = array( 'success_message', 'email_body' );

			/**
			 * Print JS details for the custom TinyMCE "Insert Variable" button
			 *
			 * @see assets/js/vendor/tiny-mce-button.js
			 */
			$var_button = array(
				/* translators: Plugin name */
				'button_title'      => sprintf( __( 'Add %s Fields', 'hustle' ), Opt_In_Utils::get_plugin_name() ),
				'fields'            => $fields,
				'available_editors' => $available_editors,
			);
		}

		printf(
			'<script>window.hustleData = %s;</script>',
			wp_json_encode( $var_button )
		);
	}

	/**
	 * Register hustle's button for tinymce
	 *
	 * @since 4.0.0
	 * @since 4.2.0 Moved from Hustle_Module_Admin to this class.
	 *
	 * @param array $buttons Registered buttons.
	 * @return array
	 */
	public function register_tinymce_buttons( $buttons ) {
		array_unshift( $buttons, 'hustlefields' );
		return $buttons;
	}

	/**
	 * Register the listing page and its wizard
	 *
	 * @return void
	 */
	public function register_admin_menu() {
		parent::register_admin_menu();

		$this->page_edit_slug = add_submenu_page( 'hustle', $this->page_edit_title, $this->page_edit_title, $this->page_edit_capability, $this->page_edit, array( $this, 'render_edit_page' ) );

		add_action( 'load-' . $this->page_edit_slug, array( $this, 'current_page_loaded' ) );
	}

	/**
	 * Get the arguments used when rendering the main page.
	 *
	 * @since 4.0.1
	 * @return array
	 */
	protected function get_page_template_args() {

		$entries_per_page = Hustle_Settings_Admin::get_per_page( 'module' );

		$capability = array(
			'hustle_create'        => current_user_can( 'hustle_create' ),
			'hustle_access_emails' => current_user_can( 'hustle_access_emails' ),
		);

		// Don't use filter_input() here, because of see Hustle_Module_Admin::maybe_remove_paged function.
		$paged = ! empty( $_GET['paged'] ) ? (int) $_GET['paged'] : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

		$args = array(
			'module_type' => $this->module_type,
			'page'        => $paged,
			'filter'      => array( 'can_edit' => true ),
		);

		$search = filter_input( INPUT_GET, 'q' );
		if ( $search ) {
			$args['filter']['q'] = $search;
		}

		$modules = Hustle_Module_Collection::instance()->get_all(
			null,
			$args,
			$entries_per_page
		);

		$active_modules = Hustle_Module_Collection::instance()->get_all(
			true,
			array(
				'module_type' => $this->module_type,
				'count_only'  => true,
			)
		);

		return array(
			'total'            => $this->get_total_count_modules_current_type(),
			'active'           => $active_modules,
			'modules'          => $modules,
			'is_free'          => Opt_In_Utils::is_free(),
			'capability'       => $capability,
			'entries_per_page' => $entries_per_page,
			'message'          => filter_input( INPUT_GET, 'message', FILTER_SANITIZE_SPECIAL_CHARS ),
			'sui'              => $this->get_sui_summary_config( '' ),
		);
	}

	/**
	 * Hide module's edit pages from the submenu on dashboard.
	 *
	 * @since 4.0.1
	 */
	public function hide_unwanted_submenus() {
		remove_submenu_page( 'hustle', $this->page_edit );
	}

	/**
	 * Highlight submenu's parent on admin page.
	 *
	 * @since 4.0.1
	 *
	 * @param string $submenu_file The submenu file.
	 * @param string $parent_file The submenu item's parent file.
	 *
	 * @return string
	 */
	public function admin_submenu_file( $submenu_file, $parent_file ) {
		global $plugin_page;

		if ( 'hustle' !== $parent_file ) {
			return $submenu_file;
		}

		if ( $this->page_edit === $plugin_page ) {
			$submenu_file = $this->page;
		}

		return $submenu_file;
	}

	/**
	 * Redirect to the listing page when in wizard and the module wasn't found.
	 *
	 * @since 4.0.0
	 */
	public function redirect_module_not_found() {

		// We're on wizard, but the current module isn't valid. Aborting.
		$url = add_query_arg(
			array(
				'page'         => $this->page,
				'show-notice'  => 'error',
				'notice'       => 'module-not-found',
				'notice-close' => 'false',
			),
			'admin.php'
		);

		wp_safe_redirect( $url );
		exit;
	}

	/**
	 * Add data to the current json array.
	 *
	 * @since 4.3.1
	 *
	 * @return array
	 */
	protected function get_vars_to_localize() {
		$current_array = parent::get_vars_to_localize();

		// Wizard page only.
		if ( $this->module ) {

			$data         = $this->module->get_data();
			$module_metas = $this->module->get_module_metas_as_array();

			$current_array = $this->register_visibility_conditions_js_vars( $current_array );

			$current_array += $this->get_wizard_js_variables_to_localize();

			$current_array['current'] = array_merge(
				$module_metas,
				array(
					'is_optin'     => 'optin' === $this->module->module_mode,
					'listing_page' => $this->page,
					'wizard_page'  => $this->page_edit,
					'section'      => $this->get_current_section(),
					'data'         => $data,
					'shortcode_id' => $this->module->get_shortcode_id(),
				)
			);

			$type_capitalized = Opt_In_Utils::get_module_type_display_name( $this->module_type, false, true );
			$type_lowercase   = Opt_In_Utils::get_module_type_display_name( $this->module_type );

			$messages = array(
				'module_error'        => esc_html__( "Couldn't save your module settings because there were some errors on {page} tab(s). Please fix those errors and try again.", 'hustle' ),
				'module_error_reload' => esc_html__( 'Something went wrong. Please reload this page and try saving again', 'hustle' ),
				'module_created'      => /* translators: 1. module type capitalized, 2. module type in lowercase */ sprintf( esc_html__( '%1$s created successfully. Get started by adding content to your new %2$s below.', 'hustle' ), $type_capitalized, $type_lowercase ), // only when 'is_new'.
			);

			$current_array['single_module_action_nonce'] = wp_create_nonce( 'hustle_single_action' );

			$current_array['messages'] = array_merge( $current_array['messages'], $messages );

			// Listing page only.
		} elseif ( $this->page === $this->current_page ) {

			$current_array['current'] = array(
				'wizard_page' => $this->page_edit,
				'module_type' => $this->module_type,
			);

			$current_array['labels'] = array(
				'submissions' => Hustle_Module_Model::SOCIAL_SHARING_MODULE !== $this->module_type
					? /* translators: number of conversions */ esc_html__( '%d Conversions', 'hustle' )
					: /* translators: number of shares */ esc_html__( '%d Shares', 'hustle' ),
				/* translators: number of views */
				'views'       => esc_html__( '%d Views', 'hustle' ),
			);

			// Also defined in dashboard.
			$current_array['single_module_action_nonce'] = wp_create_nonce( 'hustle_single_action' );

			$total_modules            = $this->get_total_count_modules_current_type();
			$module_not_found_message = esc_html__( "Oops! The module you are looking for doesn't exist.", 'hustle' );

			if ( 0 < $total_modules && current_user_can( 'hustle_create' ) ) {
				$module_not_found_message .= sprintf(
					/* translators: 1. opening 'a' tag for adding a new module, 2. closing 'a' tag, 3. opening 'a' tag for importing */
					esc_html__( ' You can %1$screate%2$s a new module or %3$simport%2$s an existing module.', 'hustle' ),
					'<a href="#" class="hustle-create-module">',
					'</a>',
					'<a href="#" class="hustle-import-module-button">'
				);
			}

			$messages = array(
				'module_imported'       => esc_html__( 'Module successfully imported.', 'hustle' ),
				'module_duplicated'     => esc_html__( 'Module successfully duplicated.', 'hustle' ),
				'module_tracking_reset' => esc_html__( "Module's tracking data successfully reset.", 'hustle' ),
				'module_purge_emails'   => esc_html__( "Module's Email List successfully purged.", 'hustle' ),
				'module-not-found'      => $module_not_found_message,
			);

			$current_array['messages'] = array_merge( $current_array['messages'], $messages );
		}

		// Both Wizard and Listing pages.
		$current_array['messages']['days_and_months'] = array(
			'days_full'    => Hustle_Time_Helper::get_week_days(),
			'days_short'   => Hustle_Time_Helper::get_week_days( 'short' ),
			'days_min'     => Hustle_Time_Helper::get_week_days( 'min' ),
			'months_full'  => Hustle_Time_Helper::get_months(),
			'months_short' => Hustle_Time_Helper::get_months( 'short' ),
		);

		$current_array['module_tabs'] = array(
			'services' => esc_html__( 'Services', 'hustle' ),
			'display'  => esc_html__( 'Display Options', 'hustle' ),
		);

		return $current_array;
	}

	/**
	 * Include the visibility conditions variables required in js side.
	 * These used to be registered in Hustle_Module_Admin before 4.0.3.
	 *
	 * @since 4.0.3
	 *
	 * @param array $vars Current registered variables.
	 * @return array
	 */
	private function register_visibility_conditions_js_vars( $vars ) {

		$post_ids   = array();
		$page_ids   = array();
		$tag_ids    = array();
		$cat_ids    = array();
		$wc_cat_ids = array();
		$wc_tag_ids = array();
		$tags       = array();
		$cats       = array();
		$wc_cats    = array();
		$wc_tags    = array();

		$module = Hustle_Module_Model::new_instance( filter_input( INPUT_GET, 'id', FILTER_VALIDATE_INT ) );
		if ( ! is_wp_error( $module ) ) {
			$settings = $module->get_visibility()->to_array();

			$post_ids = $this->get_conditions_ids( $settings, 'posts' );
			$page_ids = $this->get_conditions_ids( $settings, 'pages' );
			$tag_ids  = $this->get_conditions_ids( $settings, 'tags' );
			$cat_ids  = $this->get_conditions_ids( $settings, 'categories' );
			if ( Opt_In_Utils::is_woocommerce_active() ) {
				$wc_cat_ids = $this->get_conditions_ids( $settings, 'wc_categories' );
				$wc_tag_ids = $this->get_conditions_ids( $settings, 'wc_tags' );
			}
		}

		if ( $tag_ids ) {
			$tags = array_map(
				array( $this, 'terms_to_select2_data' ),
				get_categories(
					array(
						'hide_empty' => false,
						'include'    => $tag_ids,
						'taxonomy'   => 'post_tag',
					)
				)
			);
		}

		if ( $cat_ids ) {
			$cats = array_map(
				array( $this, 'terms_to_select2_data' ),
				get_categories(
					array(
						'include'    => $cat_ids,
						'hide_empty' => false,
					)
				)
			);
		}

		if ( $wc_cat_ids ) {
			$wc_cats = array_map(
				array( $this, 'terms_to_select2_data' ),
				get_categories(
					array(
						'include'    => $wc_cat_ids,
						'hide_empty' => false,
						'taxonomy'   => 'product_cat',
					)
				)
			);
		}

		if ( $wc_tag_ids ) {
			$wc_tags = array_map(
				array( $this, 'terms_to_select2_data' ),
				get_categories(
					array(
						'include'    => $wc_tag_ids,
						'hide_empty' => false,
						'taxonomy'   => 'product_tag',
					)
				)
			);
		}

		$posts = Opt_In_Utils::get_select2_data( 'post', $post_ids );

		$pages = Opt_In_Utils::get_select2_data( 'page', $page_ids );

		/**
		 * Add all custom post types
		 */
		$post_types = array();
		$cpts       = get_post_types(
			array(
				'public'   => true,
				'_builtin' => false,
			),
			'objects'
		);
		foreach ( $cpts as $cpt ) {

			// Skip ms_invoice.
			if ( 'ms_invoice' === $cpt->name ) {
				continue;
			}

			$cpt_ids = $this->get_conditions_ids( $settings, $cpt->name );

			$cpt_array['name']  = $cpt->name;
			$cpt_array['label'] = $cpt->label;
			$cpt_array['data']  = Opt_In_Utils::get_select2_data( $cpt->name, $cpt_ids );

			$post_types[ $cpt->name ] = $cpt_array;
		}

		$vars['cats']       = $cats;
		$vars['wc_cats']    = $wc_cats;
		$vars['wc_tags']    = $wc_tags;
		$vars['tags']       = $tags;
		$vars['posts']      = $posts;
		$vars['post_types'] = $post_types;
		$vars['pages']      = $pages;

		$vars['countries'] = Opt_In_Utils::get_countries();
		$vars['roles']     = Opt_In_Utils::get_user_roles();
		$vars['templates'] = Opt_In_Utils::hustle_get_page_templates();

		$vars['type_singular_lower'] = Opt_In_Utils::get_module_type_display_name( $this->module_type );

		// Visibility conditions titles, labels and bodies.
		$vars['messages']['conditions'] = array(
			'visitor_logged_in'           => __( 'Logged in status', 'hustle' ),
			'shown_less_than'             => __( 'Number of times visitor has seen this module', 'hustle' ),
			'only_on_mobile'              => __( "Visitor's Device", 'hustle' ),
			'from_specific_ref'           => __( 'Referrer', 'hustle' ),
			'from_search_engine'          => __( 'Source of Arrival', 'hustle' ),
			'on_specific_url'             => __( 'Specific URL', 'hustle' ),
			'on_specific_browser'         => __( "Visitor's Browser", 'hustle' ),
			'visitor_has_never_commented' => __( 'Visitor Commented Before', 'hustle' ),
			'not_in_a_country'            => __( "Visitor's Country", 'hustle' ),
			'on_specific_roles'           => __( 'User Roles', 'hustle' ),
			'wp_conditions'               => __( 'Static Pages', 'hustle' ),
			'archive_pages'               => __( 'Archive Pages', 'hustle' ),
			'on_specific_templates'       => __( 'Page Templates', 'hustle' ),
			'user_registration'           => __( 'After Registration', 'hustle' ),
			'page_404'                    => __( '404 page', 'hustle' ),
			'posts'                       => __( 'Posts', 'hustle' ),
			'pages'                       => __( 'Pages', 'hustle' ),
			'categories'                  => __( 'Categories', 'hustle' ),
			'tags'                        => __( 'Tags', 'hustle' ),
			'wc_pages'                    => __( 'WooCommerce Pages', 'hustle' ),
			'wc_categories'               => __( 'WooCommerce Categories', 'hustle' ),
			'wc_tags'                     => __( 'WooCommerce Tags', 'hustle' ),
			'wc_archive_pages'            => __( 'WooCommerce Archives', 'hustle' ),
			'wc_static_pages'             => __( 'WooCommerce Static Pages', 'hustle' ),
			'cookie_set'                  => __( 'Browser Cookie', 'hustle' ),
		);

		$vars['messages']['condition_labels'] = array(
			'mobile_only'         => __( 'Mobile only', 'hustle' ),
			'desktop_only'        => __( 'Desktop only', 'hustle' ),
			'any_conditions'      => __( '{number} condition(s)', 'hustle' ),
			'number_views'        => '< {number}',
			'number_views_more'   => '> {number}',
			'any'                 => __( 'Any', 'hustle' ),
			'all'                 => __( 'All', 'hustle' ),
			'no'                  => __( 'No', 'hustle' ),
			'none'                => __( 'None', 'hustle' ),
			'true'                => __( 'True', 'hustle' ),
			'false'               => __( 'False', 'hustle' ),
			'logged_in'           => __( 'Logged in', 'hustle' ),
			'logged_out'          => __( 'Logged out', 'hustle' ),
			'only_these'          => __( 'Only {number}', 'hustle' ),
			'except_these'        => __( 'All except {number}', 'hustle' ),
			'reg_date'            => __( 'Day {number} ', 'hustle' ),
			'immediately'         => __( 'Immediately', 'hustle' ),
			'forever'             => __( 'Forever', 'hustle' ),
			'cookie_anything'     => __( '{name} is anything', 'hustle' ),
			'cookie_doesnt_exist' => __( '{name} does not exist', 'hustle' ),
			'cookie_value'        => __( '{name} {value_condition} {value}', 'hustle' ),
		);

		$vars['wp_conditions'] = array(
			'is_front_page' => __( 'Front page', 'hustle' ),
			'is_404'        => __( '404 page', 'hustle' ),
			'is_search'     => __( 'Search results', 'hustle' ),
		);

		$vars['wc_static_pages'] = array(
			'is_cart'           => __( 'Cart', 'hustle' ),
			'is_checkout'       => __( 'Checkout', 'hustle' ),
			'is_order_received' => __( 'Order Received', 'hustle' ),
			'is_account_page'   => __( 'My account', 'hustle' ),
		);

		$vars['archive_pages'] = array(
			'is_category'          => __( 'Category archive', 'hustle' ),
			'is_tag'               => __( 'Tag archive', 'hustle' ),
			'is_author'            => __( 'Author archive', 'hustle' ),
			'is_date'              => __( 'Date archive', 'hustle' ),
			'is_post_type_archive' => __( 'Custom post archive', 'hustle' ),
		);

		$vars['wc_archive_pages'] = array(
			'is_shop'             => __( 'Shop', 'hustle' ),
			'is_product_category' => __( 'Product Category', 'hustle' ),
			'is_product_tag'      => __( 'Product Tag', 'hustle' ),
		);

		$vars['less_than_expiration'] = array(
			1   => __( 'Day', 'hustle' ),
			7   => __( 'Week', 'hustle' ),
			30  => __( 'Month', 'hustle' ),
			365 => __( 'Year', 'hustle' ),
		);

		$vars['wp_cookie_set'] = array(
			'anything'             => __( 'is anything', 'hustle' ),
			'equals'               => __( 'equals', 'hustle' ),
			'contains'             => __( 'contains', 'hustle' ),
			'matches_pattern'      => __( 'matches a pattern', 'hustle' ),
			'doesnt_equals'        => __( 'does not equals', 'hustle' ),
			'doesnt_contain'       => __( 'does not contain', 'hustle' ),
			'doesnt_match_pattern' => __( 'does not match a pattern', 'hustle' ),
			'less_than'            => __( 'is less than', 'hustle' ),
			'less_equal_than'      => __( 'is less or equal to', 'hustle' ),
			'greater_than'         => __( 'is greater than', 'hustle' ),
			'greater_equal_than'   => __( 'is greater or equal to', 'hustle' ),
		);

		$vars['roles']     = Opt_In_Utils::get_user_roles();
		$vars['browsers']  = Opt_In_Utils::get_browsers();
		$vars['countries'] = Opt_In_Utils::get_countries();
		$vars['templates'] = Opt_In_Utils::hustle_get_page_templates();

		return $vars;
	}

	/**
	 * Gets the JS variables to be localized in Wizard for non-ssharing modules.
	 * This method is overwritten in Hustle_Sshare_Admin.
	 *
	 * @since 4.3.0
	 *
	 * @return array
	 */
	protected function get_wizard_js_variables_to_localize() {
		$is_optin = Hustle_Module_Model::OPTIN_MODE === $this->module->module_mode;

		$variables = array(
			'form_fields'    => $this->get_form_fields_js_vars(),
			'schedule'       => $this->get_schedule_js_vars(),
			'palettes'       => Hustle_Palettes_Helper::get_all_palettes( $is_optin ),
			'integrations'   => array(
				'action_nonce'  => wp_create_nonce( 'hustle_provider_action' ), // Also defined in integrations.
				'fetching_list' => esc_html__( 'Fetching integration list…', 'hustle' ),
			),
			'typography'     => array(
				'global_font_applied' => esc_html__( 'Your font was correctly applied to all elements', 'hustle' ),
				'fetch_nonce'         => wp_create_nonce( 'hustle_fetch_font_families' ),
			),
			'media_uploader' => array(
				'select_or_upload' => esc_html__( 'Select or Upload Image', 'hustle' ),
				'use_this_image'   => esc_html__( 'Use this image', 'hustle' ),
			),
			'triggers'       => array(
				'immediately_tag'       => esc_html__( 'Immediately', 'hustle' ),
				'seconds'               => esc_html__( 'seconds', 'hustle' ),
				'minutes'               => esc_html__( 'minutes', 'hustle' ),
				'hours'                 => esc_html__( 'hours', 'hustle' ),
				/* translators: 1. {time} tag to replace, 2. {unit} tag to replace */
				'delayed_tag'           => sprintf( esc_html__( 'Delay %1$s %2$s', 'hustle' ), '{time}', '{unit}' ),
				/* translators: {value}% tag to replace */
				'scroll_percentage_tag' => sprintf( esc_html__( '%s page scroll', 'hustle' ), '{value}%' ),
				/* translators: {value} tag to replace */
				'scroll_element_tag'    => sprintf( esc_html__( 'Scroll to %s', 'hustle' ), '{value}' ),
			),
		);

		$variables['defaults'] = $this->get_defaults_settings();

		return $variables;
	}

	/**
	 * Return default design settings
	 *
	 * @return array
	 */
	private function get_defaults_settings() {
		$defaults  = $this->module->get_design()->get_border_spacing_shadow_defaults( 'desktop' );
		$defaults += $this->module->get_design()->get_typography_defaults( 'desktop' );
		$defaults += $this->module->get_design()->get_border_spacing_shadow_defaults( 'mobile' );
		$defaults += $this->module->get_design()->get_typography_defaults( 'mobile' );

		$design_settings = $this->module->get_design()->to_array();
		if ( ! empty( $design_settings['base_template'] ) ) {
			// Return template settings if the module was created based on one.
			$templates_helper  = new Hustle_Templates_Helper();
			$template_settings = $templates_helper->get_template( $design_settings['base_template'], $this->module->module_mode );
			if ( ! empty( $template_settings['design'] ) ) {
				$defaults = array_merge( $defaults, $template_settings['design'] );
			}
		}

		if ( ! empty( $design_settings['base_template'] ) ) {
			$defaults['base_template'] = $design_settings['base_template'];
		}

		return $defaults;
	}

	/**
	 * Include the form fields variables required in js side.
	 * These used to be registered in Hustle_Module_Admin before 4.0.3.
	 *
	 * @since 4.0.3
	 *
	 * @return array
	 */
	private function get_form_fields_js_vars() {
		$renderer = $this->get_renderer();

		$no_fields_notice_args = array(
			array(
				'type'  => 'inline_notice',
				'icon'  => 'info',
				'value' => __( 'You don\'t have any {field_type} field in your opt-in form.', 'hustle' ),
			),
		);

		$variables = array(
			'no_fields_of_type_notice'     => $renderer->get_html_for_options( $no_fields_notice_args, true ),
			'is_required'                  => __( '{field} is required.', 'hustle' ),
			'cant_empty'                   => __( 'This field can\'t be empty.', 'hustle' ),
			'url_required_error_message'   => __( 'Your website url is required.', 'hustle' ),
			'required_error_message'       => __( 'Your {field} is required.', 'hustle' ),
			'date_validation_message'      => __( 'Please enter a valid date.', 'hustle' ),
			'time_validation_message'      => __( 'Please enter a valid time.', 'hustle' ),
			'validation_message'           => __( 'Please enter a valid {field}.', 'hustle' ),
			'recaptcha_error_message'      => __( 'reCAPTCHA verification failed. Please try again.', 'hustle' ),
			'recaptcha_validation_message' => __( 'reCAPTCHA verification failed. Please try again.', 'hustle' ),
			'gdpr_required_error_message'  => __( 'Please accept the terms and try again.', 'hustle' ),
			/* translators: 1. opening 'a' tag, 2. closing 'a' tag */
			'gdpr_message'                 => sprintf( __( 'I\'ve read and accept the %1$sterms & conditions%2$s', 'hustle' ), '<a href="#">', '</a>' ),
			'label'                        => array(
				'placeholder'            => __( 'Enter placeholder here', 'hustle' ),
				'name_label'             => __( 'Name', 'hustle' ),
				'name_placeholder'       => __( 'E.g. John', 'hustle' ),
				'email_label'            => __( 'Email Address', 'hustle' ),
				'enail_placeholder'      => __( 'E.g. john@doe.com', 'hustle' ),
				'phone_label'            => __( 'Phone Number', 'hustle' ),
				'phone_placeholder'      => __( 'E.g. +1 300 400 500', 'hustle' ),
				'address_label'          => __( 'Address', 'hustle' ),
				'address_placeholder'    => '',
				'hidden_label'           => __( 'Hidden Field', 'hustle' ),
				'hidden_placeholder'     => '',
				'url_label'              => __( 'Website', 'hustle' ),
				'url_placeholder'        => __( 'E.g. https://example.com', 'hustle' ),
				'text_label'             => __( 'Text', 'hustle' ),
				'text_placeholder'       => __( 'E.g. Enter your nick name', 'hustle' ),
				'number_label'           => __( 'Number', 'hustle' ),
				'number_placeholder'     => __( 'E.g. 1', 'hustle' ),
				'datepicker_label'       => __( 'Date', 'hustle' ),
				'datepicker_placeholder' => __( 'Choose date', 'hustle' ),
				'timepicker_label'       => __( 'Time', 'hustle' ),
				'timepicker_placeholder' => '',
				'recaptcha_label'        => 'reCAPTCHA',
				'recaptcha_placeholder'  => '',
				'gdpr_label'             => __( 'GDPR', 'hustle' ),
			),
			'recaptcha_badge_replacement'  => sprintf(
				/* translators: 1: closing 'a' tag, 2: opening privacy 'a' tag, 3: opening terms 'a' tag */
				__( 'This site is protected by reCAPTCHA and the Google %2$sPrivacy Policy%1$s and %3$sTerms of Service%1$s apply.', 'hustle' ),
				'</a>',
				'<a href="https://policies.google.com/privacy" target="_blank">',
				'<a href="https://policies.google.com/terms" target="_blank">'
			),
		);

		return $variables;
	}

	/**
	 * Includes the variables used for the Schedule functionality.
	 *
	 * @since 4.3.0
	 *
	 * @return array
	 */
	private function get_schedule_js_vars() {
		$type_lowercase = Opt_In_Utils::get_module_type_display_name( $this->module_type );

		return array(
			'wp_gmt_offset'    => get_option( 'gmt_offset' ),
			'new_schedule_set' => sprintf(
				/* translators: 1. module type in lowercase */
				esc_html__( 'Successfully added a schedule for your %1$s. However, make sure to save changes and publish your %1$s for it to start appearing as per your schedule.', 'hustle' ),
				esc_html( $type_lowercase )
			),
			'months'           => Hustle_Time_Helper::get_months(),
			'week_days'        => Hustle_Time_Helper::get_week_days( 'short' ),
			'meridiem'         => Hustle_Time_Helper::get_meridiam_periods(),
		);
	}

	/**
	 * Gets the conditions ID.
	 *
	 * @since 3.0.7
	 * @since 4.0.3 moved from Hustle_Modules_Admin to here.
	 *
	 * @param array  $settings Display settings.
	 * @param string $type posts|pages|tags|categories|{cpt}.
	 * @return array
	 */
	private function get_conditions_ids( $settings, $type ) {
		$ids = array();
		if ( ! empty( $settings['conditions'] ) ) {
			foreach ( $settings['conditions'] as $conditions ) {
				if ( ! empty( $conditions[ $type ] )
					&& ( ! empty( $conditions[ $type ][ $type ] )
					|| ! empty( $conditions[ $type ]['selected_cpts'] ) ) ) {
					$new_ids = ! empty( $conditions[ $type ][ $type ] )
					? $conditions[ $type ][ $type ]
					: $conditions[ $type ]['selected_cpts'];

					$ids = array_merge( $ids, $new_ids );
				}
			}
		}

		return array_unique( $ids );
	}

	/**
	 * Converts term object to usable object for select2
	 *
	 * @since 4.0.3 moved from Hustle_Modules_Admin to here.
	 * @param stdClass $term Term.
	 * @return stdClass
	 */
	public static function terms_to_select2_data( $term ) {
		$obj       = new stdClass();
		$obj->id   = $term->term_id;
		$obj->text = $term->name;
		return $obj;
	}

	/**
	 * Render the module's wizard page.
	 *
	 * @since 4.0.1
	 */
	public function render_edit_page() {

		$template_args = $this->get_page_edit_template_args();
		$allowed       = Opt_In_Utils::is_user_allowed( 'hustle_edit_module', $template_args['module_id'] );
		if ( ! $allowed ) {
			wp_die( esc_html__( 'Sorry, you are not allowed to access this page.' ), 403 );
		}

		if ( Hustle_Module_Model::SOCIAL_SHARING_MODULE !== $this->module_type ) {
			wp_enqueue_editor();
		}

		$main_class = $this->get_sui_wrap_class();

		?>
		<main class="<?php echo esc_attr( $main_class ); ?>">

			<?php
			$renderer = $this->get_renderer();
			$renderer->render( $this->page_edit_template_path, $template_args );
			?>

		</main>
		<?php
	}

	/**
	 * Get the args for the wizard page.
	 *
	 * @since 4.0.1
	 * @return array
	 */
	protected function get_page_edit_template_args() {
		return array(
			'section'   => $this->get_current_section( 'content' ),
			'module_id' => $this->module->module_id,
			'module'    => $this->module,
			'is_active' => (bool) $this->module->active,
			'is_optin'  => ( 'optin' === $this->module->module_mode ),
		);
	}

	/**
	 * Gets the count of the modules (both active and non active) of the current type.
	 *
	 * @since 4.2.0
	 * @return int
	 */
	private function get_total_count_modules_current_type() {

		if ( is_null( $this->module_count_type ) ) {
			$args   = array(
				'module_type' => $this->module_type,
				'count_only'  => true,
			);
			$search = filter_input( INPUT_GET, 'q' );
			if ( $search ) {
				$args['filter']['q'] = $search;
			}
			$this->module_count_type = Hustle_Module_Collection::instance()->get_all(
				null,
				$args
			);
		}
		return $this->module_count_type;
	}

	/**
	 * Get data needed for rendering the tracking charts in the listing pages
	 * Retrieved via AJAX.
	 *
	 * @since 4.0.4
	 * @since 4.2.0 Moved from Hustle_Module_Model to this class, and $module_id param added.
	 *
	 * @param int $module_id ID of the requested module.
	 */
	public static function get_tracking_charts_markup( $module_id ) {

		$module = Hustle_Model::get_module( $module_id );
		if ( is_wp_error( $module ) ) {
			return '';
		}

		$tracking_model           = Hustle_Tracking_Model::get_instance();
		$total_module_conversions = $tracking_model->count_tracking_data( $module_id, 'all_conversion' );
		$total_module_views       = $tracking_model->count_tracking_data( $module_id, 'view' );
		$last_entry_time          = $tracking_model->get_latest_conversion_time_by_module_id( $module_id );
		$rate                     = $total_module_views ? round( ( $total_module_conversions * 100 ) / $total_module_views, 1 ) : 0;
		$module_sub_types         = $module->get_sub_types( true );

		$multiple_charts = array();

		// Get each sub type's tracking data if the module type has sub types.
		if ( ! empty( $module_sub_types ) ) {

			foreach ( $module_sub_types as $slug => $display_name ) {

				$subtype         = $module->module_type . '_' . $slug;
				$views           = $tracking_model->count_tracking_data( $module_id, 'view', $subtype );
				$conversions     = $tracking_model->count_tracking_data( $module_id, 'all_conversion', $subtype );
				$conversion_rate = $views ? round( ( $conversions * 100 ) / $views, 1 ) : 0;

				$multiple_charts[ $slug ] = array(
					'display_name'    => $display_name,
					'last_entry_time' => $tracking_model->get_latest_conversion_time_by_module_id( $module_id, $subtype ),
					'views'           => $views,
					'conversions'     => $conversions,
					'conversion_rate' => $conversion_rate,
				);
			}
		}

		$render_arguments = array(
			'module'                   => $module,
			'total_module_views'       => $total_module_views,
			'total_module_conversions' => $total_module_conversions,
			'tracking_types'           => $module->get_tracking_types(),
			'last_entry_time'          => $last_entry_time,
			'rate'                     => $rate,
		);

		if ( $module->get_content()->has_cta() ) {
			$notice_for_old_data                     = $tracking_model->has_old_tracking_data( $module_id );
			$render_arguments['notice_for_old_data'] = $notice_for_old_data;
		}

		ob_start();

		$renderer = new Hustle_Layout_Helper();

		// ELEMENT: Tracking data.
		$renderer->render(
			'admin/commons/sui-listing/elements/tracking-data',
			array(
				'render_arguments' => $render_arguments,
				'multiple_charts'  => $multiple_charts,
			)
		);

		$html = ob_get_clean();

		$charts_data = self::get_charts_data( array_keys( $module_sub_types ), $total_module_views, $module );

		$data = array(
			'html'        => $html,
			'charts_data' => $charts_data,
		);

		return $data;
	}

	/**
	 * Get tracking data for building charts on listing page
	 *
	 * @since 4.0.4
	 * @since 4.2.0 Move from Hustle_Module_Model to this class. $module param added.
	 *
	 * @param array        $sub_types Module's sub types.
	 * @param int          $views Module's views count.
	 * @param Hustle_Model $module Instance of the module to get the charts data for.
	 * @return array
	 */
	private static function get_charts_data( $sub_types, $views, Hustle_Model $module ) {

		$sql_month_start_date = date( 'Y-m-d H:i:s', strtotime( '-30 days midnight' ) );// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
		$tracking_model       = Hustle_Tracking_Model::get_instance();
		$days_array           = array();
		$default_array        = array();

		for ( $h = 30; $h >= 0; $h-- ) {
			$time                   = strtotime( '-' . $h . ' days' );
			$date                   = date( 'Y-m-d', $time );// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
			$default_array[ $date ] = 0;
			$days_array[]           = date( 'M j, Y', $time );// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
		}

		$sub_types[]      = 'overall';
		$conversion_types = array( 'all' );

		if ( Hustle_Module_Model::SOCIAL_SHARING_MODULE !== $module->module_type ) {
			$conversion_types[] = 'cta';
			$conversion_types[] = 'cta_2';

			if ( Hustle_Module_Model::OPTIN_MODE === $module->module_mode ) {
				$conversion_types[] = 'optin';
			}
		}

		$data = array();
		foreach ( $sub_types as $sub_type ) {

			$chart_container_id = sprintf(
				'hustle-%1$s-%2$s-stats--%3$s',
				$module->module_type,
				$module->module_id,
				$sub_type
			);
			$data[ $sub_type ]  = array(
				'id'   => $chart_container_id,
				'days' => $days_array,
			);

			foreach ( $conversion_types as $conversion_type ) {

				$last_month_conversions = $tracking_model->get_form_latest_tracking_data_count_grouped_by_day( $module->module_id, $sql_month_start_date, $conversion_type . '_conversion', $module->module_type, $sub_type );
				$last_month_views       = $tracking_model->get_form_latest_tracking_data_count_grouped_by_day( $module->module_id, $sql_month_start_date, 'view', $module->module_type, $sub_type );

				if ( ! $last_month_conversions ) {
					$submissions_data = $default_array;
				} else {
					$submissions_array = wp_list_pluck( $last_month_conversions, 'tracked_count', 'date_created' );
					$submissions_data  = array_merge( $default_array, array_intersect_key( $submissions_array, $default_array ) );
				}

				if ( ! $last_month_views ) {
					$views_data = $default_array;
				} else {
					$views_array = wp_list_pluck( $last_month_views, 'tracked_count', 'date_created' );
					$views_data  = array_merge( $default_array, array_intersect_key( $views_array, $default_array ) );
				}

				$query_sub_type        = 'overall' === $sub_type ? null : $module->module_type . '_' . $sub_type;
				$query_conversion_type = $conversion_type . '_conversion';
				$sub_type_conversions  = $tracking_model->count_tracking_data( $module->module_id, $query_conversion_type, $query_sub_type );

				$data[ $sub_type ][ $conversion_type ] = array(
					'conversions_count' => $sub_type_conversions,
					'conversion_rate'   => $views ? round( ( $sub_type_conversions * 100 ) / $views, 1 ) : 0,
					'conversions'       => array_values( $submissions_data ),
				);
			}

			$data[ $sub_type ]['views']       = array_values( $views_data );
			$data[ $sub_type ]['conversions'] = array_values( $submissions_data );
		}

		return $data;
	}

	/**
	 * Returns whether the current module is optin.
	 * This method should be only used in Wizard pages
	 * because $this->module is only defined within Wizards.
	 *
	 * @since 4.3.0
	 * @return boolean
	 */
	public function is_optin_module() {
		return ! empty( $this->module ) && 'optin' === $this->module->module_mode;
	}

	/**
	 * Gets the font family names.
	 * Used for rendering the selects.
	 *
	 * @since 4.3.0
	 * @return array
	 */
	public function get_font_families() {
		return Hustle_Meta_Base_Design::get_font_families_names();
	}
}
