<?php
/**
 * The Site model.
 *
 * @package WP_Ultimo
 * @subpackage Models
 * @since 2.0.0
 */

namespace WP_Ultimo\Models;

use WP_Ultimo\Dependencies\Psr\Log\LogLevel;
use \WP_Ultimo\Models\Base_Model;
use \WP_Ultimo\Objects\Limitations;
use \WP_Ultimo\Database\Sites\Site_Type;
use \WP_Ultimo\UI\Template_Previewer;

// Exit if accessed directly
defined('ABSPATH') || exit;

/**
 * Site model class. Implements the Base Model.
 *
 * @since 2.0.0
 */
class Site extends Base_Model {

	use Traits\Limitable, \WP_Ultimo\Traits\WP_Ultimo_Site_Deprecated, Traits\Notable;

	/**  DEFAULT WP_SITE COLUMNS */

	/**
	 * Title of the site.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	protected $title;

	/**
	 * The site description.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	protected $description;

	/**
	 * Blog ID. Should be accessed via id.
	 *
	 * @since 2.0.0
	 * @var int
	 */
	protected $blog_id;

	/**
	 * Network ID for this site.
	 *
	 * @since 2.0.0
	 * @var int
	 */
	protected $site_id = 1;

	/**
	 * Domain name used by this site.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	protected $domain;

	/**
	 * Path of the site. Used when in sub-directory mode.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	protected $path;

	/**
	 * Alias for WP CLI support.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	protected $site_path;

	/**
	 * Date when the site was registered.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	protected $registered;

	/**
	 * Date of the last update on this site.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	protected $last_updated;

	/**
	 * If the site is being published.
	 *
	 * @since 2.0.11
	 * @var string
	 */
	protected $is_publishing;

	/**
	 * Is this a active site?
	 *
	 * @since 2.0.0
	 * @var bool
	 */
	protected $active;

	/**
	 * Is this a public site?
	 *
	 * @since 2.0.0
	 * @var bool
	 */
	protected $public = true;

	/**
	 * Is this an archived site?
	 *
	 * @since 2.0.0
	 * @var bool
	 */
	protected $archived;

	/**
	 * Is this a site with mature content?
	 *
	 * @since 2.0.0
	 * @var bool
	 */
	protected $mature;

	/**
	 * Is this an spam site?
	 *
	 * @since 2.0.0
	 * @var bool
	 */
	protected $spam;

	/**
	 * Is this site deleted?
	 *
	 * @since 2.0.0
	 * @var bool
	 */
	protected $deleted;

	/**
	 * ID of the language being used on this site.
	 *
	 * @since 2.0.0
	 * @var int
	 */
	protected $lang_id;

	/**
	 * Holds the ID of the customer that owns this site.
	 *
	 * @since 2.0.0
	 * @var int
	 */
	protected $customer_id;

	/**
	 * Holds the ID of the membership associated with this site, if any.
	 *
	 * @since 2.0.0
	 * @var int
	 */
	protected $membership_id;

	/**
	 * Local membership cache.
	 *
	 * @since 2.0.0
	 * @var null|\WP_Ultimo\Models\Membership
	 */
	private $_membership;

	/**
	 * The site template id used to create this site.
	 *
	 * @since 2.0.0
	 * @var int
	 */
	protected $template_id;

	/**
     * Duplication arguments.
     *
     * @since 2.0.0
     * @var array
     */
	private $duplication_arguments = array();

	/**
	 * The site type of this particular site.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	protected $type;

	/**
	 * ID of the featured image being used on this product.
	 *
	 * @since 2.0.0
	 * @var int
	 */
	protected $featured_image_id;

	/**
	 * Categories
	 *
	 * @since 2.0.0
	 * @var int
	 */
	protected $categories;

	/**
	 * Query Class to the static query methods.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	protected $query_class = '\\WP_Ultimo\\Database\\Sites\\Site_Query';

	/**
	 * Keeps form date from the signup form.
	 *
	 * @since 2.0.0
	 * @var array
	 */
	protected $transient;

	/**
	 * Keeps signup options for the site.
	 *
	 * @since 2.0.0
	 * @var null|array
	 */
	protected $signup_options;

	/**
	 * Keeps signup meta for the site.
	 *
	 * @since 2.0.0
	 * @var null|array
	 */
	protected $signup_meta;

	/**
	 * Set the validation rules for this particular model.
	 *
	 * To see how to setup rules, check the documentation of the
	 * validation library we are using: https://github.com/rakit/validation
	 *
	 * @since 2.0.0
	 * @link https://github.com/rakit/validation
	 * @return array
	 */
	public function validation_rules() {

		$date = wu_get_current_time('mysql', true);

		$site_types = new \WP_Ultimo\Database\Sites\Site_Type;

		$site_types = implode(',', array_values($site_types->get_options()));

		return array(
			'categories'        => 'default:',
			'featured_image_id' => 'integer|default:',
			'site_id'           => 'required|integer',
			'title'             => 'required',
			'name'              => 'required',
			'description'       => 'required|min:2',
			'domain'            => 'domain',
			'path'              => 'required|default:',
			'registered'        => "default:{$date}",
			'last_updated'      => 'default:',
			'public'            => 'boolean|default:1',
			'archived'          => 'boolean|default:0',
			'mature'            => 'boolean|default:0',
			'spam'              => 'boolean|default:0',
			'deleted'           => 'boolean|default:0',
			'is_publishing'     => 'boolean|default:0',
			'land_id'           => 'integer|default:',
			'customer_id'       => 'required|integer|exists:\WP_Ultimo\Models\Customer,id',
			'membership_id'     => 'required|integer|exists:\WP_Ultimo\Models\Membership,id',
			'template_id'       => 'integer|default:',
			'type'              => "required|in:{$site_types}",
			'signup_options'    => 'default:',
		);

	} // end validation_rules;

	/**
	 * Get the visits for this particular sites.
	 *
	 * @since 2.0.0
	 * @return int
	 */
	public function get_visits_count() {

		$visits_manager = new \WP_Ultimo\Objects\Visits($this->get_id());

		return $visits_manager->get_visit_total('first day of this month', 'last day of this month');

	} // end get_visits_count;

	/**
	 * Set the categories for the site.
	 *
	 * @since 2.0.0
	 *
	 * @param array $categories The categories this site belongs to.
	 * @return void
	 */
	public function set_categories($categories) {

		$this->meta['wu_categories'] = $categories;

		$this->categories = $categories;

	} // end set_categories;

	/**
	 * Get the list of categories.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_categories() {

		if ($this->categories === null) {

			$this->categories = $this->get_meta('wu_categories', array());

		} // end if;

		if (!is_array($this->categories)) {

			return array();

		} // end if;

		return array_filter($this->categories);

	} // end get_categories;

	/**
	 * Get featured image ID.
	 *
	 * @since 2.0.0
	 * @return int
	 */
	public function get_featured_image_id() {

		if ($this->featured_image_id === null) {

			return $this->get_meta('wu_featured_image_id');

		} // end if;

		return $this->featured_image_id;

	} // end get_featured_image_id;
	/**
	 * Get featured image url.
	 *
	 * @since 2.0.0
	 * @param string $size The size of the image to retrieve.
	 * @return string
	 */
	public function get_featured_image($size = 'wu-thumb-medium') {

		if ($this->get_type() === 'external') {

			return wu_get_asset('wp-ultimo-screenshot.png');

		} // end if;

		is_multisite() && switch_to_blog(wu_get_main_site_id());

		$size = apply_filters('wu_site_featured_image_size', $size, $this);

		$image_attributes = wp_get_attachment_image_src($this->get_featured_image_id(), $size);

		is_multisite() && restore_current_blog();

		if ($image_attributes) {

			return $image_attributes[0];

		} // end if;

		return wu_get_asset('site-placeholder-image.png', 'img');

	} // end get_featured_image;

	/**
	 * Set featured image ID.
	 *
	 * @since 2.0.0
	 * @param int $image_id The ID of the feature image of the site.
	 * @return void
	 */
	public function set_featured_image_id($image_id) {

		$this->meta['wu_featured_image_id'] = $image_id;

		$this->featured_image_id = $image_id;

	} // end set_featured_image_id;

	/**
	 * Get the preview URL.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_preview_url() {

		return Template_Previewer::get_instance()->get_preview_url($this->get_id());

	} // end get_preview_url;
	/**
	 * Get the preview URL attrs.
	 *
	 * @since 2.0.0
	 */
	public function get_preview_url_attrs(): string {

		$is_enabled = Template_Previewer::get_instance()->get_setting('enabled');

		$href = 'href="%s" target="_blank"';

		if (!$is_enabled) {

			return sprintf($href, $this->get_active_site_url());

		} // end if;

		$onclick = 'onclick="window.open(\'%s\')"';

		return sprintf($onclick, add_query_arg('open', 1, $this->get_preview_url()));

	} // end get_preview_url_attrs;

	/**
	 * Get blog ID. Should be accessed via id.
	 *
	 * @since 2.0.0
	 * @return int
	 */
	public function get_id() {

		return $this->get_blog_id();

	} // end get_id;

	/**
	 * Get blog ID. Should be accessed via id..
	 *
	 * @since 2.0.0
	 * @return int
	 */
	public function get_blog_id() {

		return (int) $this->blog_id;

	} // end get_blog_id;

	/**
	 * Set blog ID. Should be accessed via id..
	 *
	 * @since 2.0.0
	 * @param int $blog_id The blog ID. Should be accessed via id.
	 * @return void
	 */
	public function set_blog_id($blog_id) {

		$this->blog_id = $blog_id;

	} // end set_blog_id;

	/**
	 * Get network ID for this site..
	 *
	 * @since 2.0.0
	 * @return int
	 */
	public function get_site_id() {

		return $this->site_id;

	} // end get_site_id;

	/**
	 * Set network ID for this site..
	 *
	 * @since 2.0.0
	 * @param int $site_id The network ID for this site.
	 * @return void
	 */
	public function set_site_id($site_id) {

		$this->site_id = $site_id;

	} // end set_site_id;
	/**
	 * Get title of the site..
	 *
	 * @since 2.0.0
	 */
	public function get_title(): string {

		return stripslashes($this->title);

	} // end get_title;

	/**
	 * Set title of the site.
	 *
	 * @since 2.0.0
	 *
	 * @param string $title The site title.
	 * @return void
	 */
	public function set_title($title) {

		$this->title = sanitize_text_field($title);

	} // end set_title;

	/**
	 * Alias to get name.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_name() {

		return $this->get_title();

	} // end get_name;

	/**
	 * Alias to set title.
	 *
	 * @since 2.0.0
	 * @param string $title The site name.
	 * @return void
	 */
	public function set_name($title) {

		$this->set_title($title);

	} // end set_name;

	/**
	 * Gets the site description.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_description() {

		if ($this->description) {

			return $this->description;

		} // end if;

		return get_blog_option($this->get_id(), 'blogdescription');

	} // end get_description;

	/**
	 * Sets the site description.
	 *
	 * @todo This is not yet persistent.
	 *
	 * @since 2.0.0
	 * @param string $description A description for the site, usually a short text.
	 * @return void
	 */
	public function set_description($description) {

		$this->description = $description;

	} // end set_description;

	/**
	 * Get domain name used by this site..
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_domain() {

		return $this->domain;

	} // end get_domain;

	/**
	 * Set domain name used by this site..
	 *
	 * @since 2.0.0
	 * @param string $domain The site domain. You don't need to put http or https in front of your domain in this field. e.g: example.com.
	 * @return void
	 */
	public function set_domain($domain) {

		$this->domain = $domain;

	} // end set_domain;
	/**
	 * Get path of the site. Used when in sub-directory mode..
	 *
	 * @since 2.0.0
	 */
	public function get_path(): string {

		return trim($this->path, '/');

	} // end get_path;

	/**
	 * Set path of the site. Used when in sub-directory mode..
	 *
	 * @since 2.0.0
	 * @param string $path Path of the site. Used when in sub-directory mode.
	 * @return void
	 */
	public function set_path($path) {

		$this->path = $path;

	} // end set_path;

	/**
	 * Get date when the site was registered..
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_registered() {

		return $this->registered;

	} // end get_registered;

	/**
	 * Proxy for a common API.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_date_registered() {

		return $this->get_registered();

	} // end get_date_registered;

	/**
	 * Set date when the site was registered..
	 *
	 * @since 2.0.0
	 * @param string $registered Date when the site was registered.
	 * @return void
	 */
	public function set_registered($registered) {

		$this->registered = $registered;

	} // end set_registered;

	/**
	 * Get date of the last update on this site.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_last_updated() {

		return $this->last_updated;

	} // end get_last_updated;

	/**
	 * Proxy to last_updated.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_date_modified() {

		return $this->get_last_updated();

	} // end get_date_modified;

	/**
	 * Set date of the last update on this site..
	 *
	 * @since 2.0.0
	 * @param string $last_updated Date of the last update on this site.
	 * @return void
	 */
	public function set_last_updated($last_updated) {

		$this->last_updated = $last_updated;

	} // end set_last_updated;

	/**
	 * Get if the site is being published.
	 *
	 * @since 2.0.11
	 * @return int
	 */
	public function is_publishing() {

		return $this->is_publishing;

	} // end is_publishing;

	/**
	 * Set if the site is being published.
	 *
	 * @since 2.0.11
	 * @param int $publishing Holds the ID of the customer that owns this site.
	 * @return void
	 */
	public function set_publishing($publishing) {

		$this->is_publishing = $publishing;

	} // end set_publishing;

	/**
	 * Get holds the ID of the customer that owns this site.
	 *
	 * @since 2.0.0
	 * @return int
	 */
	public function is_active() {

		if ($this->active === null) {

			$this->active = $this->get_meta('wu_active', true);

		} // end if;

		return $this->active;

	} // end is_active;

	/**
	 * Set holds the ID of the customer that owns this site..
	 *
	 * @since 2.0.0
	 * @param int $active Holds the ID of the customer that owns this site.
	 * @return void
	 */
	public function set_active($active) {

		$this->meta['wu_active'] = $active;

		$this->active = $active;

	} // end set_active;

	/**
	 * Get is this a public site?.
	 *
	 * @since 2.0.0
	 * @return bool
	 */
	public function get_public() {

		return $this->public;

	} // end get_public;

	/**
	 * Set is this a public site?.
	 *
	 * @since 2.0.0
	 * @param bool $public Set true if this site is a public one, false if not.
	 * @return void
	 */
	public function set_public($public) {

		$this->public = $public;

	} // end set_public;

	/**
	 * Get is this an archived site.
	 *
	 * @since 2.0.0
	 * @return bool
	 */
	public function is_archived() {

		return $this->archived;

	} // end is_archived;

	/**
	 * Set is this an archived site?.
	 *
	 * @since 2.0.0
	 * @param bool $archived Is this an archived site.
	 * @return void
	 */
	public function set_archived($archived) {

		$this->archived = $archived;

	} // end set_archived;

	/**
	 * Get is this a site with mature content.
	 *
	 * @since 2.0.0
	 * @return bool
	 */
	public function is_mature() {

		return $this->mature;

	} // end is_mature;

	/**
	 * Set is this a site with mature content?.
	 *
	 * @since 2.0.0
	 * @param bool $mature Is this a site with mature content.
	 * @return void
	 */
	public function set_mature($mature) {

		$this->mature = $mature;

	} // end set_mature;

	/**
	 * Get is this an spam site.
	 *
	 * @since 2.0.0
	 * @return bool
	 */
	public function is_spam() {

		return $this->spam;

	} // end is_spam;

	/**
	 * Set is this an spam site?.
	 *
	 * @since 2.0.0
	 * @param bool $spam Is this an spam site.
	 * @return void
	 */
	public function set_spam($spam) {

		$this->spam = $spam;

	} // end set_spam;

	/**
	 * Get is this site deleted.
	 *
	 * @since 2.0.0
	 * @return bool
	 */
	public function is_deleted() {

		return $this->deleted;

	} // end is_deleted;

	/**
	 * Set is this site deleted?.
	 *
	 * @since 2.0.0
	 * @param bool $deleted Is this site deleted.
	 * @return void
	 */
	public function set_deleted($deleted) {

		$this->deleted = $deleted;

	} // end set_deleted;

	/**
	 * Get iD of the language being used on this site.
	 *
	 * @since 2.0.0
	 * @return int
	 */
	public function get_lang_id() {

		return $this->lang_id;

	} // end get_lang_id;

	/**
	 * Set iD of the language being used on this site.
	 *
	 * @since 2.0.0
	 * @param int $lang_id The ID of the language being used on this site.
	 * @return void
	 */
	public function set_lang_id($lang_id) {

		$this->lang_id = $lang_id;

	} // end set_lang_id;

	/**
	 * Get holds the ID of the customer that owns this site..
	 *
	 * @since 2.0.0
	 * @return int
	 */
	public function get_customer_id() {

		if ($this->customer_id === null) {

			$this->customer_id = $this->get_meta('wu_customer_id');

		} // end if;

		return (int) $this->customer_id;

	} // end get_customer_id;

	/**
	 * Set holds the ID of the customer that owns this site..
	 *
	 * @since 2.0.0
	 * @param int $customer_id The ID of the customer that owns this site.
	 * @return void
	 */
	public function set_customer_id($customer_id) {

		$this->meta['wu_customer_id'] = $customer_id;

		$this->customer_id = $customer_id;

	} // end set_customer_id;

	/**
	 * Gets the customer object associated with this membership.
	 *
	 * @since 2.0.0
	 * @return \WP_Ultimo\Models\Customer|false;
	 */
	public function get_customer() {

		return wu_get_customer($this->get_customer_id());

	} // end get_customer;

	/**
	 * Checks if a given customer should have access to this site options.
	 *
	 * @since 2.0.0
	 *
	 * @param int $customer_id The customer id to check.
	 * @return boolean
	 */
	public function is_customer_allowed($customer_id = false) {

		if (current_user_can('manage_network')) {

			return true;

		} // end if;

		if (!$customer_id) {

			$customer = WP_Ultimo()->currents->get_customer();

			$customer_id = $customer ? $customer->get_id() : 0;

		} // end if;

		$allowed = absint($customer_id) === absint($this->get_customer_id());

		return apply_filters('wu_site_is_customer_allowed', $allowed, $customer_id, $this);

	} // end is_customer_allowed;

	/**
	 * Get holds the ID of the membership associated with this site, if any..
	 *
	 * @since 2.0.0
	 * @return int
	 */
	public function get_membership_id() {

		if ($this->membership_id === null) {

			$this->membership_id = $this->get_meta('wu_membership_id');

		} // end if;

		return $this->membership_id;

	} // end get_membership_id;

	/**
	 * Set holds the ID of the membership associated with this site, if any..
	 *
	 * @since 2.0.0
	 * @param int $membership_id The ID of the membership associated with this site, if any.
	 * @return void
	 */
	public function set_membership_id($membership_id) {

		$this->meta['wu_membership_id'] = $membership_id;

		$this->membership_id = $membership_id;

	} // end set_membership_id;

	/**
	 * Checks if this site has a membership.
	 *
	 * @since 2.0.0
	 * @return boolean
	 */
	public function has_membership() {

		return !empty($this->get_membership());

	} // end has_membership;

	/**
	 * Checks if the site has a product.
	 *
	 * @since 2.0.0
	 * @return boolean
	 */
	public function has_product() {

		return $this->has_membership() && $this->get_membership()->has_product();

	} // end has_product;

	/**
	 * Gets the membership object associated with this membership.
	 *
	 * @since 2.0.0
	 * @return \WP_Ultimo\Models\Membership|false;
	 */
	public function get_membership() {

		if ($this->_membership !== null) {

			return $this->_membership;

		} // end if;

		if (function_exists('wu_get_membership')) {

			$this->_membership = wu_get_membership($this->get_membership_id());

			return $this->_membership;

		} // end if;

		global $wpdb;

		$table_name = "{$wpdb->base_prefix}wu_memberships";

		$membership_id = $this->get_membership_id();

		if (!$membership_id) {

			return false;

		} // end if;

		$query = $wpdb->prepare("SELECT * FROM {$table_name} WHERE id = %d LIMIT 1", $membership_id); // phpcs:ignore

		$results = $wpdb->get_row($query); // phpcs:ignore

		if (!$results) {

			return false;

		} // end if;

		$this->_membership = new \WP_Ultimo\Models\Membership($results);

		return $this->_membership;

	} // end get_membership;

	/**
	 * Returns the plan that created this site.
	 *
	 * @since 2.0.0
	 * @return \WP_Ultimo\Models\Product|false
	 */
	public function get_plan() {

		if ($this->has_membership()) {

			return $this->get_membership()->get_plan();

		} // end if;

		return false;

	} // end get_plan;
 /**
  * Get template ID.
  *
  * @since 2.0.0
  * @return int|bool
  */
 public function get_template_id() {

		if ($this->template_id === null) {

			$this->template_id = $this->get_meta('wu_template_id');

		} // end if;

		return $this->template_id;

	} // end get_template_id;

	/**
	 * Set the template ID.
	 *
	 * @since 2.0.0
	 * @param int $template_id The ID of the templated used to create this site.
	 * @return void
	 */
	public function set_template_id($template_id) {

		$this->meta['wu_template_id'] = absint($template_id);

		$this->template_id = $template_id;

	} // end set_template_id;

	/**
	 * Gets the site object associated with this membership.
	 *
	 * @since 2.0.0
	 * @return \WP_Ultimo\Models\Site|false;
	 */
	public function get_template() {

		return wu_get_site($this->get_template_id());

	} // end get_template;

	/**
	 * Returns the default duplication arguments.
	 *
	 * @since 2.0.0
	 * @return array
	 */
	protected function get_default_duplication_arguments() {

		return array(
			'keep_users' => true,
			'copy_files' => true,
			'public'     => true,
		);

	} // end get_default_duplication_arguments;

	/**
	 * Convert the Ultimo instance to a WP_Site.
	 *
	 * @since 2.0.11
	 * @return \WP_Site
	 */
	public function to_wp_site() {

		return get_site($this->get_id());

	} // end to_wp_site;

	/**
	 * Get duplication arguments..
	 *
	 * @since 2.0.0
	 * @return array
	 */
	public function get_duplication_arguments() {

		$args = wp_parse_args($this->duplication_arguments, $this->get_default_duplication_arguments());

		return $args;

	} // end get_duplication_arguments;

	/**
	 * Set duplication arguments..
	 *
	 * @since 2.0.0
	 * @param array $duplication_arguments Duplication arguments.
	 * @return void
	 */
	public function set_duplication_arguments($duplication_arguments) {

		$this->duplication_arguments = $duplication_arguments;

	} // end set_duplication_arguments;

	/**
	 * Get the site type of this particular site..
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_type() {

		if ($this->get_id() && is_main_site($this->get_id())) {

			return 'main';

		} // end if;

		if ($this->type === null) {

			$type = $this->get_meta('wu_type');

			$this->type = $type ? $type : 'default';

		} // end if;

		return $this->type;

	} // end get_type;

	/**
	 * Set the site type of this particular site.
	 *
	 * @since 2.0.0
	 * @param string $type The type of this particular site. Can be default, site_template, customer_owned, pending, external, main or other values added by third-party add-ons.
	 * @options \WP_Ultimo\Database\Sites\Site_Type
	 * @return void
	 */
	public function set_type($type) {

		$this->meta = (array) $this->meta;

		$this->meta['wu_type'] = $type;

		$this->type = $type;

	} // end set_type;
 /**
  * Get the primary mapped domain for this site.
  *
  * @since 2.0.0
  * @return \WP_Ultimo\Models\Domain|false
  */
 public function get_primary_mapped_domain() {

		if (!function_exists('wu_get_domains')) {

			return false;

		} // end if;

		$domains = wu_get_domains(array(
			'primary_domain' => true,
			'blog_id'        => $this->get_id(),
			'stage__not_in'  => \WP_Ultimo\Models\Domain::INACTIVE_STAGES,
			'number'         => 1,
		));

		return empty($domains) ? false : $domains[0];

	} // end get_primary_mapped_domain;

	/**
	 * Returns the active site URL, which can be a mapped domain.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_active_site_url() {

		if (!$this->get_id()) {

			return $this->get_site_url();

		} // end if;

		$domain = $this->get_primary_mapped_domain();

		if ($domain) {

			return $domain->get_url();

		} // end if;

		return $this->get_site_url();

	} // end get_active_site_url;

	/**
	 * Returns the original URL for the blog.
	 *
	 * This is useful when we need to know the original URL, without
	 * mapping applied.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_site_url() {

		$url = set_url_scheme(esc_url(sprintf($this->get_domain() . '/' . $this->get_path())));

		return $url;

	} // end get_site_url;

	/**
	 * Checks if this model was already saved to the database.
	 *
	 * @since 2.0.0
	 * @return bool
	 */
	public function exists() {

		return !empty($this->blog_id);

	} // end exists;

	/**
	 * Override te constructor due to this being a native table.
	 *
	 * @since 2.0.0
	 *
	 * @param mixed $object Object containing the parameters.
	 */
	public function __construct($object = null) {

		parent::__construct($object);

		if (is_array($object)) {

			$object = (object) $object;

		} // end if;

		$details = get_blog_details($this->get_blog_id());

		if ($details && $this->title === null) {

			$this->set_title($details->blogname);

		} // end if;

		/*
		 * Quick fix for WP CLI, since it uses the --path arg to do other things.
		 */
		if (!$this->path && is_object($object) && isset($object->site_path)) {

			$this->path = $object->site_path;

		} // end if;

		$object = (object) $object;

	} // end __construct;

	/**
	 * Gets the form data saved at the time of the site creation.
	 *
	 * @since 2.0.0
	 * @return array
	 */
	public function get_transient() {

		if ($this->transient === null) {

			$this->transient = $this->get_meta('wu_transient');

		} // end if;

		return $this->transient;

	} // end get_transient;

	/**
	 * Holds the form data at the time of registration.
	 *
	 * @since 2.0.0
	 * @param array $transient Form data.
	 * @return void
	 */
	public function set_transient($transient) {

		$this->meta['wu_transient'] = $transient;

		$this->transient = $transient;

	} // end set_transient;

	/**
	 * Get signup options for the site.
	 *
	 * @since 2.0.0
	 * @return array
	 */
	public function get_signup_options() {

		return is_array($this->signup_options) ? $this->signup_options : array();

	} // end get_signup_options;

	/**
	 * Set signup options for the site.
	 *
	 * @since 2.0.0
	 * @param array $signup_options Keeps signup options for the site.
	 * @return void
	 */
	public function set_signup_options($signup_options) {

		$this->signup_options = $signup_options;

	} // end set_signup_options;

	/**
	 * Get signup meta for the site.
	 *
	 * @since 2.0.0
	 * @return array
	 */
	public function get_signup_meta() {

		return is_array($this->signup_meta) ? $this->signup_meta : array();

	} // end get_signup_meta;

	/**
	 * Set signup meta for the site.
	 *
	 * @since 2.0.0
	 * @param array $signup_meta Keeps signup meta for the site.
	 * @return void
	 */
	public function set_signup_meta($signup_meta) {

		$this->signup_meta = $signup_meta;

	} // end set_signup_meta;

	/**
	 * Returns the Label for a given type.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_type_label() {

		$type = new Site_Type($this->get_type());

		return $type->get_label();

	} // end get_type_label;

	/**
	 * Gets the classes for a given class.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_type_class() {

		$type = new Site_Type($this->get_type());

		return $type->get_classes();

	} // end get_type_class;

	/**
	 * Adds magic methods to return options.
	 *
	 * @since 2.0.0
	 *
	 * @param string $name Method name.
	 * @param array  $args List of arguments.
	 * @throws \BadMethodCallException Throws exception when method is not found.
	 * @return mixed
	 */
	public function __call($name, $args) {

		if (strpos($name, 'get_option_') !== false) {

			$option = str_replace('get_option_', '', $name);

			return get_blog_option($this->get_id(), $option, false);

		} // end if;

		throw new \BadMethodCallException(__CLASS__ . "::$name()");

	} // end __call;

	/**
	 * Checks if this is the primary site of the customer.
	 *
	 * @since 2.0.0
	 * @return boolean
	 */
	public function is_customer_primary_site() {

		$customer = $this->get_customer();

		if (!$customer) {

			return false;

		} // end if;

		$user_id = $customer->get_user_id();

		if (!$user_id) {

			return false;

		} // end if;

		$primary_site_id = get_user_option('primary_blog', $user_id);

		return absint($primary_site_id) === absint($this->get_id());

	} // end is_customer_primary_site;
 /**
  * Delete the model from the database.
  *
  * @since 2.0.0
  * @return \WP_Error|bool
  */
 public function delete() {

		if (!$this->get_id()) {

			return new \WP_Error("wu_{$this->model}_delete_unsaved_item", __('Item not found.', 'wp-ultimo'));

		} // end if;

		/**
		 * Fires after an object is stored into the database.
		 *
		 * @since 2.0.0
		 *
		 * @param \WP_Ultimo\Models\Base_Model $this The object instance.
		 */
		do_action("wu_{$this->model}_pre_delete", $this); // @phpstan-ignore-line

		try {

			$result = (bool) wp_delete_site($this->get_id());

		} catch (\Throwable $e) {

			$result = false;

			wu_log_add('fatal-error', $e->getMessage(), LogLevel::ERROR);

		} // end try;

		/**
		 * Fires after an object is stored into the database.
		 *
		 * @since 2.0.0
		 *
		 * @param bool       $result True if the object was successfully deleted.
		 * @param Base_Model $this   The object instance.
		 */
		do_action("wu_{$this->model}_post_delete", $result, $this); // @phpstan-ignore-line

		wp_cache_flush();

		return $result;

	} // end delete;

	/**
	 * Replaces meta fields with the data collected during signup.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	protected function handles_existing_search_and_replace() {

		$transient = $this->get_transient();

		if ($transient) {

			add_filter('wu_search_and_replace_on_duplication', function($replace_list, $from_site_id, $to_site_id) use ($transient) {

				foreach ($transient as $transient_key => $transient_value) {

					$key = sprintf('{{%s}}', $transient_key);

					$replace_list[$key] = $transient_value;

				} // end foreach;

				return $replace_list;

			}, 9, 3);

		} // end if;

	} // end handles_existing_search_and_replace;

	/**
	 * Save (create or update) the model on the database.
	 *
	 * @since 2.0.0
	 *
	 * @return bool
	 */
	public function save() {
		/*
		 * Prepares the substitutions.
		 */
		$this->handles_existing_search_and_replace();

		/**
		 * In order to be backwards compatible here, we'll have to do some crazy stuff,
		 * like overload the form session with the meta data saved on the pending site.
		 */
		if (has_filter('wu_search_and_replace_on_duplication')) {

			$transient = $this->get_transient();

			$session = wu_get_session('signup');

			$session->set('form', $transient);

			$session->commit();

		} // end if;

		$data = get_object_vars($this);

		$original_site = new self($data['_original']);

		$original_customer_id = $original_site->get_customer_id();

		unset($data['_original']);

		$data_unserialized = $data;

		$saved = true;

		$new = !$this->exists();

		if ($new) {

			$network = get_network();

			$domain = $this->get_domain() ? $this->get_domain() : $network->domain;

			$network_id = $this->get_site_id() ? $this->get_site_id() : get_current_network_id();

			$user_id = get_current_user_id();

			$customer = wu_get_customer($this->get_customer_id());

			/*
			 * By default, use the current user email address.
			 */
			$email = wp_get_current_user() ? wp_get_current_user()->user_email : get_network_option(null, 'admin_email');

			if ($customer) {

				$user_id = $customer->get_user_id();

				$email = $customer->get_email_address();

			} // end if;

			/*
			 * Decide if we need to duplicate this site, or create a new one.
			 */
			if ($this->get_template()) {

				$saved = \WP_Ultimo\Helpers\Site_Duplicator::duplicate_site($this->get_template_id(), $this->get_title(), array(
					'email'   => $email,
					'path'    => $this->get_path(),
					'domain'  => $domain,
					'meta'    => $this->get_signup_options(),
					'user_id' => $user_id ? $user_id : 0,
				));

				if (is_wp_error($saved)) {

					// Here we check if the site was created and if so, get the ID to finish the execution to ensure the customer is set.

					$error = $saved;
					$saved = get_blog_id_from_url($domain, $this->get_path());

					if ($saved === 0 || $saved === wu_get_main_site_id()) {

						return $error;

					} // end if;

				} // end if;

			} else {

				$saved = wpmu_create_blog($domain, $this->get_path(), $this->get_title(), $user_id, $this->get_signup_options(), $network_id);

				if ($saved && $this->get_public()) {

					$site_id = $saved;

					wp_update_site($site_id, array(
						'public' => $this->get_public(),
					));

				} // end if;

				/**
				 * Fires after a site is created for the first time.
				 *
				 * @since 2.0.0
				 *
				 * @param array      $data The object data that will be stored.
				 * @param \WP_Ultimo\Models\Base_Model $this The object instance.
				 */
				do_action('wu_site_created', $data, $this); // @phpstan-ignore-line

			} // end if;

			if (!is_wp_error($saved) && wu_get_setting('enable_screenshot_generator', true)) {

				wu_enqueue_async_action('wu_async_take_screenshot', array(
					'site_id' => $saved,
				), 'site');

			} // end if;

		} else {

			$saved = wp_update_site($this->get_id(), $this->to_array());

		} // end if;

		if (is_wp_error($saved)) {

			return $saved;

		} // end if;

		$this->blog_id = $saved;

		switch_to_blog($this->blog_id);

		foreach ($this->get_signup_options() as $key => $value) {

			update_option($key, $value);

		} // end foreach;

		restore_current_blog();

		foreach ($this->get_signup_meta() as $key => $value) {

			update_site_meta($saved, $key, $value);

		} // end foreach;

		foreach ($this->meta as $key => $value) {

			update_site_meta($saved, $key, $value);

		} // end foreach;

		/**
		 * Handles membership
		 */
		$membership = $this->get_membership();

		if ($membership) {

			$customer_id = $membership->get_customer_id();

			$this->set_customer_id($customer_id);

		} // end if;

		/**
		 * Handles customers.
		 */
		$customer = $this->get_customer();

		if ($customer) {

			$role = wu_get_setting('default_role', 'administrator');

			if ($membership && $membership->has_limitations()) {

				$role = $membership->get_limitations()->customer_user_role->get_limit();

			} // end if;

			update_site_meta($this->get_id(), 'wu_customer_id', $customer->get_id());

			$user_id = $customer->get_user_id();

			add_user_to_blog($this->get_id(), $user_id, $role);

		} elseif ($this->get_type() !== Site_Type::CUSTOMER_OWNED && $original_customer_id) {

			$user_id = wu_get_customer($original_customer_id)->get_user_id();

			remove_user_from_blog($user_id, $this->get_id());

		} // end if;

		update_blog_option($this->get_id(), 'blogname', $this->get_name());

		update_blog_option($this->get_id(), 'blogdescription', $this->get_description());

		/**
		 * Fires after an object is stored into the database.
		 *
		 * @since 2.0.0
		 *
		 * @param array      $model The model slug.
		 * @param array      $data The object data that will be stored, serialized.
		 * @param array      $data_unserialized The object data that will be stored.
		 * @param \WP_Ultimo\Models\Base_Model $this The object instance.
		 * @param array      $new If this object is a new one.
		 */
		do_action('wu_model_post_save', $this->model, $data, $data_unserialized, $this, $new); // @phpstan-ignore-line

		/**
		 * Fires after an object is stored into the database.
		 *
		 * @since 2.0.0
		 *
		 * @param array      $data The object data that will be stored.
		 * @param \WP_Ultimo\Models\Base_Model $this The object instance.
		 * @param array      $new If this object is a new one.
		 */
		do_action("wu_{$this->model}_post_save", $data, $this, $new); // @phpstan-ignore-line

		if (isset($session)) {

			$session->destroy();

		} // end if;

		return $saved;

	} // end save;

	/**
	 * By default, we just use the to_array method, but you can rewrite this.
	 *
	 * @since 2.0.0
	 * @return array
	 */
	public function to_search_results() {

		$search_result = $this->to_array();

		$search_result['siteurl'] = $this->get_active_site_url();

		return $search_result;

	} // end to_search_results;

	/**
	 * Returns a list of sites based on the type.
	 *
	 * Type can be customer_owned or template.
	 *
	 * @since 2.0.0
	 *
	 * @param string $type Type to return. Can be customer_owned or template.
	 * @param array  $query_args Additional query args.
	 * @return array
	 */
	public static function get_all_by_type($type = 'customer_owned', $query_args = array()) {

		global $wpdb;

		if ($type === 'pending') {

			$table_name = "{$wpdb->base_prefix}wu_membershipmeta";

			$customer_id = (int) wu_get_isset($query_args, 'customer_id');

			$customer_id_query = '';

			if ($customer_id) {

				$memberships = wu_get_memberships(array(
					'fields'      => array('id'),
					'customer_id' => $customer_id,
				));

				$memberships_str = '';

				foreach ($memberships as $membership) {

					$memberships_str = !empty($memberships_str) ? $memberships_str . ', ' . $membership->id : $membership->id;

				} // end foreach;

				$customer_id_query = !empty($memberships_str) ? "AND wu_membership_id IN ($memberships_str)" : '';

			} // end if;

			$sql = "SELECT meta_value FROM {$table_name} WHERE meta_key = 'pending_site' $customer_id_query ORDER BY meta_id DESC"; // phpcs:ignore

			$results = array_column($wpdb->get_results($sql), 'meta_value'); // phpcs:ignore

			$results = array_map(function($item) {

				$pending_site = unserialize($item);

				$pending_site->set_type('pending');

				return $pending_site;

			}, $results);

			return $results;

		} // end if;

		$query = $query_args;

		$query['meta_query'] = array(
			array(
				'key'   => 'wu_type',
				'value' => $type,
			),
		);

		return static::query($query);

	} // end get_all_by_type;

	/**
	 * Returns a list of sites of given categories
	 *
	 * @since 2.1.3
	 *
	 * @param array $categories Array of categories names.
	 * @param array $query_args Additional query args.
	 * @return array
	 */
	public static function get_all_by_categories($categories = array(), $query_args = array()) {

		global $wpdb;

		$query = $query_args;

		$query['meta_query'] = array(
			array(
				'key'     => 'wu_categories',
				'value'   => maybe_serialize($categories),
				'compare' => 'LIKE',
			),
		);

		return static::query($query);

	}  // end get_all_by_categories;

	/**
	 * Get the list of all Site Template Categories.
	 *
	 * @since 2.0.0
	 * @param array $sites An array of selected site ids or site objects.
	 * @return array
	 */
	public static function get_all_categories($sites = array()) {

		global $wpdb;

		$cache = wp_cache_get('site_categories', 'sites');

		if (is_array($cache)) {

			return $cache;

		} // end if;

		$final_array = array();

		$query = "SELECT DISTINCT meta_value FROM {$wpdb->base_prefix}blogmeta WHERE meta_key = %s";

		if (!empty($sites)) {

			// Ensures that $sites is a indexed array
			$sites = array_values($sites);

			if (is_a($sites[0], \WP_Ultimo\Models\Site::class)) {

				$array_sites = json_decode(json_encode($sites), true);

				$sites = array_values(array_column($array_sites, 'blog_id'));

			} // end if;

			$query .= ' AND blog_id IN (' . implode(', ', $sites) . ')';

		} // end if;

		$results = $wpdb->get_results($wpdb->prepare($query, 'wu_categories'), ARRAY_A); // phpcs:ignore

		$all_arrays = array_column($results, 'meta_value');

		$all_arrays = array_map('maybe_unserialize', $all_arrays);

		if ($all_arrays) {

			$filtered_array = array();

			foreach ($all_arrays as $array) {

				if (is_array($array)) {

					$filtered_array = array_merge($filtered_array, $array);

				} // end if;

			} // end foreach;

			$all_arrays = array_filter($filtered_array);

			$all_arrays = array_unique($all_arrays);

			$final_array = array_combine($all_arrays, $all_arrays);

		} // end if;

		wp_cache_set('site_categories', $final_array, 'sites');

		return $final_array;

	} // end get_all_categories;

	/**
	 * List of limitations that need to be merged.
	 *
	 * Every model that is limitable (imports this trait)
	 * needs to declare explicitly the limitations that need to be
	 * merged. This allows us to chain the merges, and gives us
	 * a final list of limitations at the end of the process.
	 *
	 * In the case of sites, we need to get the membership
	 * limitations.
	 *
	 * @see \WP_Ultimo\Models\Traits\Trait_Limitable
	 * @since 2.0.0
	 * @return array
	 */
	public function limitations_to_merge() {

		$limitations_to_merge = array();

		$membership = $this->get_membership();

		if ($membership) {

			$membership_limitations = $membership->get_limitations();

			$limitations_to_merge[] = $membership_limitations;

		} // end if;

		return $limitations_to_merge;

	} // end limitations_to_merge;

} // end class Site;
