Changeset 3455410
- Timestamp:
- 02/06/2026 01:16:01 PM (2 weeks ago)
- Location:
- pluxo-blueprint/trunk
- Files:
-
- 21 edited
-
includes/Modules/Builders/class-pluxo-blueprint-builders-renderer.php (modified) (1 diff)
-
includes/Modules/Builders/class-pluxo-blueprint-builders-repository.php (modified) (3 diffs)
-
includes/Modules/Content_Types/class-pluxo-blueprint-content-types-repository.php (modified) (7 diffs)
-
includes/Modules/Content_Types/class-pluxo-blueprint-module-content-types.php (modified) (2 diffs)
-
includes/Modules/Cookies_Consent/class-pluxo-blueprint-cookies-consent-scan.php (modified) (3 diffs)
-
includes/Modules/Cookies_Consent/class-pluxo-blueprint-module-cookies-consent.php (modified) (8 diffs)
-
includes/Modules/Header_Footer/class-pluxo-blueprint-header-footer-repository.php (modified) (1 diff)
-
includes/Modules/Header_Footer/class-pluxo-blueprint-header-footer-scan.php (modified) (5 diffs)
-
includes/Modules/Plugins/class-pluxo-blueprint-plugins-renderer.php (modified) (3 diffs)
-
includes/Modules/Site_Info/class-pluxo-blueprint-module-site-info.php (modified) (1 diff)
-
includes/Modules/Site_Info/class-pluxo-blueprint-site-info-renderer.php (modified) (1 diff)
-
includes/Modules/Site_Info/class-pluxo-blueprint-site-info-repository.php (modified) (3 diffs)
-
includes/Modules/Themes/class-pluxo-blueprint-themes-repository.php (modified) (1 diff)
-
includes/Modules/Tracking/Data/class-pluxo-blueprint-tracking-registry.php (modified) (2 diffs)
-
includes/Modules/Tracking/Services/class-pluxo-blueprint-tracking-scanner.php (modified) (3 diffs)
-
includes/Modules/Translators/class-pluxo-blueprint-translators-repository.php (modified) (13 diffs)
-
includes/Modules/Users/class-pluxo-blueprint-module-users.php (modified) (3 diffs)
-
includes/PDF/class-pluxo-blueprint-pdf-generator.php (modified) (1 diff)
-
includes/PDF/templates/overview-print.php (modified) (1 diff)
-
pluxo-blueprint.php (modified) (2 diffs)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
pluxo-blueprint/trunk/includes/Modules/Builders/class-pluxo-blueprint-builders-renderer.php
r3453209 r3455410 56 56 <div class="pluxo-blueprint-accordion-body"> 57 57 58 <?php $this->render_actions( $last_scan ); ?> 58 <?php 59 $is_print = defined( 'PLUXO_BLUEPRINT_PRINT_VIEW' ) && PLUXO_BLUEPRINT_PRINT_VIEW; 60 if ( ! $is_print ) { 61 $this->render_actions( $last_scan ); 62 } 63 ?> 59 64 60 65 <?php if ( '' !== $summary ) : ?> -
pluxo-blueprint/trunk/includes/Modules/Builders/class-pluxo-blueprint-builders-repository.php
r3453209 r3455410 139 139 ); 140 140 141 $wpbakery_active = $this->is_plugin_active_by_slug( $plugins, 'js_composer/js_composer.php' ); 141 $wpbakery_active = $this->is_any_plugin_active_by_slugs( 142 $plugins, 143 array( 144 'js_composer/js_composer.php', 145 'js_composer_theme/js_composer.php', 146 ) 147 ); 142 148 $bricks_active = $this->is_plugin_active_by_slug( $plugins, 'bricks/bricks.php' ); 143 149 … … 171 177 'wpbakery' => array( 172 178 'label' => 'WPBakery', 173 'detected' => $wpbakery_active, 174 'notes' => __( 'Detection uses WPBakery shortcodes in content (best-effort).', 'pluxo-blueprint' ), 179 'detected' => true, 180 'notes' => $wpbakery_active 181 ? __( 'Plugin detected. Usage scan searches WPBakery shortcodes in content (best-effort).', 'pluxo-blueprint' ) 182 : __( 'Usage scan searches WPBakery shortcodes in content (best-effort). Plugin not detected as active.', 'pluxo-blueprint' ), 175 183 'type' => 'content_like', 176 184 'like' => '[vc_', … … 662 670 return is_plugin_active( $plugin_file ); 663 671 } 672 673 /** 674 * Check if any plugin in a list is active. 675 * 676 * @param array<string, array<string,mixed>> $plugins Plugins array. 677 * @param string[] $plugin_files Plugin files. 678 * @return bool 679 */ 680 private function is_any_plugin_active_by_slugs( $plugins, $plugin_files ) { 681 $plugin_files = array_map( 'strval', (array) $plugin_files ); 682 $plugin_files = array_values( array_filter( array_map( 'trim', $plugin_files ) ) ); 683 684 if ( empty( $plugin_files ) ) { 685 return false; 686 } 687 688 foreach ( $plugin_files as $plugin_file ) { 689 if ( $this->is_plugin_active_by_slug( $plugins, $plugin_file ) ) { 690 return true; 691 } 692 } 693 694 return false; 695 } 664 696 } -
pluxo-blueprint/trunk/includes/Modules/Content_Types/class-pluxo-blueprint-content-types-repository.php
r3453209 r3455410 92 92 93 93 /** 94 * Check if a post type starts with any blocked prefix. 95 * 96 * @param string $type Post type key. 97 * @param string[] $prefixes Prefix list. 98 * @return bool 99 */ 100 private function starts_with_any_prefix( $type, $prefixes ) { 101 $type = (string) $type; 102 $prefixes = (array) $prefixes; 103 104 foreach ( $prefixes as $prefix ) { 105 $prefix = (string) $prefix; 106 if ( '' === $prefix ) { 107 continue; 108 } 109 if ( 0 === strpos( $type, $prefix ) ) { 110 return true; 111 } 112 } 113 114 return false; 115 } 116 117 /** 118 * Decide whether a post type should be treated as Primary content (public-facing). 119 * 120 * @param string $type Post type key. 121 * @param object $obj Post type object. 122 * @param bool $is_system_type Whether it was already classified as system. 123 * @param string[] $primary_allowlist Forced Primary list. 124 * @param string[] $primary_blocked_prefixes Blocked prefixes list. 125 * @return bool 126 */ 127 private function is_primary_content_type( $type, $obj, $is_system_type, $primary_allowlist, $primary_blocked_prefixes ) { 128 $type = (string) $type; 129 130 // Always primary. 131 if ( in_array( $type, array( 'post', 'page' ), true ) ) { 132 return true; 133 } 134 135 // Forced Primary via filter. 136 if ( in_array( $type, (array) $primary_allowlist, true ) ) { 137 return true; 138 } 139 140 // Anything already classified as system is not primary. 141 if ( $is_system_type ) { 142 return false; 143 } 144 145 // Block known plugin/internal prefixes. 146 if ( $this->starts_with_any_prefix( $type, $primary_blocked_prefixes ) ) { 147 return false; 148 } 149 150 $is_public = ! empty( $obj->public ); 151 $is_publicly_queryable = ! empty( $obj->publicly_queryable ); 152 $has_archive = ! empty( $obj->has_archive ); 153 154 // If it's not public-facing by signals, it's not primary. 155 if ( ! $is_public ) { 156 return false; 157 } 158 159 if ( ! $is_publicly_queryable && ! $has_archive ) { 160 return false; 161 } 162 163 // Heuristic: if it contains "_" and lacks strong public signals, treat as system/plugin. 164 // (We keep this conservative to avoid hiding legit CPTs.) 165 if ( false !== strpos( $type, '_' ) && ! $has_archive ) { 166 return false; 167 } 168 169 return true; 170 } 171 172 /** 94 173 * Build module data: counts + diagnostics for built-in + custom post types. 95 174 * … … 124 203 $system_post_types = (array) apply_filters( 'pluxo_blueprint_system_post_types', $system_post_types ); 125 204 205 // Post type prefixes that are typically internal/plugin-driven (best-effort). 206 $primary_blocked_prefixes = array( 207 'wpr_', 208 'elementskit_', 209 ); 210 211 // Allow developers to extend/override blocked prefixes. 212 $primary_blocked_prefixes = (array) apply_filters( 'pluxo_blueprint_primary_post_type_blocked_prefixes', $primary_blocked_prefixes ); 213 214 // Post types that should always be treated as Primary (besides post/page). 215 $primary_allowlist = array(); 216 217 // Allow developers to force some post types into Primary. 218 $primary_allowlist = (array) apply_filters( 'pluxo_blueprint_primary_post_type_allowlist', $primary_allowlist ); 219 220 // Post types that should always be treated as System. 221 $system_force_list = array(); 222 223 // Allow developers to force some post types into System. 224 $system_force_list = (array) apply_filters( 'pluxo_blueprint_system_post_types_force', $system_force_list ); 225 126 226 $primary_builtin_types = 0; 127 227 $primary_custom_types = 0; … … 143 243 $private = isset( $counts->private ) ? (int) $counts->private : 0; 144 244 145 $is_public = ! empty( $obj->public );146 $is_hier = ! empty( $obj->hierarchical );147 $has_archive = ! empty( $obj->has_archive );148 $show_in_rest = ! empty( $obj->show_in_rest );149 $is_builtin = ! empty( $obj->_builtin );245 $is_public = ! empty( $obj->public ); 246 $is_hier = ! empty( $obj->hierarchical ); 247 $has_archive = ! empty( $obj->has_archive ); 248 $show_in_rest = ! empty( $obj->show_in_rest ); 249 $is_builtin = ! empty( $obj->_builtin ); 150 250 $is_system_type = false; 151 251 … … 165 265 } 166 266 267 // System signals (existing logic). 167 268 if ( 0 === strpos( (string) $type, 'acf-' ) ) { 168 269 $is_system_type = true; … … 179 280 // If it's not really public-facing, treat it as system by default. 180 281 if ( empty( $obj->publicly_queryable ) && empty( $obj->has_archive ) && 'post' !== $type && 'page' !== $type ) { 282 $is_system_type = true; 283 } 284 285 // Forced system list (wins). 286 if ( in_array( (string) $type, (array) $system_force_list, true ) ) { 181 287 $is_system_type = true; 182 288 } … … 197 303 $item_count_for_type = (int) $total; 198 304 199 if ( $is_system_type ) { 200 $system_rows[] = $row; 201 } else { 305 if ( $this->is_primary_content_type( $type, $obj, $is_system_type, $primary_allowlist, $primary_blocked_prefixes ) ) { 202 306 $primary_rows[] = $row; 203 307 … … 210 314 $primary_custom_types++; 211 315 } 316 } else { 317 $system_rows[] = $row; 212 318 } 213 319 } -
pluxo-blueprint/trunk/includes/Modules/Content_Types/class-pluxo-blueprint-module-content-types.php
r3453209 r3455410 43 43 public function render() { 44 44 $data = $this->repository->get_content_types_data(); 45 $is_print = defined( 'PLUXO_BLUEPRINT_PRINT_VIEW' ) && PLUXO_BLUEPRINT_PRINT_VIEW; 45 46 46 47 $pill_class = ! empty( $data['pill']['class'] ) ? (string) $data['pill']['class'] : 'pluxo-blueprint-pill--good'; … … 144 145 145 146 if ( ! empty( $data['system_rows'] ) ) { 146 $toggle_id = 'pluxo-bp-content-types-system-toggle';147 $wrap_id = 'pluxo-bp-content-types-system-wrap';148 149 147 $system_count = (int) count( $data['system_rows'] ); 150 148 151 $toggle_label = sprintf( 152 /* translators: 1: number of system/plugin content types */ 153 esc_html__( '%1$d System & Plugin types were also detected. Tick/untick the box to show or hide this section.', 'pluxo-blueprint' ), 154 $system_count 155 ); 156 157 echo '<div class="pluxo-blueprint-toggle-row" style="margin-top: 12px;">'; 158 echo '<label for="' . esc_attr( $toggle_id ) . '">'; 159 echo '<input type="checkbox" id="' . esc_attr( $toggle_id ) . '" style="margin-right: 8px;" />'; 160 echo esc_html( $toggle_label ); 161 echo '</label>'; 162 echo '</div>'; 163 164 // Hidden by default. 165 echo '<div id="' . esc_attr( $wrap_id ) . '" style="display:none; margin-top: 12px;">'; 166 167 echo '<p class="description" style="margin-top: 0;">' . esc_html__( 'These content types are typically used internally by plugins, themes, or WordPress features.', 'pluxo-blueprint' ) . '</p>'; 168 169 echo '<table class="widefat striped pluxo-blueprint-table">'; 170 echo '<thead><tr>'; 171 echo '<th>' . esc_html__( 'Post type', 'pluxo-blueprint' ) . '</th>'; 172 echo '<th>' . esc_html__( 'Label', 'pluxo-blueprint' ) . '</th>'; 173 echo '<th>' . esc_html__( 'Total', 'pluxo-blueprint' ) . '</th>'; 174 echo '<th>' . esc_html__( 'Published', 'pluxo-blueprint' ) . '</th>'; 175 echo '<th>' . esc_html__( 'Drafts', 'pluxo-blueprint' ) . '</th>'; 176 echo '<th>' . esc_html__( 'Private', 'pluxo-blueprint' ) . '</th>'; 177 echo '<th>' . esc_html__( 'Public', 'pluxo-blueprint' ) . '</th>'; 178 echo '<th>' . esc_html__( 'Hierarchical', 'pluxo-blueprint' ) . '</th>'; 179 echo '<th>' . esc_html__( 'Has archive', 'pluxo-blueprint' ) . '</th>'; 180 echo '<th>' . esc_html__( 'REST API', 'pluxo-blueprint' ) . '</th>'; 181 echo '</tr></thead>'; 182 183 echo '<tbody>'; 184 185 foreach ( (array) $data['system_rows'] as $row ) { 186 echo '<tr>'; 187 echo '<td><code>' . esc_html( (string) $row['post_type'] ) . '</code></td>'; 188 echo '<td>' . esc_html( (string) $row['label'] ) . '</td>'; 189 echo '<td>' . esc_html( (string) $row['total'] ) . '</td>'; 190 echo '<td>' . esc_html( (string) $row['published'] ) . '</td>'; 191 echo '<td>' . esc_html( (string) $row['drafts'] ) . '</td>'; 192 echo '<td>' . esc_html( (string) $row['private'] ) . '</td>'; 193 echo '<td>' . esc_html( (string) $row['public'] ) . '</td>'; 194 echo '<td>' . esc_html( (string) $row['hierarchical'] ) . '</td>'; 195 echo '<td>' . esc_html( (string) $row['has_archive'] ) . '</td>'; 196 echo '<td>' . esc_html( (string) $row['show_in_rest'] ) . '</td>'; 197 echo '</tr>'; 149 // In print/PDF view, always show the System & Plugin section (no toggles). 150 if ( $is_print ) { 151 echo '<div style="margin-top: 12px;">'; 152 echo '<p class="description" style="margin-top: 0;">' . esc_html__( 'These content types are typically used internally by plugins, themes, or WordPress features.', 'pluxo-blueprint' ) . '</p>'; 153 154 echo '<table class="widefat striped pluxo-blueprint-table">'; 155 echo '<thead><tr>'; 156 echo '<th>' . esc_html__( 'Post type', 'pluxo-blueprint' ) . '</th>'; 157 echo '<th>' . esc_html__( 'Label', 'pluxo-blueprint' ) . '</th>'; 158 echo '<th>' . esc_html__( 'Total', 'pluxo-blueprint' ) . '</th>'; 159 echo '<th>' . esc_html__( 'Published', 'pluxo-blueprint' ) . '</th>'; 160 echo '<th>' . esc_html__( 'Drafts', 'pluxo-blueprint' ) . '</th>'; 161 echo '<th>' . esc_html__( 'Private', 'pluxo-blueprint' ) . '</th>'; 162 echo '<th>' . esc_html__( 'Public', 'pluxo-blueprint' ) . '</th>'; 163 echo '<th>' . esc_html__( 'Hierarchical', 'pluxo-blueprint' ) . '</th>'; 164 echo '<th>' . esc_html__( 'Has archive', 'pluxo-blueprint' ) . '</th>'; 165 echo '<th>' . esc_html__( 'REST API', 'pluxo-blueprint' ) . '</th>'; 166 echo '</tr></thead>'; 167 168 echo '<tbody>'; 169 170 foreach ( (array) $data['system_rows'] as $row ) { 171 echo '<tr>'; 172 echo '<td><code>' . esc_html( (string) $row['post_type'] ) . '</code></td>'; 173 echo '<td>' . esc_html( (string) $row['label'] ) . '</td>'; 174 echo '<td>' . esc_html( (string) $row['total'] ) . '</td>'; 175 echo '<td>' . esc_html( (string) $row['published'] ) . '</td>'; 176 echo '<td>' . esc_html( (string) $row['drafts'] ) . '</td>'; 177 echo '<td>' . esc_html( (string) $row['private'] ) . '</td>'; 178 echo '<td>' . esc_html( (string) $row['public'] ) . '</td>'; 179 echo '<td>' . esc_html( (string) $row['hierarchical'] ) . '</td>'; 180 echo '<td>' . esc_html( (string) $row['has_archive'] ) . '</td>'; 181 echo '<td>' . esc_html( (string) $row['show_in_rest'] ) . '</td>'; 182 echo '</tr>'; 183 } 184 185 echo '</tbody>'; 186 echo '</table>'; 187 echo '</div>'; 188 189 } else { 190 // Normal UI: toggleable section. 191 $toggle_id = 'pluxo-bp-content-types-system-toggle'; 192 $wrap_id = 'pluxo-bp-content-types-system-wrap'; 193 194 $toggle_label = sprintf( 195 /* translators: 1: number of system/plugin content types */ 196 esc_html__( '%1$d System & Plugin types were also detected. Tick/untick the box to show or hide this section.', 'pluxo-blueprint' ), 197 $system_count 198 ); 199 200 echo '<div class="pluxo-blueprint-toggle-row" style="margin-top: 12px;">'; 201 echo '<label for="' . esc_attr( $toggle_id ) . '">'; 202 echo '<input type="checkbox" id="' . esc_attr( $toggle_id ) . '" style="margin-right: 8px;" />'; 203 echo esc_html( $toggle_label ); 204 echo '</label>'; 205 echo '</div>'; 206 207 // Hidden by default. 208 echo '<div id="' . esc_attr( $wrap_id ) . '" style="display:none; margin-top: 12px;">'; 209 210 echo '<p class="description" style="margin-top: 0;">' . esc_html__( 'These content types are typically used internally by plugins, themes, or WordPress features.', 'pluxo-blueprint' ) . '</p>'; 211 212 echo '<table class="widefat striped pluxo-blueprint-table">'; 213 echo '<thead><tr>'; 214 echo '<th>' . esc_html__( 'Post type', 'pluxo-blueprint' ) . '</th>'; 215 echo '<th>' . esc_html__( 'Label', 'pluxo-blueprint' ) . '</th>'; 216 echo '<th>' . esc_html__( 'Total', 'pluxo-blueprint' ) . '</th>'; 217 echo '<th>' . esc_html__( 'Published', 'pluxo-blueprint' ) . '</th>'; 218 echo '<th>' . esc_html__( 'Drafts', 'pluxo-blueprint' ) . '</th>'; 219 echo '<th>' . esc_html__( 'Private', 'pluxo-blueprint' ) . '</th>'; 220 echo '<th>' . esc_html__( 'Public', 'pluxo-blueprint' ) . '</th>'; 221 echo '<th>' . esc_html__( 'Hierarchical', 'pluxo-blueprint' ) . '</th>'; 222 echo '<th>' . esc_html__( 'Has archive', 'pluxo-blueprint' ) . '</th>'; 223 echo '<th>' . esc_html__( 'REST API', 'pluxo-blueprint' ) . '</th>'; 224 echo '</tr></thead>'; 225 226 echo '<tbody>'; 227 228 foreach ( (array) $data['system_rows'] as $row ) { 229 echo '<tr>'; 230 echo '<td><code>' . esc_html( (string) $row['post_type'] ) . '</code></td>'; 231 echo '<td>' . esc_html( (string) $row['label'] ) . '</td>'; 232 echo '<td>' . esc_html( (string) $row['total'] ) . '</td>'; 233 echo '<td>' . esc_html( (string) $row['published'] ) . '</td>'; 234 echo '<td>' . esc_html( (string) $row['drafts'] ) . '</td>'; 235 echo '<td>' . esc_html( (string) $row['private'] ) . '</td>'; 236 echo '<td>' . esc_html( (string) $row['public'] ) . '</td>'; 237 echo '<td>' . esc_html( (string) $row['hierarchical'] ) . '</td>'; 238 echo '<td>' . esc_html( (string) $row['has_archive'] ) . '</td>'; 239 echo '<td>' . esc_html( (string) $row['show_in_rest'] ) . '</td>'; 240 echo '</tr>'; 241 } 242 243 echo '</tbody>'; 244 echo '</table>'; 245 246 echo '</div>'; // end wrap. 247 248 echo '<script> 249 (function() { 250 var toggle = document.getElementById("' . esc_js( $toggle_id ) . '"); 251 var wrap = document.getElementById("' . esc_js( $wrap_id ) . '"); 252 if (!toggle || !wrap) { return; } 253 toggle.addEventListener("change", function() { 254 wrap.style.display = toggle.checked ? "block" : "none"; 255 }); 256 })(); 257 </script>'; 198 258 } 199 200 echo '</tbody>';201 echo '</table>';202 203 echo '</div>'; // end wrap.204 205 echo '<script>206 (function() {207 var toggle = document.getElementById("' . esc_js( $toggle_id ) . '");208 var wrap = document.getElementById("' . esc_js( $wrap_id ) . '");209 if (!toggle || !wrap) { return; }210 toggle.addEventListener("change", function() {211 wrap.style.display = toggle.checked ? "block" : "none";212 });213 })();214 </script>';215 259 } 216 260 ?> -
pluxo-blueprint/trunk/includes/Modules/Cookies_Consent/class-pluxo-blueprint-cookies-consent-scan.php
r3453209 r3455410 15 15 16 16 /** 17 * Cache option name for Cookies & Consent scan. 18 * 19 * @var string 20 */ 21 private $option_name = 'pluxo_blueprint_cookies_consent_scan'; 22 23 /** 24 * Cache TTL (seconds). 25 * 26 * @var int 27 */ 28 private $cache_ttl = 86400; // 24 hours. 29 30 /** 17 31 * Run scan and return structured raw data. 18 32 * 33 * @param array<string,mixed> $args { 34 * Optional. Arguments. 35 * @type bool $force Force refresh (ignore cache). 36 * @type bool $allow_remote Allow external provider fetches (CookieYes CDN) when possible. 37 * } 19 38 * @return array<string, mixed> 20 39 */ 21 public function run() { 40 public function run( $args = array() ) { 41 $args = is_array( $args ) ? $args : array(); 42 $force = ! empty( $args['force'] ); 43 $allow_remote = ! empty( $args['allow_remote'] ); 44 45 // Cache (skip only when forcing). 46 if ( ! $force ) { 47 $cached = get_option( $this->option_name ); 48 if ( is_array( $cached ) && ! empty( $cached['metadata']['scanned_at_gmt'] ) ) { 49 $last = strtotime( (string) $cached['metadata']['scanned_at_gmt'] ); 50 if ( $last && ( time() - $last ) < $this->cache_ttl ) { 51 return $cached; 52 } 53 } 54 } 55 22 56 $providers = Pluxo_Blueprint_Cookies_Consent_Registry::get_providers(); 23 57 … … 86 120 87 121 $cookies_all = $this->dedupe_cookie_list_by_provider_and_name( $cookies_all ); 88 89 return array( 122 $home_signals = $this->detect_cookieyes_homepage_signals(); 123 124 $result = array( 90 125 'providers' => $out_providers, 91 126 'cookies' => $cookies_all, 92 127 'metadata' => array( 93 'scanned_at_gmt' => gmdate( 'c' ), 128 'scanned_at_gmt' => gmdate( 'c' ), 129 'cookieyes_web_detected' => ! empty( $home_signals['cookieyes_web_detected'] ), 130 'cookieyes_gcm_categories' => ! empty( $home_signals['cookieyes_gcm_categories'] ) ? (array) $home_signals['cookieyes_gcm_categories'] : array(), 94 131 ), 95 132 ); 133 134 // Save cache. 135 update_option( $this->option_name, $result, false ); 136 137 return $result; 96 138 } 97 139 … … 491 533 return $out; 492 534 } 535 536 /** 537 * Best-effort: detect CookieYes signals in the homepage HTML. 538 * 539 * - CookieYes web dashboard script: 540 * <script id="cookieyes" src="https://cdn-cookieyes.com/client_data/.../script.js"> 541 * - CookieYes GCM config: 542 * <script id="cookie-law-info-gcm-var-js"> var _ckyGcm = {...}</script> 543 * 544 * @return array<string, mixed> 545 */ 546 private function detect_cookieyes_homepage_signals() { 547 $out = array( 548 'cookieyes_web_detected' => false, 549 'cookieyes_gcm_categories' => array(), 550 ); 551 552 $url = home_url( '/' ); 553 554 $response = wp_remote_get( 555 $url, 556 array( 557 'timeout' => 6, 558 'redirection' => 2, 559 'user-agent' => 'Pluxo Blueprint; Cookies scanner', 560 'limit_response_size' => 250000, 561 ) 562 ); 563 564 if ( is_wp_error( $response ) ) { 565 return $out; 566 } 567 568 $code = (int) wp_remote_retrieve_response_code( $response ); 569 if ( $code < 200 || $code >= 400 ) { 570 return $out; 571 } 572 573 $body = (string) wp_remote_retrieve_body( $response ); 574 if ( '' === trim( $body ) ) { 575 return $out; 576 } 577 578 // 1) Detect CookieYes web script (best-effort). 579 // We look for id="cookieyes" AND the CookieYes CDN pattern. 580 if ( false !== stripos( $body, 'id="cookieyes"' ) && false !== stripos( $body, 'cdn-cookieyes.com/client_data/' ) ) { 581 $out['cookieyes_web_detected'] = true; 582 } 583 584 // 2) Extract categories from _ckyGcm var (best-effort). 585 $categories = $this->extract_cookieyes_gcm_categories_from_html( $body ); 586 if ( ! empty( $categories ) ) { 587 $out['cookieyes_gcm_categories'] = $categories; 588 } 589 590 return $out; 493 591 } 592 593 /** 594 * Best-effort: extract CookieYes categories from the _ckyGcm config script. 595 * 596 * Example: 597 * var _ckyGcm = {"status":true,"default_settings":[{"analytics":"denied","advertisement":"denied",...}]...} 598 * 599 * @param string $html Homepage HTML. 600 * @return array<int, string> 601 */ 602 private function extract_cookieyes_gcm_categories_from_html( $html ) { 603 $html = (string) $html; 604 605 // Capture the JSON object assigned to var _ckyGcm = {...}; 606 $matches = array(); 607 if ( ! preg_match( '/var\s+_ckyGcm\s*=\s*(\{.*?\})\s*;/s', $html, $matches ) ) { 608 return array(); 609 } 610 611 $json = isset( $matches[1] ) ? (string) $matches[1] : ''; 612 $json = trim( $json ); 613 614 if ( '' === $json ) { 615 return array(); 616 } 617 618 $data = json_decode( $json, true ); 619 if ( ! is_array( $data ) ) { 620 return array(); 621 } 622 623 $default_settings = isset( $data['default_settings'] ) ? $data['default_settings'] : null; 624 if ( ! is_array( $default_settings ) || empty( $default_settings[0] ) || ! is_array( $default_settings[0] ) ) { 625 return array(); 626 } 627 628 $first = (array) $default_settings[0]; 629 630 // Known keys we care about (ignore regions and other non-category fields). 631 $key_map = array( 632 'necessary' => 'Necessary', 633 'functional' => 'Functional', 634 'analytics' => 'Analytics', 635 'advertisement' => 'Advertisement', 636 'ad_user_data' => 'Ad user data', 637 'ad_personalization' => 'Ad personalization', 638 ); 639 640 $out = array(); 641 642 foreach ( $key_map as $key => $label ) { 643 if ( array_key_exists( $key, $first ) ) { 644 $out[] = $label; 645 } 646 } 647 648 return array_values( array_unique( $out ) ); 649 } 650 } -
pluxo-blueprint/trunk/includes/Modules/Cookies_Consent/class-pluxo-blueprint-module-cookies-consent.php
r3453209 r3455410 85 85 'summary' => esc_html__( 'Cookies & consent scanner not available.', 'pluxo-blueprint' ), 86 86 'cookies' => array(), 87 'cookieyes_any_detected' => false, 87 88 ); 88 89 } … … 94 95 $cookies = isset( $scan['cookies'] ) ? (array) $scan['cookies'] : array(); 95 96 97 $metadata = isset( $scan['metadata'] ) && is_array( $scan['metadata'] ) ? (array) $scan['metadata'] : array(); 98 99 $cookieyes_web_detected = ! empty( $metadata['cookieyes_web_detected'] ); 100 $cookieyes_gcm_categories = ! empty( $metadata['cookieyes_gcm_categories'] ) && is_array( $metadata['cookieyes_gcm_categories'] ) 101 ? array_values( array_filter( array_map( 'trim', (array) $metadata['cookieyes_gcm_categories'] ) ) ) 102 : array(); 103 96 104 $detected_names = array(); 97 105 $notes = array(); 106 107 $cookieyes_plugin_detected = false; 98 108 99 109 foreach ( $providers as $p ) { … … 105 115 $active = ! empty( $p['active'] ); 106 116 117 if ( $active && 'CookieYes' === $name ) { 118 $cookieyes_plugin_detected = true; 119 } 120 107 121 if ( $active && '' !== $name ) { 108 122 $detected_names[] = $name; … … 116 130 $detected_names = array_values( array_unique( array_filter( $detected_names ) ) ); 117 131 $notes = array_values( array_unique( array_filter( $notes ) ) ); 132 133 if ( $cookieyes_web_detected && ! $cookieyes_plugin_detected ) { 134 $detected_names[] = __( 'CookieYes (No plugin)', 'pluxo-blueprint' ); 135 } 136 137 $detected_names = array_values( array_unique( array_filter( $detected_names ) ) ); 138 139 $cookieyes_any_detected = ( $cookieyes_plugin_detected || $cookieyes_web_detected ); 118 140 119 141 $cookie_count = count( $cookies ); … … 123 145 : esc_html__( 'No consent plugin', 'pluxo-blueprint' ); 124 146 125 $pill_label = sprintf( 126 /* translators: 1: detected plugin names, 2: cookie count, 3: cookie/cookies label */ 127 esc_html__( '%1$s / %2$d cookies', 'pluxo-blueprint' ), 128 $plugins_label, 129 (int) $cookie_count 130 ); 131 132 $pill_class = ( ! empty( $detected_names ) ) 133 ? ( ( $cookie_count > 0 ) ? 'pluxo-blueprint-pill--neutral' : 'pluxo-blueprint-pill--neutral' ) 134 : 'pluxo-blueprint-pill--neutral'; 135 136 $summary_lines = array(); 137 138 if ( empty( $detected_names ) ) { 139 $summary_lines[] = esc_html__( 'No compatible cookie/consent plugin was detected.', 'pluxo-blueprint' ); 147 if ( $cookieyes_any_detected ) { 148 $pill_label = sprintf( 149 /* translators: %s: detected provider names */ 150 esc_html__( '%s / Cookies cannot be detected', 'pluxo-blueprint' ), 151 $plugins_label 152 ); 153 } else { 154 $pill_label = sprintf( 155 /* translators: 1: detected plugin names, 2: cookie count */ 156 esc_html__( '%1$s / %2$d cookies', 'pluxo-blueprint' ), 157 $plugins_label, 158 (int) $cookie_count 159 ); 160 } 161 162 $pill_class = ( ! empty( $detected_names ) ) 163 ? ( ( $cookie_count > 0 ) ? 'pluxo-blueprint-pill--neutral' : 'pluxo-blueprint-pill--neutral' ) 164 : 'pluxo-blueprint-pill--neutral'; 165 166 $summary_lines = array(); 167 168 if ( empty( $detected_names ) ) { 169 $summary_lines[] = esc_html__( 'No compatible cookie/consent plugin was detected.', 'pluxo-blueprint' ); 170 } else { 171 if ( $cookieyes_any_detected ) { 172 $summary_lines[] = sprintf( 173 /* translators: %s: detected provider names */ 174 esc_html__( '%s detected on this website.', 'pluxo-blueprint' ), 175 $plugins_label 176 ); 140 177 } else { 141 178 $summary_lines[] = sprintf( … … 145 182 (int) $cookie_count 146 183 ); 184 } 147 185 148 186 foreach ( $notes as $note ) { 149 187 $summary_lines[] = $note; 188 } 189 } 190 191 if ( $cookieyes_any_detected ) { 192 if ( ! empty( $cookieyes_gcm_categories ) ) { 193 $summary_lines[] = sprintf( 194 /* translators: %s: cookie categories */ 195 esc_html__( 'CookieYes detected on this website, with these cookie categories configured: %s.', 'pluxo-blueprint' ), 196 implode( ', ', $cookieyes_gcm_categories ) 197 ); 198 } else { 199 $summary_lines[] = esc_html__( 'CookieYes detected on this website.', 'pluxo-blueprint' ); 200 } 201 202 $summary_lines[] = esc_html__( 'Due to technical limitations, CookieYes cookies cannot be listed by this plugin.', 'pluxo-blueprint' ); 203 204 if ( $cookieyes_web_detected && ! $cookieyes_plugin_detected ) { 205 $summary_lines[] = esc_html__( 'The CookieYes WordPress plugin was not detected. This is likely the CookieYes web dashboard script connected directly to the site.', 'pluxo-blueprint' ); 150 206 } 151 207 } … … 163 219 'summary' => implode( "\n", $summary_lines ), 164 220 'cookies' => $cookies, 221 'cookieyes_any_detected' => $cookieyes_any_detected, 165 222 ); 166 223 } … … 246 303 247 304 $cookies = ! empty( $data['cookies'] ) ? (array) $data['cookies'] : array(); 305 $cookieyes_any_detected = ! empty( $data['cookieyes_any_detected'] ); 248 306 249 307 if ( empty( $cookies ) ) { 250 $lines[] = 'No cookies were listed by the supported consent plugins.'; 308 if ( $cookieyes_any_detected ) { 309 $lines[] = 'CookieYes was detected, but cookies cannot be listed due to technical limitations.'; 310 } else { 311 $lines[] = 'No cookies were listed by the supported consent plugins.'; 312 } 313 251 314 return Pluxo_Blueprint_Markdown_Helper::normalise( implode( "\n", $lines ) ); 252 315 } -
pluxo-blueprint/trunk/includes/Modules/Header_Footer/class-pluxo-blueprint-header-footer-repository.php
r3453209 r3455410 218 218 } 219 219 220 return $line1 . "\n" . $line2 . "\n\n" . $line3; 220 // ---- Best-effort hints block (requested) ---- 221 $hints = array(); 222 223 $is_block_theme = ! empty( $signals['is_block_theme'] ); 224 $theme_type = $is_block_theme ? __( 'Block theme (FSE)', 'pluxo-blueprint' ) : __( 'Classic theme', 'pluxo-blueprint' ); 225 226 $hints[] = sprintf( 227 /* translators: %s is the theme type. */ 228 __( 'Theme type: %s.', 'pluxo-blueprint' ), 229 $theme_type 230 ); 231 232 if ( $is_block_theme ) { 233 $hints[] = __( 'Header/footer likely controlled via Template Parts (Site Editor).', 'pluxo-blueprint' ); 234 } 235 236 // Classic theme + no builder templates detected => suggest likely files. 237 $no_builders = ( 238 ( empty( $signals['elementor_headers'] ) && empty( $signals['elementor_footers'] ) ) 239 && ( empty( $signals['elementskit_headers'] ) && empty( $signals['elementskit_footers'] ) ) 240 ); 241 242 if ( ! $is_block_theme && $no_builders ) { 243 $files = isset( $signals['theme_files_hint'] ) && is_array( $signals['theme_files_hint'] ) 244 ? array_values( array_filter( array_map( 'strval', (array) $signals['theme_files_hint'] ) ) ) 245 : array(); 246 247 if ( ! empty( $files ) ) { 248 $hints[] = sprintf( 249 /* translators: %s is a comma-separated list of theme files. */ 250 __( 'Possible theme files: %s.', 'pluxo-blueprint' ), 251 implode( ', ', $files ) 252 ); 253 } 254 } 255 256 // Optional markers (1–3 paths). 257 $markers = isset( $signals['theme_markers'] ) && is_array( $signals['theme_markers'] ) 258 ? array_values( array_filter( array_map( 'strval', (array) $signals['theme_markers'] ) ) ) 259 : array(); 260 261 if ( ! empty( $markers ) ) { 262 $hints[] = sprintf( 263 /* translators: %s is a comma-separated list of theme paths. */ 264 __( 'Found markers in theme: %s.', 'pluxo-blueprint' ), 265 implode( ', ', $markers ) 266 ); 267 } 268 269 $hints_block = ''; 270 if ( ! empty( $hints ) ) { 271 $hints_block = "\n\n" . __( 'Best-effort hints', 'pluxo-blueprint' ) . "\n" . implode( "\n", $hints ); 272 } 273 274 return $line1 . "\n" . $line2 . "\n\n" . $line3 . $hints_block; 221 275 } 222 276 } -
pluxo-blueprint/trunk/includes/Modules/Header_Footer/class-pluxo-blueprint-header-footer-scan.php
r3453209 r3455410 20 20 */ 21 21 public function run() { 22 $is_block_theme = $this->is_block_theme(); 23 22 24 $results = array( 23 25 'signals' => array( 24 'is_block_theme' => $this->is_block_theme(), 26 'is_block_theme' => $is_block_theme, 27 'theme_type' => $is_block_theme ? 'block' : 'classic', 28 'theme_files_hint' => $this->get_theme_files_hint(), 29 'theme_markers' => $this->find_theme_markers(), 30 25 31 'elementor_available' => $this->is_elementor_available(), 26 32 'elementor_headers' => 0, 27 33 'elementor_footers' => 0, 34 28 35 'elementskit_available' => $this->is_elementskit_available(), 29 36 'elementskit_headers' => 0, 30 37 'elementskit_footers' => 0, 38 31 39 'active_hf_plugins' => array(), 32 40 'mixed_system_detected' => false, … … 91 99 92 100 /** 101 * Best-effort: provide "possible theme files" hint (classic themes). 102 * 103 * @return string[] List like ['header.php', 'footer.php'] (only the ones that exist). 104 */ 105 private function get_theme_files_hint() { 106 $out = array(); 107 108 $theme = wp_get_theme(); 109 if ( ! $theme || ! is_object( $theme ) ) { 110 return $out; 111 } 112 113 $dirs = array(); 114 115 $child = $theme->get_stylesheet_directory(); 116 if ( is_string( $child ) && '' !== $child ) { 117 $dirs[] = $child; 118 } 119 120 $parent = $theme->get_template_directory(); 121 if ( is_string( $parent ) && '' !== $parent && $parent !== $child ) { 122 $dirs[] = $parent; 123 } 124 125 $files = array( 'header.php', 'footer.php' ); 126 127 foreach ( $files as $file ) { 128 foreach ( $dirs as $dir ) { 129 $path = trailingslashit( $dir ) . $file; 130 if ( file_exists( $path ) ) { 131 $out[] = $file; 132 break; 133 } 134 } 135 } 136 137 $out = array_values( array_unique( $out ) ); 138 139 return $out; 140 } 141 142 /** 143 * Best-effort: find a few "markers" in the active theme that suggest where header/footer is coming from. 144 * 145 * This is intentionally lightweight and returns at most 3 paths. 146 * 147 * @return string[] Short list of relative-ish paths (filenames) where we found markers. 148 */ 149 private function find_theme_markers() { 150 $theme = wp_get_theme(); 151 if ( ! $theme || ! is_object( $theme ) ) { 152 return array(); 153 } 154 155 $base_dirs = array(); 156 157 $child = $theme->get_stylesheet_directory(); 158 if ( is_string( $child ) && '' !== $child ) { 159 $base_dirs[] = $child; 160 } 161 162 $parent = $theme->get_template_directory(); 163 if ( is_string( $parent ) && '' !== $parent && $parent !== $child ) { 164 $base_dirs[] = $parent; 165 } 166 167 // Candidate files only (avoid exhaustive scans). 168 $candidates = array( 169 'header.php', 170 'footer.php', 171 'functions.php', 172 'templates/index.html', 173 'parts/header.html', 174 'parts/footer.html', 175 ); 176 177 $needles = array( 178 'wp_head(', 179 'wp_footer(', 180 'wp:template-part', 181 'template-part', 182 ); 183 184 $found = array(); 185 186 foreach ( $base_dirs as $dir ) { 187 foreach ( $candidates as $rel ) { 188 $path = trailingslashit( $dir ) . $rel; 189 190 if ( ! file_exists( $path ) || ! is_readable( $path ) ) { 191 continue; 192 } 193 194 $contents = file_get_contents( $path ); // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContents 195 if ( false === $contents || '' === $contents ) { 196 continue; 197 } 198 199 foreach ( $needles as $needle ) { 200 if ( false !== strpos( $contents, $needle ) ) { 201 $found[] = $rel; 202 break; 203 } 204 } 205 206 if ( count( $found ) >= 3 ) { 207 break 2; 208 } 209 } 210 } 211 212 $found = array_values( array_unique( $found ) ); 213 214 return $found; 215 } 216 217 /** 93 218 * Determine whether Elementor is available (active). 94 219 * … … 147 272 'update_post_term_cache' => false, 148 273 149 // Best-effort: Elementor stores template types in post meta. Query is limited to 1 result to minimise impact.150 274 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 151 275 'meta_query' => array( … … 276 400 'update_post_term_cache' => false, 277 401 278 // Best-effort: ElementsKit may classify templates via taxonomy. Query is limited to 1 result to minimise impact.279 402 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query 280 403 'tax_query' => array( … … 318 441 'update_post_term_cache' => false, 319 442 320 // Best-effort fallback: ElementsKit may store template type in post meta. Query is limited to 1 result to minimise impact.321 443 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 322 444 'meta_query' => $meta_or, -
pluxo-blueprint/trunk/includes/Modules/Plugins/class-pluxo-blueprint-plugins-renderer.php
r3453209 r3455410 250 250 251 251 if ( '' === $value ) { 252 return esc_html( $dash );252 return $dash; 253 253 } 254 254 255 255 $plugin_uri = isset( $plugin['plugin_uri'] ) ? trim( (string) $plugin['plugin_uri'] ) : ''; 256 256 if ( '' !== $plugin_uri && 0 === strpos( $plugin_uri, 'http' ) ) { 257 return $ value . ' (' . $plugin_uri . ')';257 return $this->escape_markdown_table_cell( $value . ' (' . $plugin_uri . ')' ); 258 258 } 259 259 260 return $ value;260 return $this->escape_markdown_table_cell( $value ); 261 261 262 262 case 'version': 263 263 $value = isset( $plugin['version'] ) ? trim( (string) $plugin['version'] ) : ''; 264 return ( '' !== $value ) ? $ value : esc_html( $dash );264 return ( '' !== $value ) ? $this->escape_markdown_table_cell( $value ) : $dash; 265 265 266 266 case 'status': 267 267 $value = isset( $plugin['status'] ) ? trim( (string) $plugin['status'] ) : ''; 268 return ( '' !== $value ) ? ucfirst( $value ) : esc_html( esc_html( $dash ) );268 return ( '' !== $value ) ? $this->escape_markdown_table_cell( ucfirst( $value ) ) : $dash; 269 269 270 270 case 'type': 271 271 $value = isset( $plugin['type'] ) ? trim( (string) $plugin['type'] ) : ''; 272 return ( '' !== $value ) ? ucfirst( str_replace( '_', ' ', $value ) ) : esc_html( $dash );272 return ( '' !== $value ) ? $this->escape_markdown_table_cell( ucfirst( str_replace( '_', ' ', $value ) ) ) : $dash; 273 273 274 274 case 'update_available': … … 277 277 case 'update_status': 278 278 $value = isset( $plugin['update_status'] ) ? trim( (string) $plugin['update_status'] ) : ''; 279 return ( '' !== $value ) ? $ value : esc_html( $dash );279 return ( '' !== $value ) ? $this->escape_markdown_table_cell( $value ) : $dash; 280 280 281 281 default: 282 return esc_html( $dash );282 return $dash; 283 283 } 284 284 } … … 437 437 <?php 438 438 } 439 440 /** 441 * Escape special characters for Markdown tables. 442 * 443 * In Markdown tables, the pipe character "|" separates columns. 444 * Some parsers (e.g. Confluence) do not reliably honour "\|" inside tables, 445 * so we replace pipes with the HTML entity instead. 446 * 447 * @param string $text Cell text. 448 * @return string 449 */ 450 private function escape_markdown_table_cell( $text ) { 451 $text = (string) $text; 452 453 // Replace pipes with a safe representation that still renders as "|". 454 $text = str_replace( '|', '|', $text ); 455 456 return $text; 457 } 439 458 } -
pluxo-blueprint/trunk/includes/Modules/Site_Info/class-pluxo-blueprint-module-site-info.php
r3453209 r3455410 137 137 if ( is_array( $general_info ) ) { 138 138 foreach ( $general_info as $label => $value ) { 139 $rows[] = array( (string) $label, (string) $value);139 $rows[] = array( (string) $label, trim( wp_strip_all_tags( (string) $value ) ) ); 140 140 } 141 141 } -
pluxo-blueprint/trunk/includes/Modules/Site_Info/class-pluxo-blueprint-site-info-renderer.php
r3453209 r3455410 41 41 $allowed = array( 42 42 'strong' => array(), 43 'a' => array( 44 'href' => array(), 45 'rel' => array(), 46 ), 43 47 ); 44 48 -
pluxo-blueprint/trunk/includes/Modules/Site_Info/class-pluxo-blueprint-site-info-repository.php
r3453209 r3455410 23 23 $site_url = home_url(); 24 24 $wp_ver = get_bloginfo( 'version' ); 25 $wp_update_version = $this->get_wordpress_core_update_version(); 25 26 $php_ver = PHP_VERSION; 26 27 … … 93 94 } 94 95 96 $wp_value = (string) $wp_ver; 97 98 if ( '' !== $wp_update_version && version_compare( $wp_update_version, $wp_ver, '>' ) ) { 99 $update_url = admin_url( 'update-core.php' ); 100 101 $wp_value .= ' ' . sprintf( 102 /* translators: 1: update url, 2: WordPress version available for update */ 103 __( '(Update <a href="%1$s">version %2$s</a> available)', 'pluxo-blueprint' ), 104 esc_url( $update_url ), 105 esc_html( $wp_update_version ) 106 ); 107 } 108 95 109 return array( 96 110 __( 'Site Name', 'pluxo-blueprint' ) => (string) $site_name, 97 111 __( 'Site URL', 'pluxo-blueprint' ) => (string) $site_url, 98 __( 'WordPress Version', 'pluxo-blueprint' ) => (string) $wp_v er,112 __( 'WordPress Version', 'pluxo-blueprint' ) => (string) $wp_value, 99 113 __( 'PHP Version', 'pluxo-blueprint' ) => (string) $php_value, 100 114 __( 'Database Version', 'pluxo-blueprint' ) => ( '' !== $db_ver ) ? $db_ver : __( 'Not available', 'pluxo-blueprint' ), … … 103 117 __( 'Generated On', 'pluxo-blueprint' ) => (string) $generated, 104 118 ); 119 } 120 121 /** 122 * Best-effort: get the available WordPress core update version (if any). 123 * 124 * @return string Empty string when no upgrade offer is found. 125 */ 126 private function get_wordpress_core_update_version() { 127 if ( ! function_exists( 'get_core_updates' ) ) { 128 require_once ABSPATH . 'wp-admin/includes/update.php'; 129 } 130 131 if ( ! function_exists( 'get_core_updates' ) ) { 132 return ''; 133 } 134 135 $core_updates = get_core_updates( array( 'dismissed' => false ) ); 136 137 if ( ! is_array( $core_updates ) || empty( $core_updates ) ) { 138 return ''; 139 } 140 141 foreach ( $core_updates as $update ) { 142 if ( ! is_object( $update ) ) { 143 continue; 144 } 145 146 if ( empty( $update->response ) || 'upgrade' !== $update->response ) { 147 continue; 148 } 149 150 // In most WP installs, the target version is in $update->current. 151 if ( ! empty( $update->current ) ) { 152 return (string) $update->current; 153 } 154 155 // Fallback: some offers may use ->version. 156 if ( ! empty( $update->version ) ) { 157 return (string) $update->version; 158 } 159 160 break; 161 } 162 163 return ''; 105 164 } 106 165 -
pluxo-blueprint/trunk/includes/Modules/Themes/class-pluxo-blueprint-themes-repository.php
r3453209 r3455410 159 159 $summary_paragraphs[] = sprintf( 160 160 /* translators: %d: total number of installed themes. */ 161 '<strong>' . __( 'This website has %d installed themes, which is more than recommended. Consider keeping only the active theme (and its parent theme, if applicable) to reduce maintenance overhead and potential security risks.', 'pluxo-blueprint' ) . '</strong> ',161 '<strong>' . __( 'This website has %d installed themes, which is more than recommended. Consider keeping only the active theme (and its parent theme, if applicable) to reduce maintenance overhead and potential security risks.', 'pluxo-blueprint' ) . '</strong>', 162 162 (int) $total_themes 163 163 ); -
pluxo-blueprint/trunk/includes/Modules/Tracking/Data/class-pluxo-blueprint-tracking-registry.php
r3453209 r3455410 22 22 'label' => __( 'Google Tag Manager', 'pluxo-blueprint' ), 23 23 'plugin_signatures' => array( 24 'duracelltomi-google-tag-manager/duracelltomi-google-tag-manager.php', 24 // GTM4WP (Google Tag Manager for WordPress by gtm4wp.com). 25 'duracelltomi-google-tag-manager/duracelltomi-google-tag-manager-for-wordpress.php', 26 27 // Site Kit can inject GTM/GA depending on config. 25 28 'google-site-kit/google-site-kit.php', 26 29 ), 27 30 'html_signatures' => array( 31 // Classic GTM script. 28 32 '~googletagmanager\.com/gtm\.js~i', 33 34 // GTM4WP fingerprints (covers your exact example). 35 '~gtm4wp_datalayer_name~i', 36 '~Google Tag Manager for WordPress by gtm4wp\.com~i', 37 '~gtm4wp\.com~i', 29 38 ), 30 39 'id_patterns' => array( … … 39 48 'google-analytics-for-wordpress/googleanalytics.php', 40 49 'monsterinsights/monsterinsights.php', 50 51 // GA Google Analytics plugin. 52 'ga-google-analytics/ga-google-analytics.php', 41 53 ), 42 54 'html_signatures' => array( 43 55 '~www\.googletagmanager\.com/gtag/js~i', 44 56 '~google-analytics\.com/analytics\.js~i', 57 58 // GA4 common inline patterns. 59 '~\bgTag\(|\bgtag\(~i', 45 60 ), 46 61 'id_patterns' => array( -
pluxo-blueprint/trunk/includes/Modules/Tracking/Services/class-pluxo-blueprint-tracking-scanner.php
r3453209 r3455410 46 46 } 47 47 48 if ( ! $force && is_array( $cached ) && ! empty( $cached['last_scan_gmt'] ) ) { 49 $last = strtotime( (string) $cached['last_scan_gmt'] ); 50 if ( $last && ( time() - $last ) < $this->cache_ttl ) { 51 return $cached; 48 if ( ! $force && is_array( $cached ) ) { 49 $stamp = ''; 50 51 if ( ! empty( $cached['metadata']['scanned_at_gmt'] ) ) { 52 $stamp = (string) $cached['metadata']['scanned_at_gmt']; 53 } elseif ( ! empty( $cached['last_scan_gmt'] ) ) { 54 $stamp = (string) $cached['last_scan_gmt']; 55 } 56 57 if ( '' !== $stamp ) { 58 $last = strtotime( $stamp ); 59 if ( $last && ( time() - $last ) < $this->cache_ttl ) { 60 return $cached; 61 } 52 62 } 53 63 } … … 70 80 private function run_scan() { 71 81 if ( ! function_exists( 'get_plugins' ) ) { 82 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 83 } 84 85 if ( ! function_exists( 'is_plugin_active' ) ) { 72 86 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 73 87 } … … 208 222 * @return array<int, array<string, string>> 209 223 */ 210 private function match_plugins( $signatures, $active ) {224 private function match_plugins( $signatures, $active_unused = array() ) { 211 225 $matches = array(); 212 226 213 foreach ( $signatures as $sig ) { 227 if ( ! function_exists( 'is_plugin_active' ) ) { 228 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 229 } 230 231 foreach ( (array) $signatures as $sig ) { 214 232 $sig = (string) $sig; 215 216 if ( in_array( $sig, $active, true ) ) { 233 if ( '' === $sig ) { 234 continue; 235 } 236 237 if ( is_plugin_active( $sig ) ) { 217 238 $matches[] = array( 218 239 'plugin_file' => $sig, -
pluxo-blueprint/trunk/includes/Modules/Translators/class-pluxo-blueprint-translators-repository.php
r3453209 r3455410 19 19 public function get_module_data() { 20 20 $tools = $this->detect_translation_tools(); 21 $languages = $this->get_site_languages( );21 $languages = $this->get_site_languages( $tools ); 22 22 $pill = $this->get_pill_data( $tools, $languages ); 23 23 $summary = $this->get_summary_text( $tools, $languages ); … … 43 43 'sitepress-multilingual-cms/sitepress.php', // WPML core. 44 44 'polylang/polylang.php', 45 'polylang-pro/polylang.php', 45 46 'translatepress-multilingual/index.php', 46 47 'weglot/weglot.php', … … 81 82 * Get best-effort site languages. 82 83 * 84 * - If Polylang is active and functions are available, returns the configured Polylang languages. 85 * - Otherwise falls back to WordPress locale + available languages (installed/available translations). 86 * 87 * @param array<string, array<int, string>> $tools Tools detection. 83 88 * @return array<string, mixed> 84 89 */ 85 private function get_site_languages() { 90 private function get_site_languages( $tools = array() ) { 91 $labels = array(); 92 $locales = array(); 93 $source = 'wordpress'; 94 95 $has_polylang = ! empty( $tools['multilingual'] ) && is_array( $tools['multilingual'] ) 96 && ( in_array( 'Polylang', $tools['multilingual'], true ) || in_array( 'Polylang Pro', $tools['multilingual'], true ) ); 97 98 // Prefer Polylang configured languages if available. 99 if ( $has_polylang && function_exists( 'pll_languages_list' ) ) { 100 // Best-effort: get locale codes (e.g. en_GB, pt_PT). 101 $pll_locales = pll_languages_list( 102 array( 103 'fields' => 'locale', 104 ) 105 ); 106 107 if ( is_array( $pll_locales ) ) { 108 foreach ( $pll_locales as $loc ) { 109 $loc = (string) $loc; 110 if ( '' !== $loc ) { 111 $locales[] = $loc; 112 } 113 } 114 } 115 116 $locales = array_values( array_unique( $locales ) ); 117 118 foreach ( $locales as $locale ) { 119 $labels[] = $this->get_human_locale_label( $locale ); 120 } 121 122 $labels = array_values( array_unique( array_filter( array_map( 'strval', $labels ) ) ) ); 123 sort( $labels ); 124 125 if ( ! empty( $labels ) ) { 126 $source = 'polylang'; 127 return array( 128 'locales' => $locales, 129 'labels' => $labels, 130 'source' => $source, 131 ); 132 } 133 } 134 135 // Fallback: WordPress locale + available languages (installed/available translations). 86 136 $current_locale = (string) get_locale(); 87 137 $available = get_available_languages(); 88 89 $locales = array();90 138 91 139 if ( '' !== $current_locale ) { … … 102 150 $locales = array_values( array_unique( $locales ) ); 103 151 104 $labels = array();105 152 foreach ( $locales as $locale ) { 106 153 $labels[] = $this->get_human_locale_label( $locale ); 107 154 } 108 155 156 $labels = array_values( array_unique( array_filter( array_map( 'strval', $labels ) ) ) ); 109 157 sort( $labels ); 110 158 … … 112 160 'locales' => $locales, 113 161 'labels' => $labels, 162 'source' => $source, 114 163 ); 115 164 } … … 133 182 } 134 183 135 $names = array_values( array_unique( $names) );184 $names = array_values( array_unique( array_filter( array_map( 'strval', $names ) ) ) ); 136 185 137 186 $lang_count = ! empty( $languages['locales'] ) ? count( (array) $languages['locales'] ) : 0; … … 141 190 : __( 'No translators', 'pluxo-blueprint' ); 142 191 192 $source = ! empty( $languages['source'] ) ? (string) $languages['source'] : 'wordpress'; 193 $suffix = ( 'polylang' === $source ) 194 ? __( 'languages', 'pluxo-blueprint' ) 195 : __( 'locales', 'pluxo-blueprint' ); 196 143 197 $label = sprintf( 144 /* translators: 1: plugin names, 2: number of languages .*/145 __( '%1$s / %2$d languages', 'pluxo-blueprint' ),198 /* translators: 1: plugin names, 2: number of languages/locales, 3: suffix label */ 199 __( '%1$s / %2$d %3$s', 'pluxo-blueprint' ), 146 200 $plugin_label, 147 (int) $lang_count 148 ); 149 150 $class = 'pluxo-blueprint-pill--neutral'; 151 152 if ( ! empty( $tools['multilingual'] ) ) { 153 $class = 'pluxo-blueprint-pill--neutral'; 154 } elseif ( ! empty( $tools['string_tools'] ) ) { 155 $class = 'pluxo-blueprint-pill--neutral'; 156 } 201 (int) $lang_count, 202 $suffix 203 ); 157 204 158 205 return array( 159 'class' => $class,206 'class' => 'pluxo-blueprint-pill--neutral', 160 207 'label' => $label, 161 208 ); … … 172 219 $has_multilingual = ! empty( $tools['multilingual'] ); 173 220 $has_string_tools = ! empty( $tools['string_tools'] ); 221 $source = ! empty( $languages['source'] ) ? (string) $languages['source'] : 'wordpress'; 174 222 175 223 $lang_count = ! empty( $languages['labels'] ) ? count( (array) $languages['labels'] ) : 0; … … 179 227 $lang_list = implode( ', ', array_map( 'sanitize_text_field', (array) $languages['labels'] ) ); 180 228 } 229 230 $language_label = ( 'polylang' === $source ) 231 ? __( 'language(s)', 'pluxo-blueprint' ) 232 : __( 'language pack(s) installed/available in WordPress', 'pluxo-blueprint' ); 181 233 182 234 if ( $has_multilingual ) { … … 189 241 if ( $lang_count > 0 ) { 190 242 $line2 = sprintf( 191 /* translators: 1: number of languages, 2: languages list .*/192 __( 'This site currently has %1$d language(s) available: %2$s.', 'pluxo-blueprint' ),243 /* translators: 1: number of languages, 2: languages list, 3: label */ 244 __( 'This site currently has %1$d %3$s: %2$s.', 'pluxo-blueprint' ), 193 245 (int) $lang_count, 194 $lang_list 246 $lang_list, 247 $language_label 195 248 ); 196 249 … … 210 263 if ( $lang_count > 0 ) { 211 264 $line2 = sprintf( 212 /* translators: 1: number of languages, 2: languages list .*/213 __( 'This site currently has %1$d language(s) available: %2$s.', 'pluxo-blueprint' ),265 /* translators: 1: number of languages, 2: languages list, 3: label */ 266 __( 'This site currently has %1$d %3$s: %2$s.', 'pluxo-blueprint' ), 214 267 (int) $lang_count, 215 $lang_list 268 $lang_list, 269 $language_label 216 270 ); 217 271 … … 222 276 } 223 277 224 if ( $lang_count > 0) {278 if ( $lang_count > 1 ) { 225 279 return sprintf( 226 /* translators: 1: number of languages, 2: languages list.*/227 esc_html__( 'No translation plugins were detected. This site currently has %1$d language(s) available: %2$s.', 'pluxo-blueprint' ),280 /* translators: 1: number, 2: list, 3: label */ 281 __( 'No supported translation plugins were detected. However, this site appears to have %1$d %3$s: %2$s. This may indicate an unsupported multilingual plugin, a custom setup, or leftover language packs.', 'pluxo-blueprint' ), 228 282 (int) $lang_count, 229 $lang_list 283 $lang_list, 284 $language_label 230 285 ); 231 286 } … … 248 303 'gtranslate/gtranslate.php' => 'GTranslate', 249 304 'loco-translate/loco.php' => 'Loco Translate', 305 'polylang-pro/polylang.php' => 'Polylang Pro', 250 306 ); 251 307 -
pluxo-blueprint/trunk/includes/Modules/Users/class-pluxo-blueprint-module-users.php
r3453209 r3455410 80 80 foreach ( $lines as $line ) : 81 81 ?> 82 <p class="pluxo-blueprint-muted"> 83 <?php echo esc_html( $line ); ?> 84 </p> 82 <p class="pluxo-blueprint-muted"> 83 <?php 84 echo wp_kses( 85 $line, 86 array( 87 'strong' => array(), 88 ) 89 ); 90 ?> 91 </p> 85 92 <?php endforeach; ?> 86 93 <?php endif; ?> … … 140 147 ); 141 148 149 $has_high_admin_ratio = false; 150 151 if ( $total_users >= 4 && $admin_count > 0 ) { 152 $admin_ratio = $admin_count / $total_users; 153 $has_high_admin_ratio = ( $admin_ratio > 0.5 ); 154 } 155 142 156 if ( 0 === $total_users ) { 143 157 $pill_class = 'pluxo-blueprint-pill--warn'; 144 158 $pill_label = __( 'No users', 'pluxo-blueprint' ); 145 } elseif ( $admin_count > 0 ) { 159 } elseif ( $has_high_admin_ratio ) { 160 $pill_class = 'pluxo-blueprint-pill--warning'; 161 162 $pill_label = sprintf( 163 /* translators: 1: total users, 2: admins count */ 164 __( '%1$d users / %2$d admins (high admin ratio)', 'pluxo-blueprint' ), 165 (int) $total_users, 166 (int) $admin_count 167 ); 168 } else { 146 169 $pill_class = 'pluxo-blueprint-pill--neutral'; 147 170 } … … 158 181 (int) $admin_count 159 182 ); 183 184 if ( $has_high_admin_ratio ) { 185 $summary_lines[] = '<strong>' . __( 'A high proportion of users have administrator access. Consider limiting admin accounts to reduce security risk and accidental changes.', 'pluxo-blueprint' ) . '</strong>'; 186 } 160 187 161 188 if ( ! empty( $role_parts ) ) { -
pluxo-blueprint/trunk/includes/PDF/class-pluxo-blueprint-pdf-generator.php
r3453209 r3455410 63 63 } 64 64 65 if ( ! defined( 'PLUXO_BLUEPRINT_PRINT_VIEW' ) ) { 66 define( 'PLUXO_BLUEPRINT_PRINT_VIEW', true ); 67 } 68 65 69 require $template; 66 70 exit; -
pluxo-blueprint/trunk/includes/PDF/templates/overview-print.php
r3453209 r3455410 20 20 ?> 21 21 <style> 22 /* Basic print-friendly styling */ 23 body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; margin: 24px; color: #111; } 24 h1 { margin: 0 0 8px; font-size: 22px; } 25 h2 { font-size: 16px; margin-top: 18px; } 26 .pluxo-blueprint-muted { color: #555; margin: 0 0 16px; } 27 .pluxo-blueprint-export-actions { margin: 16px 0 22px; } 28 .pluxo-blueprint-export-actions button { padding: 10px 14px; cursor: pointer; } 22 /* Basic print-friendly styling */ 23 body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; margin: 24px; color: #111; } 24 h1 { margin: 0 0 8px; font-size: 22px; } 25 .pluxo-blueprint-muted { color: #555; margin: 0 0 16px; } 29 26 30 /* Make accordions printable: open all and remove interactive hints */ 31 details.pluxo-blueprint-accordion { border: 1px solid #ddd; border-radius: 10px; padding: 10px 12px; margin: 10px 0; } 32 summary { list-style: none; cursor: default; } 33 summary::-webkit-details-marker { display: none; } 27 details.pluxo-blueprint-accordion { 28 border: 1px solid #ddd; 29 border-radius: 12px; 30 padding: 14px 16px; 31 margin: 26px 0; /* 🔥 mais separação entre módulos */ 32 background: #fafafa; /* leve fundo para diferenciar */ 33 } 34 34 35 /* Hide buttons / scan actions in export*/36 .pluxo-blueprint-button,37 .pluxo-blueprint-scan-actions,38 button,39 a.button,40 a.page-title-action { display: none !important; }35 /* --- Module header (summary) typography/layout for print --- */ 36 details.pluxo-blueprint-accordion > summary { 37 display: flex; 38 align-items: center; 39 justify-content: space-between; 40 gap: 12px; 41 41 42 /* Tables */ 43 table { width: 100%; border-collapse: collapse; margin-top: 10px; } 44 th, td { border: 1px solid #ddd; padding: 8px; vertical-align: top; text-align: left; } 45 th { background: #f5f5f5; } 42 margin: -14px -16px 14px; /* puxa até às bordas do card */ 43 padding: 14px 16px; 46 44 47 @media print { 48 body { margin: 0; } 49 .pluxo-blueprint-export-actions { display: none !important; } 50 details { break-inside: avoid; } 51 } 45 background: #f0f0f0; 46 border-bottom: 1px solid #ddd; 47 border-radius: 12px 12px 0 0; 48 49 font-size: 18px; 50 font-weight: 700; 51 line-height: 1.25; 52 } 53 54 .pluxo-blueprint-accordion-body { 55 padding-top: 6px; 56 } 57 58 table { 59 margin-top: 14px; 60 margin-bottom: 6px; 61 } 62 63 /* Hide any "rescan" links in print, even if they aren't styled as buttons */ 64 a[href*="pluxo_rescan"] { display: none !important; } 65 66 /* If you use a title span in some modules, keep it aligned */ 67 details.pluxo-blueprint-accordion > summary .pluxo-blueprint-accordion-title { 68 font-size: 18px; 69 font-weight: 700; 70 } 71 72 /* Ensure the pill doesn't stick to the title */ 73 details.pluxo-blueprint-accordion > summary .pluxo-blueprint-summary-actions { 74 display: inline-flex; 75 align-items: center; 76 gap: 10px; 77 margin-left: auto; 78 } 79 80 /* Make pills slightly smaller in print */ 81 details.pluxo-blueprint-accordion > summary .pluxo-blueprint-pill { 82 font-size: 12px; 83 padding: 4px 10px; 84 border-radius: 999px; 85 white-space: nowrap; 86 } 87 88 .pluxo-blueprint-summary-actions { 89 display: inline-flex; 90 align-items: center; 91 gap: 10px; 92 white-space: nowrap; 93 } 94 95 .pluxo-blueprint-pill { 96 display: inline-block; 97 padding: 2px 10px; 98 border: 1px solid #ddd; 99 border-radius: 999px; 100 font-size: 12px; 101 line-height: 18px; 102 } 103 104 /* Hide buttons / scan actions / interactive controls in export */ 105 .pluxo-blueprint-button, 106 .pluxo-blueprint-scan-actions, 107 .pluxo-blueprint-copy-md-btn, 108 textarea, 109 button, 110 a.button, 111 a.page-title-action, 112 form, 113 input, 114 select, 115 label { display: none !important; } 116 117 /* Tables */ 118 table { width: 100%; border-collapse: collapse; margin-top: 10px; } 119 th, td { border: 1px solid #ddd; padding: 8px; vertical-align: top; text-align: left; } 120 th { background: #f5f5f5; } 121 122 @media print { 123 body { margin: 0; } 124 .pluxo-blueprint-export-actions { display: none !important; } 125 details { break-inside: avoid; } 126 } 52 127 </style> 53 128 </head> -
pluxo-blueprint/trunk/pluxo-blueprint.php
r3453209 r3455410 4 4 * Plugin URI: https://pluxo.dev/#pluxo-blueprint 5 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. 06 * Version: 1.2.1 7 7 * Requires at least: 6.0 8 8 * Requires PHP: 7.4 … … 63 63 64 64 if ( ! defined( 'PLUXO_BLUEPRINT_VERSION' ) ) { 65 define( 'PLUXO_BLUEPRINT_VERSION', '1.2. 0' );65 define( 'PLUXO_BLUEPRINT_VERSION', '1.2.1' ); 66 66 } 67 67 -
pluxo-blueprint/trunk/readme.txt
r3453209 r3455410 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1.2. 07 Stable tag: 1.2.1 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 58 58 59 59 == Changelog == 60 = 1.2.1 = 61 * Fixes in some of the modules in the overview 62 60 63 = 1.2.0 = 61 64 * Add: New Overview dashboard organised into multiple documentation modules
Note: See TracChangeset
for help on using the changeset viewer.