Changeset 3391996
- Timestamp:
- 11/08/2025 02:06:55 AM (5 months ago)
- Location:
- volixta-ssl-security-headers/trunk
- Files:
-
- 7 edited
-
NOTICE.txt (modified) (1 diff)
-
includes/helpers.php (modified) (1 diff)
-
includes/mixed-content-fixer.php (modified) (7 diffs)
-
readme.txt (modified) (2 diffs)
-
uninstall.php (modified) (4 diffs)
-
views/admin-page.php (modified) (9 diffs)
-
volixta-ssl-security-headers.php (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
volixta-ssl-security-headers/trunk/NOTICE.txt
r3388964 r3391996 1 1 Volixta SSL & Security Headers — Trademark & Licensing Notice 2 Version: 1. 0.102 Version: 1.1.0 3 3 Date: 2025-09-15 4 4 -
volixta-ssl-security-headers/trunk/includes/helpers.php
r3371940 r3391996 228 228 function volissam_has_valid_ssl(): bool 229 229 { 230 if (! volissam_request_is_https()) return false;231 if (volissam_is_localhost()) return false;232 230 $host = volissam_current_host(); 233 231 if ($host === '') return false; 234 $ctx = @stream_context_create(['ssl' => [ 235 'capture_peer_cert' => true, 236 'SNI_enabled' => true, 237 'peer_name' => $host, 238 ]]); 239 $conn = @stream_socket_client("ssl://" . $host . ":443", $errno, $errstr, 5, STREAM_CLIENT_CONNECT, $ctx); 240 if (! $conn) return true; // best-effort: request is HTTPS, do not block admin flow 241 $params = stream_context_get_params($conn); 242 if (empty($params['options']['ssl']['peer_certificate'])) return true; 243 $parsed = @openssl_x509_parse($params['options']['ssl']['peer_certificate']); 244 if (! $parsed) return true; 245 $now = time(); 246 $from = $parsed['validFrom_time_t'] ?? 0; 247 $to = $parsed['validTo_time_t'] ?? 0; 248 if ($now < $from || $now > $to) return false; 249 return volissam_hostname_matches_cert($host, $parsed); 250 } 232 if (volissam_is_localhost()) return false; 233 234 // --- Étape 1 : si la requête actuelle est déjà HTTPS 235 if (volissam_request_is_https()) { 236 $ctx = @stream_context_create(['ssl' => [ 237 'capture_peer_cert' => true, 238 'SNI_enabled' => true, 239 'peer_name' => $host, 240 ]]); 241 $conn = @stream_socket_client("ssl://{$host}:443", $errno, $errstr, 5, STREAM_CLIENT_CONNECT, $ctx); 242 if (!$conn) return true; // best-effort 243 $params = stream_context_get_params($conn); 244 if (empty($params['options']['ssl']['peer_certificate'])) return true; 245 $parsed = @openssl_x509_parse($params['options']['ssl']['peer_certificate']); 246 if (!$parsed) return true; 247 $now = time(); 248 $from = $parsed['validFrom_time_t'] ?? 0; 249 $to = $parsed['validTo_time_t'] ?? 0; 250 if ($now < $from || $now > $to) return false; 251 return volissam_hostname_matches_cert($host, $parsed); 252 } 253 254 // --- Étape 2 : le site est encore en HTTP, test HTTPS distant 255 $url = 'https://' . $host; 256 $response = wp_remote_get($url, [ 257 'timeout' => 5, 258 'redirection' => 2, 259 'sslverify' => true, // on veut détecter un vrai certificat public 260 ]); 261 262 if (is_wp_error($response)) { 263 return false; 264 } 265 266 $code = wp_remote_retrieve_response_code($response); 267 if ($code >= 200 && $code < 400) { 268 // SSL répond, même si WP est encore en HTTP 269 return true; 270 } 271 272 return false; 273 } 274 251 275 252 276 // ======= SSL parsing helpers <<<<< -
volixta-ssl-security-headers/trunk/includes/mixed-content-fixer.php
r3371940 r3391996 1 1 <?php 2 2 // Exit if accessed directly. 3 if (! defined('ABSPATH')) { 4 exit; 5 } 3 if ( ! defined( 'ABSPATH' ) ) { 4 exit; 5 } 6 7 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber 8 6 9 7 10 /** … … 9 12 * Returns an array of pairs: [['http'=>'http://ex.com','https'=>'https://ex.com'], ...] 10 13 */ 11 function volissam_mcf_base_scheme_candidates(): array 12 { 13 $home = (string) get_option('home'); 14 $host = wp_parse_url($home, PHP_URL_HOST); 15 $port = wp_parse_url($home, PHP_URL_PORT); 16 17 if (! is_string($host) || $host === '') { 18 if (function_exists('volissam_current_host')) { 19 $host = volissam_current_host(); 20 } else { 21 $host = isset($_SERVER['HTTP_HOST']) 22 ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) 23 : ''; 24 $host = strtolower(preg_replace('#:\d+$#', '', trim($host))); 25 } 26 } 27 28 $cands = []; 29 if ($host !== '') { 30 $cands[] = $host; 31 if (stripos($host, 'www.') === 0) { 32 $cands[] = substr($host, 4); 33 } else { 34 $cands[] = 'www.' . $host; 35 } 36 } 37 $cands = array_values(array_unique(array_filter($cands))); 38 39 $out = []; 40 foreach ($cands as $h) { 41 $hp = $h . ($port ? ':' . (int) $port : ''); 42 $out[] = ['http' => 'http://' . $hp, 'https' => 'https://' . $hp]; 43 } 44 return $out ?: [['http' => 'http://', 'https' => 'https://']]; // safe fallback 14 function volissam_mcf_base_scheme_candidates(): array { 15 $home = (string) get_option( 'home' ); 16 $host = wp_parse_url( $home, PHP_URL_HOST ); 17 $port = wp_parse_url( $home, PHP_URL_PORT ); 18 19 if ( ! is_string( $host ) || $host === '' ) { 20 if ( function_exists( 'volissam_current_host' ) ) { 21 $host = volissam_current_host(); 22 } else { 23 $host = isset( $_SERVER['HTTP_HOST'] ) 24 ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) 25 : ''; 26 $host = strtolower( preg_replace( '#:\d+$#', '', trim( $host ) ) ); 27 } 28 } 29 30 $cands = []; 31 if ( $host !== '' ) { 32 $cands[] = $host; 33 if ( stripos( $host, 'www.' ) === 0 ) { 34 $cands[] = substr( $host, 4 ); 35 } else { 36 $cands[] = 'www.' . $host; 37 } 38 } 39 $cands = array_values( array_unique( array_filter( $cands ) ) ); 40 41 $out = []; 42 foreach ( $cands as $h ) { 43 $hp = $h . ( $port ? ':' . (int) $port : '' ); 44 $out[] = [ 'http' => 'http://' . $hp, 'https' => 'https://' . $hp ]; 45 } 46 return $out ?: [ [ 'http' => 'http://', 'https' => 'https://' ] ]; // safe fallback 45 47 } 46 48 … … 48 50 * Recursive, serialization-safe http->https replacement. 49 51 */ 50 function volissam_mcf_recursive_replace($data, string $http, string $https) 51 { 52 if (is_string($data)) { 53 return str_replace($http, $https, $data); 54 } 55 if (is_array($data)) { 56 foreach ($data as $k => $v) { 57 $data[$k] = volissam_mcf_recursive_replace($v, $http, $https); 58 } 59 return $data; 60 } 61 if (is_object($data)) { 62 foreach (get_object_vars($data) as $prop => $v) { 63 $data->$prop = volissam_mcf_recursive_replace($v, $http, $https); 64 } 65 return $data; 66 } 67 return $data; 52 function volissam_mcf_recursive_replace( $data, string $http, string $https ) { 53 if ( is_string( $data ) ) { 54 return str_replace( $http, $https, $data ); 55 } 56 if ( is_array( $data ) ) { 57 foreach ( $data as $k => $v ) { 58 $data[ $k ] = volissam_mcf_recursive_replace( $v, $http, $https ); 59 } 60 return $data; 61 } 62 if ( is_object( $data ) ) { 63 foreach ( get_object_vars( $data ) as $prop => $v ) { 64 $data->$prop = volissam_mcf_recursive_replace( $v, $http, $https ); 65 } 66 return $data; 67 } 68 return $data; 68 69 } 69 70 … … 73 74 * @return array { sql: string, params: array } 74 75 */ 75 function volissam_mcf_like_or_clause(string $column, array $http_pairs, callable $esc_like): array 76 { 77 $likes = []; 78 $params = []; 79 foreach ($http_pairs as $pair) { 80 $likes[] = "$column LIKE %s"; 81 $params[] = '%' . $esc_like($pair['http']) . '%'; 82 } 83 $sql = '(' . implode(' OR ', $likes) . ')'; 84 return ['sql' => $sql, 'params' => $params]; 76 function volissam_mcf_like_or_clause( string $column, array $http_pairs, callable $esc_like ): array { 77 $likes = []; 78 $params = []; 79 foreach ( $http_pairs as $pair ) { 80 $likes[] = "$column LIKE %s"; 81 $params[] = '%' . $esc_like( $pair['http'] ) . '%'; 82 } 83 $sql = '(' . implode( ' OR ', $likes ) . ')'; 84 return [ 'sql' => $sql, 'params' => $params ]; 85 85 } 86 86 … … 90 90 * @return array { sql: string, params: array } 91 91 */ 92 function volissam_mcf_replace_expr(string $column, array $pairs): array 93 { 94 $expr = $column; 95 $params = []; 96 foreach ($pairs as $pair) { 97 $expr = "REPLACE($expr, %s, %s)"; 98 $params[] = $pair['http']; 99 $params[] = $pair['https']; 100 } 101 return ['sql' => $expr, 'params' => $params]; 92 function volissam_mcf_replace_expr( string $column, array $pairs ): array { 93 $expr = $column; 94 $params = []; 95 foreach ( $pairs as $pair ) { 96 $expr = "REPLACE($expr, %s, %s)"; 97 $params[] = $pair['http']; 98 $params[] = $pair['https']; 99 } 100 return [ 'sql' => $expr, 'params' => $params ]; 102 101 } 103 102 … … 107 106 * @return array { posts, postmeta, options, total } 108 107 */ 109 function volissam_mcf_scan(): array 110 { 111 global $wpdb; 112 113 if (function_exists('wp_raise_memory_limit')) { 114 wp_raise_memory_limit('admin'); 115 } 116 if (function_exists('set_time_limit')) { 117 @set_time_limit(300); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged 118 } 119 120 $pairs = volissam_mcf_base_scheme_candidates(); 121 122 // -------- POSTS -------- 123 $clause = volissam_mcf_like_or_clause('post_content', $pairs, [$wpdb, 'esc_like']); 124 $cache_key_posts = 'volissam_mcf_scan_posts_' . md5(maybe_serialize($clause['params'])); 125 $posts = wp_cache_get($cache_key_posts, 'volissam_mcf'); 126 if ($posts === false) { 127 /* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching */ 128 $posts = (int) $wpdb->get_var( 129 $wpdb->prepare( 130 "SELECT COUNT(*) FROM {$wpdb->posts} WHERE {$clause['sql']}", 131 ...$clause['params'] 132 ) 133 ); 134 /* phpcs:enable */ 135 wp_cache_set($cache_key_posts, $posts, 'volissam_mcf', 60); 136 } 137 138 // -------- POSTMETA -------- 139 $clause = volissam_mcf_like_or_clause('meta_value', $pairs, [$wpdb, 'esc_like']); 140 $cache_key_meta = 'volissam_mcf_scan_postmeta_' . md5(maybe_serialize($clause['params'])); 141 $postmeta = wp_cache_get($cache_key_meta, 'volissam_mcf'); 142 if ($postmeta === false) { 143 /* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching */ 144 $postmeta = (int) $wpdb->get_var( 145 $wpdb->prepare( 146 "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE {$clause['sql']}", 147 ...$clause['params'] 148 ) 149 ); 150 /* phpcs:enable */ 151 wp_cache_set($cache_key_meta, $postmeta, 'volissam_mcf', 60); 152 } 153 154 // -------- OPTIONS (exclure transients) -------- 155 $clause = volissam_mcf_like_or_clause('option_value', $pairs, [$wpdb, 'esc_like']); 156 $ex1 = $wpdb->esc_like('_transient_%'); 157 $ex2 = $wpdb->esc_like('_site_transient_%'); 158 159 $cache_key_opts = 'volissam_mcf_scan_options_' . md5(maybe_serialize([$ex1, $ex2, $clause['params']])); 160 $options = wp_cache_get($cache_key_opts, 'volissam_mcf'); 161 if ($options === false) { 162 /* phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching */ 163 $options = (int) $wpdb->get_var( 164 $wpdb->prepare( 165 "SELECT COUNT(*) FROM {$wpdb->options} 166 WHERE option_name NOT IN ('home','siteurl') 167 AND option_name NOT LIKE %s 168 AND option_name NOT LIKE %s 169 AND {$clause['sql']}", 170 $ex1, 171 $ex2, 172 ...$clause['params'] 173 ) 174 ); 175 /* phpcs:enable */ 176 wp_cache_set($cache_key_opts, $options, 'volissam_mcf', 60); 177 } 178 179 180 $total = $posts + $postmeta + $options; 181 return compact('posts', 'postmeta', 'options', 'total'); 182 } 183 108 function volissam_mcf_scan(): array { 109 global $wpdb; 110 111 if ( function_exists( 'wp_raise_memory_limit' ) ) { 112 wp_raise_memory_limit( 'admin' ); 113 } 114 if ( function_exists( 'set_time_limit' ) ) { 115 @set_time_limit( 300 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged 116 } 117 118 $pairs = volissam_mcf_base_scheme_candidates(); 119 120 // -------- POSTS -------- 121 $clause = volissam_mcf_like_or_clause( 'post_content', $pairs, [ $wpdb, 'esc_like' ] ); 122 $cache_key_posts = 'volissam_mcf_scan_posts_' . md5( maybe_serialize( $clause['params'] ) ); 123 $posts = wp_cache_get( $cache_key_posts, 'volissam_mcf' ); 124 if ( $posts === false ) { 125 /* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching */ 126 // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter 127 $posts = (int) $wpdb->get_var( 128 $wpdb->prepare( 129 "SELECT COUNT(*) FROM {$wpdb->posts} WHERE {$clause['sql']}", 130 ...$clause['params'] 131 ) 132 ); 133 wp_cache_set( $cache_key_posts, $posts, 'volissam_mcf', 60 ); 134 } 135 136 // -------- POSTMETA -------- 137 $clause = volissam_mcf_like_or_clause( 'meta_value', $pairs, [ $wpdb, 'esc_like' ] ); 138 $cache_key_meta = 'volissam_mcf_scan_postmeta_' . md5( maybe_serialize( $clause['params'] ) ); 139 $postmeta = wp_cache_get( $cache_key_meta, 'volissam_mcf' ); 140 if ( $postmeta === false ) { 141 /* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching */ 142 // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter 143 $postmeta = (int) $wpdb->get_var( 144 $wpdb->prepare( 145 "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE {$clause['sql']}", 146 ...$clause['params'] 147 ) 148 ); 149 wp_cache_set( $cache_key_meta, $postmeta, 'volissam_mcf', 60 ); 150 } 151 152 // -------- OPTIONS (exclude transients) -------- 153 $clause = volissam_mcf_like_or_clause( 'option_value', $pairs, [ $wpdb, 'esc_like' ] ); 154 $ex1 = $wpdb->esc_like( '_transient_%' ); 155 $ex2 = $wpdb->esc_like( '_site_transient_%' ); 156 157 $cache_key_opts = 'volissam_mcf_scan_options_' . md5( maybe_serialize( [ $ex1, $ex2, $clause['params'] ] ) ); 158 $options = wp_cache_get( $cache_key_opts, 'volissam_mcf' ); 159 if ( $options === false ) { 160 /* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching */ 161 // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter 162 $options = (int) $wpdb->get_var( 163 $wpdb->prepare( 164 "SELECT COUNT(*) FROM {$wpdb->options} 165 WHERE option_name NOT IN ('home','siteurl') 166 AND option_name NOT LIKE %s 167 AND option_name NOT LIKE %s 168 AND {$clause['sql']}", 169 $ex1, 170 $ex2, 171 ...$clause['params'] 172 ) 173 ); 174 wp_cache_set( $cache_key_opts, $options, 'volissam_mcf', 60 ); 175 } 176 177 $total = $posts + $postmeta + $options; 178 return compact( 'posts', 'postmeta', 'options', 'total' ); 179 } 184 180 185 181 /** … … 188 184 * @return array { posts, postmeta, options, total } 189 185 */ 190 function volissam_mcf_fix(): array 191 { 192 global $wpdb; 193 194 if (function_exists('wp_raise_memory_limit')) { 195 wp_raise_memory_limit('admin'); 196 } 197 if (function_exists('set_time_limit')) { 198 @set_time_limit(300); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged 199 } 200 if (function_exists('wp_defer_term_counting')) { 201 wp_defer_term_counting(true); 202 } 203 if (function_exists('wp_defer_comment_counting')) { 204 wp_defer_comment_counting(true); 205 } 206 207 $pairs = volissam_mcf_base_scheme_candidates(); 208 $updated_posts = 0; 209 $updated_meta = 0; 210 $updated_opts = 0; 211 212 // POSTS: single UPDATE with nested REPLACE + WHERE LIKE(OR) 213 if ($pairs) { 214 $rep = volissam_mcf_replace_expr('post_content', $pairs); 215 $clause = volissam_mcf_like_or_clause('post_content', $pairs, [$wpdb, 'esc_like']); 216 217 // $rep['sql'] and $clause['sql'] are safe fragments composed of placeholders only. 218 /* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare */ 219 $updated_posts = (int) $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching 220 $wpdb->prepare( 221 "UPDATE {$wpdb->posts} 222 SET post_content = {$rep['sql']} 223 WHERE {$clause['sql']}", 224 ...array_merge($rep['params'], $clause['params']) 225 ) 226 ); 227 /* phpcs:enable */ 228 } 229 230 // POSTMETA: batch, serialization-safe 231 $last_id = 0; 232 do { 233 $clause = volissam_mcf_like_or_clause('meta_value', $pairs, [$wpdb, 'esc_like']); 234 235 /* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber */ 236 $rows = $wpdb->get_results( 237 $wpdb->prepare( 238 "SELECT meta_id, meta_value 239 FROM {$wpdb->postmeta} 240 WHERE meta_id > %d AND {$clause['sql']} 241 ORDER BY meta_id ASC 242 LIMIT 2000", 243 $last_id, 244 ...$clause['params'] 245 ) 246 ); 247 /* phpcs:enable */ 248 249 if (! $rows) { 250 break; 251 } 252 253 foreach ($rows as $row) { 254 $last_id = (int) $row->meta_id; 255 256 $orig = maybe_unserialize($row->meta_value); 257 $repl = $orig; 258 259 foreach ($pairs as $p) { 260 $repl = volissam_mcf_recursive_replace($repl, $p['http'], $p['https']); 261 } 262 263 if ($repl !== $orig) { 264 // Uses the Metadata API (no direct DB call; handles serialization & cache) 265 update_metadata_by_mid('post', (int) $row->meta_id, $repl); 266 $updated_meta++; 267 } 268 } 269 } while (true); 270 271 // OPTIONS: batch, serialization-safe (exclude transients) 272 $ex1 = $wpdb->esc_like('_transient_%'); 273 $ex2 = $wpdb->esc_like('_site_transient_%'); 274 $last_id = 0; 275 276 do { 277 $clause = volissam_mcf_like_or_clause('option_value', $pairs, [$wpdb, 'esc_like']); 278 279 /* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber */ 280 $rows = $wpdb->get_results( 281 $wpdb->prepare( 282 "SELECT option_id, option_name, option_value, autoload 283 FROM {$wpdb->options} 284 WHERE option_id > %d 285 AND option_name NOT IN ('home','siteurl') 286 AND option_name NOT LIKE %s 287 AND option_name NOT LIKE %s 288 AND {$clause['sql']} 289 ORDER BY option_id ASC 290 LIMIT 1000", 291 $last_id, 292 $ex1, 293 $ex2, 294 ...$clause['params'] 295 ) 296 ); 297 /* phpcs:enable */ 298 299 300 if (! $rows) { 301 break; 302 } 303 304 foreach ($rows as $row) { 305 $last_id = (int) $row->option_id; 306 307 $name = (string) $row->option_name; 308 $orig = maybe_unserialize($row->option_value); 309 $repl = $orig; 310 311 foreach ($pairs as $p) { 312 $repl = volissam_mcf_recursive_replace($repl, $p['http'], $p['https']); 313 } 314 315 if ($repl !== $orig) { 316 $autoload_yes = ($row->autoload === 'yes'); 317 update_option($name, $repl, $autoload_yes); 318 $updated_opts++; 319 } 320 } 321 } while (true); 322 323 if (function_exists('wp_defer_term_counting')) { 324 wp_defer_term_counting(false); 325 } 326 if (function_exists('wp_defer_comment_counting')) { 327 wp_defer_comment_counting(false); 328 } 329 330 $total = $updated_posts + $updated_meta + $updated_opts; 331 332 return [ 333 'posts' => $updated_posts, 334 'postmeta' => $updated_meta, 335 'options' => $updated_opts, 336 'total' => $total, 337 ]; 338 } 186 function volissam_mcf_fix(): array { 187 global $wpdb; 188 189 if ( function_exists( 'wp_raise_memory_limit' ) ) { 190 wp_raise_memory_limit( 'admin' ); 191 } 192 if ( function_exists( 'set_time_limit' ) ) { 193 @set_time_limit( 300 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged 194 } 195 if ( function_exists( 'wp_defer_term_counting' ) ) { 196 wp_defer_term_counting( true ); 197 } 198 if ( function_exists( 'wp_defer_comment_counting' ) ) { 199 wp_defer_comment_counting( true ); 200 } 201 202 $pairs = volissam_mcf_base_scheme_candidates(); 203 $updated_posts = 0; 204 $updated_meta = 0; 205 $updated_opts = 0; 206 207 // POSTS 208 if ( $pairs ) { 209 $rep = volissam_mcf_replace_expr( 'post_content', $pairs ); 210 $clause = volissam_mcf_like_or_clause( 'post_content', $pairs, [ $wpdb, 'esc_like' ] ); 211 212 /* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching */ 213 // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter 214 $updated_posts = (int) $wpdb->query( 215 $wpdb->prepare( 216 "UPDATE {$wpdb->posts} 217 SET post_content = {$rep['sql']} 218 WHERE {$clause['sql']}", 219 ...array_merge( $rep['params'], $clause['params'] ) 220 ) 221 ); 222 } 223 224 // POSTMETA 225 $last_id = 0; 226 do { 227 $clause = volissam_mcf_like_or_clause( 'meta_value', $pairs, [ $wpdb, 'esc_like' ] ); 228 229 /* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching */ 230 // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter 231 $rows = $wpdb->get_results( 232 $wpdb->prepare( 233 "SELECT meta_id, meta_value 234 FROM {$wpdb->postmeta} 235 WHERE meta_id > %d AND {$clause['sql']} 236 ORDER BY meta_id ASC 237 LIMIT 2000", 238 $last_id, 239 ...$clause['params'] 240 ) 241 ); 242 243 if ( ! $rows ) { 244 break; 245 } 246 247 foreach ( $rows as $row ) { 248 $last_id = (int) $row->meta_id; 249 $orig = maybe_unserialize( $row->meta_value ); 250 $repl = $orig; 251 252 foreach ( $pairs as $p ) { 253 $repl = volissam_mcf_recursive_replace( $repl, $p['http'], $p['https'] ); 254 } 255 256 if ( $repl !== $orig ) { 257 update_metadata_by_mid( 'post', (int) $row->meta_id, $repl ); 258 $updated_meta++; 259 } 260 } 261 } while ( true ); 262 263 // OPTIONS 264 $ex1 = $wpdb->esc_like( '_transient_%' ); 265 $ex2 = $wpdb->esc_like( '_site_transient_%' ); 266 $last_id = 0; 267 268 do { 269 $clause = volissam_mcf_like_or_clause( 'option_value', $pairs, [ $wpdb, 'esc_like' ] ); 270 271 /* phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching */ 272 // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter 273 $rows = $wpdb->get_results( 274 $wpdb->prepare( 275 "SELECT option_id, option_name, option_value, autoload 276 FROM {$wpdb->options} 277 WHERE option_id > %d 278 AND option_name NOT IN ('home','siteurl') 279 AND option_name NOT LIKE %s 280 AND option_name NOT LIKE %s 281 AND {$clause['sql']} 282 ORDER BY option_id ASC 283 LIMIT 1000", 284 $last_id, 285 $ex1, 286 $ex2, 287 ...$clause['params'] 288 ) 289 ); 290 291 if ( ! $rows ) { 292 break; 293 } 294 295 foreach ( $rows as $row ) { 296 $last_id = (int) $row->option_id; 297 $name = (string) $row->option_name; 298 $orig = maybe_unserialize( $row->option_value ); 299 $repl = $orig; 300 301 foreach ( $pairs as $p ) { 302 $repl = volissam_mcf_recursive_replace( $repl, $p['http'], $p['https'] ); 303 } 304 305 if ( $repl !== $orig ) { 306 $autoload_yes = ( $row->autoload === 'yes' ); 307 update_option( $name, $repl, $autoload_yes ); 308 $updated_opts++; 309 } 310 } 311 } while ( true ); 312 313 if ( function_exists( 'wp_defer_term_counting' ) ) { 314 wp_defer_term_counting( false ); 315 } 316 if ( function_exists( 'wp_defer_comment_counting' ) ) { 317 wp_defer_comment_counting( false ); 318 } 319 320 $total = $updated_posts + $updated_meta + $updated_opts; 321 322 return [ 323 'posts' => $updated_posts, 324 'postmeta' => $updated_meta, 325 'options' => $updated_opts, 326 'total' => $total, 327 ]; 328 } 329 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -
volixta-ssl-security-headers/trunk/readme.txt
r3388964 r3391996 3 3 Tags: security headers, mixed content, ssl, https 4 4 Requires at least: 5.8 5 Tested up to: 6.8 .36 Stable tag: 1. 0.105 Tested up to: 6.8 6 Stable tag: 1.1.0 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 10 11 Easily enable SSL/HTTPS in WordPress, force 301 redirects, fix mixed content, and apply modern security headers (HSTS, CSP, X-Frame-Options). Safe, admin-only toolkit.11 Add modern security headers, enable SSL/HTTPS, fix mixed content, and force 301 redirects. Fast and safe. 12 12 13 13 == Description == … … 115 115 116 116 == Changelog == 117 = 1.1.0 – 2025-11-08 = 118 - Improved code compliance: added PHPCS annotations for dynamic SQL clauses to avoid false warnings. 119 - Ensured all SQL queries remain fully prepared and secure. 120 - Improved SSL detection to recognize valid certificates even when the site still uses HTTP. 121 - Added admin notice suggesting HTTPS activation when a valid SSL is detected. 122 - Updated UI for clearer SSL and security headers status display. 123 - Internal cleanup for plugin review and coding standards validation. 124 117 125 = 1.0.10 = 118 126 * Updated readme.txt -
volixta-ssl-security-headers/trunk/uninstall.php
r3371940 r3391996 1 1 <?php 2 2 // Exit if uninstall not called from WordPress. 3 if ( ! defined( 'WP_UNINSTALL_PLUGIN' )) {3 if (! defined('WP_UNINSTALL_PLUGIN')) { 4 4 exit; 5 5 } 6 6 7 7 // Markers 8 defined( 'VOLISSAM_MARKER' ) || define( 'VOLISSAM_MARKER', 'Volixta Security Headers');9 defined( 'VOLISSAM_REDIRECT_MARKER' ) || define( 'VOLISSAM_REDIRECT_MARKER', 'Volixta HTTPS Redirect');8 defined('VOLISSAM_MARKER') || define('VOLISSAM_MARKER', 'Volixta Security Headers'); 9 defined('VOLISSAM_REDIRECT_MARKER') || define('VOLISSAM_REDIRECT_MARKER', 'Volixta HTTPS Redirect'); 10 10 11 11 // WP includes 12 if ( ! function_exists( 'trailingslashit' )) {12 if (! function_exists('trailingslashit')) { 13 13 require_once ABSPATH . 'wp-includes/formatting.php'; 14 14 } 15 if ( ! function_exists( 'get_home_path' )) {15 if (! function_exists('get_home_path')) { 16 16 require_once ABSPATH . 'wp-admin/includes/file.php'; 17 17 } 18 18 19 19 // --- Helpers 20 function volissam_uninstall_htaccess_path(): string { 21 $home_path = function_exists( 'get_home_path' ) ? get_home_path() : ABSPATH; 22 return trailingslashit( $home_path ) . '.htaccess'; 20 function volissam_uninstall_htaccess_path(): string 21 { 22 $home_path = function_exists('get_home_path') ? get_home_path() : ABSPATH; 23 return trailingslashit($home_path) . '.htaccess'; 23 24 } 24 25 … … 26 27 * Supprime complètement un bloc .htaccess (y compris # BEGIN / # END). 27 28 */ 28 function volissam_uninstall_remove_block_fully( string $marker ): void { 29 function volissam_uninstall_remove_block_fully(string $marker): void 30 { 29 31 $file = volissam_uninstall_htaccess_path(); 30 if ( ! $file || ! file_exists( $file ) || ! wp_is_writable( $file )) {32 if (! $file || ! file_exists($file) || ! wp_is_writable($file)) { 31 33 return; 32 34 } 33 35 34 $contents = file_get_contents( $file);35 if ( $contents === false) {36 $contents = file_get_contents($file); 37 if ($contents === false) { 36 38 return; 37 39 } … … 40 42 $pattern = sprintf( 41 43 '/\n?# BEGIN %1$s.*?# END %1$s\n?/s', 42 preg_quote( $marker, '/')44 preg_quote($marker, '/') 43 45 ); 44 $new = preg_replace( $pattern, "\n", $contents);46 $new = preg_replace($pattern, "\n", $contents); 45 47 46 if ( $new !== null && $new !== $contents) {47 file_put_contents( $file, $new);48 if ($new !== null && $new !== $contents) { 49 file_put_contents($file, $new); 48 50 } 49 51 } 50 52 51 function volissam_uninstall_delete_all_options_for_blog(): void { 52 delete_option( 'volissam_php_redirect_enabled' ); 53 delete_option( 'volissam_headers_options' ); 54 delete_option( 'volissam_mcf_last_scan' ); // Mixed Content Fixer last scan 53 function volissam_uninstall_delete_all_options_for_blog(): void 54 { 55 delete_option('volissam_php_redirect_enabled'); 56 delete_option('volissam_headers_options'); 57 delete_option('volissam_mcf_last_scan'); // Mixed Content Fixer last scan 55 58 // Transients used in admin UI 56 delete_transient( 'volissam_flash_notice');57 delete_transient( 'volissam_last_error');59 delete_transient('volissam_flash_notice'); 60 delete_transient('volissam_last_error'); 58 61 } 59 62 60 63 // --- Cleanup .htaccess backups 61 function volissam_uninstall_cleanup_ht_backups(): void { 64 function volissam_uninstall_cleanup_ht_backups(): void 65 { 62 66 $file = volissam_uninstall_htaccess_path(); 63 if ( ! $file) {67 if (! $file) { 64 68 return; 65 69 } 66 70 67 71 $pattern = $file . '.bak-*'; 68 $files = @glob( $pattern, GLOB_NOSORT) ?: [];72 $files = @glob($pattern, GLOB_NOSORT) ?: []; 69 73 70 foreach ( $files as $bak) {74 foreach ($files as $bak) { 71 75 // security: only delete expected pattern in same directory 72 if ( is_file( $bak ) && strpos( $bak, basename( $file ) . '.bak-' ) !== false) {73 wp_delete_file( $bak);76 if (is_file($bak) && strpos($bak, basename($file) . '.bak-') !== false) { 77 wp_delete_file($bak); 74 78 } 75 79 } … … 77 81 78 82 // --- Exécution désinstallation globale (seulement sur site principal en multisite) 79 if ( ! is_multisite() || is_main_site()) {83 if (! is_multisite() || is_main_site()) { 80 84 volissam_uninstall_cleanup_ht_backups(); 81 volissam_uninstall_remove_block_fully( VOLISSAM_MARKER);82 volissam_uninstall_remove_block_fully( VOLISSAM_REDIRECT_MARKER);85 volissam_uninstall_remove_block_fully(VOLISSAM_MARKER); 86 volissam_uninstall_remove_block_fully(VOLISSAM_REDIRECT_MARKER); 83 87 } 84 88 85 89 // --- Remove options (single / multisite) 86 if ( is_multisite()) {87 $ page = 1;90 if (is_multisite()) { 91 $volissam_page = 1; 88 92 do { 89 $sites = get_sites( 90 [ 91 'fields' => 'ids', 92 'number' => 200, 93 'offset' => ( $page - 1 ) * 200, 94 ] 95 ); 96 foreach ( $sites as $blog_id ) { 97 switch_to_blog( $blog_id ); 93 $volissam_sites = get_sites([ 94 'fields' => 'ids', 95 'number' => 200, 96 'offset' => ($volissam_page - 1) * 200, 97 ]); 98 foreach ($volissam_sites as $blog_id) { 99 switch_to_blog($blog_id); 98 100 volissam_uninstall_delete_all_options_for_blog(); 99 101 restore_current_blog(); 100 102 } 101 $ page++;102 } while ( ! empty( $sites ) && count( $sites ) === 200);103 $volissam_page++; 104 } while (! empty($volissam_sites) && count($volissam_sites) === 200); 103 105 104 106 // Cleanup site-wide options 105 delete_site_option( 'volissam_php_redirect_enabled');106 delete_site_option( 'volissam_headers_options');107 delete_site_option( 'volissam_mcf_last_scan');107 delete_site_option('volissam_php_redirect_enabled'); 108 delete_site_option('volissam_headers_options'); 109 delete_site_option('volissam_mcf_last_scan'); 108 110 } else { 109 111 volissam_uninstall_delete_all_options_for_blog(); -
volixta-ssl-security-headers/trunk/views/admin-page.php
r3371940 r3391996 2 2 if (! defined('ABSPATH')) exit; 3 3 /** @var array $state */ 4 $ notice = $state['notice'] ?? null;5 $ cfg = $state['headersConfig'];6 $ ms_sub = (is_multisite() && !is_main_site());4 $volissam_notice = $state['notice'] ?? null; 5 $volissam_cfg = $state['headersConfig']; 6 $volissam_ms_sub = (is_multisite() && !is_main_site()); 7 7 ?> 8 8 <div class="wrap volissam-wrap" data-volissam-state="<?php echo esc_attr(wp_json_encode($state)); ?>"> 9 9 <h1>🔒 Volixta SSL & Security Headers</h1> 10 10 11 <?php if (!empty($ notice) && is_array($notice)): ?>12 <div class="notice notice-<?php echo esc_attr($ notice[0]); ?>">13 <p><?php echo esc_html($ notice[1]); ?></p>11 <?php if (!empty($volissam_notice) && is_array($volissam_notice)): ?> 12 <div class="notice notice-<?php echo esc_attr($volissam_notice[0]); ?>"> 13 <p><?php echo esc_html($volissam_notice[1]); ?></p> 14 14 </div> 15 15 <?php endif; ?> … … 46 46 <td><?php echo $state['sslValid'] ? '✅ ' . esc_html__('Yes', 'volixta-ssl-security-headers') : '❌ ' . esc_html__('No', 'volixta-ssl-security-headers'); ?></td> 47 47 </tr> 48 <?php if (!$state['httpsRequest'] && $state['sslValid']): ?> 49 <tr> 50 <th><?php echo esc_html__('SSL detected on server', 'volixta-ssl-security-headers'); ?></th> 51 <td>🌐 <?php echo esc_html__('Yes, valid certificate found even if site is HTTP', 'volixta-ssl-security-headers'); ?></td> 52 </tr> 53 <?php endif; ?> 54 48 55 <?php if (!$state['isLocal'] && !empty($state['sslInfo'])): 49 $ info= $state['sslInfo'];50 $ exp = !empty($info['validTo']) ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), (int)$info['validTo']) : '';51 $ days = isset($info['daysRemaining']) ? (int)$info['daysRemaining'] : null;52 $ soon = is_int($days) && $days >= 0 && $days <= 30;56 $volissam_notice = $state['sslInfo']; 57 $volissam_exp = !empty($volissam_notice['validTo']) ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), (int)$volissam_notice['validTo']) : ''; 58 $volissam_days = isset($volissam_notice['daysRemaining']) ? (int)$volissam_notice['daysRemaining'] : null; 59 $volissam_soon = is_int($volissam_days) && $volissam_days >= 0 && $volissam_days <= 30; 53 60 ?> 54 61 <tr> 55 62 <th><?php echo esc_html__('SSL issuer', 'volixta-ssl-security-headers'); ?></th> 56 <td><?php echo $ info['issuer'] ? esc_html($info['issuer']) : '<em>' . esc_html__('Unknown', 'volixta-ssl-security-headers') . '</em>'; ?></td>63 <td><?php echo $volissam_notice['issuer'] ? esc_html($volissam_notice['issuer']) : '<em>' . esc_html__('Unknown', 'volixta-ssl-security-headers') . '</em>'; ?></td> 57 64 </tr> 58 65 <tr> 59 66 <th><?php echo esc_html__('SSL expires', 'volixta-ssl-security-headers'); ?></th> 60 67 <td> 61 <?php if ($ exp): ?>62 <span><?php echo esc_html($ exp); ?></span>63 <?php if (is_int($ days) && $days >= 0): ?>64 <span style="margin-left:8px;<?php echo $ soon ? 'color:#b71c1c;font-weight:600' : 'opacity:.8'; ?>">68 <?php if ($volissam_exp): ?> 69 <span><?php echo esc_html($volissam_exp); ?></span> 70 <?php if (is_int($volissam_days) && $volissam_days >= 0): ?> 71 <span style="margin-left:8px;<?php echo $volissam_soon ? 'color:#b71c1c;font-weight:600' : 'opacity:.8'; ?>"> 65 72 <?php 66 if ($ soon) {73 if ($volissam_soon) { 67 74 printf( 68 75 /* translators: %d: Number of days until the SSL certificate expires (certificate expires soon) */ 69 76 esc_html__('%d days remaining (renew soon)', 'volixta-ssl-security-headers'), 70 esc_html(absint($ days))77 esc_html(absint($volissam_days)) 71 78 ); 72 79 } else { … … 74 81 /* translators: %d: Number of days until the SSL certificate expires */ 75 82 esc_html__('%d days remaining', 'volixta-ssl-security-headers'), 76 esc_html(absint($ days))83 esc_html(absint($volissam_days)) 77 84 ); 78 85 } … … 122 129 </section> 123 130 124 <?php if ($ ms_sub): ?>131 <?php if ($volissam_ms_sub): ?> 125 132 <div class="notice notice-info"> 126 133 <p><?php echo esc_html__('Multisite: .htaccess is shared across the network. Only the main site can modify it.', 'volixta-ssl-security-headers'); ?></p> … … 137 144 138 145 <?php if ($state['isLocal']): ?> 139 <?php $ can_switch_local = ($state['httpsRequest'] && !$state['wpHttps']); ?>146 <?php $volissam_can_switch_local = ($state['httpsRequest'] && !$state['wpHttps']); ?> 140 147 <section class="volissam-panel"> 141 148 <h2><?php echo esc_html__('Local HTTPS (mkcert)', 'volixta-ssl-security-headers'); ?></h2> 142 <?php $ https_url = preg_replace('/^http:/', 'https:', home_url('/')); ?>149 <?php $volissam_https_url = preg_replace('/^http:/', 'https:', home_url('/')); ?> 143 150 <p><?php echo wp_kses_post(__('To get a trusted local certificate (no browser warnings), use <code>mkcert</code> or enable a local SSL certificate in your local tool.', 'volixta-ssl-security-headers')); ?></p> 144 151 <ol style="margin-left:1em"> … … 162 169 <?php wp_nonce_field('volissam_action', 'volissam_nonce'); ?> 163 170 <input type="hidden" name="volissam_action" value="activate_wp_ssl" /> 164 <button class="button button-primary volissam-confirm" type="submit" <?php echo $ can_switch_local ? '' : 'disabled'; ?>>171 <button class="button button-primary volissam-confirm" type="submit" <?php echo $volissam_can_switch_local ? '' : 'disabled'; ?>> 165 172 <?php echo esc_html__('Activate HTTPS for WordPress (local)', 'volixta-ssl-security-headers'); ?> 166 173 </button> … … 196 203 197 204 <div class="volissam-row"> 198 <?php $ redirectApplied = ($state['htRedirect'] || $state['phpRedirectOn']); ?>205 <?php $volissam_redirect_applied = ($state['htRedirect'] || $state['phpRedirectOn']); ?> 199 206 <form method="post" class="volissam-actions" data-volissam-action="enable_https_redirect"> 200 207 <?php wp_nonce_field('volissam_action', 'volissam_nonce'); ?> 201 208 <input type="hidden" name="volissam_action" value="enable_https_redirect" /> 202 209 <button class="button volissam-confirm" type="submit" 203 <?php echo ($ redirectApplied || ! $state['sslValid']) ? 'disabled' : ''; ?>>210 <?php echo ($volissam_redirect_applied || ! $state['sslValid']) ? 'disabled' : ''; ?>> 204 211 🚀 <?php echo esc_html__('Enable HTTPS Redirect (301)', 'volixta-ssl-security-headers'); ?> 205 212 </button> … … 210 217 <?php endif; ?> 211 218 </form> 212 <?php if ($ redirectApplied): ?>219 <?php if ($volissam_redirect_applied): ?> 213 220 <form method="post" class="volissam-actions" data-volissam-action="disable_https_redirect"> 214 221 <?php wp_nonce_field('volissam_action', 'volissam_nonce'); ?> … … 348 355 </thead> 349 356 <tbody> 350 <?php foreach ($ cfg as $name => $row):351 $ key = volissam_field_key($name);352 $ enabled = !empty($row['enabled']);353 $v alue = (string)($row['value'] ?? '');357 <?php foreach ($volissam_cfg as $volissam_name => $volissam_row): 358 $volissam_key = volissam_field_key($volissam_name); 359 $volissam_enabled = !empty($volissam_row['enabled']); 360 $volissam_value = (string)($volissam_row['value'] ?? ''); 354 361 ?> 355 362 <tr> 356 <td><code><?php echo esc_html($ name); ?></code></td>357 <td><input type="checkbox" name="enable_<?php echo esc_attr($ key); ?>" <?php checked($enabled); ?> /></td>358 <td><input type="text" name="value_<?php echo esc_attr($ key); ?>" value="<?php echo esc_attr($value); ?>" /></td>363 <td><code><?php echo esc_html($volissam_name); ?></code></td> 364 <td><input type="checkbox" name="enable_<?php echo esc_attr($volissam_key); ?>" <?php checked($volissam_enabled); ?> /></td> 365 <td><input type="text" name="value_<?php echo esc_attr($volissam_key); ?>" value="<?php echo esc_attr($volissam_value); ?>" /></td> 359 366 </tr> 360 367 <?php endforeach; ?> -
volixta-ssl-security-headers/trunk/volixta-ssl-security-headers.php
r3388964 r3391996 4 4 * Plugin Name: Volixta SSL & Security Headers 5 5 * Description: Activate SSL/HTTPS, apply modern Security Headers (incl. HSTS), fix mixed content, file-permissions audit, — simple & safe. 6 * Version: 1. 0.106 * Version: 1.1.0 7 7 * Author: HELLO SITE LLC 8 8 * Author URI: https://www.agence-hello-site.com/ … … 12 12 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 13 13 * Requires at least: 5.8 14 * Tested up to: 6.8 .314 * Tested up to: 6.8 15 15 * Requires PHP: 7.4 16 16 */ … … 18 18 if (! defined('ABSPATH')) exit; 19 19 20 define('VOLISSAM_VERSION', '1. 0.10');20 define('VOLISSAM_VERSION', '1.1.0'); 21 21 define('VOLISSAM_MARKER', 'Volixta Security Headers'); 22 22 define('VOLISSAM_REDIRECT_MARKER', 'Volixta HTTPS Redirect'); … … 31 31 32 32 33 // Load Mixed Content Fixer only in admin .33 // Load Mixed Content Fixer only in admin area. 34 34 if (is_admin()) { 35 $mcf = plugin_dir_path(__FILE__) . 'includes/mixed-content-fixer.php'; 36 if (file_exists($mcf)) { 37 require_once $mcf; 38 } 39 } 35 $volissam_mcf_file = plugin_dir_path(__FILE__) . 'includes/mixed-content-fixer.php'; 36 37 if (file_exists($volissam_mcf_file)) { 38 require_once $volissam_mcf_file; // Safe inclusion with plugin prefix. 39 } 40 } 41 40 42 41 43 /**
Note: See TracChangeset
for help on using the changeset viewer.