<?php
/**
 * Main class for Responsive Portfolio Gallery plugin functionality.
 *
 * Manages plugin initialization, text domain loading, custom columns,
 * plugin action links, and utility functions for portfolio management.
 * 
 * @package Responsive Portfolio Image Gallery 1.3 - 10 July, 2025
 * @link https://www.realwebcare.com/
 */
if (!defined('ABSPATH')) {
    exit;
}

if (!class_exists('RPGAL_Functions')) {
    class RPGAL_Functions
    {
        private static $instance;

        /**
         * Initializes the plugin by registering necessary hooks.
         *
         * Sets up text domain loading, custom columns for portfolio posts,
         * and plugin action links on the Plugins page.
         */
        private function __construct()
        {
            // Load plugin text domain for localization.
            add_action('plugins_loaded', [$this, 'rpgal_textdomain']);

            // Modify columns for portfolio post type in admin table.
            add_action('manage_rpgal_posts_columns', [$this, 'rpgal_manage_columns_for_portfolio']);

            // Populate custom columns in portfolio admin table.
            add_action('manage_rpgal_posts_custom_column', [$this, 'rpgal_populate_portfolio_columns'], 10, 2);

            // Add custom action links to plugin on Plugins page.
            add_filter('plugin_action_links_' . plugin_basename(RPGAL_AUF), [$this, 'rpgal_plugin_actions']);
        }

        /**
         * Retrieves the singleton instance of the RPGAL_Functions class.
         *
         * @return RPGAL_Functions The singleton instance.
         */
        public static function get_instances()
        {
            if (self::$instance) {
                return self::$instance;
            }

            self::$instance = new self();

            return self::$instance;
        }

        /**
         * Loads the plugin's text domain for localization.
         *
         * Ensures translations are available based on the site's locale.
         * @return void
         */
        public function rpgal_textdomain()
        {
            $locale = apply_filters('plugin_locale', get_locale(), 'responsive-portfolio-image-gallery');
            load_textdomain('responsive-portfolio-image-gallery', RPGAL_PLUGIN_PATH . 'languages/responsive-portfolio-image-gallery' . $locale . '.mo');
        }

        /**
         * Adds custom action links to the plugin on the Plugins page.
         *
         * Includes links to settings, guide, and support pages.
         * 
         * @param array $links Existing plugin action links.
         * @return array Modified array of action links.
         */
        public function rpgal_plugin_actions($links)
        {
            $settings_url = esc_url(admin_url('admin.php?page=rpgal-process', 'admin'));
            $settings_url = wp_nonce_url($settings_url, 'rpgal_settings_action');

            $guide_url = esc_url(admin_url('admin.php?page=rpgal-guide', 'admin'));
            $guide_url = wp_nonce_url($guide_url, 'rpgal_guide_action');

            $support_url = esc_url('https://wordpress.org/support/plugin/responsive-portfolio-image-gallery/');

            $links[] = '<a href="' . $settings_url . '">' . esc_html__('Settings', 'responsive-portfolio-image-gallery') . '</a>';
            $links[] = '<a href="' . $guide_url . '">' . esc_html__('Guide', 'responsive-portfolio-image-gallery') . '</a>';
            $links[] = '<a href="' . $support_url . '" target="_blank">' . esc_html__('Support', 'responsive-portfolio-image-gallery') . '</a>';
            return $links;
        }

        /**
         * Modifies the columns displayed in the portfolio post type admin table.
         *
         * Adds a featured image column and reorders the date column.
         *
         * @param array $columns Existing columns in the admin table.
         * @return array Modified columns array.
         */
        public function rpgal_manage_columns_for_portfolio($columns)
        {
            // remove columns
            unset($columns['date']);
            // add new columns
            $columns['portfolio_featured_image'] = __('Featured Image', 'responsive-portfolio-image-gallery');
            $columns['date'] = __('Date', 'responsive-portfolio-image-gallery');
            return $columns;
        }

        /**
         * Populates custom columns in the portfolio post type admin table.
         *
         * Displays the featured image for the portfolio post.
         *
         * @param string $column The name of the column to populate.
         * @param int $post_id The ID of the portfolio post.
         * @return void
         */
        public function rpgal_populate_portfolio_columns($column, $post_id)
        {
            // featured image column
            if ($column == 'portfolio_featured_image') {
                // if this portfolio has a featured image
                if (has_post_thumbnail($post_id)) {
                    $portfolio_featured_image = get_the_post_thumbnail($post_id, [50, 50]);
                    echo wp_kses_post($portfolio_featured_image);
                } else {
                    echo esc_html__('This portfolio has no featured image', 'responsive-portfolio-image-gallery');
                }
            }
        }

        /**
         * Retrieves category links for the portfolio post type.
         *
         * Returns an array of category term IDs and their names.
         *
         * @return array Associative array of term IDs and names.
         */
        public function rpgal_include_categories()
        {
            $terms = $category_link = [];
            $rpgal_post_type = 'rpgal';
            $taxonomy_objects = get_object_taxonomies($rpgal_post_type, 'objects');

            if (isset($taxonomy_objects) && !empty($taxonomy_objects)) {
                $rpgal_taxonomy = 'rpgal-category';
                $terms = get_terms($rpgal_taxonomy);

                foreach ($terms as $term) {
                    $category_link[$term->term_id] = $term->name . ' (' . $term->count . ')';
                }
            }
            return $category_link;
        }

        /**
         * Generates a custom excerpt for a portfolio post.
         *
         * @param int $length Number of words to include in the excerpt.
         * @param string $more Optional text for the "read more" link.
         * @return string Formatted excerpt with optional "read more" link.
         */
        public function rpgal_excerpt($length, $more = '')
        {
            global $post;
            $rpgal_excerpt = explode(' ', get_the_content(), $length);
            if ($more != '') {
                $rpgal_more = '<a class="rpgal-more" href="' . get_permalink($post->ID) . '">' . $more . '</a>';
            } else {
                $rpgal_more = '';
            }
            if (count($rpgal_excerpt) >= $length) {
                array_pop($rpgal_excerpt);
                $rpgal_excerpt = implode(' ', $rpgal_excerpt);
                $rpgal_excerpt = esc_textarea($rpgal_excerpt);
                $rpgal_excerpt = $rpgal_excerpt . ' ...<br />' . $rpgal_more;
            } else {
                $rpgal_excerpt = implode(' ', $rpgal_excerpt);
                $rpgal_excerpt = esc_textarea($rpgal_excerpt);
            }
            $rpgal_excerpt = apply_filters('get_the_content', $rpgal_excerpt);
            $rpgal_excerpt = str_replace(']]>', ']]&gt;', $rpgal_excerpt);
            $rpgal_excerpt = str_replace("'", "\'", $rpgal_excerpt);
            return $rpgal_excerpt;
        }

        /**
         * Generates and returns an inline JavaScript snippet for initializing the portfolio grid.
         *
         * Responsibilities:
         * - Accepts portfolio options and settings for a given gallery.
         * - Sanitizes and validates all inputs to ensure secure output.
         * - Builds the jQuery script to initialize `.rpgal_grid()` with user-defined settings like:
         *     - hover effects
         *     - expanding speed & height
         *     - thumbnail & large image arrays
         *     - title and description data
         *     - button list and tags for filtering
         * - Dynamically pulls posts and metadata to populate the gallery items.
         * - Ensures all data used in the script is safely escaped for JavaScript context.
         *
         * @param array $options  Portfolio-wide settings like excerpt length and custom excerpt text.
         * @param array $settings Gallery-specific settings like image size, effects, post loop, etc.
         * @return string Inline <script> block with JSON-encoded configuration for the portfolio gallery.
         */
        public function get_inline_portfolio_script($settings = array())
        {
            $result = '';
            $temp = '';

            // Sanitize and validate settings.
            $effect = !empty($settings['effect']) ? esc_js(sanitize_text_field($settings['effect'])) : 'popup';
            $showPlain = !empty($settings['plain']) ? sanitize_text_field($settings['plain']) : 'on';
            $hoverDir = !empty($settings['hvdir']) ? sanitize_text_field($settings['hvdir']) : 'on';
            $hoverInv = !empty($settings['hvinv']) ? sanitize_text_field($settings['hvinv']) : 'off';
            $expHeight = !empty($settings['exhgt']) ? absint($settings['exhgt']) : 500;
            $imgWidth = !empty($settings['imgwd']) ? absint($settings['imgwd']) : 600;
            $imgHeight = !empty($settings['imght']) ? absint($settings['imght']) : 400;
            $titleLimit = intval(28);

            // error_log(print_r($settings['pcats'], true));

            $excerptLen = !empty($settings['excln']) ? absint($settings['excln']) : 30;
            // $includedCats = !empty($settings['pcats']) && is_array($settings['pcats']) ? array_map('sanitize_text_field', $settings['pcats']) : [];
            $includedCats = !empty($settings['pcats']) && is_array($settings['pcats'])
                ? array_map(function ($cat_id) {
                    $term = get_term(intval($cat_id), 'rpgal-category');
                    return is_wp_error($term) || !$term ? null : sanitize_text_field($term->name);
                }, $settings['pcats'])
                : [];

            // error_log(print_r($includedCats, true));

            // Start building the script.
            $result .= "jQuery(function() { jQuery('#rpgal_grid_demo').rpgal_grid({ 'showAllText': 'All', 'filterEffect': '{$effect}'";

            // Add hover effect settings.
            if ('on' === $hoverDir) {
                $result .= ", 'hoverDirection': true";
                if ('on' === $hoverInv) {
                    $result .= ", 'hoverInverse': true";
                }
            } else {
                $result .= ", 'hoverDirection': false, 'hoverInverse': false";
            }

            $result .= ", 'hoverDelay': 0";

            $result .= ", 'expandingSpeed': 500, 'expandingHeight': {$expHeight}, 'items': [";

            // Process portfolio items.
            if (!empty($settings['loop']) && $settings['loop'] instanceof WP_Query && $settings['loop']->have_posts()) {
                while ($settings['loop']->have_posts()) {
                    $settings['loop']->the_post();

                    $post_id = absint(get_the_ID());
                    $thumb_id = absint(get_post_thumbnail_id());
                    $img_url = wp_get_attachment_url($thumb_id);

                    $image_thumb = '';
                    $image_thumb = rpgal_aq_resize(esc_url_raw($img_url), $imgWidth, $imgHeight, true, true, true);
                    if (empty($image_thumb)) {
                        $image_thumb = esc_url(RPGAL_PLUGIN_URL . 'assets/images/album-cover.png');
                    }

                    // Prepare title.
                    $portfolio_title = wp_strip_all_tags(get_the_title());

                    if (mb_strlen($portfolio_title) > $titleLimit && $titleLimit > 0) {
                        $portfolio_title = mb_substr($portfolio_title, 0, $titleLimit) . '...';
                    }
                    $portfolio_title = esc_js($portfolio_title);

                    // Prepare excerpt and read more.
                    $portfolio_excerpt = $this->rpgal_excerpt($excerptLen, 'Read More');

                    $allowed_tags = [
                        'a' => [
                            'class' => [],
                            'href' => [],
                        ],
                    ];

                    /**
                     * Sanitize and encode description for JavaScript.
                     * 
                     * XSS Prevention: wp_kses_post sanitizes HTML, allowing only safe tags
                     * (e.g., <h1>, <a>, <blockquote>) and stripping malicious code (e.g., <script>),
                     * protecting against cross-site scripting.
                     */
                    $description = wp_kses_post($portfolio_excerpt);

                    /**
                     * Decode entities
                     * 
                     * HTML Rendering: html_entity_decode(..., ENT_QUOTES | ENT_HTML5, 'UTF-8')
                     * converts entities (e.g., <h1> to <h1>) so jQuery’s .html()
                     * renders tags as formatted HTML (e.g., headings, links).
                     */
                    $description = html_entity_decode($description, ENT_QUOTES | ENT_HTML5, 'UTF-8');

                    /**
                     * 'plain': Strips HTML tags with wp_strip_all_tags (e.g., <h1>Test</h1> becomes Test),
                     * ensuring no tags appear as plain text.
                     */
                    if (isset($showPlain) && $showPlain == 'on') {
                        // $description = wp_strip_all_tags( $description ); // Remove HTML tags
                        $description = wp_kses($description, $allowed_tags); // Strip all tags except <a class="rpgal-more">
                    }

                    /**
                     * JavaScript Safety: str_replace( ["'", '"'], ["\'", '\"'], ... )
                     * escapes single and double quotes, preventing JavaScript syntax errors
                     * (e.g., <a title='test'> becomes <a title=\'test\'>),
                     * ensuring portfolios display reliably.
                     */
                    $description = nl2br(preg_replace('/\s+/', ' ', $description));

                    /**
                     * Formatting: nl2br adds <br> for newlines, and preg_replace('/\s+/', ' ')
                     * normalizes whitespace, preserving your desired output.
                     * 
                     * Edge Cases: The solution handles common HTML attributes with quotes
                     * (e.g., <a title="test's">) and complex markup, as tested with your provided output.
                     */
                    $description = str_replace(["'", '"'], ["\'", '\"'], $description); // Escape JS quotes

                    // Get terms and portfolio images.
                    $terms = get_the_terms($post_id, 'rpgal-category');
                    $rpgal_portfolio = get_post_meta($post_id, '_multi_img_array', true);
                    $portfolio_images = !empty($rpgal_portfolio) ? array_map('absint', explode(',', sanitize_text_field($rpgal_portfolio))) : [];

                    // Get button data.
                    $button_text = !empty(get_post_meta($post_id, '_button_text', true)) ? sanitize_text_field(get_post_meta($post_id, '_button_text', true)) : '';
                    $button_link = !empty(get_post_meta($post_id, '_button_link', true)) ? esc_url_raw(get_post_meta($post_id, '_button_link', true)) : '';
                    $button_ntab = !empty(get_post_meta($post_id, '_button_ntab', true)) ? wp_validate_boolean(get_post_meta($post_id, '_button_ntab', true)) : false;

                    $result .= '{';

                    // Title array.
                    $result .= "'title': [\"" . $portfolio_title . "\"],";

                    // Description array.
                    $result .= "'description': ['" . $description . "'],";

                    // Thumbnails array. (use video thumbnail if available).
                    $result .= "'thumbnail': [\"" . esc_url($image_thumb) . "\"";
                    foreach ($portfolio_images as $gallery) {
                        $gallery_url = wp_get_attachment_url(absint($gallery));
                        $gallery_thumb = rpgal_aq_resize(esc_url_raw($gallery_url), $imgWidth, $imgHeight, true, true, true);
                        $result .= ', "' . esc_url($gallery_thumb) . '"';
                    }
                    $result .= "],";

                    // Large images array (includes video URLs).
                    $result .= "'large': [\"" . esc_url_raw($img_url) . "\"";
                    foreach ($portfolio_images as $gallery) {
                        $gallery_url = wp_get_attachment_url(absint($gallery));
                        // $result .= '"' . esc_url($gallery_url) . '",';
                        $result .= ', "' . esc_url($gallery_url) . '"';
                    }
                    $result .= "],";

                    // Buttons array.
                    if (!empty($button_text)) {
                        $result .= "'button_list': [";
                        $result .= "{ 'title': '" . esc_js($button_text) . "', 'url': '" . esc_url($button_link) . "', 'new_window': " . ($button_ntab ? 'true' : "'false'") . " }";
                        $result .= "],";
                    } else {
						$result .= "'button_list': [],";
					}

                    // Tags.
                    $result .= "'tags': [";
                    $tag_output = [];
                    if (!empty($terms) && is_array($terms)) {
                        foreach ($terms as $term) {
                            $term_name = sanitize_text_field($term->name);
                            if (in_array($term_name, $includedCats, true)) {
                                $tag_output[] = '"' . esc_js(str_replace(' ', '-', $term_name)) . '"';
                            }
                        }
                    }
                    $result .= implode(', ', $tag_output) . "]";

                    $result .= "},";
                }
                wp_reset_postdata();
            }

            $result = rtrim($result, ',') . "]});});";

            return $result;
        }
    }
}

RPGAL_Functions::get_instances();