<?php
/**
 * Name: Public Preview for Drafts
 * Description: Share draft posts with clients or team members using public preview links without requiring WordPress login for easy review and approval before publishing - works in both Gutenberg and Classic Editor
 * Category: Content
 * Icon: -
 * UseCases: [
 *   {
 *     "title": "Client Content Approval",
 *     "description": "Share secure preview link of draft page with clients for approval before publishing, without giving them admin access."
 *   },
 *   {
 *     "title": "Stakeholder Review",
 *     "description": "Send draft blog post to legal or compliance team for review via shareable link before publication."
 *   },
 *   {
 *     "title": "Design Feedback",
 *     "description": "Get designer feedback on new landing page layout while it's still in draft by sharing temporary preview URL."
 *   }
 * ]
 * Added: 2025-07-25
 * Tags: drafts, preview, public, sharing, token, gutenberg, classic-editor
 * WhereToFind: {
 *   "location": "Post editor sidebar (Gutenberg) or publish box (Classic)",
 *   "url": "",
 *   "instructions": "In Gutenberg: Check pre-publish panel above publish button. In Classic: Check publish box. Copy and share the preview link.",
 *   "newTab": false
 * }
 */

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

class SwitchboardPublicPreviewDrafts {

    public function __construct() {
        add_action('post_submitbox_misc_actions', [$this, 'add_preview_button']);
        add_filter('the_posts', [$this, 'allow_preview_access'], 10, 2);
        add_action('init', [$this, 'generate_preview_token'], 11);
        add_action('init', [$this, 'register_meta'], 11);
        add_action('enqueue_block_editor_assets', [$this, 'enqueue_gutenberg_assets']);
        add_filter('posts_results', [$this, 'show_draft_preview'], 10, 2);
    }

    public function register_meta() {
        register_post_meta('', '_switchboard_preview_token', [
            'show_in_rest' => true,
            'single' => true,
            'type' => 'string',
            'auth_callback' => function() {
                return current_user_can('edit_posts');
            }
        ]);
    }

    public function enqueue_gutenberg_assets() {
        global $post;

        if (!$post) {
            return;
        }

        // Generate token if it doesn't exist
        $token = get_post_meta($post->ID, '_switchboard_preview_token', true);
        if (!$token) {
            $token = wp_generate_password(20, false);
            update_post_meta($post->ID, '_switchboard_preview_token', $token);
        }

        // Build URL with proper structure for draft pages/posts
        $home_url = home_url('/');
        $preview_url = add_query_arg([
            'p' => $post->post_type === 'post' ? $post->ID : null,
            'page_id' => $post->post_type === 'page' ? $post->ID : null,
            'preview' => 'true',
            'preview_id' => $post->ID,
            'preview_token' => $token
        ], $home_url);

        // Remove null parameters
        $preview_url = remove_query_arg(array_keys(array_filter([
            'p' => $post->post_type === 'post' ? null : 'remove',
            'page_id' => $post->post_type === 'page' ? null : 'remove'
        ])), $preview_url);

        // Add inline script to create the sidebar panel
        wp_enqueue_script('wp-plugins');
        wp_enqueue_script('wp-edit-post');
        wp_enqueue_script('wp-element');
        wp_enqueue_script('wp-components');
        wp_enqueue_script('wp-data');
        wp_enqueue_script('wp-dom-ready');

        $script = "
wp.domReady(function() {
    var wp = window.wp;

    if (!wp || !wp.plugins || !wp.editPost) {
        return;
    }

    const { registerPlugin } = wp.plugins;
    const { PluginDocumentSettingPanel } = wp.editPost;
    const { createElement: el } = wp.element;
    const { Button } = wp.components;
    const { useSelect } = wp.data;

    const PublicPreviewPanel = function() {
        const postStatus = useSelect(function(select) {
            const editor = select('core/editor');
            if (!editor) {
                return null;
            }
            return editor.getEditedPostAttribute('status');
        });

        // Only show for drafts
        if (postStatus !== 'draft') {
            return null;
        }

        const previewUrl = '" . str_replace("'", "\\'", $preview_url) . "';

        const copyToClipboard = function() {
            const input = document.querySelector('.switchboard-preview-url-input');
            if (input) {
                input.select();
                input.setSelectionRange(0, 99999);

                try {
                    navigator.clipboard.writeText(previewUrl).then(function() {
                        const btn = document.querySelector('.switchboard-copy-preview-btn');
                        if (btn) {
                            btn.textContent = 'Copied!';
                            btn.style.color = '#00a32a';
                            setTimeout(function() {
                                btn.textContent = 'Copy Link';
                                btn.style.color = '';
                            }, 2000);
                        }
                    });
                } catch(err) {
                    document.execCommand('copy');
                    const btn = document.querySelector('.switchboard-copy-preview-btn');
                    if (btn) {
                        btn.textContent = 'Copied!';
                        btn.style.color = '#00a32a';
                        setTimeout(function() {
                            btn.textContent = 'Copy Link';
                            btn.style.color = '';
                        }, 2000);
                    }
                }
            }
        };

        return el(PluginDocumentSettingPanel, {
            name: 'switchboard-public-preview',
            title: 'Public Preview',
            className: 'switchboard-public-preview-panel',
            icon: 'visibility'
        },
            el('div', { style: { marginBottom: '12px' } },
                el('p', { style: { margin: '0 0 12px 0', fontSize: '13px', lineHeight: '1.5', color: '#666' } },
                    'Share this link to preview this draft without logging in.'
                ),
                el('input', {
                    type: 'text',
                    readOnly: true,
                    value: previewUrl,
                    className: 'switchboard-preview-url-input',
                    style: {
                        width: '100%',
                        padding: '6px 8px',
                        border: '1px solid #ddd',
                        borderRadius: '2px',
                        fontSize: '12px',
                        fontFamily: 'monospace',
                        marginBottom: '8px',
                        cursor: 'text',
                        backgroundColor: '#f7f7f7'
                    },
                    onClick: function(e) { e.target.select(); }
                }),
                el(Button, {
                    variant: 'secondary',
                    isSmall: true,
                    className: 'switchboard-copy-preview-btn',
                    onClick: copyToClipboard,
                    style: { width: '100%' }
                }, 'Copy Link')
            )
        );
    };

    registerPlugin('switchboard-public-preview', {
        render: PublicPreviewPanel,
        icon: 'visibility'
    });
});
        ";

        wp_add_inline_script('wp-dom-ready', $script);
    }

    public function add_preview_button($post) {
        if ($post->post_status !== 'draft') {
            return;
        }

        $token = get_post_meta($post->ID, '_switchboard_preview_token', true);
        if (!$token) {
            $token = wp_generate_password(20, false);
            update_post_meta($post->ID, '_switchboard_preview_token', $token);
        }

        // Build URL with proper structure for draft pages/posts
        $home_url = home_url('/');
        $preview_url = add_query_arg([
            'p' => $post->post_type === 'post' ? $post->ID : null,
            'page_id' => $post->post_type === 'page' ? $post->ID : null,
            'preview' => 'true',
            'preview_id' => $post->ID,
            'preview_token' => $token
        ], $home_url);

        // Remove null parameters
        $preview_url = remove_query_arg(array_keys(array_filter([
            'p' => $post->post_type === 'post' ? null : 'remove',
            'page_id' => $post->post_type === 'page' ? null : 'remove'
        ])), $preview_url);

        ?>
        <div class="misc-pub-section">
            <strong><?php esc_html_e('Public Preview:', 'wpswitchboard'); ?></strong><br>
            <input type="text" readonly value="<?php echo esc_attr($preview_url); ?>"
                   style="width: 100%; margin-top: 5px;"
                   onclick="this.select();">
            <p class="description"><?php esc_html_e('Share this link to allow public preview of this draft.', 'wpswitchboard'); ?></p>
        </div>
        <?php
    }
    
    public function generate_preview_token() {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Preview token generation uses capability check instead
        if (isset($_GET['generate_preview_token'], $_GET['post_id'])) {
            $post_id = intval($_GET['post_id']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            if (current_user_can('edit_post', $post_id)) {
                $token = wp_generate_password(20, false);
                update_post_meta($post_id, '_switchboard_preview_token', $token);
            }
        }
    }
    
    public function allow_preview_access($posts, $query) {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public preview tokens are intentionally nonce-less
        if (!isset($_GET['preview']) || !isset($_GET['preview_id']) || !isset($_GET['preview_token'])) {
            return $posts;
        }

        if (!$query->is_main_query()) {
            return $posts;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public preview tokens are intentionally nonce-less
        $post_id = intval($_GET['preview_id']);
        $token = sanitize_text_field(wp_unslash($_GET['preview_token'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $saved_token = get_post_meta($post_id, '_switchboard_preview_token', true);

        if ($token === $saved_token && $saved_token !== '') {
            $post = get_post($post_id);
            if ($post && $post->post_status === 'draft') {
                // Make the post publicly viewable by temporarily changing its status
                $post->post_status = 'publish';
                $post->filter = 'sample';
                return [$post];
            }
        }

        return $posts;
    }

    public function show_draft_preview($posts, $query) {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public preview tokens are intentionally nonce-less
        if (!isset($_GET['preview']) || !isset($_GET['preview_id']) || !isset($_GET['preview_token'])) {
            return $posts;
        }

        if (!$query->is_main_query()) {
            return $posts;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public preview tokens are intentionally nonce-less
        $post_id = intval($_GET['preview_id']);
        $token = sanitize_text_field(wp_unslash($_GET['preview_token'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $saved_token = get_post_meta($post_id, '_switchboard_preview_token', true);

        if ($token === $saved_token && $saved_token !== '') {
            // Remove WordPress's default preview restrictions
            remove_action('template_redirect', 'redirect_canonical');

            $post = get_post($post_id);
            if ($post && $post->post_status === 'draft') {
                // Set query flags to show single post
                $query->is_page = ($post->post_type === 'page');
                $query->is_single = ($post->post_type !== 'page');
                $query->is_singular = true;
                $query->is_preview = false;
                $query->is_404 = false;

                // Temporarily publish the post for display
                $post->post_status = 'publish';
                $post->filter = 'sample';

                return [$post];
            }
        }

        return $posts;
    }

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

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