Plugin Directory

Changeset 3453209


Ignore:
Timestamp:
02/03/2026 07:16:39 PM (3 weeks ago)
Author:
pluxo
Message:

Release 1.2.0: add modules overview and other smaller changes

Location:
pluxo-blueprint
Files:
59 added
4 deleted
9 edited

Legend:

Unmodified
Added
Removed
  • pluxo-blueprint/trunk/README.md

    r3444357 r3453209  
    11# Pluxo Blueprint
    22
    3 Pluxo Blueprint is a WordPress plugin that helps you generate a clear PDF snapshot of your site documentation, including installed plugins, themes, users and roles, detected page builders, and high-level tracking signals.
     3Pluxo Blueprint is a WordPress plugin that helps you generate a clear and structured documentation snapshot of your website.
    44
    5 It also helps you document your installed plugins by displaying detailed information and exporting the plugin list to CSV, JSON, or Markdown, so you can easily use it for documentation in tools like Confluence, Notion, or OneNote.
     5Inside the WordPress admin, it provides an Overview dashboard organised into expandable modules, covering key areas such as site information, themes, plugins, content types, users and roles, page builders, tracking sources, cookies & consent, and translation tools.
     6
     7Each module can be copied individually as Markdown, making it easy to reuse the documentation in tools like Confluence, Notion, or OneNote. You can also generate a one-click PDF export based on the full Overview, ideal for sharing a complete technical snapshot of the website.
     8
     9Pluxo Blueprint runs entirely inside your WordPress installation, uses no external services, and does not send or track any data.
  • pluxo-blueprint/trunk/assets/css/admin.css

    r3444357 r3453209  
    139139    margin-right: 16px;
    140140}
     141
     142.pluxo-blueprint-tabs {
     143    margin-top: 12px;
     144}
     145
     146.pluxo-blueprint-grid {
     147    display: grid;
     148    grid-template-columns: 1fr;
     149    gap: 16px;
     150    margin-top: 16px;
     151}
     152
     153@media (min-width: 1100px) {
     154    .pluxo-blueprint-grid {
     155        grid-template-columns: 2fr 1fr;
     156        align-items: start;
     157    }
     158}
     159
     160.pluxo-blueprint-muted {
     161    opacity: 0.85;
     162}
     163
     164.pluxo-blueprint-accordion {
     165    border: 1px solid rgba(0, 0, 0, 0.08);
     166    border-radius: 10px;
     167    padding: 10px 12px;
     168    margin-top: 10px;
     169    background: #fff;
     170}
     171
     172.pluxo-blueprint-accordion summary {
     173    cursor: pointer;
     174    display: flex;
     175    align-items: center;
     176    justify-content: space-between;
     177    gap: 12px;
     178    font-weight: 600;
     179}
     180
     181.pluxo-blueprint-accordion summary::-webkit-details-marker {
     182    display: none;
     183}
     184
     185.pluxo-blueprint-accordion-body {
     186    margin-top: 10px;
     187}
     188
     189.pluxo-blueprint-pill {
     190    display: inline-flex;
     191    align-items: center;
     192    padding: 2px 8px;
     193    border-radius: 999px;
     194    font-size: 12px;
     195    font-weight: 600;
     196    background: rgba(0, 0, 0, 0.06);
     197}
     198
     199.pluxo-blueprint-list {
     200    margin: 10px 0 0 18px;
     201}
     202
     203.pluxo-blueprint-accordion-table {
     204    margin-top: 12px;
     205    overflow-x: auto;
     206}
     207
     208.pluxo-blueprint-accordion-table table {
     209    margin-top: 0;
     210}
     211
     212.pluxo-blueprint-pill--danger strong,
     213.pluxo-blueprint-pill--warning strong,
     214.pluxo-blueprint-pill--good strong {
     215    font-weight: 700;
     216}
     217
     218.pluxo-blueprint-pill--good {
     219    background: #edf7ed;
     220    border-color: #46b450;
     221}
     222
     223.pluxo-blueprint-pill--warning {
     224    background: #fcf9e8;
     225    border-color: #dba617;
     226}
     227
     228.pluxo-blueprint-pill--danger {
     229    background: #fcf0f1;
     230    border-color: #d63638;
     231}
     232
     233.pluxo-blueprint-divider {
     234    border: 0;
     235    border-top: 1px solid #e5e7eb;
     236    margin: 24px 0;
     237}
     238
     239.pluxo-blueprint-summary-actions {
     240    display: inline-flex;
     241    align-items: center;
     242    gap: 8px;
     243    margin-left: auto;
     244}
     245
     246.pluxo-blueprint-copy-md-btn {
     247    border: 1px solid #c3c4c7;
     248    background: #fff;
     249    color: #1d2327;
     250    border-radius: 6px;
     251    padding: 4px 10px;
     252    font-size: 12px;
     253    line-height: 1.6;
     254    cursor: pointer;
     255}
     256
     257.pluxo-blueprint-copy-md-btn:hover {
     258    background: #f6f7f7;
     259}
     260
     261.pluxo-blueprint-copy-md-btn.is-copied {
     262    border-color: #00a32a;
     263}
     264
     265.pluxo-blueprint-copy-md-btn.is-error {
     266    border-color: #d63638;
     267}
     268
     269.pluxo-blueprint-accordion > summary {
     270    display: flex;
     271    align-items: center;
     272    justify-content: space-between;
     273    gap: 12px;
     274    cursor: pointer;
     275}
  • pluxo-blueprint/trunk/includes/PDF/class-pluxo-blueprint-pdf-generator.php

    r3444357 r3453209  
    11<?php
    22/**
    3  * PDF generator for Pluxo Blueprint.
     3 * Print export generator for Pluxo Blueprint (browser print to PDF).
    44 *
    55 * @package Pluxo_Blueprint
     
    2525     */
    2626    public function __construct( $plugin_path ) {
     27        if ( ! is_string( $plugin_path ) || '' === $plugin_path ) {
     28            $this->plugin_path = '';
     29            return;
     30        }
     31
    2732        $real = realpath( $plugin_path );
    2833
     
    3641
    3742    /**
    38      * Render HTML for the PDF.
     43     * Render an Overview print page (browser print to PDF).
    3944     *
    40      * @return string
    41      */
    42     private function render_html() {
    43         $template = $this->plugin_path . '/includes/PDF/templates/site-doc.php';
    44 
    45         if ( ! file_exists( $template ) ) {
    46             return '';
    47         }
    48 
    49         $real_template = realpath( $template );
    50 
    51         // Ensure the template is inside the plugin path.
    52         if ( false === $real_template || 0 !== strpos( $real_template, $this->plugin_path ) ) {
    53             return '';
    54         }
    55 
    56         ob_start();
    57         require $real_template;
    58         $html = ob_get_clean();
    59 
    60         return is_string( $html ) ? $html : '';
    61     }
    62 
    63     /**
    64      * Stream the PDF as a download.
    65      *
     45     * @param string $modules_html Modules HTML.
    6646     * @return void
    6747     */
    68     public function stream_pdf() {
    69 
     48    public function render_overview_print_page( $modules_html ) {
    7049        if ( ! current_user_can( 'manage_options' ) ) {
    71             wp_die( esc_html__( 'You do not have permission to export PDFs.', 'pluxo-blueprint' ) );
     50            wp_die( esc_html__( 'You do not have permission to access this export.', 'pluxo-blueprint' ) );
    7251        }
    7352
    7453        if ( '' === $this->plugin_path ) {
    75             wp_die( esc_html__( 'Invalid plugin path for PDF generation.', 'pluxo-blueprint' ) );
     54            wp_die( esc_html__( 'Invalid plugin path for export rendering.', 'pluxo-blueprint' ) );
    7655        }
    7756
    78         // EN-UK: Load Composer autoloader only if Dompdf is not already loaded.
    79         if ( ! class_exists( '\Dompdf\Dompdf' ) ) {
    80             $autoload = trailingslashit( $this->plugin_path ) . 'vendor/autoload.php';
     57        $modules_html = (string) $modules_html;
    8158
    82             if ( file_exists( $autoload ) ) {
    83                 require_once $autoload;
    84             }
     59        $template = $this->plugin_path . '/includes/PDF/templates/overview-print.php';
     60
     61        if ( ! file_exists( $template ) ) {
     62            wp_die( esc_html__( 'Export template is missing. Please reinstall the plugin.', 'pluxo-blueprint' ) );
    8563        }
    8664
    87         if ( ! class_exists( '\Dompdf\Dompdf' ) || ! class_exists( '\Dompdf\Options' ) ) {
    88             wp_die( esc_html__( 'PDF library is missing. Please reinstall the plugin.', 'pluxo-blueprint' ) );
    89         }
    90 
    91         $html = $this->render_html();
    92         if ( '' === $html ) {
    93             wp_die( esc_html__( 'Failed to render PDF template.', 'pluxo-blueprint' ) );
    94         }
    95 
    96         // Dompdf hardening options:
    97         // - Disable remote assets by default (safer).
    98         // - Restrict file access to within plugin directory via chroot.
    99         $options = new \Dompdf\Options();
    100         $options->set( 'isRemoteEnabled', false );
    101         $options->set( 'chroot', $this->plugin_path );
    102         $options->set( 'defaultFont', 'DejaVu Sans' );
    103 
    104         $dompdf = new \Dompdf\Dompdf( $options );
    105 
    106         $dompdf->loadHtml( $html, 'UTF-8' );
    107         $dompdf->setPaper( 'A4', 'portrait' );
    108         $dompdf->render();
    109 
    110         $timestamp = gmdate( 'Ymd-His' );
    111         $filename  = 'pluxo-blueprint-site-docs-' . $timestamp . '.pdf';
    112 
    113         // Prevent caching and avoid output compression issues.
    114         nocache_headers();
    115 
    116         if ( function_exists( 'ini_set' ) ) {
    117             // Best-effort: avoid corrupted binary output on hosts with output compression enabled.
    118             // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
    119             @ini_set( 'zlib.output_compression', 'Off' );
    120         }
    121 
    122         // Prevent any accidental output from corrupting the PDF.
    123         while ( ob_get_level() > 0 ) {
    124             // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
    125             @ob_end_clean();
    126         }
    127 
    128         // Stream as attachment (download).
    129         $dompdf->stream(
    130             $filename,
    131             array(
    132                 'Attachment' => true,
    133             )
    134         );
    135 
     65        require $template;
    13666        exit;
    13767    }
  • pluxo-blueprint/trunk/includes/class-pluxo-blueprint.php

    r3444357 r3453209  
    1010}
    1111
     12require_once __DIR__ . '/Admin/class-pluxo-blueprint-overview.php';
     13require_once __DIR__ . '/Admin/class-pluxo-blueprint-admin-page.php';
     14
     15require_once __DIR__ . '/Modules/Site_Info/class-pluxo-blueprint-site-info-repository.php';
     16require_once __DIR__ . '/Modules/Site_Info/class-pluxo-blueprint-site-info-renderer.php';
     17require_once __DIR__ . '/Modules/Site_Info/class-pluxo-blueprint-module-site-info.php';
     18
     19require_once __DIR__ . '/Modules/Themes/class-pluxo-blueprint-themes-repository.php';
     20require_once __DIR__ . '/Modules/Themes/class-pluxo-blueprint-module-themes.php';
     21
     22require_once __DIR__ . '/Modules/Content_Types/class-pluxo-blueprint-content-types-repository.php';
     23require_once __DIR__ . '/Modules/Content_Types/class-pluxo-blueprint-module-content-types.php';
     24
     25require_once __DIR__ . '/Modules/Plugins/class-pluxo-blueprint-plugins-repository.php';
     26require_once __DIR__ . '/Modules/Plugins/class-pluxo-blueprint-plugins-renderer.php';
     27require_once __DIR__ . '/Modules/Plugins/class-pluxo-blueprint-plugins-inventory.php';
     28require_once __DIR__ . '/Modules/Plugins/class-pluxo-blueprint-module-plugins.php';
     29
     30require_once __DIR__ . '/Modules/Builders/class-pluxo-blueprint-builders-repository.php';
     31require_once __DIR__ . '/Modules/Builders/class-pluxo-blueprint-builders-renderer.php';
     32require_once __DIR__ . '/Modules/Builders/class-pluxo-blueprint-module-builders.php';
     33
     34require_once __DIR__ . '/Modules/Builders/Theme_Builder_Elementor/class-pluxo-blueprint-theme-builder-elementor-repository.php';
     35require_once __DIR__ . '/Modules/Builders/Theme_Builder_Elementor/class-pluxo-blueprint-theme-builder-elementor-renderer.php';
     36require_once __DIR__ . '/Modules/Builders/Theme_Builder_Elementor/class-pluxo-blueprint-module-theme-builder-elementor.php';
     37
     38require_once __DIR__ . '/Modules/Header_Footer/class-pluxo-blueprint-header-footer-scan.php';
     39require_once __DIR__ . '/Modules/Header_Footer/class-pluxo-blueprint-header-footer-repository.php';
     40require_once __DIR__ . '/Modules/Header_Footer/class-pluxo-blueprint-header-footer-renderer.php';
     41require_once __DIR__ . '/Modules/Header_Footer/class-pluxo-blueprint-module-header-footer.php';
     42
     43require_once __DIR__ . '/Modules/Menus/class-pluxo-blueprint-menus-repository.php';
     44require_once __DIR__ . '/Modules/Menus/class-pluxo-blueprint-menus-renderer.php';
     45require_once __DIR__ . '/Modules/Menus/class-pluxo-blueprint-module-menus.php';
     46
     47require_once __DIR__ . '/Modules/Translators/class-pluxo-blueprint-translators-repository.php';
     48require_once __DIR__ . '/Modules/Translators/class-pluxo-blueprint-translators-renderer.php';
     49require_once __DIR__ . '/Modules/Translators/class-pluxo-blueprint-module-translators.php';
     50
     51require_once __DIR__ . '/Modules/Tracking/class-pluxo-blueprint-tracking-repository.php';
     52require_once __DIR__ . '/Modules/Tracking/class-pluxo-blueprint-tracking-renderer.php';
     53require_once __DIR__ . '/Modules/Tracking/class-pluxo-blueprint-module-tracking.php';
     54require_once __DIR__ . '/Modules/Tracking/Services/class-pluxo-blueprint-tracking-scanner.php';
     55require_once __DIR__ . '/Modules/Tracking/Data/class-pluxo-blueprint-tracking-registry.php';
     56
     57require_once __DIR__ . '/Modules/Cookies_Consent/class-pluxo-blueprint-cookies-consent-registry.php';
     58require_once __DIR__ . '/Modules/Cookies_Consent/class-pluxo-blueprint-cookies-consent-scan.php';
     59require_once __DIR__ . '/Modules/Cookies_Consent/class-pluxo-blueprint-module-cookies-consent.php';
     60
     61require_once __DIR__ . '/Modules/Users/class-pluxo-blueprint-users-repository.php';
     62require_once __DIR__ . '/Modules/Users/class-pluxo-blueprint-module-users.php';
     63
     64require_once __DIR__ . '/Helpers/class-pluxo-blueprint-markdown-helper.php';
     65
    1266require_once __DIR__ . '/PDF/class-pluxo-blueprint-pdf-generator.php';
    1367
     
    1569
    1670    /**
    17      * Option name for storing selected export columns.
     71     * Admin page hook suffix.
     72     *
     73     * This is used to load assets only on the plugin admin screen.
    1874     *
    1975     * @var string
    2076     */
    21     private $columns_option_name = 'pluxo_blueprint_columns';
    22 
    23     /**
    24      * Admin page hook suffix.
    25      *
    26      * This is used to load assets only on the plugin admin screen.
    27      *
    28      * @var string
    29      */
    3077    private $admin_page_hook = '';
    3178
    3279    /**
     80     * Admin page controller.
     81     *
     82     * @var Pluxo_Blueprint_Admin_Page
     83     */
     84    private $admin_page;
     85
     86    /**
    3387     * Constructor.
    3488     */
    3589    public function __construct() {
     90        $this->admin_page       = new Pluxo_Blueprint_Admin_Page( $this );
    3691        $this->init_hooks();
    37     }
    38 
    39     /**
    40      * Handle CSV export request.
    41      *
    42      * Generates and streams a CSV file with the selected export columns.
    43      */
    44     public function handle_export_csv() {
    45         // Security: check capability.
    46         if ( ! current_user_can( 'manage_options' ) ) {
    47             wp_die( esc_html__( 'You do not have permission to export this data.', 'pluxo-blueprint' ) );
    48         }
    49 
    50         // Security: verify nonce.
    51         if (
    52             ! isset( $_POST['pluxo_blueprint_export_csv_nonce'] ) ||
    53             ! wp_verify_nonce(
    54                 sanitize_text_field( wp_unslash( $_POST['pluxo_blueprint_export_csv_nonce'] ) ),
    55                 'pluxo_blueprint_export_csv'
    56             )
    57         ) {
    58             wp_die( esc_html__( 'Security check failed. Please try again.', 'pluxo-blueprint' ) );
    59         }
    60 
    61         // Get selected column keys (e.g. [ 'name', 'slug', ... ]).
    62         $selected_keys = $this->get_selected_export_columns();
    63 
    64         if ( empty( $selected_keys ) ) {
    65             wp_die( esc_html__( 'No columns selected for export.', 'pluxo-blueprint' ) );
    66         }
    67 
    68         // Map selected keys to full column definitions.
    69         $available_columns = $this->get_export_columns();
    70         $selected_columns  = array();
    71 
    72         foreach ( $selected_keys as $key ) {
    73             if ( isset( $available_columns[ $key ] ) ) {
    74                 $selected_columns[ $key ] = $available_columns[ $key ];
    75             }
    76         }
    77 
    78         if ( empty( $selected_columns ) ) {
    79             wp_die( esc_html__( 'Export configuration is not available.', 'pluxo-blueprint' ) );
    80         }
    81 
    82         // Get the full inventory of plugins.
    83         $inventory = $this->get_plugin_inventory();
    84 
    85         // Prepare CSV output.
    86         $filename = sprintf(
    87             'pluxo-blueprint-plugins-%s.csv',
    88             gmdate( 'Ymd-His' )
    89         );
    90 
    91         // Tell the browser this is a CSV download.
    92         nocache_headers();
    93         header( 'Content-Type: text/csv; charset=utf-8' );
    94         header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
    95 
    96         // Open output stream.
    97         $fh = fopen( 'php://output', 'w' );
    98 
    99         if ( false === $fh ) {
    100             wp_die( esc_html__( 'Could not open output stream for CSV export.', 'pluxo-blueprint' ) );
    101         }
    102 
    103         // Build the header row using the column labels.
    104         $header_row = array();
    105         foreach ( $selected_columns as $column_key => $column_def ) {
    106             $header_row[] = isset( $column_def['label'] ) ? $column_def['label'] : $column_key;
    107         }
    108 
    109         fputcsv( $fh, $header_row );
    110 
    111         // Output each plugin row with only the selected columns.
    112         foreach ( $inventory as $plugin ) {
    113             $row = array();
    114 
    115             foreach ( $selected_columns as $column_key => $column_def ) {
    116                 $value = isset( $plugin[ $column_key ] ) ? $plugin[ $column_key ] : '';
    117 
    118                 // Ensure scalar for CSV.
    119                 if ( is_bool( $value ) ) {
    120                     $value = $value ? '1' : '0';
    121                 } elseif ( is_array( $value ) || is_object( $value ) ) {
    122                     $value = wp_json_encode( $value );
    123                 }
    124 
    125                 $row[] = $value;
    126             }
    127 
    128             fputcsv( $fh, $row );
    129         }
    130 
    131         exit;
    132     }
    133 
    134     /**
    135      * Handle JSON export request.
    136      *
    137      * Generates and streams a JSON file containing an array of objects
    138      * (one object per plugin) with the selected export columns.
    139      */
    140     public function handle_export_json() {
    141         // Capability check: only admins (manage_options) can export.
    142         if ( ! current_user_can( 'manage_options' ) ) {
    143             wp_die( esc_html__( 'You do not have permission to export this data.', 'pluxo-blueprint' ) );
    144         }
    145 
    146         // Security: nonce check.
    147         if (
    148             ! isset( $_POST['pluxo_blueprint_export_json_nonce'] ) ||
    149             ! wp_verify_nonce(
    150                 sanitize_text_field( wp_unslash( $_POST['pluxo_blueprint_export_json_nonce'] ) ),
    151                 'pluxo_blueprint_export_json'
    152             )
    153         ) {
    154             wp_die( esc_html__( 'Security check failed. Please try again.', 'pluxo-blueprint' ) );
    155         }
    156 
    157         // Get selected column keys (e.g. [ 'name', 'slug', ... ]).
    158         $selected_keys = $this->get_selected_export_columns();
    159 
    160         if ( empty( $selected_keys ) ) {
    161             wp_die( esc_html__( 'No columns selected for export.', 'pluxo-blueprint' ) );
    162         }
    163 
    164         // Get the full inventory of plugins.
    165         $inventory = $this->get_plugin_inventory();
    166 
    167         $data = array();
    168 
    169         // Build an array of objects (associative arrays) using only the selected columns.
    170         foreach ( $inventory as $plugin ) {
    171             $item = array();
    172 
    173             foreach ( $selected_keys as $column_key ) {
    174                 $value = isset( $plugin[ $column_key ] ) ? $plugin[ $column_key ] : '';
    175 
    176                 // JSON lida bem com booleanos, por isso mantemos true/false.
    177                 if ( is_bool( $value ) ) {
    178                     $value = $value ? true : false;
    179                 }
    180 
    181                 $item[ $column_key ] = $value;
    182             }
    183 
    184             $data[] = $item;
    185         }
    186 
    187         // Encode to JSON with pretty print for readability.
    188         $json = wp_json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
    189 
    190         if ( false === $json ) {
    191             // Fallback to empty array if encoding fails.
    192             $json = '[]';
    193         }
    194 
    195         // Send JSON as a download.
    196         nocache_headers();
    197 
    198         header( 'Content-Type: application/json; charset=utf-8' );
    199 
    200         $filename = 'pluxo-blueprint-plugins-' . gmdate( 'Y-m-d' ) . '.json';
    201         header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
    202         header( 'Content-Length: ' . strlen( $json ) );
    203 
    204         echo $json; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    205         exit;
    206     }
    207    
    208     /**
    209      * Sanitize a value for safe use inside a Markdown table cell.
    210      *
    211      * Confluence and some Markdown parsers may not respect backslash-escaped pipes,
    212      * so we replace literal pipes with an HTML entity.
    213      *
    214      * @param mixed $value Cell value.
    215      * @return string
    216      */
    217     private function sanitize_markdown_cell( $value ) {
    218         // Normalise to string.
    219         if ( is_bool( $value ) ) {
    220             $value = $value ? 'true' : 'false';
    221         } elseif ( is_array( $value ) || is_object( $value ) ) {
    222             $value = wp_json_encode( $value );
    223         }
    224 
    225         $value = (string) $value;
    226 
    227         // Replace pipes with HTML entity to avoid breaking table columns.
    228         $value = str_replace( '|', '&#124;', $value );
    229 
    230         // Replace newlines/tabs with spaces (tables don't like them).
    231         $value = preg_replace( '/\r\n|\r|\n|\t/', ' ', $value );
    232 
    233         // Trim excessive whitespace.
    234         $value = trim( preg_replace( '/\s+/', ' ', $value ) );
    235 
    236         return $value;
    237     }
    238 
    239     /**
    240      * Handle Markdown export request.
    241      *
    242      * Generates and streams a Markdown table of the plugin inventory
    243      * using the currently selected export columns.
    244      */
    245     public function handle_export_markdown() {
    246         // Capability check: only admins (manage_options) can export.
    247         if ( ! current_user_can( 'manage_options' ) ) {
    248             wp_die( esc_html__( 'You do not have permission to export this data.', 'pluxo-blueprint' ) );
    249         }
    250 
    251         // Security: nonce check.
    252         if (
    253             ! isset( $_POST['pluxo_blueprint_export_markdown_nonce'] ) ||
    254             ! wp_verify_nonce(
    255                 sanitize_text_field( wp_unslash( $_POST['pluxo_blueprint_export_markdown_nonce'] ) ),
    256                 'pluxo_blueprint_export_markdown'
    257             )
    258         ) {
    259             wp_die( esc_html__( 'Security check failed. Please try again.', 'pluxo-blueprint' ) );
    260         }
    261 
    262         // Get selected column keys (e.g. [ 'name', 'slug', ... ]).
    263         $selected_keys = $this->get_selected_export_columns();
    264 
    265         if ( empty( $selected_keys ) ) {
    266             wp_die( esc_html__( 'No columns selected for export.', 'pluxo-blueprint' ) );
    267         }
    268 
    269         // Map selected keys to full column definitions.
    270         $available_columns = $this->get_export_columns();
    271         $selected_columns  = array();
    272 
    273         foreach ( $selected_keys as $key ) {
    274             if ( isset( $available_columns[ $key ] ) ) {
    275                 $selected_columns[ $key ] = $available_columns[ $key ];
    276             }
    277         }
    278 
    279         if ( empty( $selected_columns ) ) {
    280             wp_die( esc_html__( 'Export configuration is not available.', 'pluxo-blueprint' ) );
    281         }
    282 
    283         // Get the full inventory of plugins.
    284         $inventory = $this->get_plugin_inventory();
    285 
    286         // Build Markdown lines.
    287         $header_labels = array();
    288         foreach ( $selected_columns as $column_key => $column_def ) {
    289             $header_labels[] = isset( $column_def['label'] ) ? $column_def['label'] : $column_key;
    290         }
    291 
    292         $lines = array();
    293 
    294         // Header row.
    295         $lines[] = '| ' . implode( ' | ', $header_labels ) . ' |';
    296 
    297         // Separator row.
    298         $separator_cells = array_fill( 0, count( $header_labels ), ' --- ' );
    299         $lines[] = '| ' . implode( ' | ', $separator_cells ) . ' |';
    300 
    301         // Data rows.
    302         foreach ( $inventory as $plugin ) {
    303             $row_values = array();
    304 
    305             foreach ( $selected_columns as $column_key => $column_def ) {
    306                 $value = isset( $plugin[ $column_key ] ) ? $plugin[ $column_key ] : '';
    307 
    308                 // Normalise to string.
    309                 if ( is_bool( $value ) ) {
    310                     $value = $value ? 'true' : 'false';
    311                 } elseif ( is_array( $value ) || is_object( $value ) ) {
    312                     $value = wp_json_encode( $value );
    313                 }
    314 
    315                 $row_values[] = $this->sanitize_markdown_cell( $value );
    316                
    317             }
    318 
    319             $lines[] = '| ' . implode( ' | ', $row_values ) . ' |';
    320         }
    321 
    322         $markdown = implode( "\n", $lines );
    323 
    324         // Send Markdown as a downloadable file.
    325         nocache_headers();
    326         header( 'Content-Type: text/markdown; charset=utf-8' );
    327         header(
    328             'Content-Disposition: attachment; filename=pluxo-blueprint-plugins-' . gmdate( 'Ymd-His' ) . '.md'
    329         );
    330 
    331         echo $markdown; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    332         exit;
    333     }
    334 
    335     /**
    336      * Handle PDF export request.
    337      *
    338      * Streams a PDF download with site documentation.
    339      *
    340      * @return void
    341      */
    342     public function handle_export_pdf() {
    343 
    344         if ( ! current_user_can( 'manage_options' ) ) {
    345             wp_die( esc_html__( 'You do not have permission to export this data.', 'pluxo-blueprint' ) );
    346         }
    347 
    348         check_admin_referer( 'pluxo_blueprint_export_pdf', 'pluxo_blueprint_export_pdf_nonce' );
    349 
    350         $generator = new Pluxo_Blueprint_PDF_Generator( PLUXO_BLUEPRINT_PLUGIN_DIR );
    351         $generator->stream_pdf();
    352 
    353         exit;
    35492    }
    35593
     
    363101        add_action( 'admin_menu', array( $this, 'remove_default_pluxo_submenu' ), 999 );
    364102
    365         // Handle saving of export column preferences.
    366         add_action( 'admin_init', array( $this, 'handle_save_export_columns' ) );
    367 
    368         // Handle CSV export action.
    369         add_action( 'admin_post_pluxo_blueprint_export_csv', array( $this, 'handle_export_csv' ) );
    370 
    371         // Handle JSON export action.
    372         add_action( 'admin_post_pluxo_blueprint_export_json', array( $this, 'handle_export_json' ) );
    373 
    374         // Handle Markdown export action.
    375         add_action( 'admin_post_pluxo_blueprint_export_markdown', array( $this, 'handle_export_markdown' ) );
    376 
    377         // Handle PDF export action.
    378         add_action( 'admin_post_pluxo_blueprint_export_pdf', array( $this, 'handle_export_pdf' ) );
    379 
    380103        // Enqueue admin assets only on our page.
    381104        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
    382105        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_menu_assets' ) );
     106        add_action( 'admin_init', array( $this, 'handle_overview_actions' ) );
     107
     108        add_action( 'admin_post_pluxo_blueprint_export_overview_pdf', array( $this, 'handle_export_overview_pdf' ) );
     109
    383110    }
    384111
     
    407134            'manage_options',                           // Capability.
    408135            'pluxo-blueprint',                          // Slug of the actual page.
    409             array( $this, 'render_admin_page' )         // Your existing renderer.
     136            array( $this->admin_page, 'render' )       // Your existing renderer.
    410137        );
    411138    }
     
    472199            true
    473200        );
    474     }
    475 
     201
     202                $copy_js_relative_path = 'assets/js/admin-copy-markdown.js';
     203
     204        $copy_js_file_path = PLUXO_BLUEPRINT_PLUGIN_DIR . $copy_js_relative_path;
     205        $copy_js_file_url  = PLUXO_BLUEPRINT_PLUGIN_URL . $copy_js_relative_path;
     206
     207        $copy_js_version = file_exists( $copy_js_file_path ) ? (string) filemtime( $copy_js_file_path ) : PLUXO_BLUEPRINT_VERSION;
     208
     209        wp_enqueue_script(
     210            'pluxo-blueprint-admin-copy-md',
     211            $copy_js_file_url,
     212            array(),
     213            $copy_js_version,
     214            true
     215        );
     216    }
     217   
    476218    /**
    477219     * Enqueue admin menu assets across wp-admin.
     
    496238
    497239    /**
    498      * Render the main admin page for the plugin.
    499      */
    500     public function render_admin_page() {
     240     * Handle Overview actions (e.g. Builders scan).
     241     *
     242     * @return void
     243     */
     244    public function handle_overview_actions() {
     245        if ( ! is_admin() ) {
     246            return;
     247        }
     248
     249        // Only handle our own admin page submissions.
     250        $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
     251        if ( 'pluxo-blueprint' !== $page ) {
     252            return;
     253        }
     254
     255        if ( empty( $_POST['pluxo_blueprint_action'] ) ) {
     256            return;
     257        }
     258
     259        $action = sanitize_text_field( wp_unslash( $_POST['pluxo_blueprint_action'] ) );
     260
     261        if ( 'builders_scan' !== $action ) {
     262            return;
     263        }
     264
     265        // Nonce check.
     266        if ( empty( $_POST['pluxo_blueprint_builders_scan_nonce'] ) ) {
     267            return;
     268        }
     269
     270        $nonce = sanitize_text_field( wp_unslash( $_POST['pluxo_blueprint_builders_scan_nonce'] ) );
     271
     272        if ( ! wp_verify_nonce( $nonce, 'pluxo_blueprint_builders_scan' ) ) {
     273            return;
     274        }
     275
     276        // Capability check.
    501277        if ( ! current_user_can( 'manage_options' ) ) {
    502278            return;
    503279        }
    504280
    505         $inventory         = $this->get_plugin_inventory();
    506         $available_columns = $this->get_export_columns();
    507         $selected_columns  = $this->get_selected_export_columns();
    508         ?>
    509 
    510 <?php
    511         $logo_url = PLUXO_BLUEPRINT_PLUGIN_URL . 'assets/images/logo.png';
    512         ?>
    513 
    514 <div class="wrap pluxo-blueprint-wrap">
    515     <div class="pluxo-blueprint-logo-wrapper">
    516         <img class="pluxo-blueprint-logo" src="<?php echo esc_url( $logo_url ); ?>" alt="<?php esc_attr_e( 'Pluxo Blueprint logo', 'pluxo-blueprint' ); ?>">
    517         <div class="pluxo-blueprint-title">
    518             <h1 class="wp-heading-inline">
    519                 <?php echo esc_html__( 'Pluxo Blueprint', 'pluxo-blueprint' ); ?>
    520             </h1>
    521             <p class="pluxo-blueprint-description">
    522                 <?php
    523                         echo wp_kses_post(
    524                             sprintf(
    525                                 /* translators: %s: Plugin description. */
    526                                 __(
    527                                     'Pluxo Blueprint is a WordPress plugin that helps you %1$sgenerate a clear PDF snapshot of your site documentation, including installed plugins, themes, users and roles, detected page builders, and high-level tracking signals.%2$s
    528                                    
    529                                     It also allows you to export your plugin inventory to CSV, JSON, or Markdown, making it easy to reuse the data for documentation or development workflows in tools like Confluence, Notion, or OneNote.
    530 ',
    531                                     'pluxo-blueprint'
    532                                 ),
    533                                 '<strong>',
    534                                 '</strong><br><br>'
    535                             )
    536                         );
    537                        
    538                         ?>
    539             </p>
    540         </div>
    541     </div>
    542 
    543     <div class="pluxo-blueprint-card">
    544         <div class="pluxo-blueprint-card-header">
    545             <h2><?php echo esc_html__( 'PDF Export with Website Documentation', 'pluxo-blueprint' ); ?></h2><br>
    546                 <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
    547                     <?php wp_nonce_field( 'pluxo_blueprint_export_pdf', 'pluxo_blueprint_export_pdf_nonce' ); ?>
    548                     <input type="hidden" name="action" value="pluxo_blueprint_export_pdf" />
    549                     <?php submit_button( __( 'Export PDF', 'pluxo-blueprint' ), 'secondary', 'pluxo_blueprint_export_pdf_button', false ); ?>
    550                 </form><br>
    551             <p>
    552                 <?php
    553                 echo esc_html__(
    554                     'Generate a one-click PDF snapshot of your site documentation.',
    555                     'pluxo-blueprint'
    556                 );
    557                 ?>
    558             </p>
    559         </div>
    560 
    561         <div class="pluxo-blueprint-card-body">
    562             <p class="description">
    563                 <?php
    564                 echo esc_html__(
    565                     'The PDF export provides a high-level overview of your WordPress setup, designed for documentation, audits, or sharing with clients.',
    566                     'pluxo-blueprint'
    567                 );
    568                 ?>
    569             </p>
    570 
    571             <ul class="pluxo-blueprint-help-list pluxo-blueprint-help-list--compact">
    572                 <li>
    573                     <strong><?php echo esc_html__( 'Plugins', 'pluxo-blueprint' ); ?></strong>
    574                     <?php
    575                     echo esc_html__(
    576                         '– Lists active and inactive plugins, including version information and update status when available.',
    577                         'pluxo-blueprint'
    578                     );
    579                     ?>
    580                 </li>
    581 
    582                 <li>
    583                     <strong><?php echo esc_html__( 'Themes', 'pluxo-blueprint' ); ?></strong>
    584                     <?php
    585                     echo esc_html__(
    586                         '– Shows the active theme and any inactive themes installed on the site.',
    587                         'pluxo-blueprint'
    588                     );
    589                     ?>
    590                 </li>
    591 
    592                 <li>
    593                     <strong><?php echo esc_html__( 'Users & Roles', 'pluxo-blueprint' ); ?></strong>
    594                     <?php
    595                     echo esc_html__(
    596                         '– Displays user roles and a summary of assigned users (usernames only).',
    597                         'pluxo-blueprint'
    598                     );
    599                     ?>
    600                 </li>
    601 
    602                 <li>
    603                     <strong><?php echo esc_html__( 'Page Builders', 'pluxo-blueprint' ); ?></strong>
    604                     <?php
    605                     echo esc_html__(
    606                         '– Detects common page builders such as Elementor or the WordPress default editor.',
    607                         'pluxo-blueprint'
    608                     );
    609                     ?>
    610                 </li>
    611 
    612                 <li>
    613                     <strong><?php echo esc_html__( 'Tracking & Analytics (best-effort)', 'pluxo-blueprint' ); ?></strong>
    614                     <?php
    615                     echo esc_html__(
    616                         '– Reports potential tracking or analytics signals based on detected plugins or theme files.',
    617                         'pluxo-blueprint'
    618                     );
    619                     ?>
    620                 </li>
    621 
    622                 <li>
    623                     <strong><?php echo esc_html__( 'Cookies (conditional)', 'pluxo-blueprint' ); ?></strong>
    624                     <?php
    625                     echo esc_html__(
    626                         '– Cookie details are only included if a supported cookie/GDPR plugin is detected (for example, CookieYes).',
    627                         'pluxo-blueprint'
    628                     );
    629                     ?>
    630                 </li>
    631             </ul>
    632             <p class="description">
    633                 <?php
    634                 echo esc_html__(
    635                     'This report is generated locally and does not make external requests. All information is provided on a best-effort basis and should be reviewed before being shared externally.',
    636                     'pluxo-blueprint'
    637                 );
    638                 ?>
    639             </p>
    640         </div>
    641     </div>
    642 
    643     <div class="pluxo-blueprint-card">
    644         <div class="pluxo-blueprint-card-header">
    645             <h2><?php esc_html_e( 'Installed Plugins', 'pluxo-blueprint' ); ?></h2><br>
    646                     <div class="pluxo-blueprint-card-body">
    647             <p class="description">
    648                 <?php echo esc_html__( 'Tick the boxes for the columns you want to keep in your exports. You can adjust these settings at any time and Pluxo Blueprint will remember your choices.', 'pluxo-blueprint' ); ?>
    649             </p>
    650 
    651             <form method="post">
    652                 <?php wp_nonce_field( 'pluxo_blueprint_save_columns', 'pluxo_blueprint_columns_nonce' ); ?>
    653 
    654                 <table class="form-table" role="presentation">
    655                     <tbody>
    656                         <tr>
    657                             <th scope="row">
    658                                 <label><?php echo esc_html__( 'Columns to export', 'pluxo-blueprint' ); ?></label>
    659                             </th>
    660                             <td>
    661                                 <?php foreach ( $available_columns as $column_key => $column ) : ?>
    662                                 <?php
    663                                         $is_required = ( 'name' === $column_key );
    664                                         $is_checked  = in_array( $column_key, $selected_columns, true );
    665                                         ?>
    666 
    667                                 <?php if ( $is_required ) : ?>
    668                                 <!-- Hidden input to ensure the "name" column is always submitted -->
    669                                 <input type="hidden" name="pluxo_blueprint_columns[]" value="name" />
    670                                 <?php endif; ?>
    671 
    672                                 <label class="pluxo-blueprint-column-option">
    673                                     <input type="checkbox" name="pluxo_blueprint_columns[]" value="<?php echo esc_attr( $column_key ); ?>" <?php checked( $is_checked ); ?> <?php disabled( $is_required ); ?> />
    674                                     <?php echo esc_html( $column['label'] ); ?>
    675                                 </label>
    676                                 <?php endforeach; ?>
    677                             </td>
    678                         </tr>
    679                     </tbody>
    680                 </table>
    681 
    682                 <p>
    683                     <button type="submit" name="pluxo_blueprint_save_columns" class="button button-primary">
    684                         <?php echo esc_html__( 'Refresh plugins table below', 'pluxo-blueprint' ); ?>
    685                     </button>
    686                 </p>
    687             </form><br>
    688         </div>
    689             <p><?php esc_html_e( 'Review the detected plugins and export them as CSV, JSON or Markdown.', 'pluxo-blueprint' ); ?></p><br>
    690             <p class="description">
    691                 <?php echo esc_html__( 'Choose the format that best fits your workflow:', 'pluxo-blueprint' ); ?>
    692             </p>
    693             <ul class="pluxo-blueprint-help-list pluxo-blueprint-help-list--compact">
    694                 <li>
    695                     <strong><?php echo esc_html__( 'CSV', 'pluxo-blueprint' ); ?></strong>
    696                     <?php echo esc_html__( '– Ideal for spreadsheets or importing into other tools.', 'pluxo-blueprint' ); ?>
    697                 </li>
    698                 <li>
    699                     <strong><?php echo esc_html__( 'JSON', 'pluxo-blueprint' ); ?></strong>
    700                     <?php echo esc_html__( '– Good for developers and automation workflows.', 'pluxo-blueprint' ); ?>
    701                 </li>
    702                 <li>
    703                     <strong><?php echo esc_html__( 'Markdown', 'pluxo-blueprint' ); ?></strong>
    704                     <?php echo esc_html__( '– Perfect for documentation, tickets or README files.', 'pluxo-blueprint' ); ?>
    705                 </li>
    706             </ul>
    707         </div>
    708 
    709         <div class="pluxo-blueprint-card-body">
    710             <div class="pluxo-blueprint-actions">
    711                 <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
    712                     <?php
    713                                 wp_nonce_field( 'pluxo_blueprint_export_csv', 'pluxo_blueprint_export_csv_nonce' );
    714                                 ?>
    715                     <input type="hidden" name="action" value="pluxo_blueprint_export_csv" />
    716                     <?php submit_button( __( 'Export CSV', 'pluxo-blueprint' ), 'secondary', 'pluxo_blueprint_export_csv_button', false ); ?>
    717                 </form>
    718 
    719                 <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
    720                     <?php wp_nonce_field( 'pluxo_blueprint_export_json', 'pluxo_blueprint_export_json_nonce' ); ?>
    721                     <input type="hidden" name="action" value="pluxo_blueprint_export_json" />
    722                     <?php submit_button( __( 'Export JSON', 'pluxo-blueprint' ), 'secondary', 'pluxo_blueprint_export_json_button', false ); ?>
    723                 </form>
    724 
    725                 <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
    726                     <?php wp_nonce_field( 'pluxo_blueprint_export_markdown', 'pluxo_blueprint_export_markdown_nonce' ); ?>
    727                     <input type="hidden" name="action" value="pluxo_blueprint_export_markdown" />
    728                     <?php submit_button( __( 'Export Markdown', 'pluxo-blueprint' ), 'secondary', 'pluxo_blueprint_export_markdown_button', false ); ?>
    729                 </form>
    730             </div><br>
    731 
    732             <table class="widefat fixed striped pluxo-blueprint-table">
    733                 <thead>
    734                     <tr>
    735                         <?php foreach ( $selected_columns as $column_key ) : ?>
    736                         <th scope="col">
    737                             <?php
    738                                             if ( isset( $available_columns[ $column_key ]['label'] ) ) {
    739                                                 echo esc_html( $available_columns[ $column_key ]['label'] );
    740                                             } else {
    741                                                 echo esc_html( $column_key );
    742                                             }
    743                                             ?>
    744                         </th>
    745                         <?php endforeach; ?>
    746                     </tr>
    747                 </thead>
    748                 <tbody>
    749                     <?php if ( empty( $inventory ) ) : ?>
    750                     <tr>
    751                         <td colspan="<?php echo esc_attr( max( 1, count( $selected_columns ) ) ); ?>">
    752                             <?php echo esc_html__( 'No plugins found.', 'pluxo-blueprint' ); ?>
    753                         </td>
    754                     </tr>
    755                     <?php else : ?>
    756                     <?php foreach ( $inventory as $plugin ) : ?>
    757                     <tr>
    758                         <?php foreach ( $selected_columns as $column_key ) : ?>
    759                         <td>
    760                             <?php
    761                                                     switch ( $column_key ) {
    762                                                         case 'name':
    763                                                             echo isset( $plugin['name'] ) ? esc_html( $plugin['name'] ) : '';
    764                                                             break;
    765                                                         case 'version':
    766                                                             echo isset( $plugin['version'] ) ? esc_html( $plugin['version'] ) : '';
    767                                                             break;
    768                                                         case 'author':
    769                                                             echo isset( $plugin['author'] ) ? esc_html( $plugin['author'] ) : '';
    770                                                             break;
    771                                                         case 'status':
    772                                                             echo isset( $plugin['status'] ) ? esc_html( ucfirst( $plugin['status'] ) ) : '';
    773                                                             break;
    774                                                         case 'slug':
    775                                                             echo isset( $plugin['slug'] ) ? esc_html( $plugin['slug'] ) : '';
    776                                                             break;
    777                                                         case 'plugin_uri':
    778                                                             if ( ! empty( $plugin['plugin_uri'] ) ) {
    779                                                                 $url   = esc_url( $plugin['plugin_uri'] );
    780                                                                 $label = esc_html( $plugin['plugin_uri'] );
    781                                                                 echo '<a href="' . esc_url( $url ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $label ) . '</a>';
    782                                                             } else {
    783                                                                 echo '';
    784                                                             }
    785                                                             break;
    786                                                         default:
    787                                                             // Unknown column, output nothing for now.
    788                                                             echo '';
    789                                                             break;
    790                                                     }
    791                                                     ?>
    792                         </td>
    793                         <?php endforeach; ?>
    794                     </tr>
    795                     <?php endforeach; ?>
    796                     <?php endif; ?>
    797                 </tbody>
    798             </table>
    799         </div>
    800     </div>
    801     <div class="pluxo-blueprint-footer">
    802         <p>
    803             <?php esc_html_e( 'Made by', 'pluxo-blueprint' ); ?>
    804             <a href="https://pluxo.dev/" target="_blank" rel="noopener noreferrer"><strong>Pluxo</strong></a>
    805         </p>
    806     </div>
    807 </div>
    808 <?php
    809     }
    810 
    811     /**
    812      * Get a structured inventory of all installed plugins.
    813      *
    814      * @return array[]
    815      */
    816     public function get_plugin_inventory() {
    817         // Ensure the WordPress plugin API functions are available.
    818         if ( ! function_exists( 'get_plugins' ) || ! function_exists( 'is_plugin_active' ) ) {
    819             require_once ABSPATH . 'wp-admin/includes/plugin.php';
    820         }
    821 
    822         $all_plugins = get_plugins(); // [ 'plugin-folder/plugin-file.php' => [ plugin headers... ] ]
    823         $inventory   = array();
    824 
    825         foreach ( $all_plugins as $plugin_file => $plugin_data ) {
    826             $is_active = is_plugin_active( $plugin_file );
    827 
    828             $slug = dirname( $plugin_file );
    829             if ( '.' === $slug ) {
    830                 $slug = $plugin_file; // fallback.
    831             }
    832 
    833             $plugins_base_dir = trailingslashit( dirname( rtrim( PLUXO_BLUEPRINT_PLUGIN_DIR, "/\\" ) ) );
    834             $absolute_path    = $plugins_base_dir . ltrim( $plugin_file, "/\\" );
    835 
    836             $inventory[] = array(
    837                 'name'        => isset( $plugin_data['Name'] ) ? $plugin_data['Name'] : '',
    838                 'slug'        => $slug,
    839                 'plugin_file' => $plugin_file,
    840                 'status'      => $is_active ? 'active' : 'inactive',
    841                 'is_active'   => $is_active,
    842                 'version'     => isset( $plugin_data['Version'] ) ? $plugin_data['Version'] : '',
    843                 'plugin_uri'  => isset( $plugin_data['PluginURI'] ) ? $plugin_data['PluginURI'] : '',
    844                 'author'      => isset( $plugin_data['Author'] ) ? $plugin_data['Author'] : '',
    845                 'author_uri'  => isset( $plugin_data['AuthorURI'] ) ? $plugin_data['AuthorURI'] : '',
    846                 'text_domain' => isset( $plugin_data['TextDomain'] ) ? $plugin_data['TextDomain'] : '',
    847                 'network'     => isset( $plugin_data['Network'] ) ? $plugin_data['Network'] : '',
    848                 'title'       => isset( $plugin_data['Title'] ) ? $plugin_data['Title'] : '',
    849                 'author_name' => isset( $plugin_data['AuthorName'] ) ? $plugin_data['AuthorName'] : '',
    850                 'path' => $absolute_path,
    851             );
    852         }
    853 
    854         // Sort plugins alphabetically by name.
    855         usort(
    856             $inventory,
    857             function( $a, $b ) {
    858                 return strcasecmp( $a['name'], $b['name'] );
    859             }
    860         );
    861 
    862         return $inventory;
    863     }
    864 
    865     /**
    866      * Get the available plugin inventory export columns.
    867      *
    868      * @return array<string, array<string, string>> Column definitions.
    869      */
    870     private function get_export_columns() {
    871         return array(
    872             'name'       => array(
    873                 'label'       => __( 'Name', 'pluxo-blueprint' ),
    874                 'description' => __( 'The display name of the plugin.', 'pluxo-blueprint' ),
    875             ),
    876             'slug'       => array(
    877                 'label'       => __( 'Slug', 'pluxo-blueprint' ),
    878                 'description' => __( 'The plugin folder slug (directory name).', 'pluxo-blueprint' ),
    879             ),
    880             'version'    => array(
    881                 'label'       => __( 'Version', 'pluxo-blueprint' ),
    882                 'description' => __( 'The currently installed plugin version.', 'pluxo-blueprint' ),
    883             ),
    884             'author'     => array(
    885                 'label'       => __( 'Author', 'pluxo-blueprint' ),
    886                 'description' => __( 'The author of the plugin as declared in the plugin header.', 'pluxo-blueprint' ),
    887             ),
    888             'status'     => array(
    889                 'label'       => __( 'Status', 'pluxo-blueprint' ),
    890                 'description' => __( 'Whether the plugin is active or inactive.', 'pluxo-blueprint' ),
    891             ),
    892             'plugin_uri' => array(
    893                 'label'       => __( 'Plugin URL', 'pluxo-blueprint' ),
    894                 'description' => __( 'The plugin web site or documentation URL.', 'pluxo-blueprint' ),
    895             ),
    896         );
    897     }
    898 
    899     /**
    900      * Get the list of selected export columns.
    901      *
    902      * Ensures the "name" column is always included.
    903      *
    904      * @return array
    905      */
    906     public function get_selected_export_columns() {
    907         $columns         = $this->get_export_columns();
    908         $saved_columns   = get_option( $this->columns_option_name );
    909         $available_keys  = array_keys( $columns );
    910         $selected_keys   = array();
    911 
    912         if ( is_array( $saved_columns ) && ! empty( $saved_columns ) ) {
    913             // Keep only valid and existing column keys.
    914             $selected_keys = array_values(
    915                 array_intersect( $available_keys, $saved_columns )
    916             );
    917         } else {
    918             // Default: all available columns.
    919             $selected_keys = $available_keys;
    920         }
    921 
    922         // Ensure the "name" column is always selected if it exists.
    923         if ( in_array( 'name', $available_keys, true ) && ! in_array( 'name', $selected_keys, true ) ) {
    924             array_unshift( $selected_keys, 'name' );
    925             $selected_keys = array_values( array_unique( $selected_keys ) );
    926         }
    927 
    928         return $selected_keys; 
    929     }
    930 
    931     /**
    932      * Handle saving of export column preferences.
    933      */
    934     public function handle_save_export_columns() {
    935         if ( ! isset( $_POST['pluxo_blueprint_columns_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['pluxo_blueprint_columns_nonce'] ) ), 'pluxo_blueprint_save_columns' ) ) {
    936             return;
    937         }
    938 
     281        // Run scan and save results.
     282        if ( class_exists( 'Pluxo_Blueprint_Module_Builders' ) ) {
     283            $module = new Pluxo_Blueprint_Module_Builders( $this );
     284            $module->run_scan_and_save();
     285        }
     286
     287        // Redirect to avoid form resubmission.
     288        wp_safe_redirect( admin_url( 'admin.php?page=pluxo-blueprint&tab=overview' ) );
     289        exit;
     290    }
     291
     292    /**
     293     * Handle Overview export (print to PDF).
     294     *
     295     * @return void
     296     */
     297    public function handle_export_overview_pdf() {
    939298        if ( ! current_user_can( 'manage_options' ) ) {
    940             return;
    941         }
    942 
    943         $columns_input = array();
    944 
    945         if ( isset( $_POST['pluxo_blueprint_columns'] ) ) {
    946             // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Input is unslashed then sanitised via sanitize_key() below.
    947             $raw_columns   = (array) wp_unslash( $_POST['pluxo_blueprint_columns'] );
    948 
    949             $columns_input = array_map( 'sanitize_key', $raw_columns );
    950             $columns_input = array_values( array_filter( $columns_input ) );
    951 
    952             $allowed_columns = array_keys( $this->get_export_columns() );
    953             $columns_input   = array_values(
    954                 array_intersect( $columns_input, $allowed_columns )
    955             );
    956         }
    957 
    958         $available_columns = $this->get_export_columns();
    959         $available_keys    = array_keys( $available_columns );
    960 
    961         // Keep only valid column keys that exist in the available columns.
    962         $columns = array_values(
    963             array_intersect( $available_keys, $columns_input )
    964         );
    965 
    966         // If nothing valid was selected, fall back to all columns.
    967         if ( empty( $columns ) ) {
    968             $columns = $available_keys;
    969         }
    970 
    971         // Ensure the "name" column is always present if it exists.
    972         if ( in_array( 'name', $available_keys, true ) && ! in_array( 'name', $columns, true ) ) {
    973             array_unshift( $columns, 'name' );
    974             $columns = array_values( array_unique( $columns ) );
    975         }
    976 
    977         update_option( $this->columns_option_name, $columns );
    978 
    979         // Redirect back to the admin page to avoid resubmission.
    980         $redirect_url = add_query_arg(
    981             array(
    982                 'page'    => 'pluxo-blueprint',
    983                 'updated' => 'true',
    984             ),
    985             admin_url( 'admin.php' )
    986         );
    987 
    988         wp_safe_redirect( $redirect_url );
     299            wp_die( esc_html__( 'You do not have permission to access this export.', 'pluxo-blueprint' ) );
     300        }
     301
     302        check_admin_referer( 'pluxo_blueprint_export_overview_pdf' );
     303
     304        require_once __DIR__ . '/Admin/class-pluxo-blueprint-overview.php';
     305
     306        $overview = new Pluxo_Blueprint_Overview( $this );
     307
     308        // Build modules HTML using the existing render() output.
     309        ob_start();
     310        $overview->render_export_view();
     311        $modules_html = (string) ob_get_clean();
     312
     313        require_once __DIR__ . '/PDF/class-pluxo-blueprint-pdf-generator.php';
     314
     315        $plugin_dir = defined( 'PLUXO_BLUEPRINT_PLUGIN_DIR' )
     316            ? PLUXO_BLUEPRINT_PLUGIN_DIR
     317            : plugin_dir_path( dirname( __DIR__ ) );
     318
     319        $generator = new Pluxo_Blueprint_PDF_Generator( $plugin_dir );
     320
     321        $generator->render_overview_print_page( $modules_html );
     322
    989323        exit;
    990324    }
    991 
    992325}
  • pluxo-blueprint/trunk/pluxo-blueprint.php

    r3444793 r3453209  
    33 * Plugin Name:       Pluxo Blueprint for Website Documentation
    44 * Plugin URI:        https://pluxo.dev/#pluxo-blueprint
    5  * Description:       Generate a PDF snapshot of your WordPress for site documentation and export plugin inventory to CSV, JSON, or Markdown.
    6  * Version:           1.1.1
     5 * Description:       Generate a structured PDF snapshot of your WordPress site documentation, with a modular overview and easy Markdown copy for reuse.
     6 * Version:           1.2.0
    77 * Requires at least: 6.0
    88 * Requires PHP:      7.4
     
    6363
    6464if ( ! defined( 'PLUXO_BLUEPRINT_VERSION' ) ) {
    65     define( 'PLUXO_BLUEPRINT_VERSION', '1.1.1' );
     65    define( 'PLUXO_BLUEPRINT_VERSION', '1.2.0' );
    6666}
    6767
  • pluxo-blueprint/trunk/readme.txt

    r3444793 r3453209  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.1.1
     7Stable tag: 1.2.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Generate a one-click PDF snapshot of your WordPress Website Documentation, including plugins, themes, users, and environment details.
     11Generate a one-click PDF snapshot of your WordPress Website Documentation, including site configuration, modules overview, and key technical signals.
    1212
    1313== Description ==
    14 Pluxo Blueprint is a WordPress plugin that helps you generate a clear PDF snapshot of your site documentation, including installed plugins, active theme information, users and roles, detected page builders, and high-level tracking signals.
     14Pluxo Blueprint is a WordPress plugin that helps you generate a clear, structured overview of your website configuration and documentation.
    1515
    16 It also allows you to export your plugin inventory to CSV, JSON, or Markdown, making it easy to reuse the data for documentation or development workflows in tools like Confluence, Notion, or OneNote.
     16Inside the WordPress admin, it provides a clean Overview screen organised into expandable modules (accordions), covering areas such as site information, themes, plugins, users & roles, page builders, tracking sources, cookies & consent, and more.
     17
     18You can also copy each module as Markdown (one module at a time), making it easy to reuse the content in documentation tools like Confluence, Notion, or OneNote.
    1719
    1820Pluxo Blueprint runs entirely inside your WordPress installation and does not rely on external services or tracking.
    1921
    2022== Features ==
    21 * Generate a one-click PDF snapshot of your WordPress site documentation
    22 * Includes site information such as:
    23   * Installed plugins
    24   * Active theme
     23* Overview dashboard with multiple documentation modules (accordion layout)
     24* One-click PDF export based on the full Overview content
     25* Per-module “Copy as Markdown” button for easy reuse in external documentation
     26* Includes high-level documentation areas such as:
     27  * Site information (versions, language, timezone, etc.)
     28  * Themes overview
     29  * Plugins overview
     30  * Content types overview
    2531  * Users and roles
    26   * Detected page builders
    27   * High-level tracking signals
    28 * View a detailed table of installed plugins in the WordPress admin
    29 * Choose which plugin data columns to include in exports
    30 * Export plugin inventory to:
    31   * CSV
    32   * JSON
    33   * Markdown (ideal for Notion, Confluence, GitHub, etc.)
     32  * Detected page builders and theme builders
     33  * Menus and locations
     34  * Tracking sources (high-level signals)
     35  * Cookies & consent (best-effort, provider-based)
     36  * Translators and multilingual tools (best-effort detection)
    3437* No external services or tracking
    3538* All data stays inside your WordPress installation
    3639
    3740== Screenshots ==
    38 1. Main admin page showing the plugin inventory table and export options.
    39 2. PDF export option generating site documentation.
     411. Overview screen showing the full list of modules and the PDF export button.
     422. Example module (Tracking Sources) with the accordion expanded.
    4043
    4144== Installation ==
     
    5255
    5356= Does the plugin require external libraries? =
    54 The plugin bundles all required libraries internally and does not rely on external services.
     57No. The PDF export uses a print-friendly page that relies on your browser’s built-in Print → Save as PDF.
    5558
    5659== Changelog ==
     60= 1.2.0 =
     61* Add: New Overview dashboard organised into multiple documentation modules
     62* Add: “Copy as Markdown” button on each module for quick reuse in documentation tools
     63* Improve: PDF export now uses the Overview modules content for a complete documentation snapshot
     64* Remove: CSV/JSON/Markdown file exports (replaced by per-module Markdown copy and the Overview-based PDF export)
     65
    5766= 1.1.1 =
    5867* Small fixes and change of plugin name
     
    7685
    7786== Upgrade Notice ==
    78 = 1.1.0 =
    79 Adds PDF export for complete site documentation with no external dependencies.
     87= 1.2.0 =
     88Introduces a new Overview dashboard with multiple modules, per-module “Copy as Markdown”, and a PDF export based on the full modules content. Removes CSV/JSON/Markdown file exports.
Note: See TracChangeset for help on using the changeset viewer.