<?php

/**
 * @package     MultipleAuthors
 * @author      PublishPress <help@publishpress.com>
 * @copyright   Copyright (C) 2018 PublishPress. All rights reserved.
 * @license     GPLv2 or later
 * @since       1.1.0
 */

namespace MultipleAuthors\Classes;

use MultipleAuthors\Classes\Legacy\Util;
use MultipleAuthors\Classes\Objects\Author;
use MultipleAuthors\Factory;
use MA_Author_Boxes;
use stdClass;
use WP_Error;

/**
 * Utility methods for managing authors
 *
 * Based on Bylines.
 *
 * @package MultipleAuthors\Classes
 *
 */
class Utils
{
    const USER_BY_SLUG_CACHE_GROUP = 'publishpress-authors-user-by-slug';

    /**
     * @var array
     */
    protected static $supported_post_types = [];

    /**
     * @var array
     */
    protected static $pages_whitelist = [
        'post.php',
        'post-new.php',
        'edit.php',
        'edit-tags.php',
        'term.php',
        'admin.php',
    ];

    /**
     * @var array
     */
    private static $enabledPostTypes = null;

    /**
     * @var string
     */
    protected static $defaultLayout = null;

    /**
     * Convert co-authors to authors on a post.
     *
     * Errors if the post already has authors. To re-convert, remove authors
     * from the post.
     *
     * @param int $post_id ID for the post to convert.
     *
     * @return object|WP_Error Result object if successful; WP_Error on error.
     */
    public static function convert_post_coauthors($post_id)
    {
        if (! function_exists('get_coauthors')) {
            return new WP_Error(
                'authors_missing_cap',
                __('Co-Authors Plus must be installed and active.', 'publishpress-authors')
            );
        }
        $post = get_post($post_id);
        if (! $post) {
            return new WP_Error('authors_missing_post', "Invalid post: {$post_id}");
        }
        $authors = get_the_terms($post_id, 'author');
        if ($authors && ! is_wp_error($authors)) {
            return new WP_Error('authors_post_has_authors', "Post {$post_id} already has authors.");
        }
        $authors = [];
        $result = new stdClass();
        $result->created = 0;
        $result->existing = 0;
        $result->post_id = 0;
        $coauthors = get_coauthors($post_id);
        foreach ($coauthors as $coauthor) {
            switch ($coauthor->type) {
                case 'wpuser':
                    $author = Author::get_by_user_id($coauthor->ID);
                    if ($author) {
                        $authors[] = $author;
                        $result->existing++;
                    } else {
                        $author = Author::create_from_user($coauthor->ID);
                        if (is_wp_error($author)) {
                            return $author;
                        }
                        $authors[] = $author;
                        $result->created++;
                    }
                    break;
                case 'guest-author':
                    $author = Author::get_by_term_slug($coauthor->user_nicename);
                    if ($author) {
                        $authors[] = $author;
                        $result->existing++;
                    } else {
                        $args = [
                            'display_name' => $coauthor->display_name,
                            'slug' => $coauthor->user_nicename,
                        ];
                        $author = Author::create($args);
                        if (is_wp_error($author)) {
                            return $author;
                        }
                        $ignored = [
                            'ID',
                            'display_name',
                            'user_nicename',
                            'user_login',
                        ];
                        foreach ($coauthor as $key => $value) {
                            if (in_array($key, $ignored, true)) {
                                continue;
                            }
                            if ('linked_account' === $key) {
                                $key = 'user_id';
                                $user = get_user_by('login', $value);
                                $value = $user ? $user->ID : '';
                            }
                            if ('' !== $value) {
                                update_term_meta($author->term_id, $key, $value);
                            }
                        }
                        $authors[] = $author;
                        $result->created++;
                    }
                    break;
            } // End switch().
        } // End foreach().
        if (empty($authors) || count($coauthors) !== count($authors)) {
            return new WP_Error(
                'authors_post_missing_coauthors',
                "Failed to convert some authors for post {$post_id}."
            );
        }

        Utils::set_post_authors($post_id, $authors, true);

        do_action('publishpress_authors_flush_cache_for_post', $post_id);

        return $result;
    }

    /**
     * Set the authors for a post
     *
     * @param int $postId ID for the post to modify.
     * @param array $authors Bylines to set on the post.
     * @param bool $syncPostAuthor
     * @param int $fallbackUserId User ID for using as the author in case no author or if only guests are selected
     * @param array $categories for authors
     */
    public static function set_post_authors($postId, $authors, $syncPostAuthor = false, $fallbackUserId = null, $author_categories = [])
    {
        $post_type = get_post_field('post_type', $postId);
        $enabledPostTypes = Utils::get_enabled_post_types();

        if ($post_type && !in_array($post_type, $enabledPostTypes)) {
            return;
        }

        static::set_post_authors_name_meta($postId, $authors);

        if ($syncPostAuthor) {
            /**
             * TODO:
             * At this point, this is becoming an issue, we;ve received numerous report
             * on multiple query on page load including #2119 that reports +1,200 insert
             * on media page, and a report on some cache clearing on everypage load affecting performance.
             * I'll need to monitor the effect of this function and see any alternative that's more efficient.
             *
             * At least, we shouldn't call this function on post update again and $syncPostAuthor parameter
             * should be optional.
             */
            static::sync_post_author_column($postId, $authors, $fallbackUserId);
        }

        $authors = wp_list_pluck($authors, 'term_id');
        self::updatePostAuthorCategory($postId, $authors, $author_categories);
        wp_set_object_terms($postId, $authors, 'author');
    }

    /**
     * Update post author category
     *
     * @param integer $post_id
     * @param array $authors
     * @param array $post_author_categories
     *
     * @return void
     */
    public static function updatePostAuthorCategory($post_id, $authors, $post_author_categories) {
        global $wpdb;

        if (!current_user_can(get_taxonomy('author')->cap->assign_terms)) {
            return;
        }

        $table_name = $wpdb->prefix . 'ppma_author_relationships';
        $authors    = array_values(array_unique(array_map('intval', $authors)));

        if (empty($authors)) {
            $wpdb->delete($table_name, ['post_id' => $post_id], ['%d']);
            do_action('publishpress_authors_flush_cache_for_post', $post_id);
            return;
        }

        if (empty(array_filter(array_values($post_author_categories)))) {
            return;
        }

        $allow_multiple_categories = self::isAuthorMultipleCategoriesEnabled();
        $post_author_categories    = array_filter($post_author_categories);
        $author_category_map       = [];

        foreach ($post_author_categories as $author_id => $category_ids) {
            $author_id = (int) $author_id;

            if (empty($author_id)) {
                continue;
            }

            $category_ids = (array) $category_ids;
            $category_ids = array_values(array_unique(array_filter(array_map('intval', $category_ids))));

            if (empty($category_ids)) {
                continue;
            }

            if (!$allow_multiple_categories) {
                $category_ids = [reset($category_ids)];
            }

            $author_category_map[$author_id] = $category_ids;
        }

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

        if (!$allow_multiple_categories) {
            $existing_relations = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT category_id, author_term_id FROM {$table_name} WHERE post_id = %d ORDER BY id ASC",
                    $post_id
                ),
                ARRAY_A
            );

            $existing_author_category_map = [];
            foreach ($existing_relations as $existing_relation) {
                $existing_author_id  = isset($existing_relation['author_term_id']) ? (int) $existing_relation['author_term_id'] : 0;
                $existing_category_id = isset($existing_relation['category_id']) ? (int) $existing_relation['category_id'] : 0;

                if (empty($existing_author_id) || empty($existing_category_id) || !in_array($existing_author_id, $authors, true)) {
                    continue;
                }

                $existing_author_category_map[$existing_author_id][] = $existing_category_id;
            }

            foreach ($author_category_map as $author_id => $category_ids) {
                $primary_category_id = (int) reset($category_ids);
                $existing_category_ids = isset($existing_author_category_map[$author_id]) ? $existing_author_category_map[$author_id] : [];

                if (!empty($existing_category_ids)) {
                    array_shift($existing_category_ids);
                }

                $author_category_map[$author_id] = array_values(array_unique(array_filter(array_merge(
                    [$primary_category_id],
                    $existing_category_ids
                ))));
            }
        }


        $all_author_categories = get_ppma_author_categories([]);
        $all_author_category_ids = array_column($all_author_categories, 'id');

        // Make 'id' the array index
        $all_author_categories = array_combine($all_author_category_ids, $all_author_categories);

        // Make sure there's no relationship for authors that could have been possibly removed
        $wpdb->delete($table_name, ['post_id' => $post_id], ['%d']);

        if (!empty($authors)) {
            foreach ($authors as $author) {
                if (isset($author_category_map[$author])) {
                    foreach ($author_category_map[$author] as $category_id) {
                        if (!isset($all_author_categories[$category_id])) {
                            continue;
                        }

                        $wpdb->insert(
                            $table_name,
                            [
                                'category_id'       => $all_author_categories[$category_id]['id'],
                                'category_slug'     => $all_author_categories[$category_id]['slug'],
                                'post_id'           => $post_id,
                                'author_term_id'    => $author,
                            ],
                            [
                                '%d',
                                '%s',
                                '%d',
                                '%d',
                            ]
                        );
                    }
                }
            }
        }
        do_action('publishpress_authors_flush_cache_for_post', $post_id);
    }

    public static function detect_author_slug_mismatch($limit = 0)
    {
        global $wpdb;

        $query = "SELECT t.term_id, u.user_nicename
                FROM $wpdb->terms AS t
                INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id
                INNER JOIN $wpdb->termmeta AS tm ON tm.term_id = tt.term_id
                INNER JOIN $wpdb->users AS u ON u.ID = tm.meta_value
                WHERE
                    tt.taxonomy = 'author'
                    AND tm.meta_key = 'user_id'
                    AND u.user_nicename != t.slug
                ORDER BY t.term_id ASC";

        $limit = absint($limit);

        if ($limit > 0) {
            $query .= $wpdb->prepare(' LIMIT %d', $limit);
        }

        $results = $wpdb->get_results($query); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

        return $results;
    }

    public static function sync_author_slug_to_user_nicename($authors = false)
    {
        global $wpdb;

        if (false === $authors) {
            $authors = static::detect_author_slug_mismatch();
        }

        foreach ($authors as $row) {
            $wpdb->update($wpdb->terms, ['slug' => $row->user_nicename], ['term_id' => $row->term_id]);
        }
    }

    /**
     * @param int $postId ID for the post to modify.
     * @param array $authors Bylines to set on the post.
     * @param int|null $fallbackUserId User ID for using as the author in case no author or if only guests are selected
     */
    public static function sync_post_author_column($postId, $authors, $fallbackUserId = null)
    {

        $functionSetPostAuthor = function ($postId, $authorId) {
            global $wpdb;

            // Avoid corrupting the post_author with an empty value.
            if (empty((int)$authorId)) {
                return false;
            }

            $wpdb->update(
                $wpdb->posts,
                [
                    'post_author' => (int)$authorId,
                ],
                [
                    'ID' => $postId,
                ]
            );
            clean_post_cache($postId);

            return true;
        };

        $postAuthorHasChanged = false;
        if (!empty($authors)) {
            foreach ($authors as $author) {
                if (
                    ! is_object($author)
                    || is_wp_error($author)
                    || empty($author)
                ) {
                    continue;
                }

                $isGuest = (method_exists($author, 'is_guest') && $author->is_guest());

                if ($isGuest) {
                    continue;
                }

                if ($functionSetPostAuthor($postId, $author->user_id)) {
                    $postAuthorHasChanged = true;
                }

                break;
            }
        }

        if (! $postAuthorHasChanged) {
            $fallbackUserId = (int)$fallbackUserId;

            if (! empty($fallbackUserId)) {
                $functionSetPostAuthor($postId, $fallbackUserId);
            }

            // Check if the post has any author set. If not an existent author, create one and set the author term.
            $post = get_post($postId);

            if (empty($authors) && ! empty($post->post_author)) {
                $author = Author::get_by_user_id($post->post_author);

                if (empty($author)) {
                    $author = Author::create_from_user($post->post_author);
                }

                if (is_object($author) && ! is_wp_error($author)) {
                    Utils::set_post_authors($postId, [$author], false);
                }
            } elseif ($fallbackUserId !== (int)$post->post_author || empty($fallbackUserId)) {
                $functionSetPostAuthor($postId, get_current_user_id());
            }
        }
    }

    /**
     * Save a metadata with the post authors' name. Add compatibility to
     * Yoast for using in the custom title, and other 3rd party plugins.
     *
     * @param $post_id
     * @param $authors
     */
    public static function set_post_authors_name_meta($post_id, $authors)
    {
        if (! is_array($authors)) {
            $authors = [];
        }

        $metadata = 'ppma_authors_name';

        if (empty($authors)) {
            delete_post_meta($post_id, $metadata);
        } else {
            $names = [];

            foreach ($authors as $author) {
                if (! is_object($author) && is_numeric($author)) {
                    $author = Author::get_by_term_id($author);
                }

                if (is_object($author)) {
                    $names[] = $author->name;
                }
            }

            if (! empty($names)) {
                $names = implode(', ', $names);

                update_post_meta($post_id, $metadata, $names);
            }
        }
    }

    /**
     * Helper to only add javascript to necessary pages. Avoids bloat in admin.
     *
     * @return bool
     */
    public static function is_valid_page()
    {
        global $pagenow;


        $valid = (bool)in_array($pagenow, self::$pages_whitelist);

        if (! $valid) {
            return false;
        }

        if (in_array($pagenow, ['edit-tags.php', 'term.php'])) {
            $taxonomy = isset($_GET['taxonomy']) ? sanitize_text_field($_GET['taxonomy']) : null;

            if ('author' !== $taxonomy) {
                return false;
            }
        } elseif (in_array($pagenow, ['admin.php'])) {
            if (isset($_GET['page']) && $_GET['page'] === 'ppma-modules-settings' && isset($_GET['module']) && $_GET['module'] === 'multiple-authors-settings') {
                return true;
            }
        } else {
            return self::is_post_type_enabled() && self::current_user_can_set_authors();
        }

        return true;
    }

    /**
     * Whether or not PublishPress Authors is enabled for this post type
     * Must be called after init
     *
     * @param string $postType The name of the post type we're considering
     *
     * @return bool Whether or not it's enabled
     * @since 3.0
     *
     */
    public static function is_post_type_enabled($postType = null)
    {
        $legacyPlugin = Factory::getLegacyPlugin();

        if (empty(self::$supported_post_types)) {
            self::$supported_post_types = self::get_post_types_that_support_authors();
        }

        if (empty($postType)) {
            $postType = Util::getCurrentPostType();
        }

        $isSupported = (bool)in_array($postType, self::$supported_post_types);

        if (! $isSupported) {
            return false;
        }

        $enabledPostTypes = self::get_enabled_post_types();

        return (bool)in_array($postType, $enabledPostTypes);
    }

    public static function get_enabled_post_types()
    {
        $legacyPlugin = Factory::getLegacyPlugin();

        if (empty(self::$enabledPostTypes)) {
            if (isset($legacyPlugin->modules->multiple_authors)) {
                $post_types = Util::get_post_types_for_module($legacyPlugin->modules->multiple_authors);
            } else {
                $post_types = [];
            }
            self::$enabledPostTypes = $post_types;
        }

        return self::$enabledPostTypes;
    }

    private static function get_post_types_to_force_authors_support()
    {
        $postTypesToForceAuthorsSupport = [
            // LearnPress.
            'lp_course',
            'lp_lesson',
            'lp_quiz',
            // WooCommerce.
            'product',
        ];

        $postTypesToForceAuthorsSupport = apply_filters(
            'publishpress_authors_post_types_to_force_author_support',
            $postTypesToForceAuthorsSupport
        );

        return $postTypesToForceAuthorsSupport;
    }

    /**
     * Returns a list of post types which supports authors.
     */
    public static function get_post_types_that_support_authors()
    {
        if (empty(self::$supported_post_types)) {
            // Get the post types which supports authors
            $post_types_with_authors = array_values(get_post_types());
            // Get post types which doesn't support authors, but should support Multiple Authors.
            $thirdPartySupport = self::get_post_types_to_force_authors_support();


            foreach ($post_types_with_authors as $key => $name) {
                // Ignore some 3rd party post types.
                if (in_array($name, $thirdPartySupport)) {
                    continue;
                }

                if (! post_type_supports($name, 'author') || in_array($name, ['revision', 'attachment'])) {
                    unset($post_types_with_authors[$key]);
                }
            }

            /**
             * @depreacted
             *
             * @param array $post_types_with_authors Post types that support authors.
             */
            self::$supported_post_types = apply_filters_deprecated(
                'coauthors_supported_post_types',
                [$post_types_with_authors],
                '3.5.0',
                'publishpress_authors_supported_post_types'
            );

            /**
             * Modify post types that use authors.
             *
             * @depreacted
             *
             * @param array $post_types_with_authors Post types that support authors.
             */
            self::$supported_post_types = apply_filters_deprecated(
                'authors_post_types',
                [self::$supported_post_types],
                '3.5.0',
                'publishpress_authors_supported_post_types'
            );

            /**
             * Modify post types that use authors.
             *
             * @param array $post_types_with_authors Post types that support authors.
             */
            self::$supported_post_types = apply_filters(
                'publishpress_authors_supported_post_types',
                self::$supported_post_types
            );
        }


        return self::$supported_post_types;
    }

    /**
     * Checks to see if the current user can set authors or not
     *
     * @param null $post
     *
     * @return bool|mixed|void
     */
    public static function current_user_can_set_authors($post = null)
    {
        if (empty($post)) {
            $post = get_post();
            if (empty($post)) {
                if (isset($_GET['post'])) {
                    $post = get_post((int)$_GET['post']);
                } else {
                    return false;
                }
            }
        }

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

        $post_type = $post->post_type;

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

        $current_user = wp_get_current_user();

        if (empty($current_user)) {
            return false;
        }
        // Super admins can do anything
        if (function_exists('is_super_admin') && is_super_admin()) {
            return true;
        }

        $taxonomy = get_taxonomy('author');
        if ($taxonomy !== false && current_user_can($taxonomy->cap->assign_terms)) {
            $can_set_authors = true;
        } else {
            $can_set_authors = isset($current_user->allcaps['edit_others_posts']) ? $current_user->allcaps['edit_others_posts'] : false;
        }

        return apply_filters('coauthors_plus_edit_authors', $can_set_authors);
    }

    /**
     * Written because WP is_plugin_active() requires plugin folder in arg
     *
     * @param string $check_plugin_file
     *
     * @return bool|mixed
     */
    public static function isPluginActive($check_plugin_file)
    {
        if (! is_multisite()) {
            $plugins = (array)get_option('active_plugins');
            foreach ($plugins as $plugin_file) {
                if (false !== strpos($plugin_file, $check_plugin_file)) {
                    return $plugin_file;
                }
            }
        } else {
            $plugins = (array)get_site_option('active_sitewide_plugins');

            // network activated plugin names are array keys
            foreach (array_keys($plugins) as $plugin_file) {
                if (false !== strpos($plugin_file, $check_plugin_file)) {
                    return $plugin_file;
                }
            }
        }

        return false;
    }

    public static function getAuthorTaxonomyPostTypes()
    {
        $taxonomy = get_taxonomy('author');
        $postTypes = $taxonomy->object_type;

        if (!$postTypes || !is_array($postTypes)) {
            return [];
        }

        if (($keyToUnset = array_search('customize_changeset', $postTypes)) !== false) {
            unset($postTypes[$keyToUnset]);
        }

        return $postTypes;
    }

    public static function isBylineInstalled()
    {
        return function_exists('byline');
    }

    public static function isBylinesInstalled()
    {
        return defined('BYLINES_VERSION') || class_exists('\\Bylines\\Objects\\Byline');
    }

    public static function isDebugActivated()
    {
        return isset($_GET['authors_debug']) && (int)$_GET['authors_debug'] === 1;
    }

    public static function isDiviInstalled()
    {
        if (! function_exists('et_get_theme_version')) {
            return false;
        }

        if (! defined('ET_CORE')) {
            return false;
        }

        if (version_compare(et_get_theme_version(), '4.4.4', '<')) {
            return false;
        }

        return true;
    }

    public static function isEditflowInstalled()
    {
        return defined('EDIT_FLOW_VERSION') && defined('EDIT_FLOW_ROOT');
    }

    public static function isElementorInstalled()
    {
        // For now we only integrate with the Pro version because the Free one doesn't have the posts modules.

        if (! defined('ELEMENTOR_PRO_VERSION')) {
            return false;
        }

        if (version_compare(ELEMENTOR_PRO_VERSION, '2.9.3', '<')) {
            return false;
        }

        $abort = false;

        $requiredClasses = [
            '\\ElementorPro\\Modules\\Posts\\Skins\\Skin_Base',
            '\\ElementorPro\\Modules\\Posts\\Skins\\Skin_Cards',
            '\\ElementorPro\\Modules\\Posts\\Skins\\Skin_Classic',
            '\\ElementorPro\\Modules\\Posts\\Skins\\Skin_Full_Content',
            '\\ElementorPro\\Modules\\ThemeBuilder\\Skins\\Posts_Archive_Skin_Cards',
            '\\ElementorPro\\Modules\\ThemeBuilder\\Skins\\Posts_Archive_Skin_Classic',
            '\\ElementorPro\\Modules\\ThemeBuilder\\Skins\\Posts_Archive_Skin_Full_Content',
        ];

        foreach ($requiredClasses as $className) {
            if (! class_exists($className)) {
                if (defined('WP_DEBUG') && WP_DEBUG) {
                    error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
                        sprintf(
                            '[PublishPress Authors] Elementor module did not find the class %s',
                            $className
                        )
                    );
                }

                $abort = true;
            }
        }

        $requiredTraits = [
            '\\ElementorPro\\Modules\\ThemeBuilder\\Skins\\Posts_Archive_Skin_Base',
            '\\ElementorPro\\Modules\\Posts\\Skins\\Skin_Content_Base',
        ];

        foreach ($requiredTraits as $traitName) {
            if (! trait_exists($traitName)) {
                if (defined('WP_DEBUG') && WP_DEBUG) {
                    error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
                        sprintf(
                            '[PublishPress Authors] Elementor module did not find the trait %s',
                            $traitName
                        )
                    );
                }

                $abort = true;
            }
        }

        if ($abort) {
            return false;
        }

        return true;
    }

    public static function isPolylangInstalled()
    {
        return defined('POLYLANG_BASENAME');
    }

    public static function isGenesisInstalled()
    {
        return function_exists('genesis_autoload_register');
    }

    public static function isUltimatePostInstalled()
    {
        return defined('ULTP_BASE');
    }

    public static function isUltimateMemberInstalled()
    {
        return class_exists('UM_Functions');
    }

    public static function isAllInOneSeoPackInstalled()
    {
        return defined('AIOSEO_DIR');
    }

    public static function isCompatibleYoastSeoInstalled()
    {
        if (! defined('WPSEO_VERSION')) {
            return false;
        }

        if (! defined('WPSEO_FILE')) {
            return false;
        }

        if (! class_exists('Yoast\\WP\\SEO\\Config\\Schema_IDs')) {
            return false;
        }

        if (version_compare(WPSEO_VERSION, '14.1', '<')) {
            if (! get_transient('publishpress_authors_not_compatible_yoast_warning')) {
                if (defined('WP_DEBUG') && WP_DEBUG) {
                    error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
                        sprintf(
                            '[PublishPress Authors] %s %s - %s. %s',
                            __METHOD__,
                            'detected a not supported version of the Yoast SEO plugin',
                            WPSEO_VERSION,
                            'It requires 13.4.1 or later. Please, update it'
                        )
                    );
                }

                set_transient('publishpress_authors_not_compatible_yoast_warning', true, 24 * 60 * 60 * 2);
            }

            return false;
        }

        return true;
    }

    public static function isMolonguiAuthorshipActivated()
    {
        return defined('MOLONGUI_AUTHORSHIP_VERSION');
    }

    public static function getDefaultLayout()
    {
        if (! is_null(self::$defaultLayout)) {
            return self::$defaultLayout;
        }

        $args = [
            'name'           => 'author_boxes_boxed',
            'post_type'      => MA_Author_Boxes::POST_TYPE_BOXES,
            'post_status'    => 'publish',
            'posts_per_page' => 1
        ];
        $default_post = get_posts($args);

        if (!empty($default_post)) {
            $default_layout = MA_Author_Boxes::POST_TYPE_BOXES . '_' . $default_post[0]->ID;
        } else {
            $default_layout = 'boxed';
        }

        self::$defaultLayout = apply_filters('pp_multiple_authors_default_layout', $default_layout);

        return self::$defaultLayout;
    }

    public static function isWPEngineInstalled()
    {
        return class_exists('WpeCommon');
    }

    public static function getUserBySlug($slug)
    {
        $found = null;
        $user = wp_cache_get($slug, static::USER_BY_SLUG_CACHE_GROUP, false, $found);
        if (false === $user && false !== $found) {
            $user = get_user_by('slug', $slug);

            wp_cache_add($slug, $user, static::USER_BY_SLUG_CACHE_GROUP);
        }

        if (is_wp_error($user)) {
            $user = false;
        }

        return $user;
    }

    public static function isTheSEOFrameworkInstalled()
    {
        return defined('THE_SEO_FRAMEWORK_VERSION');
    }

    public static function isAuthorOfPost($postId, $author)
    {
        $postAuthors = get_post_authors($postId);
        if (empty($postAuthors)) {
            return false;
        }

        if (is_numeric($author)) {
            $author = Author::get_by_id($author);
        } elseif (is_string($author)) {
            $author = Author::get_by_term_slug($author);
        }

        if (! is_object($author)) {
            return false;
        }

        foreach ($postAuthors as $postAuthor) {
            if ($postAuthor->ID === $author->ID) {
                return true;
            }
        }

        return false;
    }

    public static function sanitizeArray($array)
    {
        $sanitizedArray = [];

        foreach ($array as $key => $value) {
            $key = sanitize_key($key);

            if (is_array($value)) {
                $sanitizedArray[$key] = self::sanitizeArray($value);
                continue;
            }

            $sanitizedArray[$key] = sanitize_text_field($value);
        }

        return $sanitizedArray;
    }

    /**
     * Helper function to check if template exist in theme/child theme.
     * We couldn't use wordpress locate_template() as it support theme compact which load
     * default template for files like sidebar.php even if it doesn't exist in theme
     *
     * @param array $template
     * @return mixed
     */
    public static function authors_locate_template($template_names)
    {
        $template = false;

        foreach ((array) $template_names as $template_name) {
            if (!$template_name ) {
                continue;
            }
            if (file_exists(STYLESHEETPATH . '/' . $template_name)) {
                $template = STYLESHEETPATH . '/' . $template_name;
                break;
            } elseif (file_exists(TEMPLATEPATH . '/' . $template_name)) {
                $template = TEMPLATEPATH . '/' . $template_name;
                break;
            }
        }

        return $template;
    }

    /**
     * Get article excerpt
     *
     * @param integer $limit
     * @param string $source
     * @param boolean $echo
     * @param boolean $read_more_link
     * @return string
     */
    public static function ppma_article_excerpt($limit, $source = null, $echo = false, $read_more_link = false)
    {
        $legacyPlugin = Factory::getLegacyPlugin();

        $excerpt = ($source === "content") ? get_the_content() : get_the_excerpt();

        $author_post_excerpt_ellipsis  = $legacyPlugin->modules->multiple_authors->options->author_post_excerpt_ellipsis;

        if (empty(trim($excerpt))) {
            $excerpt = get_the_content();
        }
        $excerpt = preg_replace(" (\[.*?\])",'',$excerpt);
        $excerpt = strip_shortcodes($excerpt);
        $excerpt = wp_strip_all_tags($excerpt);

        // If the string length exceeds $limit, we look for the nearest space
        $excerpt = strlen($excerpt) > $limit ? substr($excerpt, 0, strpos($excerpt . ' ', ' ', $limit)) : $excerpt;

        if (!empty($author_post_excerpt_ellipsis) && !empty(trim($excerpt))) {
            $excerpt .= $author_post_excerpt_ellipsis;
        }

        if ($read_more_link) {
            $excerpt .= ' <a class="read-more" href="'. esc_url(get_permalink()) .'" title="'. esc_attr__('Read more.', 'publishpress-authors') .'">'. esc_html__('Read more.', 'publishpress-authors') .'</a>';
        }

        if ($echo) {
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
            echo $excerpt;
        } else {
            return $excerpt;
        }
    }

    /**
     * Check if current active theme is block theme/support full site editing
     *
     * @return bool
     */
    public static function ppma_is_block_theme()
    {

        $is_block_theme = false;

        if (function_exists('wp_is_block_theme')
            && function_exists('block_template_part')
            && wp_is_block_theme()
        ) {
            $is_block_theme = true;
        }

        return $is_block_theme;
    }

    /**
     * Retreive block theme header
     *
     * @return string
     */
    public static function ppma_get_block_theme_header()
    {

        $block_theme_header = '';

        if (self::ppma_is_block_theme()) {
            $header_template_part = get_block_template(get_stylesheet() . '//header', 'wp_template_part');
            if ($header_template_part && isset($header_template_part->content)) {
                $block_theme_header = do_blocks($header_template_part->content);
            }
        }

        return $block_theme_header;
    }

    /**
     * Retreive block theme footer
     *
     * @return string
     */
    public static function ppma_get_block_theme_footer()
    {

        $block_theme_footer = '';

        if (self::ppma_is_block_theme()) {
            $footer_template_part = get_block_template(get_stylesheet() . '//footer', 'wp_template_part');
            if ($footer_template_part && isset($footer_template_part->content)) {
                $block_theme_footer = do_blocks($footer_template_part->content);
            }
        }

        return $block_theme_footer;
    }

    /**
     * Format block theme header
     *
     * @return void
     */
    public static function ppma_format_block_theme_header()
    {
        $fse_header = self::ppma_get_block_theme_header();
        $fse_footer = self::ppma_get_block_theme_footer();//we need to get footer as well before wp_head() call to enable fse css generator
        ?>
        <!doctype html>
        <html <?php language_attributes(); ?>>
        <head>
             <meta charset="<?php bloginfo('charset'); ?>">
             <?php wp_head(); ?>
        </head>
        <body <?php body_class(); ?>>
        <?php wp_body_open(); ?>
        <div class="wp-site-blocks">
        <?php echo $fse_header; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    }

    /**
     * Format block theme footer
     *
     * @return void
     */
    public static function ppma_format_block_theme_footer()
    {
        $fse_footer = self::ppma_get_block_theme_footer();
        ?>
        </div>
        <?php echo $fse_footer; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
        wp_footer();
        ?>
            </body>
        </html>
        <?php
    }

    /**
     * Check if Author's pro is active
     */
    public static function isAuthorsProActive()
    {
        if (class_exists('PPAuthorsPro\\Plugin')) {
            return true;
        }

        return false;
    }

    /**
     * Get Select2 strings translated by WordPress.
     *
     * @return array
     */
    public static function getSelect2I18n()
    {
        return [
            'error_loading'           => __('The results could not be loaded.', 'publishpress-authors'),
            /* translators: %d: number of characters over the maximum. */
            'input_too_long_single'   => __('Please delete %d character', 'publishpress-authors'),
            /* translators: %d: number of characters over the maximum. */
            'input_too_long_plural'   => __('Please delete %d characters', 'publishpress-authors'),
            /* translators: %d: number of characters still required. */
            'input_too_short_single'  => __('Please enter %d or more character', 'publishpress-authors'),
            /* translators: %d: number of characters still required. */
            'input_too_short_plural'  => __('Please enter %d or more characters', 'publishpress-authors'),
            'loading_more'            => __('Loading more results...', 'publishpress-authors'),
            /* translators: %d: maximum number of items that can be selected. */
            'maximum_selected_single' => __('You can only select %d item', 'publishpress-authors'),
            /* translators: %d: maximum number of items that can be selected. */
            'maximum_selected_plural' => __('You can only select %d items', 'publishpress-authors'),
            'no_results'              => __('No results found', 'publishpress-authors'),
            'searching'               => __('Searching...', 'publishpress-authors'),
            'remove_all_items'        => __('Remove all items', 'publishpress-authors'),
        ];
    }

    /**
     * Get Chosen strings translated by WordPress.
     *
     * @return array
     */
    public static function getChosenI18n()
    {
        return [
            'no_results_text' => __('No results match', 'publishpress-authors'),
        ];
    }

    /**
     * Check if the editor can assign the same author to multiple categories on one post.
     *
     * @return bool
     */
    public static function isAuthorMultipleCategoriesEnabled()
    {
        $legacyPlugin = Factory::getLegacyPlugin();

        $enabled = isset($legacyPlugin->modules->multiple_authors->options->allow_author_multiple_categories)
            && 'yes' === $legacyPlugin->modules->multiple_authors->options->allow_author_multiple_categories;

        if (!$enabled) {
            $options = get_option('multiple_authors_multiple_authors_options', []);

            if (is_array($options) && isset($options['allow_author_multiple_categories'])) {
                $enabled = 'yes' === $options['allow_author_multiple_categories'];
            } elseif (is_object($options) && isset($options->allow_author_multiple_categories)) {
                $enabled = 'yes' === $options->allow_author_multiple_categories;
            }
        }

        return (bool) apply_filters('publishpress_authors_allow_author_multiple_categories', $enabled);
    }

    /**
     * Load thickbox modal
     *
     * @param string $button_class
     * @param string $width
     * @param string $height
     * @param string $modal_content
     * @return void
     */
    public static function loadThickBoxModal($button_class = 'ppma-thickbox-botton', $width = '600', $height = '550', $modal_content = '')
    {
        add_thickbox();
        $button_id = "ppma-thickbox-content" . $button_class;
        ?>
        <div id="<?php echo esc_attr($button_id); ?>" style="display:none;">
            <div class="ppma-thickbox-modal-content"><?php echo $modal_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
        </div>
        <a
            href="#TB_inline?&width=<?php echo esc_attr($width); ?>&height=<?php echo esc_attr($height); ?>&inlineId=<?php echo esc_attr($button_id); ?>"
            class="<?php echo esc_attr($button_class); ?> thickbox">
        </a>
        <?php
    }

    /**
     * Load pro sidebar
     */
    public static function ppma_pro_sidebar()
    {
        ?>
        <div class="ppma-advertisement-right-sidebar">
            <div class="advertisement-box-content postbox ppma-advert">
                <div class="postbox-header ppma-advert">
                    <h3 class="advertisement-box-header hndle is-non-sortable">
                        <span><?php echo esc_html__('Need PublishPress Authors Support?', 'publishpress-authors'); ?></span>
                    </h3>
                </div>

                <div class="inside ppma-advert">
                    <p><?php echo esc_html__('If you need help or have a new feature request, let us know.', 'publishpress-authors'); ?>
                        <a class="advert-link" href="https://wordpress.org/plugins/publishpress-authors/" target="_blank">
                        <?php echo esc_html__('Request Support', 'publishpress-authors'); ?>
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" class="linkIcon">
                                <path
                                    d="M18.2 17c0 .7-.6 1.2-1.2 1.2H7c-.7 0-1.2-.6-1.2-1.2V7c0-.7.6-1.2 1.2-1.2h3.2V4.2H7C5.5 4.2 4.2 5.5 4.2 7v10c0 1.5 1.2 2.8 2.8 2.8h10c1.5 0 2.8-1.2 2.8-2.8v-3.6h-1.5V17zM14.9 3v1.5h3.7l-6.4 6.4 1.1 1.1 6.4-6.4v3.7h1.5V3h-6.3z"
                                ></path>
                            </svg>
                        </a>
                    </p>
                    <p>
                    <?php echo esc_html__('Detailed documentation is also available on the plugin website.', 'publishpress-authors'); ?>
                        <a class="advert-link" href="https://publishpress.com/knowledge-base/getting-started-ma/" target="_blank">
                        <?php echo esc_html__('View Knowledge Base', 'publishpress-authors'); ?>
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" class="linkIcon">
                                <path
                                    d="M18.2 17c0 .7-.6 1.2-1.2 1.2H7c-.7 0-1.2-.6-1.2-1.2V7c0-.7.6-1.2 1.2-1.2h3.2V4.2H7C5.5 4.2 4.2 5.5 4.2 7v10c0 1.5 1.2 2.8 2.8 2.8h10c1.5 0 2.8-1.2 2.8-2.8v-3.6h-1.5V17zM14.9 3v1.5h3.7l-6.4 6.4 1.1 1.1 6.4-6.4v3.7h1.5V3h-6.3z"
                                ></path>
                            </svg>
                        </a>
                    </p>
                </div>
            </div>
        </div>
        <?php
    }

    /**
     * Load layout frontend css
     */
    public static function loadLayoutFrontCss()
    {
        $legacyPlugin = Factory::getLegacyPlugin();
        $enable_font_awesome = isset($legacyPlugin->modules->multiple_authors->options->enable_font_awesome)
        ? 'yes' === $legacyPlugin->modules->multiple_authors->options->enable_font_awesome : true;

        if (is_admin() || apply_filters('publishpress_authors_load_style_in_frontend', PUBLISHPRESS_AUTHORS_LOAD_STYLE_IN_FRONTEND)) {
            wp_enqueue_style('dashicons');
            wp_enqueue_style(
                'multiple-authors-widget-css',
                PP_AUTHORS_ASSETS_URL . 'css/multiple-authors-widget.css',
                false,
                PP_AUTHORS_VERSION,
                'all'
            );
        }

        if ($enable_font_awesome) {
            wp_enqueue_style(
                'multiple-authors-fontawesome',
                'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css',
                false,
                PP_AUTHORS_VERSION,
                'all'
            );
        }
    }

    public static function isGeneratepressInstalled()
    {
        return defined('GENERATE_VERSION');
    }

    public static function isRankMathSeoInstalled()
    {
        return defined('RANK_MATH_VERSION') || defined('RANK_MATH_PRO_VERSION');
    }

    /**
     * Wrapper function replacement for get_page_by_title() that's deprecated in wordpress
     * 6.2 https://make.wordpress.org/core/2023/03/06/get_page_by_title-deprecated/
     */
    public static function get_page_by_title( $page_title, $output = OBJECT, $post_type = 'page' )
    {
        global $wpdb;

        if ( is_array( $post_type ) ) {
            $post_type           = esc_sql( $post_type );
            $post_type_in_string = "'" . implode( "','", $post_type ) . "'";
            $sql                 = $wpdb->prepare(
                "
                SELECT ID
                FROM $wpdb->posts
                WHERE post_title = %s
                AND post_type IN ($post_type_in_string)
            ",
                $page_title
            );
        } else {
            $sql = $wpdb->prepare(
                "
                SELECT ID
                FROM $wpdb->posts
                WHERE post_title = %s
                AND post_type = %s
            ",
                $page_title,
                $post_type
            );
        }

        $page = $wpdb->get_var( $sql );

        if ( $page ) {
            return get_post( $page, $output );
        }

        return null;
    }


    /**
     * Register and add inline styles.
     *
     * @param string $custom_css
     * @param string $handle
     *
     * @return void
     */
    public static function add_dummy_inline_style($custom_css, $handle = 'pma-dummy-css-handle')
    {
        wp_register_style(esc_attr($handle), false);
        wp_enqueue_style(esc_attr($handle));
        wp_add_inline_style(esc_attr($handle), $custom_css);
    }

    /**
     * Get Author display name option like wordpress
     *
     * @param integer $term_id
     *
     * @return string $output
     */
    public static function get_author_display_name_select($term_id = 0)
    {

        $profile_user = Author::get_by_term_id($term_id);
        $public_display                     = array();
        $public_display['display_nickname'] = $profile_user->nickname;
        $public_display['display_username'] = $profile_user->user_login;

        if ( ! empty( $profile_user->first_name ) ) {
            $public_display['display_firstname'] = $profile_user->first_name;
        }

        if ( ! empty( $profile_user->last_name ) ) {
            $public_display['display_lastname'] = $profile_user->last_name;
        }

        if ( ! empty( $profile_user->first_name ) && ! empty( $profile_user->last_name ) ) {
            $public_display['display_firstlast'] = $profile_user->first_name . ' ' . $profile_user->last_name;
            $public_display['display_lastfirst'] = $profile_user->last_name . ' ' . $profile_user->first_name;
        }

        if ( ! in_array( $profile_user->display_name, $public_display, true ) ) { // Only add this if it isn't duplicated elsewhere.
            $public_display = array( 'display_displayname' => $profile_user->display_name ) + $public_display;
        }

        $public_display = array_map( 'trim', $public_display );
        $public_display = array_unique( $public_display );
        ob_start();
        ?>
        <select name="name" id="name" aria-required="true" aria-describedby="name-description">
            <?php foreach ( $public_display as $id => $item ) : ?>
                <?php if (!empty($item )) : ?>
                    <option <?php selected( $profile_user->display_name, $item ); ?>><?php echo $item; ?></option>
                <?php endif; ?>
            <?php endforeach; ?>
        </select>
        <?php
        $output = ob_get_clean();

        return $output;
    }


    /**
     * Secondary admin notices function for use with admin_notices hook.
     *
     * Constructs admin notice HTML.
     *
     * @param string $message Message to use in admin notice. Optional. Default empty string.
     * @param bool $success Whether or not a success. Optional. Default true.
     * @return mixed
     */
    public static function admin_notices_helper($message = '', $success = true)
    {

        $class   = [];
        $class[] = $success ? 'updated' : 'error';
        $class[] = 'notice is-dismissible';

        $messagewrapstart = '<div id="message" class="' . esc_attr(implode(' ', $class)) . '"><p>';

        $messagewrapend = '</p></div>';

        $action = '';

        /**
         * Filters the custom admin notice for ppma.
         *
         *
         * @param string $value Complete HTML output for notice.
         * @param string $action Action whose message is being generated.
         * @param string $message The message to be displayed.
         * @param string $messagewrapstart Beginning wrap HTML.
         * @param string $messagewrapend Ending wrap HTML.
         */
        return apply_filters('ppma_admin_notice', $messagewrapstart . $message . $messagewrapend, $action, $message,
            $messagewrapstart, $messagewrapend);
    }
}
