<?php
/**
 * Output the Schema.org markup in JSON-LD format.
 *
 * @since      0.9.0
 * @package    RankMath
 * @subpackage RankMath\Schema
 * @author     Rank Math <support@rankmath.com>
 */

namespace RankMath\Schema;

use RankMath\Helper;
use RankMath\Helpers\Url;
use RankMath\Helpers\Str;
use RankMath\Helpers\Arr;
use RankMath\Paper\Paper;
use RankMath\Traits\Hooker;

defined( 'ABSPATH' ) || exit;

/**
 * JsonLD class.
 */
class JsonLD {

	use Hooker;

	/**
	 * Hold post object.
	 *
	 * @var WP_Post
	 */
	public $post = null;

	/**
	 * Hold post ID.
	 *
	 * @var ID
	 */
	public $post_id = 0;

	/**
	 * Hold post parts.
	 *
	 * @var array
	 */
	public $parts = [];

	/**
	 * The Constructor.
	 */
	public function setup() {
		$this->action( 'rank_math/head', 'json_ld', 90 );
		$this->action( 'rank_math/json_ld', 'add_context_data' );
		$this->action( 'rank_math/json_ld/preview', 'generate_preview' );
		new Block_Parser();
		new Frontend();
	}

	/**
	 * Get Schema Preview. Used in the Code Validation in Pro.
	 */
	public function generate_preview() {
		global $post;

		if ( is_singular() ) {
			$this->post    = $post;
			$this->post_id = $post->ID;
			$this->get_parts();
		}

		$data = $this->do_filter( 'json_ld', [], $this );
		unset( $data['BreadcrumbList'] );

		// Preview schema.
		$schema    = \json_decode( file_get_contents( 'php://input' ), true );
		$schema_id = $schema['schemaID'];
		if ( isset( $data[ $schema_id ] ) ) {
			$current_data = $data[ $schema_id ];
			unset( $data[ $schema_id ] );
		} else {
			$current_data = array_pop( $data );
		}
		unset( $schema['schemaID'] );

		$schema = $this->replace_variables( $schema );
		$schema = $this->filter( $schema, $this, $data );
		$schema = wp_parse_args( $schema['schema'], $current_data );
		if ( ! empty( $schema['@type'] ) && in_array( $schema['@type'], [ 'WooCommerceProduct', 'EDDProduct' ], true ) ) {
			$schema['@type'] = ! empty( $schema['hasVariant'] ) ? 'ProductGroup' : 'Product';
		}

		// Merge.
		$data = array_merge( $data, [ $schema_id => $schema ] );

		/**
		 * Filter to change the Code validation data..
		 *
		 * @param array $unsigned An array of data to output in JSON-LD.
		 */
		$data = $this->do_filter( 'schema/preview/validate', $this->do_filter( 'schema/validated_data', $this->validate_schema( $data ) ) );

		echo wp_json_encode( array_values( $data ) );
	}

	/**
	 * Function to get Old schema data. This code is used in the Schema_Converter to convert old schema data.
	 *
	 * @param  int   $post_id       Post id for conversion.
	 * @param  mixed $snippet_class Class instance of snippet.
	 * @return array
	 */
	public function get_old_schema( $post_id, $snippet_class ) {
		global $post;
		$post          = get_post( $post_id ); // phpcs:ignore
		$this->post    = $post;
		$this->post_id = $post_id;
		setup_postdata( $post );
		$this->get_parts();

		/**
		 * Collect data to output in JSON-LD.
		 *
		 * @param array  $unsigned An array of data to output in json-ld.
		 * @param JsonLD $unsigned JsonLD instance.
		 */
		return $snippet_class->process( [], $this );
	}

	/**
	 * Output the ld+json tag with the generated schema data.
	 */
	public function json_ld() {
		global $post;

		if ( is_singular() ) {
			$this->post    = $post;
			$this->post_id = $post->ID;
			$this->get_parts();
		}

		/**
		 * Filter to collect data to output in JSON-LD.
		 *
		 * @param array  $unsigned An array of data to output in JSON-LD.
		 * @param JsonLD $unsigned JsonLD instance.
		 */
		$data = $this->do_filter( 'json_ld', [], $this );
		$data = $this->do_filter( 'schema/validated_data', $this->validate_schema( $data ) );
		if ( is_array( $data ) && ! empty( $data ) ) {

			$class = defined( 'RANK_MATH_PRO_FILE' ) ? 'schema-pro' : 'schema';
			$json  = [
				'@context' => 'https://schema.org',
				'@graph'   => array_values( $data ),
			];

			$options = defined( 'RANKMATH_DEBUG' ) && RANKMATH_DEBUG ? JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES : JSON_UNESCAPED_SLASHES;

			echo '<script type="application/ld+json" class="rank-math-' . esc_attr( $class ) . '">' . wp_json_encode( wp_kses_post_deep( $json ), $options ) . '</script>' . "\n";
		}
	}

	/**
	 * Validate schema data. Removes invalid and empty values from the schema data.
	 *
	 * @param array $data Array of JSON-LD data.
	 *
	 * @return array
	 */
	private function validate_schema( $data ) {
		if ( ! is_array( $data ) || empty( $data ) ) {
			return $data;
		}

		foreach ( $data as $id => $value ) {
			if ( is_array( $value ) ) {
				// Remove aline @type.
				if ( isset( $value['@type'] ) && 1 === count( $value ) ) {
					unset( $data[ $id ] );
					continue;
				}

				// Remove empty review.
				if ( 'review' === $id && isset( $value['@type'] ) ) {
					if ( ! isset( $value['reviewRating'] ) || ! isset( $value['reviewRating']['ratingValue'] ) ) {
						unset( $data[ $id ] );
						continue;
					}
				}

				// Recursive.
				$data[ $id ] = $this->validate_schema( $value );
			}

			// Remove empty values.
			// Remove need of array_filter as this will go recursive.
			if ( '' === $value ) {
				unset( $data[ $id ] );
				continue;
			}
		}

		return $data;
	}

	/**
	 * Get Default Schema Data.
	 *
	 * @param array $data Array of JSON-LD data.
	 *
	 * @return array
	 */
	public function add_schema( $data ) {
		global $post;

		$schema = DB::get_schemas( $post->ID );

		return array_merge( $data, $schema );
	}

	/**
	 * Get Default Schema Data.
	 *
	 * @param array $data Array of json-ld data.
	 *
	 * @return array
	 */
	public function add_context_data( $data ) {
		$is_product_archive = $this->is_product_archive_page();
		$can_add_global     = $this->can_add_global_entities( $data, $is_product_archive );
		$snippets           = [
			'\\RankMath\\Schema\\Publisher'     => ! isset( $data['publisher'] ) && $can_add_global,
			'\\RankMath\\Schema\\Website'       => $can_add_global,
			'\\RankMath\\Schema\\PrimaryImage'  => is_singular() && ! post_password_required() && $can_add_global,
			'\\RankMath\\Schema\\Breadcrumbs'   => $this->can_add_breadcrumb(),
			'\\RankMath\\Schema\\Webpage'       => $can_add_global,
			'\\RankMath\\Schema\\Author'        => is_author() || ( is_singular() && $can_add_global ),
			'\\RankMath\\Schema\\Products_Page' => $is_product_archive,
			'\\RankMath\\Schema\\Singular'      => ! post_password_required() && is_singular(),
		];

		foreach ( $snippets as $class => $can_run ) {
			if ( $can_run ) {
				$class = new $class();
				$data  = $class->process( $data, $this );
			}
		}

		return $data;
	}

	/**
	 * Function to replace variables used in Schema fields.
	 *
	 * @param array  $schemas        Schema to replace.
	 * @param object $current_object Current Object.
	 * @param array  $data           Array of json-ld data.
	 *
	 * @return array
	 */
	public function replace_variables( $schemas, $current_object = [], $data = [] ) {
		$new_schemas    = [];
		$current_object = empty( $current_object ) ? get_queried_object() : $current_object;
		foreach ( $schemas as $key => $schema ) {
			if ( 'metadata' === $key ) {
				$new_schemas['isPrimary'] = ! empty( $schema['isPrimary'] );

				if ( ! empty( $schema['type'] ) && 'custom' === $schema['type'] ) {
					$new_schemas['isCustom'] = true;
				}

				continue;
			}

			$this->replace_author( $schema, $data );
			if ( is_array( $schema ) ) {
				$new_schemas[ $key ] = $this->replace_variables( $schema, $current_object, $data );
				continue;
			}

			// Need this conditions to convert date to valid ISO 8601 format.
			if ( in_array( $key, [ 'datePublished', 'uploadDate' ], true ) && '%date(Y-m-dTH:i:sP)%' === $schema ) {
				$schema = '%date(Y-m-d\TH:i:sP)%';
			}
			if ( 'dateModified' === $key && '%modified(Y-m-dTH:i:sP)%' === $schema ) {
				$schema = '%modified(Y-m-d\TH:i:sP)%';
			}

			$new_schemas[ $key ] = is_string( $schema ) && Str::contains( '%', $schema ) && ! filter_var( $schema, FILTER_VALIDATE_URL )
				? Helper::replace_vars( $schema, $current_object ) : $schema;
			if ( '' === $new_schemas[ $key ] ) {
				unset( $new_schemas[ $key ] );
			}
		}

		return $new_schemas;
	}

	/**
	 * Function to replace %author% with Author entity @id.
	 *
	 * @param array $schema Schema to replace.
	 * @param array $data   Array of json-ld data.
	 *
	 * @return void
	 */
	private function replace_author( &$schema, $data ) {
		if ( empty( $data['ProfilePage'] ) ) {
			return;
		}

		if ( empty( $schema['author'] ) || ! isset( $schema['author']['name'] ) || ! in_array( $schema['author']['name'], [ '%name%', '%post_author%' ], true ) ) {
			return;
		}

		$schema['author'] = [
			'@id'  => $data['ProfilePage']['@id'],
			'name' => get_the_author(),
		];
	}

	/**
	 * Filter schema data before adding it to ld+json.
	 *
	 * @param array  $schemas Schema to replace.
	 * @param JsonLD $jsonld  Instance of jsonld.
	 * @param array  $data   Array of json-ld data.
	 *
	 * @return array
	 */
	public function filter( $schemas, $jsonld, $data ) {
		$new_schemas = [];

		foreach ( $schemas as $key => $schema ) {
			$type = is_array( $schema['@type'] ) ? $schema['@type'][0] : $schema['@type'];
			$type = strtolower( $type );
			$type = in_array( $type, [ 'musicgroup', 'musicalbum' ], true )
				? 'music'
				: ( in_array( $type, [ 'blogposting', 'newsarticle' ], true ) ? 'article' : $type );
			$type = Str::contains( 'event', $type ) ? 'event' : $type;
			$hook = 'snippet/rich_snippet_' . $type;

			/**
			 * Short-circuit if 3rd party is interested generating his own data.
			 */
			$pre = $this->do_filter( $hook, false, $jsonld->parts, $data );
			if ( false !== $pre ) {
				$new_schemas[ $key ] = $this->do_filter( $hook . '_entity', $pre );
				$new_schemas[ $key ] = $this->do_filter( 'snippet/rich_snippet_entity', $new_schemas[ $key ] );
				continue;
			}

			$new_schemas[ $key ] = $this->do_filter( $hook . '_entity', $schema );
			$new_schemas[ $key ] = $this->do_filter( 'snippet/rich_snippet_entity', $new_schemas[ $key ] );
		}

		return $new_schemas;
	}

	/**
	 * Whether to add global schema entities.
	 *
	 * @param array $data               Array of json-ld data.
	 * @param bool  $is_product_archive Whether the current page is a Product archive.
	 * @return bool
	 */
	public function can_add_global_entities( $data = [], $is_product_archive = false ) {
		if ( ! $is_product_archive && ( is_category() || is_tag() || is_tax() ) ) {
			$object = get_queried_object();
			return $object && ! Helper::get_settings( 'titles.remove_' . $object->taxonomy . '_snippet_data' ) && ! $this->do_filter( 'snippet/remove_taxonomy_data', false, $object->taxonomy );
		}

		if ( is_front_page() || ! is_singular() || ! Helper::can_use_default_schema( $this->post_id ) || ! empty( $data ) ) {
			return true;
		}

		$schemas = DB::get_schemas( $this->post_id );
		if ( ! empty( $schemas ) ) {
			return true;
		}

		/**
		 * Allow developer to remove global schema entities.
		 *
		 * @param bool   $can_add
		 * @param JsonLD $unsigned JsonLD instance.
		 */
		return $this->do_filter( 'schema/add_global_entities', Helper::get_default_schema_type( $this->post_id, true ), $this );
	}

	/**
	 * Can add breadcrumb schema.
	 *
	 * @return bool
	 */
	private function can_add_breadcrumb() {
		/**
		 * Allow developer to disable the breadcrumb JSON-LD output.
		 *
		 * @param bool $unsigned Default: true
		 */
		return ! is_front_page() && Helper::is_breadcrumbs_enabled() && $this->do_filter( 'json_ld/breadcrumbs_enabled', true );
	}

	/**
	 * Check if current page is a WooCommerce archive page.
	 *
	 * @return bool
	 */
	private function is_product_archive_page() {
		return Helper::is_woocommerce_active() && ( ( is_tax() && in_array( get_query_var( 'taxonomy' ), get_object_taxonomies( 'product' ), true ) ) || is_shop() );
	}

	/**
	 * Add property to entity.
	 *
	 * @param string $prop   Name of the property to add into entity.
	 * @param array  $entity Array of json-ld entity.
	 * @param string $key    Property key to add into entity.
	 * @param array  $data   Array of json-ld data.
	 */
	public function add_prop( $prop, &$entity, $key = '', $data = [] ) {
		if ( empty( $prop ) ) {
			return;
		}

		$hash = [
			'email' => [ 'titles.email', 'email' ],
			'phone' => [ 'titles.phone', 'telephone' ],
		];

		if ( isset( $hash[ $prop ] ) && $value = Helper::get_settings( $hash[ $prop ][0] ) ) { // phpcs:ignore
			$entity[ $hash[ $prop ][1] ] = $value;
			return;
		}

		$perform = "add_prop_{$prop}";
		if ( method_exists( $this, $perform ) ) {
			$this->$perform( $entity, $key, $data );
		}
	}

	/**
	 * Add logo property to the entity.
	 *
	 * @param array $entity Array of JSON-LD entity.
	 */
	private function add_prop_image( &$entity ) {
		$logo = Helper::get_settings( 'titles.knowledgegraph_logo' );
		if ( ! $logo ) {
			$logo_id = \get_option( 'site_logo' );
			$logo    = $logo_id ? wp_get_attachment_image_url( $logo_id ) : '';
		}

		if ( ! $logo ) {
			return;
		}

		$entity['logo'] = [
			'@type'      => 'ImageObject',
			'@id'        => home_url( '/#logo' ),
			'url'        => $logo,
			'contentUrl' => $logo,
			'caption'    => $this->get_website_name(),
		];
		$this->add_prop_language( $entity['logo'] );

		$attachment = wp_get_attachment_metadata( Helper::get_settings( 'titles.knowledgegraph_logo_id' ), true );
		if ( ! $attachment ) {
			return;
		}

		$entity['logo']['width']  = $attachment['width'];
		$entity['logo']['height'] = $attachment['height'];
	}

	/**
	 * Add Language property to the entity.
	 *
	 * @param array $entity Array of JSON-LD entity.
	 */
	private function add_prop_language( &$entity ) {
		$entity['inLanguage'] = $this->do_filter( 'schema/language', get_bloginfo( 'language' ) );
	}

	/**
	 * Add Image property to entity.
	 *
	 * @param  array  $entity Array of json-ld entity.
	 * @param  string $key   Entity Key.
	 * @param  array  $data  Schema Data.
	 */
	private function add_prop_thumbnail( &$entity, $key, $data ) {
		if ( ! empty( $data['primaryImage'] ) ) {
			$entity[ $key ] = [ '@id' => $data['primaryImage']['@id'] ];
		}
	}

	/**
	 * Add isPartOf property to entity.
	 *
	 * @param array  $entity Array of json-ld entity.
	 * @param string $key    Entity Key.
	 */
	private function add_prop_is_part_of( &$entity, $key ) {
		$hash = [
			'website' => home_url( '/#website' ),
			'webpage' => Paper::get()->get_canonical() . '#webpage',
		];

		if ( ! empty( $hash[ $key ] ) ) {
			$entity['isPartOf'] = [ '@id' => $hash[ $key ] ];
		}
	}

	/**
	 * Add publisher property to entity
	 *
	 * @param array  $entity Entity.
	 * @param string $key    Entity Key.
	 * @param  array  $data  Schema Data.
	 */
	public function add_prop_publisher( &$entity, $key, $data ) {
		if ( empty( $data['publisher'] ) ) {
			return;
		}

		$entity[ $key ] = [ '@id' => $data['publisher']['@id'] ];
	}

	/**
	 * Add url property to entity.
	 *
	 * @param array $entity Array of JSON-LD entity.
	 */
	private function add_prop_url( &$entity ) {
		if ( $url = Helper::get_settings( 'titles.url' ) ) { // phpcs:ignore
			$entity['url'] = ! Url::is_relative( $url ) ? $url : 'http://' . $url;
		}
	}

	/**
	 * Add address property to entity.
	 *
	 * @param array $entity Array of JSON-LD entity.
	 */
	private function add_prop_address( &$entity ) {
		if ( $address = Helper::get_settings( 'titles.local_address' ) ) { // phpcs:ignore
			$entity['address'] = [ '@type' => 'PostalAddress' ] + $address;
		}
	}

	/**
	 * Add aggregateratings to entity.
	 *
	 * @param string $schema Schema to get data for.
	 * @param array  $entity Array of JSON-LD entity to attach data to.
	 */
	public function add_ratings( $schema, &$entity ) {
		$rating = Helper::get_post_meta( "snippet_{$schema}_rating" );

		// Early Bail!
		if ( ! $rating ) {
			return;
		}

		$entity['review'] = [
			'author'        => [
				'@type' => 'Person',
				'name'  => get_the_author_meta( 'display_name' ),
			],
			'datePublished' => get_post_time( 'Y-m-d\TH:i:sP', false ),
			'dateModified'  => get_post_modified_time( 'Y-m-d\TH:i:sP', false ),
			'reviewRating'  => [
				'@type'       => 'Rating',
				'ratingValue' => $rating,
				'bestRating'  => Helper::get_post_meta( "snippet_{$schema}_rating_max" ) ? Helper::get_post_meta( "snippet_{$schema}_rating_max" ) : 5,
				'worstRating' => Helper::get_post_meta( "snippet_{$schema}_rating_min" ) ? Helper::get_post_meta( "snippet_{$schema}_rating_min" ) : 1,
			],
		];
	}

	/**
	 * Get website name with a fallback to bloginfo( 'name' ).
	 *
	 * @return string
	 */
	public function get_website_name() {
		return Helper::get_settings( 'titles.website_name', $this->get_organization_name() );
	}

	/**
	 * Get website name with a fallback to bloginfo( 'name' ).
	 *
	 * @return string
	 */
	public function get_organization_name() {
		$name = Helper::get_settings( 'titles.knowledgegraph_name' );
		return $name ? $name : get_bloginfo( 'name' );
	}

	/**
	 * Set publisher/provider data for JSON-LD.
	 *
	 * @param array  $entity Array of JSON-LD entity.
	 * @param array  $organization Organization data.
	 * @param string $type         Type data set to. Default: 'publisher'.
	 */
	public function set_publisher( &$entity, $organization, $type = 'publisher' ) {
		$keys = [ '@context', '@type', 'url', 'name', 'logo', 'image', 'contactPoint', 'sameAs' ];
		foreach ( $keys as $key ) {
			if ( ! isset( $organization[ $key ] ) ) {
				continue;
			}

			$entity[ $type ][ $key ] = 'logo' !== $key ? $organization[ $key ] : [
				'@type' => 'ImageObject',
				'url'   => $organization[ $key ],
			];
		}
	}

	/**
	 * Set address for JSON-LD.
	 *
	 * @param string $schema Schema to get data for.
	 * @param array  $entity Array of JSON-LD entity to attach data to.
	 */
	public function set_address( $schema, &$entity ) {
		$address = Helper::get_post_meta( "snippet_{$schema}_address" );

		// Early Bail!
		if ( ! is_array( $address ) || empty( $address ) ) {
			return;
		}

		$entity['address'] = [ '@type' => 'PostalAddress' ];
		foreach ( $address as $key => $value ) {
			$entity['address'][ $key ] = $value;
		}
	}

	/**
	 * Set data to entity.
	 *
	 * Loop through post meta value grab data and attache it to the entity.
	 *
	 * @param array $hash   Key to get data and Value to save as.
	 * @param array $entity Array of JSON-LD entity to attach data to.
	 */
	public function set_data( $hash, &$entity ) {
		foreach ( $hash as $metakey => $dest ) {
			$entity[ $dest ] = Helper::get_post_meta( $metakey, $this->post_id );
		}
	}

	/**
	 * Get post title.
	 *
	 * Retrieves the title in this order.
	 *  1. Custom post meta set in rich snippet
	 *  2. Headline template set in Titles & Meta
	 *
	 * @param int $post_id Post ID to get title for.
	 *
	 * @return string
	 */
	public function get_post_title( $post_id = 0 ) {
		$title = Helper::get_post_meta( 'snippet_name', $post_id );

		if ( ! $title && ! empty( $this->post ) ) {
			$title = Helper::replace_vars( Helper::get_settings( "titles.pt_{$this->post->post_type}_default_snippet_name", '%seo_title%' ), $this->post );
		}

		$title = $title ? $title : Paper::get()->get_title();

		return Str::truncate( $title );
	}

	/**
	 * Get post url.
	 *
	 * @param  int $post_id Post ID to get URL for.
	 * @return string
	 */
	public function get_post_url( $post_id = 0 ) {
		$url = Helper::get_post_meta( 'snippet_url', $post_id );

		return $url ? $url : ( 0 === $post_id ? Paper::get()->get_canonical() : get_the_permalink( $post_id ) );
	}

	/**
	 * Get product description.
	 *
	 * @param  object $product Product Object.
	 * @return string
	 */
	public function get_product_desc( $product = [] ) {
		if ( empty( $product ) ) {
			return;
		}

		if ( $description = Helper::get_post_meta( 'description', $product->get_id() ) ) { //phpcs:ignore
			return $description;
		}

		$product_object = get_post( $product->get_id() );
		$description    = Paper::get_from_options( 'pt_product_description', $product_object, '%excerpt%' );

		if ( ! $description ) {
			$description = $product->get_short_description() ? $product->get_short_description() : $product->get_description();
		}

		$description = $this->do_filter( 'product_description/apply_shortcode', false ) ? do_shortcode( $description ) : Helper::strip_shortcodes( $description );
		return wp_strip_all_tags( $description, true );
	}

	/**
	 * Get product title.
	 *
	 * @param  object $product Product Object.
	 * @return string
	 */
	public function get_product_title( $product = [] ) {
		if ( empty( $product ) ) {
			return '';
		}

		if ( $title = Helper::get_post_meta( 'title', $product->get_id() ) ) { //phpcs:ignore
			return $title;
		}

		$product_object = get_post( $product->get_id() );

		$title = Paper::get_from_options( 'pt_product_title', $product_object, '%title% %sep% %sitename%' );
		return $title ? $title : $product->get_name();
	}

	/**
	 * Get post parts.
	 */
	private function get_parts() {
		$parts = [
			'title'     => $this->get_post_title(),
			'url'       => $this->get_post_url(),
			'canonical' => Paper::get()->get_canonical(),
			'modified'  => mysql2date( DATE_W3C, $this->post->post_modified, false ),
			'published' => mysql2date( DATE_W3C, $this->post->post_date, false ),
			'excerpt'   => Helper::replace_vars( '%excerpt%', $this->post ),
		];

		// Description.
		$desc = Helper::get_post_meta( 'snippet_desc' );

		if ( ! $desc ) {
			$desc = Helper::replace_vars( Helper::get_settings( "titles.pt_{$this->post->post_type}_default_snippet_desc" ), $this->post );
		}
		$parts['desc'] = $desc ? $desc : ( Helper::get_post_meta( 'description' ) ? Helper::get_post_meta( 'description' ) : $parts['excerpt'] );

		// Author.
		$author          = Helper::get_post_meta( 'snippet_author' );
		$parts['author'] = $author ? $author : get_the_author_meta( 'display_name', $this->post->post_author );

		// Modified date cannot be before publish date.
		if ( strtotime( $this->post->post_modified ) < strtotime( $this->post->post_date ) ) {
			$parts['modified'] = $parts['published'];
		}

		$this->parts = $parts;
	}

	/**
	 * Get global social profile URLs, to use in the `sameAs` property.
	 *
	 * @link https://developers.google.com/webmasters/structured-data/customize/social-profiles
	 */
	public function get_social_profiles() {
		$profiles = [ Helper::get_settings( 'titles.social_url_facebook' ) ];

		$twitter = Helper::get_settings( 'titles.twitter_author_names' );
		if ( $twitter ) {
			$profiles[] = "https://twitter.com/$twitter";
		}

		$addional_profiles = Helper::get_settings( 'titles.social_additional_profiles' );
		if ( ! empty( $addional_profiles ) ) {
			$profiles = array_merge( $profiles, Arr::from_string( $addional_profiles, "\n" ) );
		}

		return array_values( array_filter( $profiles ) );
	}
}
