<?php

namespace WPUM\Carbon_Fields\Field;

use WPUM\Carbon_Fields\Value_Set\Value_Set;
/**
 * Association field class.
 * Allows selecting and manually sorting entries from various types:
 *  - Posts
 *  - Terms
 *  - Users
 *  - Comments
 */
class Association_Field extends Field
{
    /**
     * WP_Toolset instance for WP data loading
     *
     * @var Carbon_Fields\Toolset\WP_Toolset
     */
    protected $wp_toolset;
    /**
     * Min number of selected items allowed. -1 for no limit
     *
     * @var integer
     */
    protected $min = -1;
    /**
     * Max number of selected items allowed. -1 for no limit
     *
     * @var integer
     */
    protected $max = -1;
    /**
     * Allow items to be added multiple times
     *
     * @var boolean
     */
    protected $duplicates_allowed = \false;
    /**
     * Default field value
     *
     * @var array
     */
    protected $default_value = array();
    /**
     * Types of entries to associate with.
     * @var array
     */
    protected $types = array(array('type' => 'post', 'post_type' => 'post'));
    /**
     * Create a field from a certain type with the specified label.
     *
     * @param string $type  Field type
     * @param string $name  Field name
     * @param string $label Field label
     */
    public function __construct($type, $name, $label)
    {
        $this->wp_toolset = \WPUM\Carbon_Fields\Carbon_Fields::resolve('wp_toolset');
        $this->set_value_set(new Value_Set(Value_Set::TYPE_VALUE_SET, array('type' => '', 'subtype' => '', 'id' => 0)));
        parent::__construct($type, $name, $label);
    }
    /**
     * {@inheritDoc}
     */
    public function set_value_from_input($input)
    {
        $value = array();
        if (isset($input[$this->get_name()])) {
            $value = stripslashes_deep($input[$this->get_name()]);
            if (\is_array($value)) {
                $value = \array_values($value);
            }
        }
        $this->set_value($value);
        return $this;
    }
    /**
     * {@inheritDoc}
     */
    public function set_value($value)
    {
        $value = $this->value_string_array_to_value_set($value);
        return parent::set_value($value);
    }
    /**
     * Get value string for legacy value
     *
     * @return string
     */
    protected function get_value_string_for_legacy_value($legacy_value)
    {
        $entry_type = 'post';
        $entry_subtype = 'post';
        // attempt to find a suitable type that is registered to this field as post type is not stored for legacy data
        foreach ($this->types as $type) {
            if ($type['type'] === $entry_type) {
                $entry_subtype = $type['post_type'];
                break;
            }
        }
        return $entry_type . ':' . $entry_subtype . ':' . $legacy_value;
    }
    /**
     * Convert a colon:separated:string into its expected components
     * Used for backwards compatibility to CF 1.5
     *
     * @param string $value_string
     * @return array
     */
    protected function value_string_to_property_array($value_string)
    {
        if (\is_numeric($value_string)) {
            // we are dealing with legacy data that only contains a post ID
            $value_string = $this->get_value_string_for_legacy_value($value_string);
        }
        $value_pieces = \explode(':', $value_string);
        $type = isset($value_pieces[0]) ? $value_pieces[0] : 'post';
        $subtype = isset($value_pieces[1]) ? $value_pieces[1] : 'post';
        $id = isset($value_pieces[2]) ? $value_pieces[2] : 0;
        $property_array = array(Value_Set::VALUE_PROPERTY => $value_string, 'type' => $type, 'subtype' => $subtype, 'id' => \intval($id));
        return $property_array;
    }
    /**
     * Convert a colon:separated:string into its expected components
     * Used for backwards compatibility to CF 1.5
     *
     * @param array $value_string_array
     * @return array<array>
     */
    protected function value_string_array_to_value_set($value_string_array)
    {
        $value_set = array();
        foreach ($value_string_array as $raw_value_entry) {
            $value_string = $raw_value_entry;
            if (\is_array($raw_value_entry)) {
                if (isset($raw_value_entry['type'])) {
                    // array is already in suitable format
                    $value_set[] = $raw_value_entry;
                    continue;
                }
                $value_string = $raw_value_entry[Value_Set::VALUE_PROPERTY];
            }
            $value_string = \trim($value_string);
            if (empty($value_string)) {
                continue;
            }
            $property_array = $this->value_string_to_property_array($value_string);
            $value_set[] = $property_array;
        }
        return $value_set;
    }
    /**
     * Used to get the title of an item.
     *
     * Can be overriden or extended by the `carbon_association_title` filter.
     *
     * @param int $id The database ID of the item.
     * @param string $type Item type (post, term, user, comment, or a custom one).
     * @param string $subtype The subtype - "page", "post", "category", etc.
     * @return string $title The title of the item.
     */
    protected function get_title_by_type($id, $type, $subtype = '')
    {
        $title = '';
        $method = 'get_' . $type . '_title';
        $callable = array($this->wp_toolset, $method);
        if (\is_callable($callable)) {
            $title = \call_user_func($callable, $id, $subtype);
        }
        if ($type === 'comment') {
            $max = apply_filters('carbon_fields_association_field_comment_length', 30, $this->get_base_name());
            if (\strlen($title) > $max) {
                $title = \substr($title, 0, $max) . '...';
            }
        }
        /**
         * Filter the title of the association item.
         *
         * @param string $title   The unfiltered item title.
         * @param string $name    Name of the association field.
         * @param int    $id      The database ID of the item.
         * @param string $type    Item type (post, term, user, comment, or a custom one).
         * @param string $subtype Subtype - "page", "post", "category", etc.
         */
        $title = apply_filters('carbon_fields_association_field_title', $title, $this->get_base_name(), $id, $type, $subtype);
        if (!$title) {
            $title = '(no title) - ID: ' . $id;
        }
        return $title;
    }
    /**
     * Used to get the label of an item.
     *
     * Can be overriden or extended by the `carbon_association_item_label` filter.
     *
     * @param int     $id      The database ID of the item.
     * @param string  $type    Item type (post, term, user, comment, or a custom one).
     * @param string  $subtype Subtype - "page", "post", "category", etc.
     * @return string $label The label of the item.
     */
    protected function get_item_label($id, $type, $subtype = '')
    {
        $label = $subtype ? $subtype : $type;
        if ($type === 'post') {
            $post_type_object = get_post_type_object($subtype);
            $label = $post_type_object->labels->singular_name;
        } elseif ($type === 'term') {
            $taxonomy_object = get_taxonomy($subtype);
            $label = $taxonomy_object->labels->singular_name;
        }
        /**
         * Filter the label of the association item.
         *
         * @param string $label   The unfiltered item label.
         * @param string $name    Name of the association field.
         * @param int    $id      The database ID of the item.
         * @param string $type    Item type (post, term, user, comment, or a custom one).
         * @param string $subtype Subtype - "page", "post", "category", etc.
         */
        return apply_filters('carbon_fields_association_field_item_label', $label, $this->get_base_name(), $id, $type, $subtype);
    }
    /**
     * Get post options
     *
     * @return array $options
     */
    protected function get_post_options($type)
    {
        /**
         * Filter the default query when fetching posts for a particular field.
         *
         * @param array $args The parameters, passed to get_posts().
         */
        $filter_name = 'carbon_fields_association_field_options_' . $this->get_base_name() . '_' . $type['type'] . '_' . $type['post_type'];
        $args = apply_filters($filter_name, array('post_type' => $type['post_type'], 'posts_per_page' => -1, 'fields' => 'ids', 'suppress_filters' => \false));
        // fetch and prepare posts as association items
        $posts = get_posts($args);
        foreach ($posts as &$p) {
            $p = array('id' => \intval($p), 'title' => $this->get_title_by_type($p, $type['type'], $type['post_type']), 'type' => $type['type'], 'subtype' => $type['post_type'], 'label' => $this->get_item_label($p, $type['type'], $type['post_type']), 'is_trashed' => get_post_status($p) == 'trash', 'edit_link' => $this->get_object_edit_link($type, $p));
        }
        return $posts;
    }
    /**
     * Get term options
     *
     * @return array $options
     */
    protected function get_term_options($type)
    {
        /**
         * Filter the default parameters when fetching terms for a particular field.
         *
         * @param array $args The parameters, passed to get_terms().
         */
        $filter_name = 'carbon_fields_association_field_options_' . $this->get_base_name() . '_' . $type['type'] . '_' . $type['taxonomy'];
        $args = apply_filters($filter_name, array('hide_empty' => 0, 'fields' => 'id=>name'));
        // fetch and prepare terms as association items
        $terms = get_terms($type['taxonomy'], $args);
        foreach ($terms as $term_id => &$term) {
            $term = array('id' => \intval($term_id), 'title' => $term, 'type' => $type['type'], 'subtype' => $type['taxonomy'], 'label' => $this->get_item_label($term_id, $type['type'], $type['taxonomy']), 'is_trashed' => \false, 'edit_link' => $this->get_object_edit_link($type, $term_id));
        }
        return $terms;
    }
    /**
     * Get user options
     *
     * @return array $options
     */
    protected function get_user_options($type)
    {
        /**
         * Filter the default parameters when fetching users for a particular field.
         *
         * @param array $args The parameters, passed to get_users().
         */
        $filter_name = 'carbon_fields_association_field_options_' . $this->get_base_name() . '_' . $type['type'];
        $args = apply_filters($filter_name, array('fields' => 'ID'));
        // fetch and prepare users as association items
        $users = get_users($args);
        foreach ($users as &$u) {
            $u = array('id' => \intval($u), 'title' => $this->get_title_by_type($u, $type['type']), 'type' => $type['type'], 'subtype' => 'user', 'label' => $this->get_item_label($u, $type['type']), 'is_trashed' => \false, 'edit_link' => $this->get_object_edit_link($type, $u));
        }
        return $users;
    }
    /**
     * Get comment options
     *
     * @return array $options
     */
    protected function get_comment_options($type)
    {
        /**
         * Filter the default parameters when fetching comments for a particular field.
         *
         * @param array $args The parameters, passed to get_comments().
         */
        $filter_name = 'carbon_fields_association_field_options_' . $this->get_base_name() . '_' . $type['type'];
        $args = apply_filters($filter_name, array('fields' => 'ids'));
        // fetch and prepare comments as association items
        $comments = get_comments($args);
        foreach ($comments as &$c) {
            $c = array('id' => \intval($c), 'title' => $this->get_title_by_type($c, $type['type']), 'type' => $type['type'], 'subtype' => 'comment', 'label' => $this->get_item_label($c, $type['type']), 'is_trashed' => \false, 'edit_link' => $this->get_object_edit_link($type, $c));
        }
        return $comments;
    }
    /**
     * Generate the item options.
     *
     * @return array $options The selectable options of the association field.
     */
    public function get_options()
    {
        $options = array();
        foreach ($this->types as $type) {
            $method = 'get_' . $type['type'] . '_options';
            $callable = array($this, $method);
            if (\is_callable($callable)) {
                $options = \array_merge($options, \call_user_func($callable, $type));
            }
        }
        /**
         * Filter the final list of options, available to a certain association field.
         *
         * @param array $options Unfiltered options items.
         * @param string $name Name of the association field.
         */
        $options = apply_filters('carbon_fields_association_field_options', $options, $this->get_base_name());
        return $options;
    }
    /**
     * Retrieve the edit link of a particular object.
     *
     * @param  string $type Object type.
     * @param  int $id      ID of the object.
     * @return string       URL of the edit link.
     */
    protected function get_object_edit_link($type, $id)
    {
        switch ($type['type']) {
            case 'post':
                $edit_link = get_edit_post_link($id);
                break;
            case 'term':
                $edit_link = get_edit_term_link($id, $type['taxonomy'], $type['type']);
                break;
            case 'comment':
                $edit_link = get_edit_comment_link($id);
                break;
            case 'user':
                $edit_link = get_edit_user_link($id);
                break;
            default:
                $edit_link = \false;
        }
        return $edit_link;
    }
    /**
     * Modify the types.
     *
     * @param  array $types New types
     * @return self  $this
     */
    public function set_types($types)
    {
        $this->types = $types;
        return $this;
    }
    /**
     * Get the minimum allowed number of selected entries.
     *
     * @return int
     */
    public function get_min()
    {
        return $this->min;
    }
    /**
     * Set the minimum allowed number of selected entries.
     *
     * @param  int   $min
     * @return self  $this
     */
    public function set_min($min)
    {
        $this->min = \intval($min);
        return $this;
    }
    /**
     * Get the maximum allowed number of selected entries.
     *
     * @return int
     */
    public function get_max()
    {
        return $this->max;
    }
    /**
     * Set the maximum allowed number of selected entries.
     *
     * @param  int   $max
     * @return self  $this
     */
    public function set_max($max)
    {
        $this->max = \intval($max);
        return $this;
    }
    /**
     * Get whether entry duplicates are allowed.
     *
     * @return boolean
     */
    public function get_duplicates_allowed()
    {
        return $this->duplicates_allowed;
    }
    /**
     * Set whether entry duplicates are allowed.
     *
     * @param  boolean $allowed
     * @return self    $this
     */
    public function set_duplicates_allowed($allowed)
    {
        $this->duplicates_allowed = $allowed;
        return $this;
    }
    /**
     * Specify whether to allow each entry to be selected multiple times.
     * Backwards-compatibility alias.
     *
     * @param  boolean $allow
     * @return self    $this
     */
    public function allow_duplicates($allow = \true)
    {
        return $this->set_duplicates_allowed($allow);
    }
    /**
     * Converts the field values into a usable associative array.
     *
     * The association data is saved in the database in the following format:
     * 	array (
     *		0 => 'post:page:4',
     *		1 => 'term:category:2',
     *		2 => 'user:user:1',
     * 	)
     * where the value of each array item contains:
     * 	- Type of data (post, term, user or comment)
     * 	- Subtype of data (the particular post type or taxonomy)
     * 	- ID of the item (the database ID of the item)
     */
    protected function value_to_json()
    {
        $value_set = $this->get_value();
        $value = array();
        foreach ($value_set as $entry) {
            $item = array('type' => $entry['type'], 'subtype' => $entry['subtype'], 'id' => \intval($entry['id']), 'title' => $this->get_title_by_type($entry['id'], $entry['type'], $entry['subtype']), 'label' => $this->get_item_label($entry['id'], $entry['type'], $entry['subtype']), 'is_trashed' => $entry['type'] == 'post' && get_post_status($entry['id']) === 'trash');
            $value[] = $item;
        }
        return $value;
    }
    /**
     * Convert the field data into JSON representation.
     * @param  bool $load Whether to load data from the datastore.
     * @return mixed      The JSON field data.
     */
    public function to_json($load)
    {
        $field_data = parent::to_json($load);
        $field_data = \array_merge($field_data, array('value' => $this->value_to_json(), 'options' => $this->get_options(), 'min' => $this->get_min(), 'max' => $this->get_max(), 'duplicates_allowed' => $this->duplicates_allowed));
        return $field_data;
    }
}
