felixarntz/post-types-definitely

View on GitHub
inc/WPPTD/Components/Taxonomy.php

Summary

Maintainability
F
3 days
Test Coverage
<?php
/**
 * WPPTD\Components\Taxonomy class
 *
 * @package WPPTD
 * @subpackage Components
 * @author Felix Arntz <felix-arntz@leaves-and-love.net>
 * @since 0.5.0
 */

namespace WPPTD\Components;

use WPPTD\App as App;
use WPPTD\Utility as Utility;
use WPPTD\TaxonomyTableHandler as TaxonomyTableHandler;
use WPDLib\Components\Manager as ComponentManager;
use WPDLib\Components\Base as Base;
use WPDLib\FieldTypes\Manager as FieldManager;
use WPDLib\Util\Error as UtilError;

if ( ! defined( 'ABSPATH' ) ) {
    die();
}

if ( ! class_exists( 'WPPTD\Components\Taxonomy' ) ) {
    /**
     * Class for a taxonomy component.
     *
     * This denotes a taxonomy within WordPress.
     *
     * @internal
     * @since 0.5.0
     */
    class Taxonomy extends Base {

        /**
         * @since 0.6.0
         * @var WPPTD\TaxonomyTableHandler Holds the list table handler instance for this taxonomy.
         */
        protected $table_handler = null;

        /**
         * @since 0.5.0
         * @var bool Stores whether this taxonomy has already been registered.
         */
        protected $registered = false;

        /**
         * @since 0.6.0
         * @var array Stores meta field keys that contain related posts.
         */
        protected $related_posts_fields = array();

        /**
         * @since 0.6.0
         * @var array Stores meta field keys that contain related terms.
         */
        protected $related_terms_fields = array();

        /**
         * @since 0.6.0
         * @var array Stores meta field keys that contain related users.
         */
        protected $related_users_fields = array();

        /**
         * Class constructor.
         *
         * @since 0.5.0
         * @param string $slug the taxonomy slug
         * @param array $args array of taxonomy properties
         */
        public function __construct( $slug, $args ) {
            parent::__construct( $slug, $args );
            $this->table_handler = new TaxonomyTableHandler( $this );
            $this->validate_filter = 'wpptd_taxonomy_validated';
        }

        /**
         * Returns the table handler for this taxonomy.
         *
         * @since 0.6.0
         * @return WPPTD\TaxonomyTableHandler the list table handler instance for this taxonomy
         */
        public function get_table_handler() {
            return $this->table_handler;
        }

        /**
         * Checks whether this taxonomy already exists in WordPress.
         *
         * @since 0.5.0
         * @return bool true if the taxonomy exists, otherwise false
         */
        public function is_already_added() {
            return taxonomy_exists( $this->slug );
        }

        /**
         * Registers the taxonomy.
         *
         * If the taxonomy already exists, some of the arguments will be merged into the existing taxonomy object.
         *
         * @since 0.5.0
         */
        public function register() {
            if ( $this->registered ) {
                return;
            }

            if ( ! $this->is_already_added() ) {
                $_taxonomy_args = $this->args;

                unset( $_taxonomy_args['title'] );
                unset( $_taxonomy_args['singular_title'] );
                unset( $_taxonomy_args['title_gender'] );
                unset( $_taxonomy_args['messages'] );
                unset( $_taxonomy_args['help'] );

                $_taxonomy_args['label'] = $this->args['title'];

                $taxonomy_args = array();
                foreach ( $_taxonomy_args as $key => $value ) {
                    if ( null !== $value ) {
                        $taxonomy_args[ $key ] = $value;
                    }
                }

                register_taxonomy( $this->slug, null, $taxonomy_args );
            } else {
                // merge several properties into existing taxonomy
                global $wp_taxonomies;

                if ( $this->args['labels'] ) {
                    // merge the slug as $name into the arguments (required for `get_taxonomy_labels()`)
                    $wp_taxonomies[ $this->slug ]->labels = get_taxonomy_labels( (object) array_merge( $this->args, array( 'name' => $this->slug ) ) );
                    $wp_taxonomies[ $this->slug ]->label = $wp_taxonomies[ $this->slug ]->labels->name;
                }
            }

            if ( wpptd_supports_termmeta() ) {
                add_action( 'wpptd_add_term_meta_boxes_' . $this->slug, array( $this, 'add_meta_boxes' ), 10, 1 );
            }

            $this->registered = true;
        }

        /**
         * Registers the meta for this post type.
         *
         * This method only works on WordPress >= 4.6 and therefore should only be called there.
         *
         * @since 0.6.5
         */
        public function register_meta() {
            foreach ( $this->get_children( 'WPPTD\Components\TermMetabox' ) as $metabox ) {
                foreach ( $metabox->get_children( 'WPPTD\Components\TermField' ) as $field ) {
                    $field->register( $metabox, $this );
                }
            }
        }

        /**
         * Registers the metaboxes for the taxonomy.
         *
         * @since 0.6.0
         * @see WPPTD\Components\TermMetabox::register()
         * @param WP_Term $term the current term object
         */
        public function add_meta_boxes( $term ) {
            foreach ( $this->get_children( 'WPPTD\Components\TermMetabox' ) as $metabox ) {
                $metabox->register( $this );
            }
        }

        /**
         * Validates and saves all meta field values.
         *
         * It will only do that if all requirements are met.
         *
         * @since 0.6.0
         * @param integer $term_id the current term ID
         * @param WP_Term $term the term object
         * @param bool $update whether the term is being updated (true) or generated (false)
         */
        public function save_meta( $term_id, $term, $update = false ) {
            if ( ! $this->can_save_meta( $term_id, $term ) ) {
                return;
            }

            $meta_values_validated = $this->validate_meta_values( $_POST, $term_id );

            foreach ( $meta_values_validated as $field_slug => $meta_value_validated ) {
                if ( is_array( $meta_value_validated ) ) {
                    delete_term_meta( $term_id, $field_slug );
                    foreach ( $meta_value_validated as $mv ) {
                        add_term_meta( $term_id, $field_slug, $mv );
                    }
                } else {
                    update_term_meta( $term_id, $field_slug, $meta_value_validated );
                }
            }
        }

        /**
         * Enqueues all the assets needed on the term editing screen of the taxonomy.
         *
         * @since 0.6.0
         * @see WPDLib\FieldTypes\Manager::enqueue_assets()
         */
        public function enqueue_assets() {
            $_fields = array();
            foreach ( $this->get_children( 'WPPTD\Components\TermMetabox' ) as $metabox ) {
                foreach ( $metabox->get_children() as $field ) {
                    $_fields[] = $field->_field;
                }
            }

            FieldManager::enqueue_assets( $_fields );
        }

        /**
         * Renders the help tabs and sidebar on the term editing screen of the taxonomy.
         *
         * @since 0.5.0
         */
        public function render_help() {
            Utility::render_help( get_current_screen(), $this->args['help'] );
        }

        /**
         * Renders the help tabs and sidebar on the terms list screen of the taxonomy.
         *
         * @since 0.5.0
         */
        public function render_list_help() {
            Utility::render_help( get_current_screen(), $this->args['list_help'] );
        }

        /**
         * Returns the custom term updated messages for this taxonomy.
         *
         * @since 0.5.0
         * @return array the custom messages
         */
        public function get_updated_messages() {
            return $this->args['messages'];
        }

        /**
         * Validates the arguments array.
         *
         * @since 0.5.0
         * @param WPPTD\Components\PostType $parent the parent component
         * @return bool|WPDLib\Util\Error an error object if an error occurred during validation, true if it was validated, false if it did not need to be validated
         */
        public function validate( $parent = null ) {
            $status = parent::validate( $parent );

            if ( $status === true ) {

                if ( in_array( $this->slug, array( 'post_format', 'link_category', 'nav_menu' ) ) ) {
                    return new UtilError( 'no_valid_taxonomy', sprintf( __( 'The taxonomy slug %s is forbidden since it would interfere with WordPress Core functionality.', 'post-types-definitely' ), $this->slug ), '', ComponentManager::get_scope() );
                }

                // show notice if slug contains dashes
                if ( strpos( $this->slug, '-' ) !== false ) {
                    App::doing_it_wrong( __METHOD__, sprintf( __( 'The taxonomy slug %s contains dashes which is discouraged. It will still work for the most part, but we recommend to adjust the slug if possible.', 'post-types-definitely' ), $this->slug ), '0.5.0' );
                }

                // generate titles if not provided
                $this->args = Utility::validate_titles( $this->args, $this->slug, 'taxonomy' );

                // generate taxonomy labels
                $this->args = Utility::validate_labels( $this->args, 'labels', 'taxonomy' );

                // generate taxonomy updated messages
                $this->args = Utility::validate_labels( $this->args, 'messages', 'taxonomy' );

                // set some defaults
                if ( null === $this->args['rewrite'] ) {
                    if ( $this->args['public'] ) {
                        $this->args['rewrite'] = array(
                            'slug'            => str_replace( '_', '-', $this->slug ),
                            'with_front'    => true,
                            'hierarchical'    => $this->args['hierarchical'],
                            'ep_mask'        => EP_NONE,
                        );
                    } else {
                        $this->args['rewrite'] = false;
                    }
                }

                // handle REST API default
                if ( null === $this->args['show_in_rest'] ) {
                    if ( null !== $this->args['public'] ) {
                        $this->args['show_in_rest'] = $this->args['public'];
                    } else {
                        $this->args['show_in_rest'] = false;
                    }
                }

                $this->args = Utility::validate_ui_args( $this->args );

                $this->args = Utility::validate_position_args( $this->args );

                // handle term table
                $this->args = TaxonomyTableHandler::validate_args( $this->args );

                // handle help
                $this->args = Utility::validate_help_args( $this->args, 'help' );

                // handle list help
                $this->args = Utility::validate_help_args( $this->args, 'list_help' );
            }

            return $status;
        }

        /**
         * Returns the keys of the arguments array and their default values.
         *
         * Read the plugin guide for more information about the taxonomy arguments.
         *
         * @since 0.5.0
         * @return array
         */
        protected function get_defaults() {
            $defaults = array(
                'title'              => '',
                'singular_title'     => '',
                'title_gender'       => 'n',
                'labels'             => array(),
                'messages'           => array(),
                'description'        => '',
                'public'             => true,
                'show_ui'            => null,
                'show_in_menu'       => null,
                'show_in_nav_menus'  => null,
                'show_tagcloud'      => null,
                'show_in_quick_edit' => null,
                'show_in_rest'       => null,
                'show_admin_column'  => false,
                'capabilities'       => array(),
                'hierarchical'       => false,
                'rewrite'            => null,
                'query_var'          => true,
                'sort'               => null,
                'position'           => null,
                'table_columns'      => array(),
                'row_actions'        => array(),
                'bulk_actions'       => array(),
                'help'               => array(
                    'tabs'               => array(),
                    'sidebar'            => '',
                ),
                'list_help'          => array(
                    'tabs'               => array(),
                    'sidebar'            => '',
                ),
            );

            /**
             * This filter can be used by the developer to modify the default values for each taxonomy component.
             *
             * @since 0.5.0
             * @param array the associative array of default values
             */
            return apply_filters( 'wpptd_taxonomy_defaults', $defaults );
        }

        /**
         * Returns whether this component supports multiple parents.
         *
         * @since 0.5.0
         * @return bool
         */
        protected function supports_multiparents() {
            return true;
        }

        /**
         * Returns whether this component supports global slugs.
         *
         * If it does not support global slugs, the function either returns false for the slug to be globally unique
         * or the class name of a parent component to ensure the slug is unique within that parent's scope.
         *
         * @since 0.5.0
         * @return bool|string
         */
        protected function supports_globalslug() {
            return false;
        }

        /**
         * Checks whether all requirements to save meta values for a term are met.
         *
         * @since 0.6.0
         * @param integer $term_id the term ID to save
         * @param WP_Term $term the term object
         * @return bool whether the meta values can be saved
         */
        protected function can_save_meta( $term_id, $term ) {
            if ( ! wpptd_supports_termmeta() ) {
                return false;
            }

            if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
                return false;
            }

            if ( wpptd_get_taxonomy( $term_id ) != $this->slug ) {
                return false;
            }

            $taxonomy_obj = get_taxonomy( $this->slug );
            if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) {
                return false;
            }

            return true;
        }

        /**
         * Validates a term's meta values for all meta fields of the taxonomy.
         *
         * It iterates through all the meta fields of the term and validates each one's value.
         * If a field is not set for some reason, its default value is saved.
         *
         * Furthermore this function adds settings errors if any occur.
         *
         * @since 0.6.0
         * @param array $meta_values array of submitted meta values
         * @param integer $term_id the current term ID
         * @return array the validated meta values
         */
        protected function validate_meta_values( $meta_values, $term_id ) {
            $meta_values_validated = array();

            $meta_values_old = array();

            $errors = array();

            $changes = false;

            foreach ( $this->get_children( 'WPPTD\Components\TermMetabox' ) as $metabox ) {
                foreach ( $metabox->get_children() as $field ) {
                    $meta_value_old = wpptd_get_term_meta_value( $term_id, $field->slug );
                    if ( $meta_value_old === null ) {
                        $meta_value_old = $field->default;
                    }
                    $meta_values_old[ $field->slug ] = $meta_value_old;

                    $meta_value = null;
                    if ( isset( $meta_values[ $field->slug ] ) ) {
                        $meta_value = $meta_values[ $field->slug ];
                    }

                    list( $meta_value_validated, $error, $changed ) = $this->validate_meta_value( $field, $meta_value, $meta_value_old );

                    $meta_values_validated[ $field->slug ] = $meta_value_validated;
                    if ( $error ) {
                        $errors[ $field->slug ] = $error;
                    } elseif ( $changed ) {
                        $changes = true;
                    }
                }
            }

            if ( $changes ) {
                /**
                 * This action can be used to perform additional steps when the meta values of this taxonomy were updated.
                 *
                 * @since 0.6.0
                 * @param array the updated meta values as $field_slug => $value
                 * @param array the previous meta values as $field_slug => $value
                 */
                do_action( 'wpptd_update_term_meta_values_' . $this->slug, $meta_values_validated, $meta_values_old );
            }

            /**
             * This filter can be used by the developer to modify the validated meta values right before they are saved.
             *
             * @since 0.6.0
             * @param array the associative array of meta keys (fields slugs) and their values
             */
            $meta_values_validated = apply_filters( 'wpptd_validated_term_meta_values_' . $this->slug, $meta_values_validated );

            $this->add_settings_message( $errors, $term_id );

            return $meta_values_validated;
        }

        /**
         * Validates a meta value.
         *
         * @since 0.6.0
         * @param WPPTD\Components\TermField $field field object to validate the meta value for
         * @param mixed $meta_value the meta value to validate
         * @param mixed $meta_value_old the previous meta value
         * @return array an array containing the validated value, a variable possibly containing a WP_Error object and a boolean value whether the meta value has changed
         */
        protected function validate_meta_value( $field, $meta_value, $meta_value_old ) {
            $meta_value = $field->validate_meta_value( $meta_value );
            $error = false;
            $changed = false;

            if ( is_wp_error( $meta_value ) ) {
                $error = $meta_value;
                $meta_value = $meta_value_old;
            } elseif ( $meta_value != $meta_value_old ) {
                /**
                 * This action can be used to perform additional steps when the meta value for a specific field of this taxonomy has been updated.
                 *
                 * @since 0.6.0
                 * @param mixed the updated meta value
                 * @param mixed the previous meta value
                 */
                do_action( 'wpptd_update_term_meta_value_' . $this->slug . '_' . $field->slug, $meta_value, $meta_value_old );

                $changed = true;
            }

            return array( $meta_value, $error, $changed );
        }

        /**
         * Adds settings errors and/or updated messages for the current term of this taxonomy.
         *
         * @since 0.6.0
         * @param array $errors an array (possibly) containing validation errors as $field_slug => $wp_error
         * @param integer $term_id the ID of the current term
         */
        protected function add_settings_message( $errors, $term_id ) {
            if ( count( $errors ) > 0 ) {
                $error_text = __( 'Some errors occurred while trying to save the following term meta:', 'post-types-definitely' );
                foreach ( $errors as $field_slug => $error ) {
                    $error_text .= '<br/><em>' . $field_slug . '</em>: ' . $error->get_error_message();
                }

                set_transient( 'wpptd_term_meta_error_' . $this->slug . '_' . $term_id, $error_text, 120 );
            }
        }

    }

}