<?php

defined( 'ABSPATH' ) || exit;

/**
 * WPEX_Card Class.
 *
 * @copyright WPExplorer.com
 * @license All Rights Reserved. This is proprietary code. Do not copy, share or redistribute without permission.
 */
if ( ! class_exists( 'WPEX_Card' ) ) {

	#[\AllowDynamicProperties]
	class WPEX_Card {

		/**
		 * The card arguments.
		 */
		public $args = [];

		/**
		 * The card style.
		 */
		public $style = 'blog_1';

		/**
		 * Post ID for dynamic cards.
		 */
		public $post_id = null;

		/**
		 * Check if displaying a mock card.
		 */
		private $is_mock = null;

		/**
		 * Check if the dialog should be added, allows for dynamically enabling
		 * the dialog when creating custom cards via the WP admin.
		 */
		private $include_dialog = false;

		/**
		 * Check if the modal should be added, allows for dynamically enabling
		 * the modal when creating custom cards via the WP admin.
		 */
		private $include_modal = false;

		/**
		 * Term object for term based cards.
		 */
		public $term = null;

		/**
		 * Unique card ID.
		 */
		public $unique_id = null;

		/**
		 * Current element args.
		 */
		public $el_args = [];

		/**
		 * Holds array of all card styles to prevent extra lookups.
		 */
		private static $card_styles = null;

		/**
		 * Class instance - returns the current card object.
		 */
		public static $instance;

		/**
		 * Stores all card instances - for threaded cards.
		 */
		private static $instances = [];

		/**
		 * Create or retrieve the class instance.
		 */
		public static function instance() {
			return static::$instance;
		}

		/**
		 * Initialization.
		 */
		public function __construct( $args ) {
			if ( ! is_array( $args ) || empty( $args ) ) {
				return;
			}

			$this->args = $args;

			foreach ( get_object_vars( $this ) as $key => $value ) {
				if ( isset( $this->args[ $key ] ) ) {
					$this->$key = $this->args[ $key ];
				}
			}

			/**
			 * Filters the card style.
			 * 
			 * @param string $style
			 * @param array $this Current WPEX_Card object.
			 */
			$this->style = apply_filters( 'wpex_card_style', $this->style, $this );

			// Store this instance so we can access it globally.
			self::$instance = $this;
			self::$instances[] = $this;
		}

		/**
		 * Render card.
		 */
		final public function render() {
			$template = $this->locate_template();

			if ( ! empty( $template ) ) {

				if ( $this->is_mock() ) {
					$this->create_mock_post();
					$template_content = require $template;
					$this->delete_mock_post();
				} else {
					$template_content = require $template;
				}

				if ( $template_content ) {
					$is_custom_template = $this->is_template();
					$card_style_safe    = sanitize_html_class( $this->style );
					$this_class         = "wpex-card";

					if ( $this->is_featured() ) {
						$this_class .= ' wpex-card-featured';
					}

					$this_class .= " wpex-card-{$card_style_safe}";

					if ( ! empty( $this->args['el_class'] ) && is_string( $this->args['el_class'] ) ) {
						$this_class .= " {$this->args['el_class']}";
					}

					if ( $is_custom_template ) {
						$meta_el_class = (string) $this->get_template_meta( 'el_class' );
						if ( $meta_el_class ) {
							$this_class .= " {$meta_el_class}";
						}
					}

					if ( function_exists( 'vcex_get_css_animation' )
						&& ! empty( $this->args['css_animation'] )
						&& 'none' !== $this->args['css_animation'] ) {
						$css_animation = (string) vcex_get_css_animation( $this->args['css_animation'] );
						if ( $css_animation ) {
							$this_class .= ' ' . trim( $css_animation );
						}
					}

					$card_html = '<div class="'. esc_attr( trim( $this_class ) ) . '">';
						$card_html .= $template_content;
						$link_type = $this->get_var( 'link_type' );
						// @note we can only have 1 popup type: modal or dialog
						if ( 'dialog' === $link_type || true === $this->include_dialog ) {
							$card_html .= $this->get_dialog();
						} elseif ( 'modal' === $link_type || true === $this->include_modal ) {
							$card_html .= $this->get_modal();
						}
					$card_html .= '</div>';

				}

			}

			array_pop( self::$instances );
			self::$instance = ! empty( self::$instances ) ? end( self::$instances ) : null;

			return $card_html ?? '';
		}

		/**
		 * Locate card template.
		 */
		final protected function locate_template() {
			$style = trim( (string) $this->style );
			$template = '';

			if ( ! $style ) {
				return;
			}

			// Get card styles
			self::get_card_styles();

			// These cards define a custom template path
			if ( ! empty( self::$card_styles[ $style ]['template'] ) ) {
				$template = self::$card_styles[ $style ]['template'];
				if ( ! file_exists( $template ) ) {
					$template = ''; // prevent errors with non existing templates
				}
			} else {
				// Custom cards created via wp admin
				// @important we can't check if the template is included in the self::$card_styles array
				// because of conlicts with WPML where the template may not be in the array
				if ( $this->is_template() ) {
					$template = WPEX_THEME_DIR . '/cards/template.php';
				}
				// Default theme cards or custom cards defined in the child theme
				elseif ( array_key_exists( $style, self::$card_styles ) ) {
					if ( str_contains( $style, '_' ) ) {
						$category = strstr( $style, '_', true );
					} else {
						$category = $style;
					}
					$path = "cards/{$category}/{$style}.php";
					$template = locate_template( $path, false );
					// Template not found, lets try and grab it from the root directory.
					if ( ! $template ) {
						$template = locate_template( "cards/{$style}.php", false );
					}
				}
			}

			/**
			 * Filters the card template.
			 *
			 * @param string $template
			 * @param array $this Current WPEX_Card object.
			 */
			$template = apply_filters( 'wpex_card_template', $template, $this );

			return (string) $template;
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Card Modals ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Set modal template.
		 */
		protected function set_modal_template(): void {
			$this->enqueue_lightbox(); // ensure script is loaded since link_type may not be modal or dialog
			$modal_template = $this->args['modal_template'] ?? '';

			// If the modal template isn't defined let's get it from the card template
			if ( ! $modal_template && empty( $this->args['modal_content'] ) && $this->is_template() ) {
				$modal_template_cf = $this->get_template_meta( 'wpex_card_modal_template_id' );
				if ( $modal_template_cf ) {
					$modal_template = $modal_template_cf;
					$this->args['modal_template'] = $modal_template;
				}
			}

			// Check modal settings from template - these should override!
			if ( $modal_template ) {
				$modal_template_width = get_post_meta( $modal_template, 'wpex_modal_width', true );
				if ( $modal_template_width ) {
					$this->args['modal_width'] = $modal_template_width;
				}
				$modal_template_br = get_post_meta( $modal_template, 'wpex_modal_border_radius', true );
				if ( $modal_template_br ) {
					$this->args['modal_border_radius'] = $modal_template_br;
				}
			}
		}

		/**
		 * Returns the card modal id.
		 */
		public function get_modal_id() {
			return 'wpex-card-modal-' . absint( $this->get_var( 'unique_id' ) );
		}

		/**
		 * Helper function allows for enabling the modal.
		 */
		public function enable_modal() {
			$this->include_modal = true;
		}

		/**
		 * Return card modal (fancybox).
		 */
		protected function get_modal() {
			$this->set_modal_template();

			// Define modal class
			$modal_class = 'wpex-card-modal'; // we use 'card-modal' as this was added before wpex-modal existed

			// Add modal template ID as modal class modifier
			if ( ! empty( $this->args['modal_template'] ) ) {
				$modal_class .= ' wpex-modal--' . absint( $this->args['modal_template'] );
			}

			// Modal inline CSS
			$modal_css = '';
			if ( ! empty( $this->args['modal_width'] ) ) {
				$modal_width_safe = absint( $this->args['modal_width'] );
				if ( $modal_width_safe ) {
					$modal_css .= "width:{$modal_width_safe}px;";
					$modal_class .= ' wpex-card-modal--custom-width';
				}
			}
			if ( $modal_css ) {
				$modal_css = ' style="' . esc_attr( $modal_css ) .'"';
			}

			// Add utility classes
			$modal_class .= ' wpex-hidden wpex-shadow-lg';
			$modal_class .= ' ' . $this->get_modal_border_radius_class( 'wpex-rounded-sm' );

			// Begin modal output
			$output = '<div id="' . esc_attr( $this->get_modal_id() ) . '" class="' . esc_attr( $modal_class ) . '"' . $modal_css . '>';

				// Modal Title
				if ( $title = $this->get_modal_title() ) {
					$output .= '<div class="wpex-card-modal-title wpex-p-20 wpex-border-b wpex-border-solid wpex-border-gray-200 wpex-text-xl wpex-text-black wpex-text-center wpex-font-bold">';
						$output .= esc_html( $title );
					$output .= '</div>';
				}

				// Modal content
				if ( $content = $this->get_modal_content() ) {
					$output .= $this->get_modal_content_css();
					$output .= '<div class="wpex-card-modal-body wpex-py-40 wpex-px-20 wpex-last-mb-0 wpex-clr">';
						$output .= $content; // already parsed for display
					$output .= '</div>';
				}

				// Modal footer
				$output .= '<div class="wpex-card-modal-footer wpex-p-20 wpex-text-right wpex-border-t wpex-border-solid wpex-border-gray-200">';
					$output .= '<button href="javascript:;" data-fancybox-close class="theme-button wpex-rounded-full">';
						$output .= esc_html__( 'Close', 'total' );
					$output .= '</button>';
				$output .= '</div>';

			$output .= '</div>';

			return $output;
		}

		/**
		 * Helper function allows for enabling the dialog.
		 */
		public function enable_dialog() {
			$this->include_dialog = true;
		}

		/**
		 * Return card dialog; technically also a modal but we use different name for new one.
		 */
		protected function get_dialog() {
			$this->set_modal_template();

			// Define modal class
			$modal_class = 'wpex-modal';

			// Add modal template ID as modal class modifier
			if ( ! empty( $this->args['modal_template'] ) ) {
				$modal_class .= ' wpex-modal--' . absint( $this->args['modal_template'] );
			}

			// Modal inline CSS
			$modal_css = '';
			if ( ! empty( $this->args['modal_width'] ) ) {
				$modal_width_safe = absint( $this->args['modal_width'] );
				if ( $modal_width_safe ) {
					$modal_css .= "width:{$modal_width_safe}px;";
					$modal_class .= ' wpex-modal--custom-width';
				}
			}
			if ( $modal_css ) {
				$modal_css = ' style="' . esc_attr( $modal_css ) .'"';
			}

			// Add utility classes to modal
			$modal_class .= ' wpex-shadow-lg wpex-p-0';
			$modal_class .= ' ' . $this->get_modal_border_radius_class( 'wpex-rounded' );

			// Begin modal output
			$output = '<dialog id="' . esc_attr( $this->get_modal_id() ) . '" class="' . esc_attr( $modal_class ) . '"' . $modal_css . '><div class="wpex-modal__inner">';

				// Modal title
				if ( $title = $this->get_modal_title() ) {
					$output .= '<div class="wpex-modal__header wpex-flex wpex-items-center wpex-gap-20 wpex-p-20 wpex-border-b wpex-border-solid wpex-border-main">';
						$output .= '<div class="wpex-modal__title wpex-heading wpex-text-xl">' . esc_html( $title ) . '</div>';
				} else {
					$output .= '<div class="wpex-modal__header wpex-flex wpex-items-center wpex-gap-20 wpex-pt-20 wpex-px-20">';
				}
					$output .= '<button class="wpex-modal__close wpex-close-modal wpex-unstyled-button wpex-flex wpex-items-center wpex-justify-center wpex-ml-auto" aria-label="' . esc_attr__( 'close modal', 'total' ) . '">' . totaltheme_get_svg( 'material-close', 24 ) . '</button>';
				$output .= '</div>';

				// Modal content
				if ( $content = $this->get_modal_content() ) {
					$output .= $this->get_modal_content_css();
					$output .= '<div class="wpex-modal__body wpex-p-20 wpex-last-mb-0 wpex-clr">';
						$output .= $content; // already parsed for display
					$output .= '</div>';
				}

			$output .= '</div></dialog>';

			return $output;
		}

		/**
		 * Return modal border radius class.
		 */
		private function get_modal_border_radius_class( string $default ): string {
			$border_radius = $this->args['modal_border_radius'] ?? '';
			if ( $border_radius && in_array( $border_radius, [ 'none', 'md', 'lg', 'xl' ], true ) ) {
				if ( 'none' === $border_radius ) {
					$border_radius = '0';
				}
				return "wpex-rounded-{$border_radius}";
			} else {
				return $default;
			}
		}

		/**
		 * Return modal content css.
		 */
		private function get_modal_content_css(): string {
			$css = '';
			if ( WPEX_VC_ACTIVE ) {
				$wpb_style = totaltheme_get_instance_of( 'Integration\WPBakery\Shortcode_Inline_Style' );
				if ( $wpb_style ) {
					if ( ! empty( $this->args['modal_template'] ) ) {
						$css .= $wpb_style->get_css( $this->args['modal_template'], true );
					}
					/**
					 * This is added for extra precaution, but it can cause the same CSS to be added twice.
					 * Users shouldn't be using WPBakery on the post and also in the template,
					 * so it's a non-issue.
					 */
					if ( $this->post_id ) {
						$css .= $wpb_style->get_css( $this->post_id );
					}
				}
			}
			if ( $css ) {
				$css = '<style>' . wp_strip_all_tags( $css ) . '</style>';
			}
			return $css;
		}

		/**
		 * Return card modal (lightbox) settings.
		 */
		public function get_modal_settings() {
			$settings = [
				'type'       => 'inline',
			//	'buttons'    => 'false',
				'small-btn'  => 'true',
				'auto-focus' => 'false',
				'touch'      => 'false',
			];

			/**
			 * Filters the card modal settings
			 *
			 * @param array $settings
			 * @param object $this Current WPEX_Card object.
			 */
			$this->modal_settings = (array) apply_filters( 'wpex_card_modal_settings', $settings, $this );

			return $this->modal_settings;
		}

		/**
		 * Return card modal title.
		 */
		protected function get_modal_title() {
			$title = '';

			if ( ! empty( $this->args['modal_template'] ) ) {
				if ( '1' === get_post_meta( $this->args['modal_template'], 'wpex_modal_hide_title', true ) ) {
					$this->args['modal_title'] = false;
				} else {
					$custom_modal_title = get_post_meta( $this->args['modal_template'], 'wpex_modal_title', true );
					if ( $custom_modal_title ) {
						$this->args['modal_title'] = wp_strip_all_tags( do_shortcode( totaltheme_replace_vars( $custom_modal_title ) ) );
					}
				}
			}

			if ( array_key_exists( 'modal_title', $this->args )
				&& ( false === $this->args['modal_title'] || 'false' === $this->args['modal_title'] )
			) {
				$title = '';
			} elseif ( ! empty( $this->args['modal_title'] )
				&& is_string( $this->args['modal_title'] )
				&& 'true' !== $this->args['modal_title']
			) {
				$title = $this->args['modal_title'];
			} elseif ( $this->post_id ) {
				$post = get_post( $this->post_id );
				if ( ! empty( $post ) && ! is_wp_error( $post ) ) {
					$title = get_the_title( $this->post_id );
				}
			} elseif ( ! empty( $this->term->name ) ) {
				$title = $this->term->name;
			}

			/**
			 * Filters the card modal title
			 *
			 * @param string $title
			 * @param object $this Current WPEX_Card object.
			 */
			$title = apply_filters( 'wpex_card_modal_title', $title, $this );

			return $title;
		}

		/**
		 * Return card modal content.
		 */
		protected function get_modal_content() {
			$content = '';

			if ( ! empty( $this->args['modal_template'] ) && empty( $this->args['modal_content'] ) ) {
				$temp_post = get_post( $this->args['modal_template'] );
				if ( $temp_post ) {
					$template_type = totaltheme_get_post_builder_type( $this->args['modal_template'] );
					if ( 'elementor' === $template_type ) {
						$this->args['modal_content'] = wpex_get_elementor_content_for_display( $this->args['modal_template'] );
					} else {
						$this->args['modal_content'] = $temp_post->post_content;
					}
				}
			}

			if ( ! empty( $this->args['modal_content'] ) ) {
				$content = $this->args['modal_content'];
			} elseif ( $this->post_id ) {
				$post = get_post( $this->post_id );
				if ( ! empty( $post ) && ! is_wp_error( $post ) ) {
					if ( function_exists( 'wpex_get_current_post_id' ) ) {
						$current_post = wpex_get_current_post_id();
					} else {
						$current_post = get_queried_object_id();
					}
					// !!! Important check to prevent infinite loops !!!!
					if ( $current_post !== $this->post_id ) {
						$content = $post->post_content;
					}
				}
			} elseif ( ! empty( $this->term->description ) ) {
				$content = $this->term->description;
			}

			/**
			 * Filters the card modal content.
			 *
			 * @param string $content
			 * @param object $this Current WPEX_Card object.
			 */
			$content = (string) apply_filters( 'wpex_card_modal_content', $content, $this );

			// Parse content AFTER filters so that users can pass on content to the filter that contains shortcodes
			if ( $content && ( empty( $template_type ) || 'elementor' !== $template_type ) ) {
				if ( function_exists( 'totaltheme_render_content' ) ) {
					$content = totaltheme_render_content( $content );
				} else {
					$content = do_shortcode( wp_kses_post( $content ) );
				}
			}

			return $content;
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Links ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Check if a card has a link.
		 */
		public function has_link(): bool {
			return (bool) $this->get_var( 'url' );
		}

		/**
		 * Get card url.
		 */
		public function get_url() {
			$link = '';

			$type = $this->get_var( 'link_type' );

			if ( 'none' === $type ) {
				return;
			}

			if ( ! empty( $this->args['url'] ) ) {
				$url = $this->args['url'];
			} elseif ( ! in_array( $type, [ 'dialog', 'modal', 'post', 'term' ] )
				&& $this->is_template()
				&& $custom_field = $this->get_template_meta( 'link_custom_field' )
			) {
				if ( $this->term ) {
					$url = get_term_meta( $this->term->term_id, $custom_field, true );
				} else {
					$url = get_post_meta( $this->post_id, $custom_field, true );
				}
				if ( is_array( $url ) ) {
					$url = $url['url'] ?? $url['href'] ?? $url[0];
				}
			} else {

				$url = $this->get_card_meta( 'url' );

				if ( empty( $url ) ) {
					switch ( $type ) {
						case 'dialog':
							$url = '#';
							break;
						case 'modal':
							$url = '#' . $this->get_modal_id();
							break;
						case 'lightbox':
							$url = $this->get_var( 'lightbox_url' );
							break;
						case 'post': // legacy
						case 'term': // legacy
						default:
							// @note we don't use is_post_publicly_viewable because we still want
							// to link to posts that may be privatized.
							if ( $this->post_id ) {
								if ( is_post_type_viewable( $this->get_post_type() ) ) {
									if ( function_exists( 'wpex_get_permalink' ) ) {
										$url = wpex_get_permalink( $this->post_id );
									} else {
										$url = get_permalink( $this->post_id );
									}
								}
							} elseif ( $this->term ) {
								if ( is_term_publicly_viewable( $this->term ) ) {
									$url = get_term_link( $this->term );
								}
							}
							break;
					}
				}

			}

			/**
			 * Filters the card URL.
			 * 
			 * @param string $url The URL.
			 * @param object $this Current WPEX_Card object.
			 */
			$url = (string) apply_filters( 'wpex_card_url', $url, $this );

			if ( $url ) {
				$url = esc_url( $url );
			}

			$this->url = $url;

			return $this->url;
		}

		/**
		 * Get card link type.
		 */
		public function get_link_type() {
			$type = '';

			// Check custom template first as this is the "default" value
			if ( $this->is_template() ) {
				$meta_type = $this->get_template_meta( 'link_type' );
				if ( $meta_type ) {
					$type = $meta_type;
				}
			}

			// Card specific link type should override the default
			if ( ! empty( $this->args['link_type'] ) ) {
				$type = $this->args['link_type'];
			}

			// Last we check the post meta which should override the template and card args
			if ( $this->post_id ) {
				$meta_type = get_post_meta( $this->post_id, 'wpex_card_link_type', true );
				if ( $meta_type ) {
					$type = $meta_type;
				}
			}

			/**
			 * Filters the card link type.
			 * 
			 * @param string $type Link type.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->link_type = (string) apply_filters( 'wpex_card_link_type', $type, $this );

			if ( 'lightbox_group' === $this->link_type ) {
				$this->link_type = 'lightbox';
				$this->has_lightbox_group = true;
			}

			return $this->link_type;
		}

		/**
		 * Get card link target.
		 */
		public function get_link_target() {
			$target = '_self';

			if ( ! empty( $this->args['link_target'] ) ) {
				$target = $this->args['link_target'];
			} elseif ( $this->post_id ) {
				$target = get_post_meta( $this->post_id, 'wpex_card_link_target', true ) ?: $target;
			}

			/**
			 * Filters the card link target.
			 *
			 * @param string $target The link target.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->link_target = (string) apply_filters( 'wpex_card_link_target', $target, $this );

			return $this->link_target;
		}

		/**
		 * Get card link title.
		 */
		public function get_link_title() {
			$link_title = '';

			if ( ! empty( $this->args['link_title'] ) ) {
				$link_title = $this->args['link_title'];
			}

			/**
			 * Filters the card link title.
			 *
			 * @param string $title The link title.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->link_title = (string) apply_filters( 'wpex_card_link_title', $link_title, $this );

			return $this->link_title;
		}

		/**
		 * Get card link rel attribute.
		 */
		public function get_link_rel() {
			$rel = '';

			if ( ! empty( $this->args['link_rel'] ) ) {
				$rel = $this->args['link_rel'];
			} elseif( $this->post_id ) {
				$rel = get_post_meta( $this->post_id, 'wpex_card_link_rel', true );
			}

			/**
			 * Filters the card link rel.
			 *
			 * @param string $rel The link rel.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->link_rel = (string) apply_filters( 'wpex_card_link_rel', $rel, $this );

			return $this->link_rel;
		}

		/**
		 * Get custom card link data attributes.
		 */
		public function get_link_data() {
			$data = [];

			if ( ! empty( $this->args['link_data'] ) ) {
				$data = $this->args['link_data'];
				if ( function_exists( 'wp_parse_list' ) ) {
					$data = wp_parse_list( $data );
				}
			}

			/**
			 * Filters the card link data.
			 *
			 * @param array $data The link data.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->link_data = (array) apply_filters( 'wpex_card_link_data', $data, $this );

			return $this->link_data;
		}

		/**
		 * Get link attributes.
		 */
		public function get_link_attributes( $attributes = [] ) {
			$defaults_attributes = [
				'href' => $this->get_var( 'url' ),
			];

			$attrs = wp_parse_args( $attributes, $defaults_attributes );

			$type   = $this->get_var( 'link_type' );
			$target = $this->get_var( 'link_target' );
			$title  = $this->get_var( 'link_title' );
			$rel    = $this->get_var( 'link_rel' );
			$data   = $this->get_var( 'link_data' );

			$class = [];

			switch ( $type ) {
				case 'local':
					$class[] = 'local-scroll-link';
					break;
				case 'dialog':
					$attrs['role'] = 'button';
					$attrs['aria-controls'] = $this->get_modal_id();
					$attrs['aria-expanded'] = 'false';
					$class[] = 'wpex-open-modal';
					break;
				case 'modal':
				case 'lightbox':
					$lightbox_type = $this->get_var( 'lightbox_type' );
					$class[] = $this->get_var( 'lightbox_class' );
					$lightbox_data = $this->get_var( 'lightbox_data' );
					if ( ! empty( $lightbox_data ) && is_array( $lightbox_data ) ) {
						foreach ( $lightbox_data as $data_key => $data_val ) {
							$data[ $data_key ] = $data_val;
						}
					}
					$attrs['role'] = 'button';
					$this->enqueue_lightbox();
					break;
				default:
					break;
			}

			switch ( $target ) {
				case 'blank':
				case '_blank':
					$attrs['target'] = '_blank';
					$targeted_rel = apply_filters( 'wpex_targeted_link_rel', 'noopener noreferrer', $attrs['href'] );
					if ( $rel ) {
						$attrs['rel'] = $rel . ' ' . $targeted_rel;
					} else {
						$attrs['rel'] = $targeted_rel;
					}
					break;
				default:
					if ( $rel ) {
						$attrs['rel'] = $rel;
					}
					break;
			}

			if ( $data && is_array( $data ) ) {

				foreach ( $data as $datak => $datav ) {
					$attrs['data-' . $datak] = $datav;
				}

			}

			if ( $class ) {
				$attrs['class'] = implode( ' ', $class );
			}

			if ( $title ) {
				$attrs['title'] = esc_attr( $title );
			}

			/**
			 * Filters the card link attributes.
			 *
			 * @param array $attributes The link attributes.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->link_attributes = (array) apply_filters( 'wpex_card_link_attributes', $attrs, $this );

			return $this->link_attributes;
		}

		/**
		 * Get card link open tag.
		 */
		public function get_link_open( $args = [] ) {
			if ( ! $this->has_link() ) {
				return;
			}

			$default_args = [
				'class'      => '',
				'attributes' => [],
			];

			$args = wp_parse_args( $args, $default_args );

			if ( ! empty( $args['attributes'] ) ) {
				$attrs = $this->get_link_attributes( $args['attributes'] );
			} else {
				$attrs = $this->get_var( 'link_attributes' );
			}

			if ( $args['class'] ) {
				if ( empty( $attrs['class'] ) ) {
					$attrs['class'] = $args['class'];
				} else {
					$attrs['class'] .= ' ' . trim( (string) $args['class'] );
				}
			}

			$attrs = array_map( 'esc_attr', $attrs ); // escape attribute values.

			$html = '<a';

				foreach ( $attrs as $name => $value ) {
					if ( ! empty( $value ) ) {
						$html .= ' ' . $name . '="' . $value . '"'; // note $value already escaped
					} elseif ( 'download' === $name ) {
						$html .= ' ' . $name;
					}
				}

			$html .= '>';

			$this->link_open = $html;

			return $this->link_open;
		}

		/**
		 * Get card link close tag.
		 */
		public function get_link_close() {
			if ( $this->has_link() ) {
				return '</a>';
			}
		}

		/**
		 * Check if it's possible to add a link around the whole card.
		 */
		public function has_link_wrap(): bool {
			return ( $this->has_link() && ! $this->has_thumbnail_overlay() && 'video' !== $this->get_var( 'media_type' ) );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Lightbox ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card lightbox url.
		 */
		public function get_lightbox_url() {
			$lightbox_url = '';

			if ( isset( $this->args['lightbox_url'] ) ) {
				$lightbox_url = $this->args['lightbox_url'];
			} elseif ( isset( $this->args['url'] ) ) {
				$lightbox_url = $this->args['url'];
			} else {
				if ( $this->post_id || $this->term ) {
					$lightbox_type = $this->get_var( 'lightbox_type' );
					switch ( $lightbox_type ) {
						case 'video':
							$lightbox_url = $this->get_var( 'lightbox_video' );
							break;
						case 'gallery':
							$lightbox_url = '#';
							break;
						case 'thumbnail':
						default:
							$thumbnail_id = $this->get_var( 'thumbnail_id' );
							if ( $thumbnail_id ) {
								$lightbox_url = wpex_get_lightbox_image( $thumbnail_id );
							}
							break;
					}
				} else {
					$lightbox_url = $this->get_var( 'thumbnail_url' );
				}
			}

			/**
			 * Filters the card lightbox url.
			 *
			 * @param string $lightbox_url The lightbox url.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->lightbox_url = (string) apply_filters( 'wpex_card_lightbox_url', $lightbox_url, $this );

			return $this->lightbox_url;
		}

		/**
		 * Get card lightbox type.
		 */
		public function get_lightbox_type() {
			$lightbox_type = 'thumbnail';

			if ( isset( $this->args['lightbox_type'] ) ) {
				$lightbox_type = $this->args['lightbox_type'];
			} elseif ( $this->post_id ) {

				$link_type = $this->get_var( 'link_type' );

				switch ( $link_type ) {
					case 'modal':
						if ( $this->get_card_meta( 'url' ) ) {
							$lightbox_type = 'iframe';
						} else {
							$lightbox_type = 'modal';
						}
						break;
					default:
						$video_check = true;
						if ( 'post' === $this->get_post_type() && 'video' !== $this->get_post_format() ) {
							$video_check = false;
						}
						if ( $video_check && $this->get_var( 'lightbox_video' ) ) {
							$lightbox_type = 'video';
						} elseif ( empty( $this->has_lightbox_group ) && wpex_has_post_gallery( $this->post_id ) ) {
							$lightbox_type = 'gallery';
						}
						break;
				}

			}

			/**
			 * Filters the card lightbox type.
			 *
			 * @param string $lightbox_type The lightbox type.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->lightbox_type = (string) apply_filters( 'wpex_card_lightbox_type', $lightbox_type, $this );

			return $this->lightbox_type;
		}

		/**
		 * Get lightbox video.
		 */
		public function get_lightbox_video() {
			$video = wpex_get_post_video_oembed_url( $this->post_id );

			if ( $video ) {
				$video = wpex_get_video_embed_url( $video );
			}

			/**
			 * Filters the card lightbox video.
			 *
			 * @param string $video The lightbox video.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->lightbox_video = (string) apply_filters( 'wpex_card_lightbox_video', $video, $this );

			return $this->lightbox_video;
		}

		/**
		 * Get lightbox class.
		 */
		public function get_lightbox_class() {
			$lightbox_class = '';
			$lightbox_type  = $this->get_var( 'lightbox_type' );

			switch ( $lightbox_type ) {
				case 'gallery':
					$lightbox_class = 'wpex-lightbox-gallery';
					$supports_group = false;
					break;
				case 'modal':
				case 'iframe':
				default:
					if ( isset( $this->has_lightbox_group ) && true === $this->has_lightbox_group ) {
						$lightbox_class = 'wpex-lightbox-group-item';
					} else {
						$lightbox_class = 'wpex-lightbox';
					}
					break;
			}

			/**
			 * Filters the card lightbox class.
			 *
			 * @param string $lightbox_class The lightbox class.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->lightbox_class = (string) apply_filters( 'wpex_card_lightbox_class', $lightbox_class, $this );

			return $this->lightbox_class;
		}

		/**
		 * Get lightbox data.
		 */
		public function get_lightbox_data() {
			$lightbox_data = [];

			$lightbox_type = $this->get_var( 'lightbox_type' );

			switch ( $lightbox_type ) {
				case 'modal':
					$modal_settings = $this->get_var( 'modal_settings' );
					foreach ( $modal_settings as $msk => $msv ) {
						$lightbox_data[ $msk ] = $msv;
					}
					break;
				case 'gallery':
					$lightbox_data['gallery'] = $this->get_var( 'lightbox_gallery_data' );
					break;
				case 'iframe':
					$lightbox_data['type'] = 'iframe';
					break;
				default:
					// No data needed here.
					break;
			}

			/**
			 * Filters the card lightbox data attributes.
			 *
			 * @param array $lightbox_data The lightbox data attributes.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->lightbox_data = (array) apply_filters( 'wpex_card_lightbox_data', $lightbox_data, $this );

			return $this->lightbox_data;
		}

		/**
		 * Get lightbox gallery data.
		 */
		public function get_lightbox_gallery_data() {
			$this->lightbox_gallery_data = wpex_parse_inline_lightbox_gallery( wpex_get_gallery_ids( $this->post_id ) );
			return $this->lightbox_gallery_data;
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Element ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card element.
		 */
		public function get_element( $args = [] ) {
			$default_args = [
				'name'                => '',
				'class'               => '',
				'link'                => false,
				'link_class'          => '',
				'link_rel'            => '', // used for custom links only
				'link_target'         => '', // used for custom links only
				'content'             => '',
				'screen_reader_text'  => '',
				'sanitize_content'    => true,
				'html_tag'            => 'div',
				'before'              => '',
				'after'               => '',
				'icon'                => '',
				'icon_class'          => '',
				'icon_bidi'           => false,
				'svg'                 => '',
				'prefix'              => '',
				'suffix'              => '',
				'css'                 => '',
				'data'                => '',
				'overlay'             => false,
				'attributes'          => [],
				'link_attributes'     => [],
				'strip_overlay_links' => false,
			];

			$args = wp_parse_args( $args, $default_args );

			$this->el_args = $args;

			$content = $this->parse_element_content( $args['name'] );

			if ( empty( $content ) && '0' !== $content ) {
				return;
			}

			$class = $this->parse_element_class();

			$content_out = '';

			if ( true === $args['link'] ) {

				$content_out .= $this->get_link_open( [
					'class'      => $args['link_class'],
					'attributes' => $args['link_attributes'] ?? null,
				] );

			} elseif ( ! empty( $args['link'] ) ) {

				$link_attrs = [
					'href'   => esc_url( $args['link'] ),
					'class'  => esc_attr( $args['link_class'] ),
					'rel'    => esc_attr( $args['link_rel'] ),
					'target' => esc_attr( $args['link_target'] ),
				];

				$link_attrs = wp_parse_args( $args['link_attributes'], $link_attrs );

				$content_out .= '<a ' . wpex_parse_attrs( $link_attrs ) . '>';

			}

			if ( ! empty( $args['icon'] ) && is_string( $args['icon'] ) ) {
				$icon_class = $args['icon_class'] ?? '';
				if ( str_contains( $args['icon'], 'ticon' ) ) {
					$icon_class_array = explode( ' ', str_replace( 'ticon ', '', $args['icon'] ) );
					if ( $icon_class_array ) {
						foreach ( $icon_class_array as $icon_class_array_v ) {
							if ( empty( $icon_name ) && str_starts_with( $icon_class_array_v, 'ticon-' ) ) {
								$icon_name = str_replace( 'ticon-', '', $icon_class_array_v );
							} else {
								$icon_class .= " {$icon_class_array_v}";
							}
						}
						$icon_class = trim( $icon_class );
					}
				}
				if ( $icon_html = totaltheme_get_icon( $icon_name ?? $args['icon'], $icon_class, '', $args['icon_bidi'] ) ) {
					$content_out .= $icon_html;
				} else {
					$content_out .= '<span class="' . esc_attr( trim( "{$args['icon']} {$icon_class}" ) ) . '" aria-hidden="true"></span>';
				}
			}

			if ( ! empty( $args['svg'] ) ) {
				$content_out .= $args['svg'];
			}

			if ( $content && ! empty( $args['screen_reader_text'] ) ) {
				$content = $content . '<span class="screen-reader-text">' . esc_html( $args['screen_reader_text'] ) . '</span>';
			}

			$content_out .= $args['prefix'] . $content . $args['suffix'];

			$content_out .= $this->get_overlay( $args['overlay'], 'inside_link', $args['strip_overlay_links'] );

			if ( true === $args['link'] ) {
				$content_out .= $this->get_link_close();
			} elseif ( ! empty( $args['link'] ) ) {
				$content_out .= '</a>';
			}

			$content_out .= $this->get_overlay( $args['overlay'], 'outside_link', $args['strip_overlay_links'] );

			if ( ! empty( $content_out ) ) {

				$el_css = $this->parse_element_css( $args );

				$output = $args['before'];

					$attributes = [
						'class' => $class,
						'style' => $el_css,
					];

					$attributes = wp_parse_args( $args['attributes'], $attributes );

					$output .= wpex_parse_html( $this->parse_element_html_tag(), $attributes, $content_out );

				$output .= $args['after'];

				$this->el_args = null;

				return $output;

			}

			$this->el_args = null;
		}

		/**
		 * Get card empty element..4
		 */
		public function get_empty_element( $args = [] ) {
			$args = wp_parse_args( $args, [
				'html_tag' => 'div',
				'class'    => '',
				'css'      => '',
			] );

			return wpex_parse_html( $args['html_tag'], [
				'class' => $args['class'],
				'style' => $args['css'],
			], '' );
		}

		/**
		 * Get current element name.
		 */
		protected function get_element_name() {
			if ( ! empty( $this->el_args['name'] ) ) {
				return $this->el_args['name'];
			}
			return '';
		}

		/**
		 * Parse element html tag.
		 */
		protected function parse_element_html_tag() {
			$html_tag = 'div';

			if ( ! empty( $this->el_args['html_tag'] ) ) {
				$html_tag = tag_escape( $this->el_args['html_tag'] );
			}

			if ( 'title' === $this->get_element_name() ) {
				$html_tag = $this->get_var( 'title_tag' );
			}

			return $html_tag;
		}

		/**
		 * Parse element class.
		 */
		protected function parse_element_class( $args = [] ) {
			if ( ! empty( $args ) ) {
				$this->el_args = $args;
			}

			$args = $this->el_args;

			$element_name = $this->get_element_name();

			$class = [];

			if ( $element_name ) {
				$class[] = "wpex-card-{$element_name}";
			} else {
				$class[] = 'wpex-card-element';
			}

			switch ( $element_name ) {
				case 'excerpt':
					$class[] = 'wpex-last-mb-0';
					break;
				case 'icon':
					// @todo create new utility class for icon size!
					if ( ! empty( $args['size'] ) ) {
						$class[] = 'wpex-icon-' . sanitize_html_class( $args['size'] );
					}
					break;
				case 'thumbnail':
					$class[] = 'wpex-relative';
					if ( $this->get_var( 'thumbnail_hover' ) ) {
						$class[] = wpex_image_hover_classes( $this->get_var( 'thumbnail_hover' ) );
					}
					if ( $this->get_var( 'thumbnail_filter' ) ) {
						$class[] = wpex_image_filter_class( $this->get_var( 'thumbnail_filter' ) );
					}
					break;
			}

			if ( ! empty( $args['class'] ) ) {
				$class_vals = wp_parse_list( $args['class'] );
				if ( ! empty( $class_vals ) && is_array( $class_vals ) ) {
					foreach ( $class_vals as $val ) {
						$class[] = $val;
					}
				}
			}

			if ( isset( $args['line_clamp'] ) ) {
				$line_clamp = absint( $args['line_clamp'] );
				if ( $line_clamp && in_array( $line_clamp, [ 1, 2 , 3 , 4 , 5 ], true ) ) {
					$class[] = "wpex-line-clamp-{$line_clamp}";
				}
			}

			if ( $args['overlay'] && 'none' !== $args['overlay'] ) {
				$overlay_class = (string) totaltheme_call_static(
					'Overlays',
					'get_parent_class',
					(string) $args['overlay']
				);
				if ( $overlay_class ) {
					$class[] = trim( (string) $overlay_class );
				}
			}

			if ( $element_name && ! empty( $this->args[$element_name . '_class'] ) ) {
				$custom_class = $this->args[$element_name . '_class'];
				if ( is_array( $custom_class ) ) {
					foreach ( $custom_class as $val ) {
						$class[] = $val;
					}
				} else {
					$class[] = $custom_class;
				}
			}

			if ( ! empty( $this->args['media_el_class'] )
				&& ( 'thumbnail' === $element_name || 'video' === $element_name ) // can't target "media" as it can cause duplicate issues.
			) {
				$class[] = $this->args['media_el_class'];
			}

			if ( $element_name && ! empty( $this->args[ "{$element_name}_font_size" ] ) ) {
				$custom_font_size = $this->args[ "{$element_name}_font_size" ];
				if ( $custom_font_size ) {
					$class = $this->modify_element_font_size( $custom_font_size, $class );
				}
			}

			if ( ! empty( $this->args['media_width'] )
				&& ( 'media' === $element_name || 'thumbnail' === $element_name )
			) {
				$media_width = $this->args['media_width'];
				if ( $media_width && 'custom' !== $media_width ) {
					$class = $this->modify_element_width( $media_width, $class );
				}
			}

			$class = array_map( 'esc_attr', $class );

			return $class;
		}

		/**
		 * Parse element content.
		 */
		protected function parse_element_content( $element = '' ) {
			$content = $this->el_args['content'] ?? '';

			if ( $content && $this->el_args['sanitize_content'] ) {
				if ( isset( $this->el_args['name'] ) && in_array( $this->el_args['name'], [ 'excerpt' ], true ) ) {
					if ( function_exists( 'totaltheme_render_content' ) ) {
						$content = totaltheme_render_content( $content );
					} else {
						$content = do_shortcode( wp_kses_post( $content ) );
					}
				} else {
					$content = do_shortcode( wp_kses_post( $content ) );
					$content = totaltheme_replace_vars( $content );
				}
			}

			return $content;
		}

		/**
		 * Parse element css.
		 */
		protected function parse_element_css() {
			$css = '';
			$args = $this->el_args;

			if ( ! empty( $args['css'] ) ) {
				$css = $args['css'];
			}

			if ( ! empty( $args['name'] ) && ! empty( $this->args[ "{$args['name']}_css" ] ) ) {
				$custom_css = $this->args[ "{$args['name']}_css" ];
				if ( is_array( $custom_css ) ) {
					$custom_css = implode( ' ', $custom_css );
				}
				$css .= ' ' . $custom_css;
			}

			if ( $css && is_string( $css ) ) {
				$css = ' style="' . esc_attr( trim( $css ) ) . '"';
			}

			return $css;
		}

		/**
		 * Modifies an element font size.
		 *
		 * @todo deprecate
		 */
		protected function modify_element_font_size( $custom_font_size = '', $classes = [] ) {
			if ( empty( $classes ) ) {
				return $classes;
			}

			$custom_font_size = wpex_sanitize_utl_font_size( $custom_font_size );

			if ( ! $custom_font_size ) {
				return $classes;
			}

			$get_font_sizes = wpex_utl_font_sizes();
			$font_sizes = [];
			$custom_size_added = false;

			if ( $get_font_sizes ) {
				foreach ( $get_font_sizes as $key => $value ) {
					if ( $key ) {
						$font_sizes[ $key ] = "wpex-text-{$key}";
					}
				}
				foreach ( $classes as $key => $val ) {
					if ( in_array( $val, $font_sizes ) ) {
						$classes[ $key ] = $custom_font_size;
						$custom_size_added = true;
						break; // no need to check multiple, each element should only have 1 font size defined.
					}
				}
				if ( ! $custom_size_added ) {
					$classes[] = $custom_font_size;
				}
			}

			return $classes;
		}

		/**
		 * Modifies an element width.
		 *
		 * @todo update to use preg_match
		 */
		protected function modify_element_width( $custom_width = '', $classes = [] ) {
			if ( empty( $classes ) || empty( $custom_width ) ) {
				return $classes;
			}

			$custom_width_safe = absint( $custom_width );

			if ( $custom_width_safe ) {

				$widths = wpex_utl_percent_widths();

				if ( $widths ) {

					$breakpoint = $this->get_breakpoint();

					// Loop through element classes
					foreach ( $classes as $key => $val ) {
						if ( 'wpex-w-100' === $val ) {
							continue; // don't modify elements intended to be 100% wide
						}

						// Alter width classes
						$class = str_replace( 'wpex-w-', '', $val );
						if ( ! empty( $class ) && array_key_exists( $class, $widths ) ) {
							$classes[ $key ] = "wpex-w-{$custom_width_safe}";
							continue;
						}

						// Alter responsive width classes
						if ( $breakpoint ) {
							$bk_class = str_replace( 'wpex-' . $breakpoint . '-w-', '', $val );
							if ( ! empty( $bk_class ) && array_key_exists( $bk_class, $widths ) ) {
								$classes[ $key ] = "wpex-{$breakpoint}-w-{$custom_width_safe}";
							}
						}

					}

				}

			}

			return $classes;
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Media ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card media.
		 */
		public function get_media( $args = [] ) {
			$default_args = [
				'class'          => '',
				'before'         => '',
				'after'          => '',
				'link'           => true,
				'overlay'        => true, // overlays can be enabled to check for overlay style or a string.
				'class'          => '',
				'css'            => '',
				'image_class'    => '',
				'image_size'     => '',
				'thumbnail_args' => [],
			];

			/**
			 * Filters the card media element arguments.
			 *
			 * @param array $args The arguments.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_media_args', wp_parse_args( $args, $default_args ), $this );

			if ( ! empty( $args['image_class'] ) ) {
				$args['thumbnail_args']['image_class'] = $args['image_class'];
			}

			if ( ! empty( $args['image_size'] ) ) {
				$args['thumbnail_args']['size'] = $args['image_size'];
			}

			if ( false === $args['link'] ) {
				$args['thumbnail_args']['link'] = false;
			}

			if ( false === $args['overlay'] ) {
				$args['thumbnail_args']['overlay'] = false;
			}

			if ( isset( $args['strip_overlay_links'] ) && true === $args['strip_overlay_links'] ) {
				$args['thumbnail_args']['strip_overlay_links'] = true;
			}

			$media_type = $this->get_var( 'media_type' );

			switch ( $media_type ) {
				case 'video':
					$media = $this->get_video();
					break;
				case 'audio':
					$media = $this->get_audio();
					break;
				case 'gallery':
					$media = $this->get_gallery_slider( [
						'thumbnail_args' => $args['thumbnail_args'],
					] );
					break;
				case 'thumbnail':
					$media = $this->get_thumbnail( $args['thumbnail_args'] );
					break;
				default:
					$media = '';
					break;
			}

			/**
			 * Filters the card media html.
			 *
			 * @param string $media The card media html.
			 * @param object $this Current WPEX_Card object.
			 * @param array $args The media element arguments.
			 */
			$media = (string) apply_filters( 'wpex_card_media', $media, $this, $args );

			if ( empty( $media ) ) {
				return;
			}

			$args['name'] = 'media';
			$class = $this->parse_element_class( $args );
			$el_css = $this->parse_element_css( $args );

			// Important - this is a wrapper element, don't use $this->get_element
			$output = $args['before'];

				$output .= wpex_parse_html( 'div', [
					'class' => $class,
					'style' => $el_css,
				], $media );

			$output .= $args['after'];

			return $output;
		}

		/**
		 * Card allowed media types.
		 */
		public function get_allowed_media() {
			if ( array_key_exists( 'allowed_media', $this->args ) ) {
				$allowed_media = (array) $this->args['allowed_media'];
			} else {
				$allowed_media = [ 'thumbnail' ];
			}

			if ( ! empty( $this->args['display_video'] ) && true === $this->args['display_video'] ) {
				$allowed_media[] = 'video';
			}
			if ( ! empty( $this->args['display_audio'] ) && true === $this->args['display_audio'] ) {
				$allowed_media[] = 'audio';
			}

			if ( ! empty( $this->args['display_gallery'] ) && true === $this->args['display_gallery'] ) {
				$allowed_media[] = 'gallery';
			}

			if ( $this->post_id && post_password_required( $this->post_id ) ) {
				$allowed_media = array_combine( $allowed_media, $allowed_media );
				unset( $allowed_media['video'] );
				unset( $allowed_media['audio'] );
			}

			/**
			 * Filters the card allowed media types.
			 *
			 * @param array $allowed_media The media types.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->allowed_media = (array) apply_filters( 'wpex_card_allowed_media', $allowed_media, $this );

			return array_filter( $this->allowed_media );
		}

		/**
		 * Card media type.
		 */
		public function get_media_type() {
			$type = '';

			$allowed_media = $this->get_var( 'allowed_media' );

			if ( $this->post_id ) {
				if ( in_array( 'video', $allowed_media ) && $this->get_var( 'post_video' ) ) {
					$type = 'video';
				} elseif ( in_array( 'audio', $allowed_media ) && wpex_has_post_audio( $this->post_id ) ) {
					$type = 'audio';
				} elseif ( in_array( 'gallery', $allowed_media ) && wpex_has_post_gallery( $this->post_id ) ) {
					$type = 'gallery';
				} elseif ( in_array( 'thumbnail', $allowed_media ) ) {
					$type = 'thumbnail'; // don't bother with the has_post_thumbnail check.
				}
			} elseif ( $this->term ) {
				$type = 'thumbnail';
			}

			/**
			 * Filters the card media type
			 *
			 * @param string $type The media type.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->media_type = (string) apply_filters( 'wpex_card_media_type', $type, $this );

			return $this->media_type;
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Thumbnail ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card thumbnail ID.
		 */
		public function get_thumbnail_id() {
			$attachment = '';

			if ( ! empty( $this->args['thumbnail_id'] ) ) {
				$attachment = $this->args['thumbnail_id'];
			} elseif ( $this->post_id ) {
				$attachment = absint( $this->get_card_meta( 'thumbnail' ) );
				if ( empty( $attachment ) ) {
					if ( 'attachment' === $this->get_post_type() ) {
						$attachment = $this->post_id;
					} else {
						$attachment = get_post_thumbnail_id( $this->post_id );
					}
				}
			} elseif( $this->term ) {
				$attachment = wpex_get_term_thumbnail_id( $this->term );
			}

			/**
			 * Filters the card thumbnail id.
			 *
			 * @param string|int $attachment The thumbnail attachment id.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->thumbnail_id = apply_filters( 'wpex_card_thumbnail_id', $attachment, $this );

			return $this->thumbnail_id;
		}

		/**
		 * Get card thumbnail url.
		 */
		public function get_thumbnail_url( $size = '' ) {
			$attachment = $this->get_var( 'thumbnail_id' );

			if ( ! $attachment ) {
				return false;
			}

			$thumbnail_url = '';

			if ( ! $size ) {
				$size = $this->get_var( 'thumbnail_size', $size );
			}

			$thumbnail_args = array(
				'attachment' => $attachment,
			);

			if ( is_array( $size ) ) {
				$thumbnail_args['width']  = $size[0] ?? '';
				$thumbnail_args['height'] = $size[1] ?? '';
				$thumbnail_args['crop']   = $size[2] ?? '';
			} else {
				$thumbnail_args['size'] = $size;
			}

			if ( function_exists( 'wpex_get_post_thumbnail_url' ) ) {
				$thumbnail_url = wpex_get_post_thumbnail_url( $thumbnail_args );
			} else {
				$thumbnail_url = wp_get_attachment_url( $attachment );
			}

			/**
			 * Filters the card thumbnail url.
			 *
			 * @param string $thumbnail_url The thumbnail url.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->thumbnail_url = (string) apply_filters( 'wpex_card_thumbnail_url', $thumbnail_url, $this );

			return $this->thumbnail_url;
		}

		/**
		 * Get card thumbnail.
		 */
		public function get_thumbnail( $args = [] ) {
			$allowed_media = $this->get_var( 'allowed_media' );

			if ( $this->is_mock() ) {
				return $this->get_mock_thumbnail( $args );
			}

			if ( ! is_array( $allowed_media ) || ! in_array( 'thumbnail', $allowed_media ) ) {
				return;
			}

			$default_args = [
				'overlay'             => true,
				'link'                => true,
				'class'               => '',
				'image_class'         => '',
				'size'                => 'full',
				'strip_overlay_links' => false,
			];

			$args = (array) apply_filters( 'wpex_card_thumbnail_args', wp_parse_args( $args, $default_args ), $this );

			$size = $this->get_var( 'thumbnail_size', $args['size'] );
			$alt  = $this->get_thumbnail_alt();

			$attachment = $this->get_var( 'thumbnail_id' );

			if ( empty( $attachment ) ) {
				return;
			}

			$image_class = 'wpex-align-middle';

			if ( ! empty( $args['image_class'] ) && is_string( $args['image_class'] ) ) {
				$image_class .= ' ' . trim( $args['image_class'] );
			}

			if ( function_exists( 'wpex_get_post_thumbnail' ) ) {
				$thumbnail_args = [
					'attachment' => $attachment,
					'alt'        => $alt,
					'class'      => $image_class,
				];

				if ( isset( $args['lazy'] ) || isset( $this->args['thumbnail_lazy'] ) ) {
					$thumbnail_args['lazy'] = $args['lazy'] ?? $this->args['thumbnail_lazy'];
				}

				if ( is_array( $size ) ) {
					$thumbnail_args['width']  = $size[0] ?? '';
					$thumbnail_args['height'] = $size[1] ?? '';
					$thumbnail_args['crop']   = $size[2] ?? '';
				} else {
					$thumbnail_args['size']   = $size;
				}

				$thumbnail = wpex_get_post_thumbnail( $thumbnail_args );
			} else {
				$thumbnail = wp_get_attachment_image( $attachment, $size, array(
					'alt'   => $alt,
					'class' => $image_class,
				) );
			}

			/**
			 * Filters the card thumbnail html.
			 *
			 * @param string $thumbnail
			 * @param object $this WPEX_Card object
			 * @param array $args
			 */
			$thumbnail = apply_filters( 'wpex_card_thumbnail', $thumbnail, $this, $args );

			if ( empty( $thumbnail ) ) {
				return;
			}

			if ( $this->post_id
				&& function_exists( 'wpex_get_entry_media_after' )
				&& 'post' === $this->get_post_type()
			) {
				$thumbnail .= wpex_get_entry_media_after( 'card' );
			}

			$args['name']             = 'thumbnail';
			$args['content']          = $thumbnail;
			$args['sanitize_content'] = false;

			if ( ! empty( $args['overlay'] ) ) {
				if ( is_string( $args['overlay'] ) ) {
					$args['overlay'] = $args['overlay'];
				} else {
					$args['overlay'] = $this->get_var( 'thumbnail_overlay_style' );
				}
			}

			return $this->get_element( $args );
		}

		/**
		 * Get card thumbnail alt.
		 */
		public function get_thumbnail_alt() {
			if ( isset( $this->thumbnail_alt ) ) {
				return $this->thumbnail_alt;
			}
			$attachment = $this->get_var( 'thumbnail_id' );
			if ( ! $attachment ) {
				return;
			}
			$alt = (string) get_post_meta( $attachment, '_wp_attachment_image_alt', true );
			if ( ! $alt ) {
				if ( ! empty( $this->args['title'] ) ) {
					$alt = (string) $this->args['title'];
				} elseif ( $this->post_id ) {
					$alt = (string) get_the_title( $this->post_id );
				} elseif ( ! empty( $this->term->name ) ) {
					$alt = $this->term->name;
				}
			}
			if ( $alt ) {
				return sanitize_text_field( $alt );
			}
		}

		/**
		 * Get card thumbnail size.
		 */
		public function get_thumbnail_size( $default_size = '' ) {
			$size = '';

			if ( isset( $this->args['thumbnail_size'] ) ) {
				$size = $this->args['thumbnail_size'];
			} else {
				$size = $default_size;
			}

			$this->thumbnail_size = apply_filters( 'wpex_card_thumbnail_size', $size, $this );

			return $this->thumbnail_size;

		}

		/**
		 * Get card thumbnail hover.
		 */
		public function get_thumbnail_hover() {
			$hover = '';

			if ( isset( $this->args['thumbnail_hover'] ) ) {
				$hover = (string) $this->args['thumbnail_hover'];
			}

			/**
			 * Filters the thumbnail hover style.
			 *
			 * @param string $hover The hover style name.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->thumbnail_hover = (string) apply_filters( 'wpex_card_thumbnail_hover', $hover, $this );

			return $this->thumbnail_hover;
		}

		/**
		 * Get card thumbnail filter.
		 */
		public function get_thumbnail_filter() {
			$filter = '';

			if ( isset( $this->args['thumbnail_filter'] ) ) {
				$filter = $this->args['thumbnail_filter'];
			}

			/**
			 * Filters the thumbnail filter style.
			 *
			 * @param string $filter The filter style name.
			 * @param object $this Current WPEX_Card object.
			 */
			$this->thumbnail_filter = apply_filters( 'wpex_card_thumbnail_size', $filter, $this );

			return $this->thumbnail_filter;
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Overlay ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card thumbnail overlay.
		 */
		public function get_thumbnail_overlay_style(): string {
			$style = '';

			if ( ! empty( $this->args['thumbnail_overlay_style'] ) ) {
				$style = $this->args['thumbnail_overlay_style'];
			} elseif ( $this->post_id ) {
				$style = $this->get_card_meta( 'thumbnail_overlay_style', true );
			}

			/**
			 * Filters the thumbnail overlay style.
			 *
			 * @param string $style The overlay style name.
			 * @param object $this Current WPEX_Card object.
			 */
			$style = (string) apply_filters( 'wpex_card_thumbnail_overlay_style', $style, $this );

			if ( 'none' === $style ) {
				$style = '';
			}

			$this->thumbnail_overlay_style = $style;

			return $this->thumbnail_overlay_style;
		}

		/**
		 * Check if a card has an overlay.
		 */
		public function has_thumbnail_overlay(): bool {
			$overlay = $this->get_var( 'thumbnail_overlay_style' );
			return ( $overlay && 'none' !== $overlay && function_exists( 'wpex_overlay' ) );
		}

		/**
		 * Get card overlay.
		 */
		public function get_overlay( $style = '', $position = 'inside', $strip_links = false ) {
			if ( empty( $style ) || ! function_exists( 'wpex_overlay' ) ) {
				return;
			}

			$args = [];

			if ( ! empty( $this->args['thumbnail_overlay_button_text'] ) ) {
				$args['overlay_button_text'] = $this->args['thumbnail_overlay_button_text'];
			}

			$link_type = $this->get_var( 'link_type' );

			switch ( $link_type ) {
				case 'modal':
				case 'lightbox':
					$args['lightbox_class'] = $this->get_var( 'lightbox_class' );
					$args['lightbox_link']  = $this->get_var( 'url' );
					$lightbox_data = $this->get_var( 'lightbox_data' );
					if ( ! empty( $lightbox_data ) && is_array( $lightbox_data ) ) {
						$args['lightbox_data'] = '';
						foreach ( $lightbox_data as $data_key => $data_val ) {
							$args['lightbox_data'] .= 'data-' . sanitize_key( $data_key ) . '="' . esc_attr( $data_val ) . '" ';
						}
						$args['lightbox_data'] = trim( $args['lightbox_data'] );
					}
					break;
				default:
					if ( 'dialog' !== $link_type ) {
						$args['post_permalink'] = $this->get_var( 'url' );
					}
					if ( ! empty( $this->args['link_target'] ) ) {
						$args['link_target'] = $this->args['link_target'];
					}
					break;
			}

			if ( $this->term ) {
				$args['post_title'] = $this->term->name ?? '';
				$args['taxonomy'] = '';
				if ( empty( $args['overlay_button_text'] ) ) {
					$args['overlay_button_text'] = esc_html__( 'View Archive', 'total' );
				}
			}

			ob_start();
				totaltheme_render_overlay( $position, $style, $args );
			$html = ob_get_clean();

			if ( $strip_links ) {
				$html = str_replace( '<a', '<span', $html );
				$html = str_replace( '</a>', '</span>', $html );
			}

			return $html;
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Gallery Slider ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card gallery slider.
		 */
		public function get_gallery_slider( $args = [] ) {
			if ( ! $this->post_id || ! function_exists( 'wpex_get_post_media_gallery_slider' ) ) {
				return;
			}

			$default_args = [
				'before'         => '',
				'after'          => '',
				'thumbnail_args' => [],
				'slider_args'    => [
					'lightbox'       => false,
					'captions'       => false,
					'slider_data'    => [
						'thumbnails' => 'false',
						'buttons'    => 'true',
						'fade'       => 'true',
					],
				],
			];

			$args = wp_parse_args( $args, $default_args );

			extract( $args );

			if ( empty( $thumbnail_args['size'] ) ) {

				$thumb_size = $this->thumbnail_size ?? 'full';

				if ( is_array( $thumb_size ) ) {
					$thumbnail_args['width']  = $thumb_size[0] ?? '';
					$thumbnail_args['height'] = $thumb_size[1] ?? '';
					$thumbnail_args['crop']   = $thumb_size[2] ?? '';
				} else {
					$thumbnail_args['size']   = $thumb_size;
				}

			}

			$slider_args['thumbnail_args'] = $thumbnail_args;

			$slider = wpex_get_post_media_gallery_slider( $this->post_id, $slider_args );

			/**
			 * Filters the card gallery slider html.
			 *
			 * @param string $html The slider html.
			 * @param object $this Current WPEX_Card object.
			 */
			$slider = (string) apply_filters( 'wpex_card_gallery_slider', $slider, $this, $args );

			if ( $slider ) {
				return $before . $slider . $after;
			}
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Video ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card video.
		 */
		public function get_video( $args = [] ) {
			$default_args = [
				'class' => '',
			];

			$args = wp_parse_args( $args, $default_args );

			$video = $this->get_post_video_html();

			/**
			 * Filters the card video html.
			 *
			 * @param string $html The video html.
			 * @param object $this Current WPEX_Card object.
			 */
			$video = (string) apply_filters( 'wpex_card_video', $video, $this, $args );

			if ( empty( $video ) ) {
				return;
			}

			$args['name']             = 'video';
			$args['content']          = $video;
			$args['sanitize_content'] = false;
			$args['link']             = false;

			return $this->get_element( $args );
		}

		/**
		 * Get post video.
		 */
		public function get_post_video() {
			if ( ! $this->post_id ) {
				return;
			}

			$this->post_video = wpex_get_post_video( $this->post_id );

			return $this->post_video;
		}

		/**
		 * Get post video html.
		 */
		public function get_post_video_html(): string {
			return ( $video = $this->get_var( 'post_video' ) ) ? (string) wpex_get_post_video_html( $video ) : '';
		}

		/**
		 * Check if a card has a video.
		 */
		public function has_video(): bool {
			return (bool) $this->get_var( 'post_video' );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Audio ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card audio.
		 */
		public function get_audio( $args = [] ) {
			$default_args = array(
				'class' => '',
			);

			$args = wp_parse_args( $args, $default_args );

			if ( $this->post_id ) {
				$audio = wpex_get_post_audio( $this->post_id );
				if ( $audio ) {
					$audio = wpex_get_post_audio_html( $audio );
				}
			}

			/**
			 * Filters the card audio html.
			 *
			 * @param string $html The audio html.
			 * @param object $this Current WPEX_Card object.
			 */
			$audio = (string) apply_filters( 'wpex_card_audio', $audio, $this, $args );

			if ( empty( $audio ) ) {
				return;
			}

			$args['name']             = 'audio';
			$args['content']          = $audio;
			$args['sanitize_content'] = false;
			$args['link']             = false;

			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Title ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Returns the title text.
		 */
		public function get_the_title( $args = [] ) {
			$title = '';

			if ( ! empty( $this->args['title'] ) ) {
				$title = $this->args['title'];
			} elseif ( ! empty( $args['content'] ) ) {
				$title = $args['content'];
			} elseif ( $this->post_id ) {
				$title = get_the_title( $this->post_id );
			} elseif ( ! empty( $this->term->name ) ) {
				$title = $this->term->name;
			}

			/**
			 * Filters the card title.
			 *
			 * @param string $title The title.
			 * @param object $this Current WPEX_Card object.
			 */
			$title = (string) apply_filters( 'wpex_card_title', $title, $this, $args );

			return $title;
		}

		/**
		 * Returns the card title element.
		 */
		public function get_title( $args = [] ) {
			$default_args = [
				'content'    => '',
				'link'       => true,
				'class'      => '',
				'show_count' => false,
			];

			/**
			 * Filters the card title element arguments.
			 *
			 * @param array $args The arguments.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_title_args', wp_parse_args( $args, $default_args ), $this );

			$title = $this->get_the_title( $args );

			if ( empty( $title ) ) {
				return;
			}

			if ( true === $args['show_count'] ) {
				$running_count = $this->get_var( 'running_count' );
				$title = "{$running_count}. {$title}";
			}

			$args['name']    = 'title';
			$args['content'] = $title;

			return $this->get_element( $args );
		}

		/**
		 * Get card title tag.
		 */
		public function get_title_tag() {
			if ( ! empty( $this->args['title_tag'] ) ) {
				$tag = $this->args['title_tag'];
			} else {
				$tag = ( 'related' === wpex_get_loop_instance() ) ? 'h4' : 'h2';
			}

			/**
			 * Filters the card title html tag.
			 *
			 * @param string $tag The title tag.
			 * @param object $this Current WPEX_Card object.
			 */
			$tag = (string) apply_filters( 'wpex_card_title_tag', $tag, $this );

			$this->title_tag = tag_escape( $tag );

			return $this->title_tag;
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Subheading ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Returns the title text.
		 */
		public function get_the_subheading( array $args ): string {
			$subheading = '';

			if ( ! empty( $this->args['subheading'] ) ) {
				$subheading = $this->args['subheading'];
			} elseif ( ! empty( $args['content'] ) ) {
				$subheading = $args['content'];
			} elseif ( $this->post_id ) {
				$post_type = $this->get_post_type();

				switch ( $post_type ) {
					case 'staff':
						$subheading = wpex_get_staff_member_position( $this->post_id );
						break;
					case 'portfolio':
						$subheading = get_post_meta( $this->post_id, 'wpex_portfolio_company', true );
						break;
				}
				if ( $meta = get_post_meta( $this->post_id, 'wpex_post_subheading', true ) ) {
					$subheading = $meta;
				}
				if ( ! $subheading ) {
					$subheading = get_post_meta( $this->post_id, 'subheading', true );
				}
			}

			/**
			 * Filters the card sub heading text.
			 *
			 * @param string $subheading The subheading.
			 * @param object $this Current WPEX_Card object.
			 */
			$subheading = (string) apply_filters( 'wpex_card_subheading', $subheading, $this, $args );

			return $subheading;
		}

		/**
		 * Returns the card subheading.
		 */
		public function get_subheading( $args = [] ) {
			$default_args = [
				'content'    => '',
				'class'      => '',
			];

			/**
			 * Filters the card sub heading args.
			 *
			 * @param array $args The subheading args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_subheading_args', wp_parse_args( $args, $default_args ), $this );

			$subheading = $this->get_the_subheading( $args );

			if ( empty( $subheading ) ) {
				return;
			}

			$args['name']    = 'subheading';
			$args['content'] = $subheading;

			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Excerpt ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Returns the default excerpt length.
		 */
		protected function get_default_excerpt_length(): int {
			return $this->is_featured() ? 40 : 20;
		}

		/**
		 * Get card excerpt.
		 */
		public function get_the_excerpt( $args = [] ) {
			$excerpt = '';

			if ( isset( $this->args['excerpt_length'] ) ) {
				$excerpt_length = $this->args['excerpt_length'];
			} else {
				$excerpt_length = $args['length'] ?? $this->get_default_excerpt_length();
			}

			$excerpt = '';

			if ( $excerpt_length && '0' !== $excerpt_length ) {
				if ( ! empty( $this->args['excerpt'] ) ) {
					$excerpt = $this->args['excerpt'];
				} elseif ( $this->post_id ) {
					$excerpt = get_post_meta( $this->post_id, 'wpex_card_excerpt', true );
					if ( empty( $excerpt ) ) {
						if ( function_exists( 'totaltheme_get_post_excerpt' ) ) {
							$excerpt_args = apply_filters( 'wpex_card_excerpt_args', [
								'post_id' => $this->post_id,
								'length'  => $excerpt_length,
							], $this );
							$excerpt = totaltheme_get_post_excerpt( $excerpt_args );
						} else {
							$excerpt = get_the_excerpt( $this->post_id );
						}
					}
				} elseif ( ! empty( $this->term->description ) ) {
					$excerpt = $this->term->description;
				}
			}

			/**
			 * Filters the card excerpt.
			 *
			 * @param string $excerpt The excerpt.
			 * @param object $this Current WPEX_Card object.
			 */
			$excerpt = (string) apply_filters( 'wpex_card_excerpt', $excerpt, $this, $args );

			return $excerpt;
		}

		/**
		 * Get card excerpt.
		 */
		public function get_excerpt( $args = [] ) {
			$default_args = [
				'link'   => false, // disabled by default but it's possible to add.
				'class'  => '',
				'length' => $this->get_default_excerpt_length(),
			];

			/**
			 * Filters the card excerpt args.
			 *
			 * @param array $args The excerpt args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_excerpt_args', wp_parse_args( $args, $default_args ), $this );

			$excerpt = $this->get_the_excerpt( $args );

			if ( empty( $excerpt ) ) {
				return;
			}

			if ( isset( $this->args['excerpt_length'] ) ) {
				$args['length'] = $this->args['excerpt_length'];
			}

			if ( isset( $this->args['excerpt_max_lines'] ) ) {
				$args['line_clamp'] = $this->args['excerpt_max_lines'];
			}

			$args['name'] = 'excerpt';
			$args['content'] = $excerpt;

			if ( '-1' === $args['length'] || '9999' === $args['length'] ) {
				$args['sanitize_content'] = false; // important because the content has already been sanitized and if we do it again things get stripped out such as <style> tags and we are also running do_shortcode twice.
			}

			unset( $args['length'] );

			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ More Link ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card more link.
		 */
		public function get_more_link( $args = [] ) {
			if ( isset( $this->args['excerpt_length'] ) && '-1' == $this->args['excerpt_length'] ) {
				return;
			}

			$custom_more_link = apply_filters( 'wpex_card_more_link_url', null, $this );

			if ( ! $this->has_link() && ! $custom_more_link ) {
				return;
			}

			$default_args = [
				'class'      => '',
				'text'       => esc_html__( 'Read more', 'total' ),
				'link'       => true,
				'link_class' => '',
			];

			/**
			 * Filters the card more link args.
			 *
			 * @param array $args The more link args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_more_link_args', wp_parse_args( $args, $default_args ), $this );

			if ( isset( $this->args['more_link_text'] ) ) {
				$args['text'] = $this->args['more_link_text'];
			}

			$more_link_text = (string) apply_filters( 'wpex_card_more_link_text', $args['text'], $this, $args );

			if ( ! empty( $more_link_text ) && '0' !== $more_link_text ) {

				$args['name'] = 'more-link';

				if ( $custom_more_link ) {
					$args['link'] = $custom_more_link;
				}

				$args['content'] = $more_link_text;

				if ( ! empty( $this->args['title'] ) ) {
					$escaped_post_title = esc_attr( $this->args['title'] );
				} else {
					$escaped_post_title = the_title_attribute( array(
						'echo' => false,
						'post' => get_post( $this->post_id ),
					) );
				}

				if ( ! empty( $this->args['more_link_aria_label'] ) ) {
					$aria_label = $this->args['more_link_aria_label'];
				} else {
					$aria_label = sprintf( esc_attr_x( '%s about %s', '*read more text* about *post name* aria label', 'total' ), esc_attr( $more_link_text ), $escaped_post_title );
					// @todo deprecate.
					$aria_label = (string) apply_filters( 'wpex_aria_label', $aria_label, 'more_link' );
				}
				$aria_label = (string) apply_filters( 'wpex_card_more_link_aria_label', $aria_label, $this, $args );
				if ( $aria_label ) {
					$args['link_attributes'] = array(
						'aria-label' => strip_shortcodes( $aria_label ),
					);
				}

				return $this->get_element( $args );
			}
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Date ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card date.
		 */
		public function get_date( $args = [] ) {
			$default_args = [
				'link'   => false,
				'type'   => 'published',
				'format' => '',
			];

			/**
			 * Filters the card date args.
			 *
			 * @param array $args The date args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_date_args', wp_parse_args( $args, $default_args ), $this );

			$date = '';

			if ( isset( $this->args['date'] ) ) {
				$date = $this->args['date'];
			} elseif ( $this->post_id ) {
				$format = $this->args['date_format'] ?? $args['format'];

				if ( 'null' === $format || '0' === $format ) {
					return; // allows the date to be disabled via the Post Cards format field
				}

				switch ( $this->get_post_type() ) {
					case 'tribe_events':
						if ( function_exists( 'tribe_get_start_date' ) ) {
							$date = tribe_get_start_date( $this->post_id, false, $format );
						}
						break;
					case 'just_event':
						if ( str_starts_with( $this->get_var( 'thumbnail_overlay_style' ), 'just-events-date' ) ) {
							if ( function_exists( 'Just_Events\get_event_formatted_time' ) ) {
								$date = Just_Events\get_event_formatted_time( $this->post_id );
							}		
						} else {
							if ( function_exists( 'Just_Events\get_event_formatted_date' ) ) {
								$je_args = [
									'start_end' => $args['start_end'] ?? 'both',
									'show_time' => $args['show_time'] ?? false,
									'format'    => $format,
								];
								if ( isset( $args['separator'] ) ) {
									$je_args['separator'] = $args['separator'];
								}
								$date = Just_Events\get_event_formatted_date( $this->post_id, $je_args );
							}
						}
						break;
				}

				if ( ! $date ) {
					switch ( $args['type'] ) {
						case 'time_ago':
							$date = human_time_diff( get_the_time( 'U' ), current_time( 'timestamp' ) ) . ' '. esc_html__( 'ago', 'total' );
							break;
						case 'modified':
							$date = get_the_modified_date( $format, $this->post_id );
							break;
						default:
							$date = get_the_date( $format, $this->post_id );
							break;
					}
				}
			}

			/**
			 * Filters the card date.
			 *
			 * @param string $date The date.
			 * @param object $this Current WPEX_Card object.
			 */
			$date = (string) apply_filters( 'wpex_card_date', $date, $this, $args );

			if ( empty( $date ) ) {
				return;
			}

			$args['name'] = 'date';
			$args['content'] = $date;

			unset( $args['type'] );
			unset( $args['format'] );

			return $this->get_element( $args );
		}

		/**
		 * Get card time.
		 */
		public function get_time( $args = [] ) {
			$default_args = array(
				'link'   => false,
				'type'   => 'published',
				'format' => '',
			);

			/**
			 * Filters the card time args.
			 *
			 * @param array $args The time args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_time_args', wp_parse_args( $args, $default_args ), $this );

			$time = '';

			if ( isset( $this->args['time'] ) ) {
				$time = $this->args['time'];
			} elseif ( $this->post_id ) {
				$format = $this->args['date_format'] ?? $args['format'];
				switch ( $args['type'] ) {
					case 'modified':
						$time = get_the_modified_time( $format, $this->post_id );
						break;
					default:
						$time = get_the_time( $format, $this->post_id );
						break;
				}
				if ( 'tribe_events' === $this->get_post_type() ) {
					$time = '';
				}
			}

			/**
			 * Filters the card time.
			 *
			 * @param string $time The time.
			 * @param object $this Current WPEX_Card object.
			 */
			$time = (string) apply_filters( 'wpex_card_time', $time, $this, $args );

			if ( empty( $time ) ) {
				return;
			}

			$args['name']    = 'time';
			$args['content'] = $time;

			unset( $args['type'] );
			unset( $args['format'] );

			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Read Time ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card read time.
		 */
		public function get_estimated_read_time( $args = [] ) {
			if ( $this->post_id ) {
				$args['content'] = totaltheme_get_post_estimated_read_time( $this->post_id, [], $this );
			} elseif ( ! empty( $this->term->description ) ) {
				$args['content'] = totaltheme_get_estimatd_read_time( $this->term->description, [], $this );
			} else {
				return;
			}

			if ( ! $args['content'] ) {
				return;
			}

			$args['name'] = 'estimated-read-time';

			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Author ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card author.
		 */
		public function get_author( $args = [] ) {
			$default_args = array(
				'link'       => true,
				'link_class' => '',
				'class'      => '',
			);

			/**
			 * Filters the card author args.
			 *
			 * @param array $args The author args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_author_args', wp_parse_args( $args, $default_args ), $this );

			$author = '';

			if ( isset( $this->args['author'] ) ) {
				$author = $this->args['author'];
			} elseif ( $this->post_id ) {

				$post = get_post( $this->post_id );

				$authordata = get_userdata( $post->post_author );

				$the_author = apply_filters( 'the_author', is_object( $authordata ) ? $authordata->display_name : null );

				if ( $the_author ) {

					if ( true === $args['link'] ) {
						$author_posts_url = get_author_posts_url( $post->post_author );
					}

					if ( ! empty( $author_posts_url ) ) {

						$link_attrs = [
							'href'  => esc_url( $author_posts_url ),
							'class' => $args['link_class'],
						];

						$author .= '<a ' . wpex_parse_attrs( $link_attrs ) . '>';
					}

					$author .= esc_html( ucwords( $the_author ) );

					if ( ! empty( $author_posts_url ) ) {
						$author .= '</a>';
					}

				}

			}

			/**
			 * Filters the card author.
			 *
			 * @param string $author The author name.
			 * @param object $this Current WPEX_Card object.
			 */
			$author = (string) apply_filters( 'wpex_card_author', $author, $this, $args );

			if ( empty( $author ) ) {
				return;
			}

			$args['name']             = 'author';
			$args['content']          = $author;
			$args['sanitize_content'] = false;
			$args['link']             = false; // important!!

			unset( $args['link_class'] );

			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Terms ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card terms list.
		 */
		public function get_terms_list( $args = [] ) {
			$default_args = [
				'class'                     => '',
				'term_class'                => '',
				'term_prefix'               => '',
				'separator'                 => ' ',
				'taxonomy'                  => '',
				'link'                      => true,
				'has_term_color'            => false,
				'has_term_background_color' => false,
			];

			/**
			 * Filters the card terms list args.
			 *
			 * @param array $args The terms list args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_terms_list_args', wp_parse_args( $args, $default_args ), $this );

			if ( empty( $args['taxonomy'] ) ) {
				if ( $this->term ) {
					$args['taxonomy'] = $this->term->taxonomy ?? '';
				} else {
					if ( isset( $this->post_id ) && function_exists( 'wpex_get_post_primary_taxonomy' ) ) {
						$args['taxonomy'] = wpex_get_post_primary_taxonomy( $this->post_id );
					} else {
						$args['taxonomy'] = 'category';
					}
				}
			}

			$terms = $this->get_terms( $args );

			if ( empty( $terms ) ) {
				return false;
			}

			$items    = [];
			$has_link = wpex_validate_boolean( $args['link'] );

			foreach ( $terms as $term ) {

				$term_class = $args['term_class'];

				if ( $args['has_term_color'] && $term_color_clasname = totaltheme_get_term_color_classname( $term ) ) {
					$term_class .= " {$term_color_clasname}";
				}

				if ( $args['has_term_background_color'] && $term_color_background_classname = totaltheme_get_term_color_background_classname( $term ) ) {
					$term_class .= " {$term_color_background_classname}";
				}

				if ( $has_link && is_taxonomy_viewable( $args['taxonomy'] ) ) {
					$term_link = get_term_link( $term );
					if ( is_wp_error( $term_link ) ) {
						unset( $term_link );
					}
				}

				if ( $has_link && ! empty( $term_link ) ) {

					$item = '<a href="' . esc_url( $term_link ) . '"';
						if ( $term_class ) {
							$item .= ' class="' . esc_attr( trim( $term_class ) ) . '"';
						}
					$item .= '>';
					if ( $args['term_prefix'] ) {
						$item .= wp_kses_post( $args['term_prefix'] );
					}
					$item .= esc_html( $term->name ) . '</a>';
					$items[] = $item;

				} else {
					$item = '<span';
						if ( $term_class ) {
							// Fix for cards that show links but they have been disabled.
							$term_class = str_replace( 'wpex-underline', '', $term_class );
							$term_class = str_replace( 'wpex-hover-underline', '', $term_class );
							$item .= ' class="' . esc_attr( trim( $term_class ) ) . '"';
						}
						$item .= '>';
						if ( $args['term_prefix'] ) {
							$item .= wp_kses_post( $args['term_prefix'] );
						}
						$item .= esc_html( $term->name ) . '</span>';
					$items[] = $item;
				}

			}

			/**
			 * Filters the card terms list html.
			 *
			 * @param string $terms_list The terms list html.
			 * @param object $this Current WPEX_Card object.
			 */
			$terms_list = (string) apply_filters( 'wpex_card_terms_list', join( $args['separator'], $items ), $this, $args );

			if ( empty( $terms_list ) ) {
				return;
			}

			$args['name']             = 'terms-list';
			$args['content']          = $terms_list;
			$args['sanitize_content'] = false;
			$args['link']             = false; // important!!!

			unset( $args['term_class'] );
			unset( $args['separator'] );
			unset( $args['taxonomy'] );

			return $this->get_element( $args );
		}

		/**
		 * Get card terms.
		 */
		public function get_terms( $args = [] ) {
			$default_args = [
				'taxonomy' => '',
			];

			$args = wp_parse_args( $args, $default_args );

			$terms = [];

			if ( $this->post_id ) {
				if ( empty( $args['taxonomy'] ) ) {
					if ( function_exists( 'wpex_get_post_primary_taxonomy' ) ) {
						$args['taxonomy'] = wpex_get_post_primary_taxonomy( $this->post_id );
					} else {
						$args['taxonomy'] = 'category';
					}
				}
				$get_terms = get_the_terms( $this->post_id, $args['taxonomy'] );
			} elseif ( ! empty( $this->term->parent ) ) {
				if ( empty( $args['taxonomy'] ) ) {
					$args['taxonomy'] = $this->term->taxonomy ?? '';
				}
				$get_terms = get_terms( [
					'taxonomy' => $args['taxonomy'],
					'include'  => $this->term->parent,
				] );
			}

			if ( ! empty( $get_terms ) && ! is_wp_error( $get_terms ) ) {
				$terms = $get_terms;
			}

			/**
			 * Filters the card terms.
			 *
			 * @param array $terms The terms list.
			 * @param object $this Current WPEX_Card object.
			 */
			return (array) apply_filters( 'wpex_card_terms', $terms, $this );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Primary Term ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card primary term.
		 */
		public function get_primary_term( $args = [] ) {
			$default_args = [
				'link'                      => true,
				'class'                     => '',
				'term_class'                => '',
				'has_term_color'            => false,
				'has_term_background_color' => false,
				'taxonomy'                  => '',
			];

			/**
			 * Filters the card primary term args.
			 *
			 * @param array $args The args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_primary_term_args', wp_parse_args( $args, $default_args ), $this );

			$primary_term = '';

			if ( isset( $this->args['primary_term'] ) ) {
				$terms = $this->args['primary_term'];
			} else {
				if ( $this->post_id ) {
					$primary_term = totaltheme_get_post_primary_term( $this->post_id, $args['taxonomy'] );
				} elseif ( ! empty( $this->term->parent ) ) {
					$primary_term = get_term( $this->term->parent, $this->term->taxonomy );
				} else {
					$primary_term = false;
				}

				$primary_term = apply_filters( 'wpex_card_primary_term', $primary_term, $this, $args );

				if ( is_object( $primary_term ) && is_a( $primary_term, 'WP_Term' ) ) {

					if ( $args['has_term_color'] && $term_color_clasname = totaltheme_get_term_color_classname( $primary_term ) ) {
						$args['term_class'] .= " {$term_color_clasname}";
					}
	
					if ( $args['has_term_background_color'] && $term_color_background_classname = totaltheme_get_term_color_background_classname( $primary_term ) ) {
						$args['term_class'] .= " {$term_color_background_classname}";
					}

					if ( $args['link'] ) {

						$link_attrs = [
							'href'  => esc_url( get_term_link( $primary_term ) ),
							'class' => $args['term_class'],
						];

						$primary_term_out = '<a ' . wpex_parse_attrs( $link_attrs) . '>' . esc_html( $primary_term->name ) . '</a>';

					} else {

						if ( $args['term_class'] ) {
							$primary_term_out = '<span class="' . esc_attr( trim( $args['term_class'] ) ) . '">' . esc_html( $primary_term->name ) . '</span>';
						} else {
							$primary_term_out = esc_html( $primary_term->name );
						}

					}

				}

			}

			if ( empty( $primary_term_out ) ) {
				return;
			}

			$args['name']             = 'primary-term';
			$args['content']          = $primary_term_out;
			$args['sanitize_content'] = false;
			$args['link']             = false; // important!!

			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Icon ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Returns the icon.
		 */
		public function get_the_icon( $args = [], $extra_class = '', $size = '', $bidi = false ) {
			$icon = $args['icon'] ?? '';

			if ( isset( $this->args['icon'] ) ) {
				$icon = $this->args['icon'];
			} elseif ( $this->post_id ) {
				$meta_icon = $this->get_card_meta( 'icon', false );
				if ( ! empty( $meta_icon ) ) {
					$icon = $meta_icon;
				}
			}

			if ( ! empty( $icon ) ) {
				$icon = totaltheme_get_icon( $icon, $extra_class, $size, $bidi );
			}
			
			/**
			 * Filters the card icon html.
			 *
			 * @param string $icon
			 * @param object $card
			 * @param array $args
			 */
			$icon = apply_filters( 'wpex_card_icon', $icon, $this, $args );

			return (string) $icon;
		}

		/**
		 * Get card icon.
		 */
		public function get_icon( $args = [] ) {
			$default_args = [
				'class' => '',
				'icon'  => '',
				'size'  => '',
				'bidi'  => false,
			];

			/**
			 * Filters the card icon args.
			 *
			 * @param array $args The args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_icon_args', wp_parse_args( $args, $default_args ), $this );

			$icon = $this->get_the_icon( $args, '', '', $args['bidi'] );

			if ( empty( $icon ) ) {
				return;
			}

			$args['name']             = 'icon';
			$args['content']          = $icon;
			$args['sanitize_content'] = false;

			unset( $args['icon'] ); // !!! important !!!
			unset( $args['bidi'] );

			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Comments ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card comment count.
		 */
		public function get_comment_count( $args = [] ) {
			$default_args = [
				'link'        => true,
				'link_class'  => '',
				'number_only' => false,
				'show_empty'  => false,
			];

			/**
			 * Filters the card comment count arguments.
			 * 
			 * @param array $args The arguments.
			 * @param object $card The card object.
			 */
			$args = (array) apply_filters( 'wpex_card_comment_count_args', wp_parse_args( $args, $default_args ), $this );

			$comment_count = '';

			if ( $this->post_id && comments_open( $this->post_id ) ) {

				$link_class = $args['link_class'] ?? '';

				if ( is_array( $link_class ) ) {
					$link_class = implode( ' ', $link_class );
				}

				if ( $args['link'] ) {
					ob_start();
						if ( $args['number_only'] ) {
							comments_popup_link( '0', '1', '%', $link_class, false );
						} else {
							comments_popup_link( false, false, false, $link_class, false );
						}
					$comment_count = ob_get_clean();
				} else {
					$comment_count = get_comments_number( $this->post_id );
				}

			}

			$comment_count = (string) apply_filters( 'wpex_card_comment_count', $comment_count, $this, $args );

			if ( empty( $comment_count ) && false === $args['show_empty'] ) {
				return;
			}

			$args['name']             = 'comment-count';
			$args['content']          = $comment_count;
			$args['sanitize_content'] = false;
			$args['link']             = false; // important!!

			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Avatar ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card avatar.
		 */
		public function get_avatar( $args = [] ) {
			$default_args = array(
				'link'        => true,
				'size'        => '',
				'class'       => '',
				'image_class' => '',
			);

			/**
			 * Filters the card avatar args.
			 *
			 * @param array $args The args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_avatar_args', wp_parse_args( $args, $default_args ), $this );

			$avatar = '';


			if ( isset( $this->args['avatar'] ) ) {
				$avatar = $this->args['avatar'];
			} elseif ( $this->post_id ) {

				$post = get_post( $this->post_id );

				if ( true === $args['link'] ) {
					$author_posts_url = get_author_posts_url( $post->post_author );
				}

				if ( ! empty( $author_posts_url ) ) {
					$avatar .= '<a href="' . esc_url( $author_posts_url ) . '">';
				}

				$avatar .= get_avatar( $post->post_author, $args['size'], '', '', array(
					'class' => $args['image_class'],
				) );

				if ( ! empty( $author_posts_url ) ) {
					$avatar .= '</a>';
				}

			}

			/**
			 * Filters the card avatar html.
			 *
			 * @param string $avatar The avatar html.
			 * @param object $this Current WPEX_Card object.
			 * @param array $args The args passed to get_avatar()
			 */
			$avatar = (string) apply_filters( 'wpex_card_avatar', $avatar, $this, $args );

			if ( empty( $avatar ) ) {
				return;
			}

			$args['name']             = 'avatar';
			$args['content']          = $avatar;
			$args['sanitize_content'] = false;
			$args['link']             = false; // important!!

			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Count ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card number.
		 */
		public function get_number( $args = [] ) {
			$default_args = array(
				'link'         => false,
				'class'        => '',
				'number'       => '',
				'prepend_zero' => false,
			);

			/**
			 * Filters the card number element args.
			 *
			 * @param array $args The args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_number_args', wp_parse_args( $args, $default_args ), $this );

			$number = $args['number'];

			if ( isset( $this->args['number'] ) ) {
				$number = $this->args['number'];
			} elseif ( ! empty( $this->post_id ) ) {
				$number = $this->get_card_meta( 'number', true );
			}

			if ( empty( $number ) ) {
				$number = $this->get_var( 'running_count' );
			}

			if ( true === wpex_validate_boolean( $args['prepend_zero'] ) ) {
				$number = sprintf( '%02d', $number );
			}

			/**
			 * Filters the card number display.
			 *
			 * @param string $number The number display.
			 * @param object $this Current WPEX_Card object.
			 */
			$number = (string) apply_filters( 'wpex_card_number', $number, $this, $args );

			if ( empty( $number ) ) {
				return;
			}

			$args['name']    = 'number';
			$args['content'] = $number;
			unset( $args['number'] );

			return $this->get_element( $args );
		}

		/**
		 * Get card count.
		 */
		public function get_running_count() {
			$running_count = get_query_var( 'wpex_loop_running_count' );

			// Applies running_count fix with paginated pages.
			// @todo apply to all is_main_query() queries and check using global $wp_query
			if ( is_search() && is_paged() && in_the_loop() ) {
				$paged = absint( get_query_var( 'paged' ) );
				if ( $paged > 1 ) {
					$posts_per_page = absint( get_query_var( 'posts_per_page' ) );
					if ( $posts_per_page ) {
						$running_count = absint( $running_count ) + $posts_per_page * ( $paged - 1 );
					}
				}
			}

			/**
			 * Filters the card running count.
			 *
			 * @param int $count The running count.
			 * @param object $this Current WPEX_Card object.
			 */
			$running_count = (int) apply_filters( 'wpex_card_running_count', $running_count, $this );

			$this->running_count = absint( $running_count );

			return $this->running_count;
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Rating ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get card rating.
		 */
		public function get_rating() {
			$rating = '';

			if ( ! empty( $this->args['rating'] ) ) {
				$rating = $this->args['rating'];
			} elseif ( $this->post_id ) {
				$rating = get_post_meta( $this->post_id, 'wpex_post_rating', true );
				if ( empty( $rating ) ) {
					$rating = $this->get_card_meta( 'rating', true );
				}
			}

			if ( empty( $rating ) && function_exists( 'wc_get_product' ) && 'product' === $this->get_post_type() ) {
				$product = wc_get_product( $this->post_id );
				if ( $product ) {
					$rating = $product->get_average_rating();
				}
			}

			/**
			 * Filters the card rating value.
			 *
			 * @param int $rating The rating.
			 * @param object $this Current WPEX_Card object.
			 */
			$rating = (int) apply_filters( 'wpex_card_rating', $rating, $this );

			$this->rating = floatval( $rating );

			return $this->rating;
		}

		/**
		 * Get card rating.
		 */
		public function get_star_rating( $args = [] ) {
			$default_args = [
				'link'   => false,
				'class'  => '',
			];

			/**
			 * Filters the card star rating element args.
			 *
			 * @param array $args The args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_star_rating_args', wp_parse_args( $args, $default_args ), $this );

			$star_rating = '';

			$rating = $this->get_rating();

			if ( $rating ) {
				$star_rating = wpex_get_star_rating( $rating );
			}

			/**
			 * Filters the card star rating element display.
			 *
			 * @param string $star_rating The star rating html.
			 * @param object $this Current WPEX_Card object.
			 */
			$star_rating = (string) apply_filters( 'wpex_card_star_rating', $star_rating, $this, $args );

			if ( empty( $star_rating ) ) {
				return;
			}

			$args['name']             = 'star-rating';
			$args['content']          = $star_rating;
			$args['sanitize_content'] = false; // already sanitized

			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Products ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Check if a product card is onsale.
		 */
		public function is_on_sale() {
			$check = false;

			if ( isset( $this->args['is_on_sale'] ) ) {
				$check = $this->args['is_on_sale'];
			} elseif ( $this->post_id ) {
				$post_type = get_post_type( $this->post_id );
				switch ( $post_type ) {
					case 'product':
						if ( class_exists( 'WooCommerce' ) ) {
							if ( function_exists( 'wc_get_product' ) ) {
								$product = wc_get_product( $this->post_id );
								if ( $product && $product->is_on_sale() ) {
									$check = true;
								}
							}
						}
						break;
				}
			}

			/**
			 * Filters whether the currently displayed post is on sale.
			 *
			 * @param bool $check Is it on sale?
			 * @param object $this Current WPEX_Card object.
			 */
			return (bool) apply_filters( 'wpex_card_is_on_sale', $check, $this );
		}

		/**
		 * Get card product price.
		 */
		public function get_price( $args = [] ) {
			$default_args = array(
				'link'  => false,
				'class' => '',
			);

			/**
			 * Filters the card price element arguments.
			 *
			 * @param array $args The args.
			 * @param object $this Current WPEX_Card object.
			 */
			$args = (array) apply_filters( 'wpex_card_price_args', wp_parse_args( $args, $default_args ), $this );

			$price = '';

			if ( ! empty( $this->args['price'] ) ) {
				$price = $this->args['price'];
			} elseif ( $this->post_id ) {

				$price = $this->get_card_meta( 'price', true );

				if ( empty( $price ) ) {

					$post_type = get_post_type( $this->post_id );

					switch ( $post_type ) {
						case 'product':
							if ( function_exists( 'wc_get_product' ) ) {
								$product = wc_get_product( $this->post_id );
								if ( $product ) {
									$price = $product->get_price_html();
								}
							}
							break;
						case 'download':
							if ( function_exists( 'edd_is_free_download' ) ) {
								if ( edd_is_free_download() ) {
									$price = esc_html__( 'Free', 'total' );
								} else {
									$price = edd_price( $this->post_id, false );
								}
							}
							break;
					}

				}

			}

			/**
			 * Filters the card price html.
			 *
			 * @param string $price The price html.
			 * @param object $this Current WPEX_Card object.
			 */
			$price = (string) apply_filters( 'wpex_card_price', $price, $this, $args );

			if ( empty( $price ) ) {
				return;
			}

			$args['name']             = 'price';
			$args['content']          = $price;
			$args['sanitize_content'] = false; // already sanitized

			return $this->get_element( $args );
		}

		/**
		 * Get card sale flash.
		 */
		public function get_sale_flash( $args = [] ) {
			if ( ! $this->is_on_sale() ) {
				return;
			}

			$default_args = [
				'link'  => false,
				'class' => '',
				'text'  => esc_html( 'Sale', 'total' ),
			];

			/**
			 * Filters the card sale flash args.
			 * 
			 * @param array $args The args.
			 * @param object $card The card object.
			 */
			$args = (array) apply_filters( 'wpex_card_sale_flash_args', wp_parse_args( $args, $default_args ), $this );

			/**
			 * Filters the card sale flash html.
			 *
			 * @param string $text The sale flash text.
			 * @param object $this Current WPEX_Card object.
			 */
			$sale_flash = (string) apply_filters( 'wpex_card_sale_flash', $args['text'] , $this, $args );

			if ( empty( $sale_flash ) ) {
				return;
			}

			$args['name']    = 'sale-flash';
			$args['content'] = $sale_flash;

			unset( $args['text'] );

			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Helpers ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Get unique_id.
		 */
		protected function get_unique_id() {
			if ( $this->post_id ) {
				$this->unique_id = $this->post_id;
			} elseif ( ! empty( $this->term->term_id ) ) {
				$this->unique_id = $this->term->term_id;
			} else {
				$this->unique_id = uniqid();
			}
			return $this->unique_id;
		}

		/**
		 * Check if the current card is a template.
		 * 
		 * @todo this should also check for 'template_id'?
		 */
		public function is_template(): bool {
			return str_starts_with( $this->style, 'template_' );
		}

		/**
		 * Get template ID.
		 */
		public function get_template_id(): int {
			$template_id = 0;

			if ( ! empty( $this->args['template_id'] ) ) {
				$template_id = absint( $this->args['template_id'] );
			} else {
				$template_id = absint( str_replace( 'template_', '', $this->style ) );
			}

			$template_id = wpex_parse_obj_id( $template_id, 'wpex_card' );
			$template_id = apply_filters_deprecated( 'wpex_get_template_id', [ $template_id, $this ], '5.10.1', 'wpex_card_template_id' );

			/**
			 * Filters the card template ID when displaying template (WPBakery/Gutenberg/Elementor) based cards.
			 *
			 * @param string $template_id
			 * @param object $this Current WPEX_Card object.
			 */
			$template_id = apply_filters( 'wpex_card_template_id', $template_id, $this );

			$this->template_id = $template_id;

			return (int) $this->template_id;
		}

		/**
		 * Check if card is featured.
		 */
		public function is_featured(): bool {
			return ( isset( $this->args['featured'] ) && true === $this->args['featured'] );
		}

		/**
		 * Check if card is even.
		 */
		public function is_even(): bool {
			return ( 0 === $this->get_var( 'running_count' ) % 2 );
		}

		/**
		 * Enqueue lightbox scripts.
		 */
		public function enqueue_lightbox(): void {
			static $already_enqueued = false;
			if ( ! $already_enqueued ) {
				$already_enqueued = true;
				if ( function_exists( 'wpex_enqueue_lightbox_scripts' ) ) {
					wpex_enqueue_lightbox_scripts();
				}
			}
		}

		/**
		 * Get card meta.
		 */
		public function get_card_meta( $key = '', $check_common = false ) {
			if ( $this->post_id && $key ) {
				$meta = get_post_meta( $this->post_id, "wpex_card_{$key}", true );
				if ( empty( $meta ) && $check_common ) {
					$meta = get_post_meta( $this->post_id, $key, true ); // check common named meta.
				}
				return $meta;
			}
		}

		/**
		 * Get template meta.
		 */
		protected function get_template_meta( string $key ) {
			return get_post_meta( $this->get_var( 'template_id' ), $key, true );
		}

		/**
		 * Get card breakpoint.
		 */
		public function get_breakpoint() {
			$breakpoint = 'md';

			if ( ! empty( $this->args['breakpoint'] ) && is_string( $this->args['breakpoint'] ) ) {
				$breakpoint = sanitize_text_field( $this->args['breakpoint'] );
			}

			/**
			 * Filters the card breakpoint.
			 *
			 * @param string $breakpoint
			 * @param object $this Current WPEX_Card object.
			 */
			$breakpoint = (string) apply_filters( 'wpex_card_breakpoint', $breakpoint, $this );

			if ( 'false' === $breakpoint || false === $breakpoint ) {
				return '';
			}

			return esc_attr( $breakpoint );
		}

		/**
		 * Get card post type.
		 */
		public function get_post_type() {
			if ( $this->post_id ) {
				return get_post_type( $this->post_id );
			}
		}

		/**
		 * Get card post format.
		 */
		public function get_post_format() {
			if ( $this->post_id ) {

				/**
				 * Filters the card post format.
				 *
				 * @param string|boolean $format
				 * @param object $this WPEX_Card
				 */
				$format = apply_filters( 'wpex_card_post_format', get_post_format( $this->post_id ), $this );

				return $format;
			}
		}

		/**
		 * Get card object variable.
		 */
		public function get_var( $var, $args = '' ) {
			if ( isset( $this->$var ) ) {
				return $this->$var;
			}

			$method_name = "get_{$var}";

			if ( method_exists( $this, $method_name ) ) {
				if ( $args ) {
					return $this->$method_name( $args );
				}
				return $this->$method_name();
			}
		}

		/**
		 * Get card custom field.
		 */
		public function get_custom_field( $args = [] ) {
			if ( empty( $args['key'] ) || ! $this->post_id ) {
				return;
			}

			$args['content'] = get_post_meta( $this->post_id, $args['key'], true );

			unset( $args['key'] );

			return $this->get_element( $args );
		}

		/**
		 * Check if card flex direction is reversed.
		 */
		public function has_flex_direction_reverse() {
			$check = false;

			if ( ! empty( $this->args['alternate_flex_direction'] )
				&& wpex_validate_boolean( $this->args['alternate_flex_direction'] )
				&& $this->is_even()
			) {
				$check = true;
			}

			return $check;
		}

		/**
		 * Check if a product card is onsale.
		 */
		public function is_event(): bool {
			return in_array( $this->get_post_type(), [ 'just_event', 'tribe_events' ] );
		}

		/**
		 * Check if it's a mock card.
		 */
		public function is_mock(): bool {
			if ( null === $this->is_mock ) {
				$this->is_mock = isset( $this->post_id ) && \in_array( $this->post_id, [ 'mock', 'dummy', 'fake' ], true );
			}
			return $this->is_mock;
		}

		/**
		 * Create mock post.
		 */
		private function create_mock_post(): void {
			$this->post_id        = -99; // Negative ID, ensuring no clash with a real post
			$post                 = new stdClass();
			$post->ID             = $this->post_id;
			$post->post_author    = 1; // Mock cards should never be displayed on the site this is fine
			$post->post_date      = current_time( 'mysql' );
			$post->post_date_gmt  = current_time( 'mysql', 1 );
			$post->post_title     = 'Example Post Title';
			$post->post_content   = 'Example post content.';
			$post->post_status    = 'publish';
			$post->comment_status = 'closed';
			$post->ping_status    = 'closed';
			$post->post_name      = 'dummy-post-' . rand( 100000, 999999 ); // Unique post name to avoid clashes
			$post->post_type      = 'post';
			$post->filter         = 'raw'; // Important to avoid filters like wpautop

			// Create the WP_Post object
			$dummy_post = new WP_Post( $post );

			// Add the mock post to the cache
			wp_cache_add( $this->post_id, $dummy_post, 'posts' );
		}

		/**
		 * Delete mock post.
		 */
		private function delete_mock_post(): void {
			wp_cache_delete( $this->post_id, 'posts' );
		}

		/**
		 * Returns a mock thumbnail.
		 * 
		 * Currently this method is used just to render the image overlay
		 * as a way to enqueue scripts.
		 */
		private function get_mock_thumbnail( $args = [] ): string {
			$args['name']             = 'thumbnail';
			$args['content']          = 'mock thumbnail';
			$args['sanitize_content'] = false;
			if ( empty( $args['overlay'] ) ) {
				$args['overlay'] = $this->get_var( 'thumbnail_overlay_style' );
			}
			return $this->get_element( $args );
		}

		/*-------------------------------------------------------------------------------*/
		/* [ Public Static Methods ]
		/*-------------------------------------------------------------------------------*/

		/**
		 * Returns link types.
		 */
		public static function get_link_types() {
			$link_types = [
				''         => esc_html__( 'Go to Item', 'total' ),
			//	'post'     => esc_html__( 'Link to Post', 'total' ), // @deprecated in 6.1 as it's the default no need to save.
				'lightbox' => esc_html__( 'Lightbox', 'total' ),
				'dialog'   => esc_html__( 'Modal (Browser)', 'total' ),
				'modal'    => esc_html__( 'Modal (Lightbox)', 'total' ),
				'none'     => esc_html__( 'None', 'total' ),
			];

			/**
			 * Filters the card link types that are allowed.
			 * 
			 * @param array $types The link types.
			 * @param object $card The card object.
			 */
			return (array) apply_filters( 'wpex_card_link_types', $link_types );
		}

		/**
		 * Define and return array of all card styles.
		 */
		public static function get_card_styles(): array {
			if ( null === self::$card_styles ) {
				self::$card_styles = (array) apply_filters( 'wpex_card_styles', self::get_default_card_styles() );
			}
			return self::$card_styles;
		}

		/**
		 * Returns default card styles.
		 */
		public static function get_default_card_styles(): array {
			$enabled = (bool) apply_filters( 'wpex_has_default_card_styles', true );

			if ( ! $enabled ) {
				return [];
			}

			$styles = [
				'button' => [
					'name' => esc_html__( 'Button', 'total' ),
				],
				'link' => [
					'name' => esc_html__( 'Simple Link', 'total' ),
				],
			];
		
			$card_categories = [
				'title' => [
					'name' => esc_html__( 'Title', 'total' ),
				],
				'post-content' => [
					'name' => esc_html__( 'Post Content', 'total' ),
				],
				'image' => [
					'name' => esc_html__( 'Image', 'total' ),
				],
				'image_cover' => [
					'name' => esc_html__( 'Image Cover', 'total' ),
				],
				'video' => [
					'name' => esc_html__( 'Video', 'total' ),
				],
				'toggle' => [
					'name' => esc_html__( 'Toggle', 'total' ),
					'count' => 2,
				],
				'simple' => [
					'name' => esc_html__( 'Simple', 'total' ),
					'count' => 9,
				],
				'news' => [
					'name' => esc_html__( 'News', 'total' ),
					'count' => 6,
				],
				'blog' => [
					'name' => esc_html__( 'Blog', 'total' ),
					'count' => 22,
				],
				'blog-list' => [
					'name' => esc_html__( 'Blog List', 'total' ),
					'count' => 22,
				],
				'magazine' => [
					'name' => esc_html__( 'Magazine', 'total' ),
					'count' => 2,
				],
				'magazine-list' => [
					'name' => esc_html__( 'Magazine List', 'total' ),
					'count' => 2,
				],
				'numbered-list' => [
					'name' => esc_html__( 'Numbered List', 'total' ),
					'count' => 6,
				],
				'overlay' => [
					'name' => esc_html__( 'Overlay', 'total' ),
					'count' => 14,
				],
				'testimonial' => [
					'name' => esc_html__( 'Testimonial', 'total' ),
					'count' => 9,
				],
				'staff' => [
					'name' => esc_html__( 'Staff', 'total' ),
					'count' => 8,
				],
				'portfolio' => [
					'name' => esc_html__( 'Portfolio', 'total' ),
					'count' => 6,
				],
				'product' => [
					'name' => esc_html__( 'Product', 'total' ),
					'count' => 4,
				],
				'search' => [
					'name' => esc_html__( 'Search', 'total' ),
					'count' => 6,
				],
				'icon-box' => [
					'name' => esc_html__( 'Icon Box', 'total' ),
					'count' => 6,
				],
			];
		
			if ( totaltheme_is_integration_active( 'just_events' ) ) {
				$card_categories['just-events'] = [
					'name' => esc_html__( 'Just Events', 'total' ),
					'count' => 2,
				];
			}

			if ( totaltheme_is_integration_active( 'tribe_events' ) ) {
				$card_categories['tribe-events'] = [
					'name' => esc_html__( 'Tribe Events', 'total' ),
					'count' => 2,
				];
			}
		
			foreach ( $card_categories as $key => $val ) {
				if ( 'image_cover' === $key ) {
					$styles[ $key ] = [
						'name' => $val['name'],
					];
				} else {
					$count = $val['count'] ?? 1;
					$x = 1;
					while ( $x <= $count ) {
						if ( 1 === $count ) {
							$name = $val['name'];
						} else {
							$name = "{$val['name']} {$x}";
						}
						$styles["{$key}_{$x}"] = [
							'name' => $name,
						];
						$x++;
					}
				}
			}

			if ( totaltheme_is_integration_active( 'woocommerce' ) ) {
				$styles['woocommerce'] = [
					'name' => 'WooCommerce',
				];
			}

			return $styles;
		}

		/**
		 * Returns array of groupd card style choices for use with select fields.
		 * 
		 * @note we use "options" and not "choices" in the naming convention to support Elementor.
		 */
		public static function get_grouped_card_style_options(): array {
			$options = [
				'' => esc_html__( 'None', 'total' ),
			];
			foreach ( self::get_card_styles() as $key => $value ) {
				if ( ! empty( $value['name'] ) ) {
					$group_name = $value['group'] ?? esc_html__( 'Default Cards', 'total' );
					$group_key  = sanitize_key( $group_name );
					if ( ! isset( $options[ $group_key ] ) ) {
						$options[ $group_key ] = [
							'label'   => $group_name,
							'options' => [],
						];
					}
					$options[ $group_key ]['options'][ $key ] = $value['name'];
				}
			}
			return $options;
		}

		/**
		 * Returns array of all card style options for use with simple select fields that don't support option groups.
		 */
		public static function get_card_style_options(): array {
			$options = [
				'' => esc_html__( 'None', 'total' ),
			];
			foreach ( self::get_card_styles() as $key => $value ) {
				if ( ! empty( $value['name'] ) ) {
					$options[ $key ] = $value['name'];
				}
			}
			return $options;
		}

	}

}
