<?php
/**
 * Name: Remove CPT Base
 * Description: Remove or change the custom post type slug from permalinks, making URLs cleaner (e.g., /products/my-product becomes /my-product)
 * Category: Content
 * Icon: -
 * UseCases: [
 *   {
 *     "title": "Cleaner Product URLs",
 *     "description": "Change /products/blue-widget to just /blue-widget for cleaner, shorter URLs."
 *   },
 *   {
 *     "title": "Portfolio URLs",
 *     "description": "Remove /portfolio/ from portfolio item URLs for a more professional appearance."
 *   },
 *   {
 *     "title": "SEO-Friendly URLs",
 *     "description": "Shorter URLs without the CPT slug can improve click-through rates and look better when shared."
 *   }
 * ]
 * Added: 2024-12-08
 * Tags: permalink, url, cpt, custom post type, slug, seo
 * WhereToFind: {
 *   "location": "Switchboard Settings Panel",
 *   "url": "",
 *   "instructions": "Select which CPTs should have their base slug removed from permalinks.",
 *   "newTab": false
 * }
 */

// Prevent direct access
if (!defined('ABSPATH')) exit;

class SwitchboardRemoveCptBase {

    private $settings = [];
    private $enabled_cpts = [];

    public function __construct() {
        $this->settings = get_option('switchboard_remove-cpt-base_settings', []);
        $this->enabled_cpts = $this->get_enabled_cpts();

        if (!empty($this->enabled_cpts)) {
            // Remove slug from permalinks
            add_filter('post_type_link', [$this, 'remove_cpt_slug'], 10, 2);

            // Parse request to handle slug-less URLs (runs before WP determines 404)
            add_filter('request', [$this, 'parse_request'], 10, 1);

            // Add rewrite rules
            add_action('init', [$this, 'add_rewrite_rules'], 99);

            // Register query var
            add_filter('query_vars', [$this, 'add_query_vars']);

            // Redirect old URLs to new URLs (301)
            add_action('template_redirect', [$this, 'redirect_old_urls']);
        }

        // Flush permalinks when settings are saved
        add_action('update_option_switchboard_remove-cpt-base_settings', [$this, 'flush_permalinks_on_save'], 10, 2);

        // Check if we need to flush on init
        add_action('init', [$this, 'maybe_flush_rewrite_rules'], 100);
    }

    /**
     * Flush permalinks when module settings are saved
     */
    public function flush_permalinks_on_save($old_value, $new_value) {
        $old_cpts = isset($old_value['post_types']) ? $old_value['post_types'] : '';
        $new_cpts = isset($new_value['post_types']) ? $new_value['post_types'] : '';

        if ($old_cpts !== $new_cpts) {
            update_option('switchboard_remove_cpt_base_flush', true);
        }
    }

    /**
     * Flush rewrite rules if flagged
     */
    public function maybe_flush_rewrite_rules() {
        if (get_option('switchboard_remove_cpt_base_flush')) {
            delete_option('switchboard_remove_cpt_base_flush');
            flush_rewrite_rules(false);
        }
    }

    /**
     * Get list of CPTs with base removal enabled
     */
    private function get_enabled_cpts() {
        if (isset($this->settings['post_types']) && !empty($this->settings['post_types'])) {
            return array_filter(array_map('trim', explode(',', $this->settings['post_types'])));
        }
        return [];
    }

    /**
     * Register query var
     */
    public function add_query_vars($vars) {
        $vars[] = 'switchboard_cpt_check';
        return $vars;
    }

    /**
     * Add rewrite rules for slug-less CPT URLs
     */
    public function add_rewrite_rules() {
        // Only add rule once (not per CPT)
        // This catches any single slug and we check if it's a CPT in parse_request
        add_rewrite_rule(
            '^([^/]+)/?$',
            'index.php?switchboard_cpt_check=$matches[1]',
            'bottom' // Low priority - pages/posts match first
        );

        // Pagination support
        add_rewrite_rule(
            '^([^/]+)/page/?([0-9]{1,})/?$',
            'index.php?switchboard_cpt_check=$matches[1]&paged=$matches[2]',
            'bottom'
        );

        // Comments pagination
        add_rewrite_rule(
            '^([^/]+)/comment-page-([0-9]{1,})/?$',
            'index.php?switchboard_cpt_check=$matches[1]&cpage=$matches[2]',
            'bottom'
        );
    }

    /**
     * Remove the CPT slug from the permalink
     */
    public function remove_cpt_slug($post_link, $post) {
        if (!in_array($post->post_type, $this->enabled_cpts, true)) {
            return $post_link;
        }

        // Only modify published posts
        if ($post->post_status !== 'publish') {
            return $post_link;
        }

        $post_type_obj = get_post_type_object($post->post_type);
        if (!$post_type_obj || !$post_type_obj->rewrite) {
            return $post_link;
        }

        // Get the slug to remove
        $slug = $post_type_obj->rewrite['slug'] ?? $post->post_type;

        // Handle with_front option
        $front = '';
        if (!empty($post_type_obj->rewrite['with_front'])) {
            global $wp_rewrite;
            $front = $wp_rewrite->front;
            if ($front === '/') {
                $front = '';
            }
        }

        // Remove the slug from the URL
        $post_link = str_replace($front . '/' . $slug . '/', '/', $post_link);

        return $post_link;
    }

    /**
     * Redirect old URLs (with CPT base) to new URLs (without base)
     */
    public function redirect_old_urls() {
        if (is_admin() || !is_singular()) {
            return;
        }

        global $post;
        if (!$post || !in_array($post->post_type, $this->enabled_cpts, true)) {
            return;
        }

        // Get current URL path
        $current_url = $_SERVER['REQUEST_URI'];
        $current_url = strtok($current_url, '?'); // Remove query string

        // Get what the URL should be
        $correct_url = get_permalink($post->ID);
        $correct_path = wp_parse_url($correct_url, PHP_URL_PATH);

        // If URLs don't match, redirect
        if ($current_url !== $correct_path && rtrim($current_url, '/') !== rtrim($correct_path, '/')) {
            // Preserve query string
            $query_string = $_SERVER['QUERY_STRING'] ?? '';
            if ($query_string) {
                $correct_url .= '?' . $query_string;
            }

            wp_redirect($correct_url, 301);
            exit;
        }
    }

    /**
     * Parse requests to handle the slug-less URLs
     */
    public function parse_request($query_vars) {
        // Check our custom query var first
        $slug = '';

        if (!empty($query_vars['switchboard_cpt_check'])) {
            $slug = sanitize_title($query_vars['switchboard_cpt_check']);
            unset($query_vars['switchboard_cpt_check']);
        } elseif (!empty($query_vars['pagename']) && empty($query_vars['post_type'])) {
            // Also check pagename for fallback
            $slug = sanitize_title($query_vars['pagename']);
        }

        if (empty($slug)) {
            return $query_vars;
        }

        // Don't interfere with existing specific post type queries
        if (!empty($query_vars['post_type']) && !in_array($query_vars['post_type'], ['page', 'any'], true)) {
            return $query_vars;
        }

        // Check if a page exists with this slug (pages take priority)
        $page = get_page_by_path($slug, OBJECT, 'page');
        if ($page && $page->post_status === 'publish') {
            return $query_vars;
        }

        // Check if a regular post exists with this slug
        $post = get_page_by_path($slug, OBJECT, 'post');
        if ($post && $post->post_status === 'publish') {
            return $query_vars;
        }

        // Check each enabled CPT for a matching post
        foreach ($this->enabled_cpts as $post_type) {
            // Verify post type exists
            if (!post_type_exists($post_type)) {
                continue;
            }

            $cpt_post = get_page_by_path($slug, OBJECT, $post_type);

            if ($cpt_post && $cpt_post->post_status === 'publish') {
                // Found a matching CPT post - rewrite the query
                return [
                    'post_type' => $post_type,
                    'name' => $slug,
                    $post_type => $slug,
                ];
            }
        }

        return $query_vars;
    }

    /**
     * Render settings panel
     */
    public function render_settings($settings = []) {
        $selected_post_types = isset($settings['post_types']) ? explode(',', $settings['post_types']) : [];
        $selected_post_types = array_filter(array_map('trim', $selected_post_types));

        // Get only custom post types (exclude built-in)
        $all_post_types = get_post_types(['public' => true, '_builtin' => false], 'objects');

        // Exclude known system CPTs
        $exclude_types = [
            'attachment', 'wp_block', 'wp_template', 'wp_template_part', 'wp_navigation',
            'acf-field-group', 'acf-field', 'wp_font_family', 'wp_font_face',
            'oembed_cache', 'user_request', 'wp_global_styles', 'customize_changeset',
        ];
        $all_post_types = array_diff_key($all_post_types, array_flip($exclude_types));

        // Filter out CPTs that have rewrite disabled or are hierarchical
        $all_post_types = array_filter($all_post_types, function($pt) {
            // Must have rewrite enabled
            if ($pt->rewrite === false) {
                return false;
            }
            // Exclude hierarchical CPTs (they have parent/child URL structures)
            if ($pt->hierarchical) {
                return false;
            }
            return true;
        });
        ?>
        <input type="hidden" name="module" value="remove-cpt-base">
        <input type="hidden" name="setting_post_types" id="setting_post_types" value="<?php echo esc_attr(implode(',', $selected_post_types)); ?>">

        <div class="sb-sp-group">
            <label class="sb-sp-label"><?php esc_html_e('Select Custom Post Types', 'wpswitchboard'); ?></label>
            <p class="sb-sp-description"><?php esc_html_e('Remove the base slug from permalinks for these custom post types.', 'wpswitchboard'); ?></p>

            <?php if (empty($all_post_types)): ?>
                <div class="sb-sp-info-box sb-sp-info-box-info">
                    <p class="sb-sp-description" style="margin: 0;"><?php esc_html_e('No compatible custom post types found. This module works with non-hierarchical custom post types that have permalinks enabled.', 'wpswitchboard'); ?></p>
                </div>
            <?php else: ?>
                <div class="switchboard-tag-selector" data-input-id="setting_post_types">
                    <div class="switchboard-selected-tags" id="selected-post-types">
                        <?php foreach ($selected_post_types as $pt_slug):
                            if (isset($all_post_types[$pt_slug])): ?>
                                <span class="switchboard-tag" data-value="<?php echo esc_attr($pt_slug); ?>">
                                    <?php echo esc_html($all_post_types[$pt_slug]->label); ?>
                                    <code style="font-size: 10px; opacity: 0.7;">/<?php echo esc_html($all_post_types[$pt_slug]->rewrite['slug'] ?? $pt_slug); ?>/</code>
                                    <span class="remove-tag">×</span>
                                </span>
                            <?php endif;
                        endforeach; ?>
                    </div>
                    <div class="switchboard-available-tags">
                        <?php foreach ($all_post_types as $pt_slug => $pt_obj):
                            $is_selected = in_array($pt_slug, $selected_post_types, true) ? 'selected' : '';
                            $rewrite_slug = is_array($pt_obj->rewrite) ? ($pt_obj->rewrite['slug'] ?? $pt_slug) : $pt_slug;
                            ?>
                            <span class="switchboard-tag-option <?php echo esc_attr($is_selected); ?>" data-value="<?php echo esc_attr($pt_slug); ?>">
                                <?php echo esc_html($pt_obj->label); ?>
                                <code style="font-size: 10px; opacity: 0.7; margin-left: 4px;">/<?php echo esc_html($rewrite_slug); ?>/</code>
                            </span>
                        <?php endforeach; ?>
                    </div>
                </div>
            <?php endif; ?>
        </div>

        <div class="sb-sp-info-box sb-sp-info-box-info">
            <h4 class="sb-sp-heading-sm"><?php esc_html_e('How It Works', 'wpswitchboard'); ?></h4>
            <ul class="sb-sp-list">
                <li class="sb-sp-list-item"><strong><?php esc_html_e('Before:', 'wpswitchboard'); ?></strong> <code>yoursite.com/products/blue-widget</code></li>
                <li class="sb-sp-list-item"><strong><?php esc_html_e('After:', 'wpswitchboard'); ?></strong> <code>yoursite.com/blue-widget</code></li>
                <li class="sb-sp-list-item"><?php esc_html_e('Old URLs automatically redirect (301) to new URLs for SEO.', 'wpswitchboard'); ?></li>
            </ul>
        </div>

        <div class="sb-sp-info-box sb-sp-info-box-success">
            <h4 class="sb-sp-heading-sm"><?php esc_html_e('Automatic Features', 'wpswitchboard'); ?></h4>
            <ul class="sb-sp-list">
                <li class="sb-sp-list-item"><?php esc_html_e('Permalinks flushed automatically when you save', 'wpswitchboard'); ?></li>
                <li class="sb-sp-list-item"><?php esc_html_e('301 redirects from old URLs to new URLs', 'wpswitchboard'); ?></li>
            </ul>
            <p class="sb-sp-description" style="margin: 8px 0 0 0;">
                <?php esc_html_e('Not working?', 'wpswitchboard'); ?>
                <a href="<?php echo esc_url(admin_url('options-permalink.php')); ?>" target="_blank" style="text-decoration: underline;">
                    <?php esc_html_e('Flush permalinks manually', 'wpswitchboard'); ?>
                </a>
            </p>
        </div>

        <div class="sb-sp-info-box sb-sp-info-box-warning">
            <h4 class="sb-sp-heading-sm"><?php esc_html_e('Important Notes', 'wpswitchboard'); ?></h4>
            <ul class="sb-sp-list">
                <li class="sb-sp-list-item"><strong><?php esc_html_e('Slug Conflicts:', 'wpswitchboard'); ?></strong> <?php esc_html_e('If a CPT post has the same slug as a page or post, the page/post will take priority.', 'wpswitchboard'); ?></li>
                <li class="sb-sp-list-item"><strong><?php esc_html_e('Unique Slugs:', 'wpswitchboard'); ?></strong> <?php esc_html_e('Ensure your CPT posts have unique slugs to avoid conflicts.', 'wpswitchboard'); ?></li>
            </ul>
        </div>

        <div class="sb-sp-info-box sb-sp-info-box-tip">
            <h4 class="sb-sp-heading-sm"><?php esc_html_e('Want to Change the Base Instead?', 'wpswitchboard'); ?></h4>
            <p class="sb-sp-description" style="margin: 0;">
                <?php esc_html_e('To change the CPT base slug (e.g., /products/ to /shop/), use our', 'wpswitchboard'); ?>
                <strong><?php esc_html_e('CPT Builder', 'wpswitchboard'); ?></strong>
                <?php esc_html_e('module and set a custom URL slug in the rewrite settings.', 'wpswitchboard'); ?>
            </p>
        </div>
        <?php
    }

    /**
     * Register data locations for cleanup system
     */
    public function register_data_locations() {
        return [
            'options' => [
                'switchboard_remove-cpt-base_settings',
                'switchboard_remove_cpt_base_flush',
            ],
            'option_patterns' => [],
            'tables' => [],
            'post_meta' => [],
            'user_meta' => [],
        ];
    }
}

$switchboard_instance = new SwitchboardRemoveCptBase();
Switchboard_Data_Registry::get_instance()->auto_register($switchboard_instance, __FILE__);
