Changeset 3750420
- Timestamp:
- 10/16/2026 12:39:58 PM (6 days ago)
- Location:
- devdome-admin-cleaner
- Files:
-
- 2 added
- 18 edited
- 1 copied
-
tags/1.0.10 (copied) (copied from devdome-admin-cleaner/trunk )
-
tags/1.0.10/devdome-admin-cleaner.php (modified) (2 diffs)
-
tags/1.0.10/includes/admin.php (modified) (1 diff)
-
tags/1.0.10/lib/devdome-core/devdome-core.php (modified) (5 diffs)
-
tags/1.0.10/lib/devdome-core/hub-account.php (modified) (1 diff)
-
tags/1.0.10/lib/devdome-core/hub-telemetry.php (added)
-
tags/1.0.10/lib/devdome-core/hub.php (modified) (1 diff)
-
tags/1.0.10/lib/devdome-core/loader.php (modified) (1 diff)
-
tags/1.0.10/lib/devdome-core/uninstall.php (modified) (1 diff)
-
tags/1.0.10/readme.txt (modified) (3 diffs)
-
tags/1.0.10/uninstall.php (modified) (1 diff)
-
trunk/devdome-admin-cleaner.php (modified) (2 diffs)
-
trunk/includes/admin.php (modified) (1 diff)
-
trunk/lib/devdome-core/devdome-core.php (modified) (5 diffs)
-
trunk/lib/devdome-core/hub-account.php (modified) (1 diff)
-
trunk/lib/devdome-core/hub-telemetry.php (added)
-
trunk/lib/devdome-core/hub.php (modified) (1 diff)
-
trunk/lib/devdome-core/loader.php (modified) (1 diff)
-
trunk/lib/devdome-core/uninstall.php (modified) (1 diff)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/uninstall.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
devdome-admin-cleaner/tags/1.0.10/devdome-admin-cleaner.php
r3721976 r3750420 4 4 Plugin URI: https://devdome.com/ 5 5 Description: Clean up your WordPress admin in one click. Hide promo notices, remove dashboard clutter, simplify the admin bar, and give client roles a simpler workspace. Part of the DevDome suite. 6 Version: 1.0. 96 Version: 1.0.10 7 7 Author: DevDome 8 8 Author URI: https://devdome.com … … 24 24 } 25 25 26 define('DEVDADCL_VERSION', '1.0. 9');26 define('DEVDADCL_VERSION', '1.0.10'); 27 27 define('DEVDADCL_DIR', plugin_dir_path(__FILE__)); 28 28 define('DEVDADCL_URL', plugin_dir_url(__FILE__)); -
devdome-admin-cleaner/tags/1.0.10/includes/admin.php
r3721976 r3750420 367 367 ?> 368 368 <div class="dd-app min-h-screen bg-gray-50 text-[#3c434a] font-sans text-[13px]"> 369 <?php if (function_exists('devdcorev1_analytics_card')) { devdcorev1_analytics_card(); } ?> 369 370 <?php if ('' !== $err_text) : ?> 370 371 <div class="dd-banner" data-ac-flash="1" style="margin:16px 24px 0;border-color:#fecaca;background:#fef2f2;color:#991b1b;"><?php echo esc_html($err_text); ?><?php if (function_exists('devdcorev1_error_report_button')) { echo devdcorev1_error_report_button('devdome-admin-cleaner', DEVDADCL_VERSION, ('db' === $err ? 'Database error during an Admin Cleaner action: ' : 'Settings save did not land: ') . $err_text, 'Admin Cleaner ' . $tab); } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped by the core helper ?></div> -
devdome-admin-cleaner/tags/1.0.10/lib/devdome-core/devdome-core.php
r3721976 r3750420 21 21 } 22 22 define('DEVDCOREV1_LOADED', true); 23 define('DEVDCOREV1_VERSION', '1.7.1 0');23 define('DEVDCOREV1_VERSION', '1.7.13'); 24 24 25 25 if (!defined('DEVDCOREV1_FEED_ENDPOINT')) { … … 295 295 add_action('rest_api_init', 'devdcorev1_beacon_rest_init'); 296 296 297 /** 298 * Cloudflare edge ranges, copied from https://www.cloudflare.com/ips-v4/ and /ips-v6/ on 2026-10-02 299 * (core 1.7.12). Shipped, never fetched at runtime; refreshed with core releases. 300 */ 301 function devdcorev1_cloudflare_cidrs() 302 { 303 return array( 304 '173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', '141.101.64.0/18', 305 '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', '197.234.240.0/22', '198.41.128.0/17', 306 '162.158.0.0/15', '104.16.0.0/13', '104.24.0.0/14', '172.64.0.0/13', '131.0.72.0/22', 307 '2400:cb00::/32', '2606:4700::/32', '2803:f800::/32', '2405:b500::/32', '2405:8100::/32', 308 '2a06:98c0::/29', '2c0f:f248::/32', 309 ); 310 } 311 312 /** True only when the TCP peer (REMOTE_ADDR) is a Cloudflare edge. Cached per request. */ 313 function devdcorev1_peer_is_cloudflare() 314 { 315 static $cached = null; 316 if ($cached !== null) { 317 return $cached; 318 } 319 $peer = isset($_SERVER['REMOTE_ADDR']) ? filter_var(trim(sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']))), FILTER_VALIDATE_IP) : false; 320 if (false === $peer) { 321 return $cached = false; 322 } 323 $idx = devdcorev1_build_cidr_index(devdcorev1_cloudflare_cidrs()); 324 return $cached = devdcorev1_ip_in_cidr_index($peer, $idx['v4'], $idx['v6']); 325 } 326 297 327 /** Client IP for the beacon (Cloudflare-aware). */ 298 328 function devdcorev1_request_ip() 299 329 { 300 // CF-Connecting-IP is believed only when a plugin vouches that the peer is Cloudflare (the bot-protection 301 // plugin checks REMOTE_ADDR against the published ranges); otherwise anyone could name a victim address. 302 $trust_cf = (bool) apply_filters('devdcorev1_trust_cf_ip', false); 330 // CF-Connecting-IP is believed only when the peer really is Cloudflare: the core checks REMOTE_ADDR against the 331 // shipped edge ranges (core 1.7.12, DeepSeek core round 1), and a plugin may still vouch through the filter. 332 // Otherwise anyone could name a victim address by sending the header themselves. 333 $trust_cf = (bool) apply_filters('devdcorev1_trust_cf_ip', devdcorev1_peer_is_cloudflare()); 303 334 foreach ($trust_cf ? array('HTTP_CF_CONNECTING_IP', 'REMOTE_ADDR') : array('REMOTE_ADDR') as $h) { 304 335 if (!empty($_SERVER[$h])) { … … 314 345 } 315 346 347 /** Delete this guard's expired transient pairs (the value row and its timeout row), nothing else. */ 348 function devdcorev1_beacon_rate_sweep() 349 { 350 global $wpdb; 351 $like = $wpdb->esc_like('_transient_timeout_devdcorev1_beacon_rate_') . '%'; 352 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- scoped sweep of this library's own expired transients, the way core's delete_expired_transients() works. 353 $wpdb->query($wpdb->prepare( 354 "DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b 355 WHERE a.option_name LIKE %s 356 AND b.option_name = CONCAT('_transient_', SUBSTRING(a.option_name, 20)) 357 AND a.option_value < %d", 358 $like, 359 time() 360 )); 361 } 362 316 363 /** 317 364 * Receive one beacon: parse + classify, then re-broadcast to listeners via the … … 334 381 // FILTER_VALIDATE_IP sanitizes the transport peer; a non-IP value (impossible from a real 335 382 // TCP peer) collapses into the shared '' bucket, which is still rate-limited. 336 $bucket_ip = (defined('DEVDCOREV1_TRUST_CF_IP') && DEVDCOREV1_TRUST_CF_IP) 383 // Core audit 2026-10-16: since 1.7.12 devdcorev1_request_ip() returns the CF-Connecting-IP header ONLY 384 // when the TCP peer is a verified Cloudflare edge (shipped ranges) and REMOTE_ADDR otherwise, so $ip can 385 // no longer be minted by a rotated header. Bucket on it: before this, every visitor behind the same 386 // Cloudflare edge shared one 120/min bucket and a busy site dropped real beacons as a flood. 387 $bucket_ip = $ip !== '' 337 388 ? $ip 338 389 : (string) filter_var(isset($_SERVER['REMOTE_ADDR']) ? trim(sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']))) : '', FILTER_VALIDATE_IP); 339 $key = 'devdcorev1_beacon_rate_' . md5($bucket_ip) . '_' . (int) floor(time() / 60); 340 $n = (int) get_transient($key); 341 if ($n >= 120) { 390 // One bucket per IP that resets every minute, not one per IP per minute (core audit 2026-10-16): 391 // the old key carried the minute, so without an object cache every IP minted two fresh wp_options 392 // rows a minute that sat expired until WordPress's daily sweep (half of all beacon rows on a fleet 393 // site). Now a returning IP reuses its row, and about one request in fifty removes the expired ones. 394 $key = 'devdcorev1_beacon_rate_' . md5($bucket_ip); 395 $minute = (int) floor(time() / 60); 396 $b = get_transient($key); 397 if (!is_array($b) || !isset($b['m'], $b['n']) || (int) $b['m'] !== $minute) { 398 $b = array('m' => $minute, 'n' => 0); 399 } 400 if ((int) $b['n'] >= 120) { 342 401 return new WP_REST_Response(array('ok' => true), 202); // accepted, dropped 343 402 } 344 set_transient($key, $n + 1, 120); 403 $b['n'] = (int) $b['n'] + 1; 404 set_transient($key, $b, 120); 405 if (!wp_using_ext_object_cache() && 1 === wp_rand(1, 50)) { 406 devdcorev1_beacon_rate_sweep(); 407 } 345 408 346 409 $flag = static function ($k) use ($req) { return $req->get_param($k) ? 1 : 0; }; … … 513 576 if (file_exists(__DIR__ . '/hub-install-selfhost.php')) { 514 577 require_once __DIR__ . '/hub-install-selfhost.php'; 578 require_once __DIR__ . '/hub-telemetry.php'; // opt-in "Help improve DevDome" usage reports, core 1.7.12 515 579 } 516 580 } -
devdome-admin-cleaner/tags/1.0.10/lib/devdome-core/hub-account.php
r3704110 r3750420 15 15 * identity options are routed to the main site through option filters, which covers every 16 16 * plugin's own get_option() calls without touching them. Subdomain multisite blogs keep their own 17 * host and their own identity. Connection state stays per blog: each blog presses Connect once 18 * and the server treats it as the same site. Core 1.7.0. 17 * host and their own identity, and so does a subdirectory blog mapped to its own domain (core 1.7.11). 18 * Connection state stays per blog: each blog presses Connect once and the server treats it as the 19 * same site. Core 1.7.0. 19 20 */ 20 21 if (!function_exists('devdcorev1_shared_identity')) { 21 22 function devdcorev1_shared_identity() 22 23 { 23 // Three cheap calls, evaluated each time: switch_to_blog() can change the answer within one request.24 returnfunction_exists('is_multisite') && is_multisite()24 // Cheap calls, evaluated each time: switch_to_blog() can change the answer within one request. 25 if (!(function_exists('is_multisite') && is_multisite() 25 26 && function_exists('is_subdomain_install') && !is_subdomain_install() 26 && function_exists('is_main_site') && !is_main_site(); 27 && function_exists('is_main_site') && !is_main_site())) { 28 return false; 29 } 30 // Core 1.7.11: identity = the DOMAIN, so a subsite MAPPED to its own domain is its own site. It shares 31 // the network's token + Site ID only while its host is still the network's host (Codex, Analytics 1.1.5). 32 $blog_host = strtolower((string) wp_parse_url(home_url('/'), PHP_URL_HOST)); 33 $net_host = function_exists('network_home_url') ? strtolower((string) wp_parse_url(network_home_url('/'), PHP_URL_HOST)) : ''; 34 return '' !== $blog_host && $blog_host === $net_host; 27 35 } 28 36 /** pre_option_<identity option>: the main site's value, read once per request. */ -
devdome-admin-cleaner/tags/1.0.10/lib/devdome-core/hub.php
r3721976 r3750420 454 454 455 455 /** Core's registry-driven Overview (the default v1 hub). */ 456 /* ------------------------- Analytics mention card (core 1.7.13) ------------------------- */ 457 // One dismissible card on a plugin's OWN settings page pointing at the free DevDome Analytics 458 // plugin on WordPress.org (a plugin mention, not an upsell: guideline 11 allows linking our own 459 // products on our own screens). Links only to wp-admin's plugin-install screen; never installs 460 // or activates anything itself. Hidden for good once dismissed. Plugins call 461 // devdcorev1_analytics_card() at the top of their settings page. 462 function devdcorev1_analytics_card_dismiss() 463 { 464 if (empty($_GET['devdcorev1_an_card_dismiss']) || !current_user_can('manage_options')) { 465 return; 466 } 467 check_admin_referer('devdcorev1_an_card'); 468 update_option('devdcorev1_an_card_dismissed', 1, false); 469 wp_safe_redirect(remove_query_arg(array('devdcorev1_an_card_dismiss', '_wpnonce'))); 470 exit; 471 } 472 add_action('admin_init', 'devdcorev1_analytics_card_dismiss'); 473 474 function devdcorev1_analytics_card() 475 { 476 if (!current_user_can('manage_options') || get_option('devdcorev1_an_card_dismissed', 0)) { 477 return; 478 } 479 // Read from the options, not is_plugin_active(): that helper lives in the admin-only plugin helpers file, which the 480 // bundled library never loads itself (wp.org review rule, kept by the suite tests). 481 $an_file = 'devdome-analytics/devdome-analytics.php'; 482 $active = in_array($an_file, (array) get_option('active_plugins', array()), true) 483 || (is_multisite() && array_key_exists($an_file, (array) get_site_option('active_sitewide_plugins', array()))); 484 if ($active) { 485 $btn_url = add_query_arg(array('page' => 'devdome-analytics'), admin_url('admin.php')); 486 $btn_txt = 'Open Analytics'; 487 } elseif (current_user_can('install_plugins')) { 488 // The search results page, not tab=plugin-information: that page is the popup's iframe body and 489 // opened directly it has no admin menu, no admin bar and no way back (core audit 2026-10-16). 490 $btn_url = add_query_arg(array('s' => 'devdome-analytics', 'tab' => 'search', 'type' => 'term'), admin_url('plugin-install.php')); 491 $btn_txt = 'Install Analytics'; 492 } else { 493 $btn_url = 'https://wordpress.org/plugins/devdome-analytics/'; 494 $btn_txt = 'View On WordPress.org'; 495 } 496 $dismiss = wp_nonce_url(add_query_arg('devdcorev1_an_card_dismiss', 1), 'devdcorev1_an_card'); 497 $tools = add_query_arg(array('page' => DEVDCOREV1_TOOLS_MENU_SLUG), admin_url('admin.php')); 498 ?> 499 <style> 500 .ddan-card{display:flex;align-items:center;gap:16px;margin:16px 24px 0;padding:14px 18px;background:#0b1224;color:#fff;border-radius:12px;position:relative;max-width:1000px;box-sizing:border-box} 501 .ddan-ic{flex:0 0 42px;width:42px;height:42px;border-radius:10px;background:#4f46e5;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:16px;letter-spacing:-.5px} 502 .ddan-body{flex:1 1 auto;min-width:0} 503 .ddan-body strong{display:block;font-size:15px;line-height:1.3;margin:0 0 3px;color:#fff;font-weight:800} 504 .ddan-body span{display:block;font-size:13px;line-height:1.45;color:#fff;font-weight:600} 505 .ddan-body .ddan-key{margin-top:6px;padding-top:6px;border-top:1px solid rgba(255,255,255,.18)} 506 .ddan-body a{color:#c7d2fe;text-decoration:underline;font-weight:700} 507 .ddan-body a:hover{text-decoration:underline} 508 .ddan-btn{flex:0 0 auto;background:#4f46e5;color:#fff;font-weight:700;font-size:13px;padding:9px 16px;border-radius:9px;text-decoration:none;white-space:nowrap} 509 .ddan-btn:hover,.ddan-btn:focus{background:#4338ca;color:#fff} 510 .ddan-x{position:absolute;top:6px;right:10px;color:#c7d2fe;text-decoration:none;font-size:18px;line-height:1} 511 .ddan-x:hover{color:#fff} 512 @media (max-width:782px){.ddan-card{flex-wrap:wrap;margin:12px 10px 0}.ddan-btn{width:100%;text-align:center}} 513 </style> 514 <div class="ddan-card"> 515 <span class="ddan-ic">DD</span> 516 <div class="ddan-body"> 517 <strong>See your real visitors, not the bots. Free up to 50,000 pageviews a month.</strong> 518 <span>DevDome Analytics shows how many real people visit your site and how many hits are bots, with no cookies.</span> 519 <span>More DevDome plugins block bad bots, remove malware, clean unused media, find broken links, manage redirects and affiliate links, tidy the admin and log what users do. <a href="<?php echo esc_url($tools); ?>">See all plugins</a></span> 520 <span class="ddan-key">One DevDome account is the key to everything: connect it once and every plugin gets its dashboard and email alerts.</span> 521 </div> 522 <a class="ddan-btn" href="<?php echo esc_url($btn_url); ?>"><?php echo esc_html($btn_txt); ?></a> 523 <a class="ddan-x" href="<?php echo esc_url($dismiss); ?>" title="Dismiss" aria-label="Dismiss">×</a> 524 </div> 525 <?php 526 } 527 456 528 function devdcorev1_hub_render_default() 457 529 { -
devdome-admin-cleaner/tags/1.0.10/lib/devdome-core/loader.php
r3721976 r3750420 53 53 } 54 54 55 DEVDCOREV1_Loader::register('1.7.1 0', __DIR__ . '/devdome-core.php');55 DEVDCOREV1_Loader::register('1.7.13', __DIR__ . '/devdome-core.php'); -
devdome-admin-cleaner/tags/1.0.10/lib/devdome-core/uninstall.php
r3704110 r3750420 42 42 delete_option('devdcorev1_inventory_consent'); 43 43 delete_option('devdcorev1_connection_gen'); 44 // Core audit 2026-10-16: the Analytics mention card dismissal (1.7.13), the update-heal records 45 // (1.7.2) and the per-user "Report a bug" bubble flag (1.7.0) were never swept. 46 delete_option('devdcorev1_an_card_dismissed'); 47 delete_option('devdcorev1_heal_leftovers'); 48 delete_option('devdcorev1_heal_untracked'); 49 delete_metadata('user', 0, 'devdcorev1_bug_hint_seen', '', true); 44 50 // The connection itself (core 1.6.1): identifiers, the site token, the cached account 45 51 // and connection state, and every short-lived connect/beacon transient. 46 foreach (array('devdcorev1_ site_id', 'devdcorev1_site_token', 'devdcorev1_account_id', 'devdcorev1_account_email', 'devdcorev1_account', 'devdcorev1_conn_state', 'devdcorev1_conn_checked', 'devdcorev1_hub_catalog') as $opt) {52 foreach (array('devdcorev1_telemetry', 'devdcorev1_telemetry_salt', 'devdcorev1_site_id', 'devdcorev1_site_token', 'devdcorev1_account_id', 'devdcorev1_account_email', 'devdcorev1_account', 'devdcorev1_conn_state', 'devdcorev1_conn_checked', 'devdcorev1_hub_catalog') as $opt) { 47 53 delete_option($opt); 48 54 } -
devdome-admin-cleaner/tags/1.0.10/readme.txt
r3721976 r3750420 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 1.0. 97 Stable tag: 1.0.10 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 190 190 191 191 == Changelog == 192 193 = 1.0.10 = 194 195 * A dismissible note at the top of the settings page mentions the free DevDome Analytics plugin, with a button that opens the plugin installer (or the Analytics screen when it is already active). Dismiss hides it for good. 196 * Shared DevDome library 1.7.13: uninstalling the last DevDome plugin also removes the note's dismissal, the update-repair records and the per-user "Report a bug" flag; the visitor-beacon flood guard keeps one short-lived record per address and clears its expired ones itself, and behind Cloudflare it counts per visitor instead of per Cloudflare address. 197 * Shared DevDome library 1.7.12: a Cloudflare visitor address header is trusted only when the connection itself comes from a Cloudflare address, so a forged header from anywhere else is ignored. 198 * Shared DevDome library 1.7.11: on a subdirectory multisite, a site mapped to its own domain is its own site and no longer shares the network's connection. 199 * Deleting the plugin also removes the cached update-check answer it kept in the database. 192 200 193 201 = 1.0.9 = … … 272 280 273 281 * Initial release: one-click admin cleanup with a live clutter score, a notice inbox with snooze/mute/restore (criticals always kept visible), per-widget dashboard cleanup, admin-bar and footer/version hiding, role-based Client Mode, and five ready-made presets. 282 283 == Upgrade Notice == 284 285 = 1.0.10 = 286 Housekeeping release. Nothing changes on the screen. 287 288 = 1.0.9 = 289 Shared DevDome library 1.7.10 and WordPress.org review fixes (escaping, no output buffering). Settings are unaffected. -
devdome-admin-cleaner/tags/1.0.10/uninstall.php
r3704110 r3750420 44 44 // plugin still installed, so removing this one never orphans the core for the others). 45 45 require_once __DIR__ . '/lib/devdome-core/uninstall.php'; 46 // The suite updater caches its update manifest under a hashed key, per site and (on multisite) network wide. 47 // Clear both spellings, or the cache outlives the plugin. 48 $devdadcl_updater_cache = 'devdome_upd_' . md5('devdome-admin-cleaner'); 49 delete_transient($devdadcl_updater_cache); 50 if (is_multisite()) { 51 delete_site_transient($devdadcl_updater_cache); 52 } 46 53 devdcorev1_uninstall_cleanup('devdome-admin-cleaner/devdome-admin-cleaner.php'); 47 54 -
devdome-admin-cleaner/trunk/devdome-admin-cleaner.php
r3721976 r3750420 4 4 Plugin URI: https://devdome.com/ 5 5 Description: Clean up your WordPress admin in one click. Hide promo notices, remove dashboard clutter, simplify the admin bar, and give client roles a simpler workspace. Part of the DevDome suite. 6 Version: 1.0. 96 Version: 1.0.10 7 7 Author: DevDome 8 8 Author URI: https://devdome.com … … 24 24 } 25 25 26 define('DEVDADCL_VERSION', '1.0. 9');26 define('DEVDADCL_VERSION', '1.0.10'); 27 27 define('DEVDADCL_DIR', plugin_dir_path(__FILE__)); 28 28 define('DEVDADCL_URL', plugin_dir_url(__FILE__)); -
devdome-admin-cleaner/trunk/includes/admin.php
r3721976 r3750420 367 367 ?> 368 368 <div class="dd-app min-h-screen bg-gray-50 text-[#3c434a] font-sans text-[13px]"> 369 <?php if (function_exists('devdcorev1_analytics_card')) { devdcorev1_analytics_card(); } ?> 369 370 <?php if ('' !== $err_text) : ?> 370 371 <div class="dd-banner" data-ac-flash="1" style="margin:16px 24px 0;border-color:#fecaca;background:#fef2f2;color:#991b1b;"><?php echo esc_html($err_text); ?><?php if (function_exists('devdcorev1_error_report_button')) { echo devdcorev1_error_report_button('devdome-admin-cleaner', DEVDADCL_VERSION, ('db' === $err ? 'Database error during an Admin Cleaner action: ' : 'Settings save did not land: ') . $err_text, 'Admin Cleaner ' . $tab); } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped by the core helper ?></div> -
devdome-admin-cleaner/trunk/lib/devdome-core/devdome-core.php
r3721976 r3750420 21 21 } 22 22 define('DEVDCOREV1_LOADED', true); 23 define('DEVDCOREV1_VERSION', '1.7.1 0');23 define('DEVDCOREV1_VERSION', '1.7.13'); 24 24 25 25 if (!defined('DEVDCOREV1_FEED_ENDPOINT')) { … … 295 295 add_action('rest_api_init', 'devdcorev1_beacon_rest_init'); 296 296 297 /** 298 * Cloudflare edge ranges, copied from https://www.cloudflare.com/ips-v4/ and /ips-v6/ on 2026-10-02 299 * (core 1.7.12). Shipped, never fetched at runtime; refreshed with core releases. 300 */ 301 function devdcorev1_cloudflare_cidrs() 302 { 303 return array( 304 '173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', '141.101.64.0/18', 305 '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', '197.234.240.0/22', '198.41.128.0/17', 306 '162.158.0.0/15', '104.16.0.0/13', '104.24.0.0/14', '172.64.0.0/13', '131.0.72.0/22', 307 '2400:cb00::/32', '2606:4700::/32', '2803:f800::/32', '2405:b500::/32', '2405:8100::/32', 308 '2a06:98c0::/29', '2c0f:f248::/32', 309 ); 310 } 311 312 /** True only when the TCP peer (REMOTE_ADDR) is a Cloudflare edge. Cached per request. */ 313 function devdcorev1_peer_is_cloudflare() 314 { 315 static $cached = null; 316 if ($cached !== null) { 317 return $cached; 318 } 319 $peer = isset($_SERVER['REMOTE_ADDR']) ? filter_var(trim(sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']))), FILTER_VALIDATE_IP) : false; 320 if (false === $peer) { 321 return $cached = false; 322 } 323 $idx = devdcorev1_build_cidr_index(devdcorev1_cloudflare_cidrs()); 324 return $cached = devdcorev1_ip_in_cidr_index($peer, $idx['v4'], $idx['v6']); 325 } 326 297 327 /** Client IP for the beacon (Cloudflare-aware). */ 298 328 function devdcorev1_request_ip() 299 329 { 300 // CF-Connecting-IP is believed only when a plugin vouches that the peer is Cloudflare (the bot-protection 301 // plugin checks REMOTE_ADDR against the published ranges); otherwise anyone could name a victim address. 302 $trust_cf = (bool) apply_filters('devdcorev1_trust_cf_ip', false); 330 // CF-Connecting-IP is believed only when the peer really is Cloudflare: the core checks REMOTE_ADDR against the 331 // shipped edge ranges (core 1.7.12, DeepSeek core round 1), and a plugin may still vouch through the filter. 332 // Otherwise anyone could name a victim address by sending the header themselves. 333 $trust_cf = (bool) apply_filters('devdcorev1_trust_cf_ip', devdcorev1_peer_is_cloudflare()); 303 334 foreach ($trust_cf ? array('HTTP_CF_CONNECTING_IP', 'REMOTE_ADDR') : array('REMOTE_ADDR') as $h) { 304 335 if (!empty($_SERVER[$h])) { … … 314 345 } 315 346 347 /** Delete this guard's expired transient pairs (the value row and its timeout row), nothing else. */ 348 function devdcorev1_beacon_rate_sweep() 349 { 350 global $wpdb; 351 $like = $wpdb->esc_like('_transient_timeout_devdcorev1_beacon_rate_') . '%'; 352 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- scoped sweep of this library's own expired transients, the way core's delete_expired_transients() works. 353 $wpdb->query($wpdb->prepare( 354 "DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b 355 WHERE a.option_name LIKE %s 356 AND b.option_name = CONCAT('_transient_', SUBSTRING(a.option_name, 20)) 357 AND a.option_value < %d", 358 $like, 359 time() 360 )); 361 } 362 316 363 /** 317 364 * Receive one beacon: parse + classify, then re-broadcast to listeners via the … … 334 381 // FILTER_VALIDATE_IP sanitizes the transport peer; a non-IP value (impossible from a real 335 382 // TCP peer) collapses into the shared '' bucket, which is still rate-limited. 336 $bucket_ip = (defined('DEVDCOREV1_TRUST_CF_IP') && DEVDCOREV1_TRUST_CF_IP) 383 // Core audit 2026-10-16: since 1.7.12 devdcorev1_request_ip() returns the CF-Connecting-IP header ONLY 384 // when the TCP peer is a verified Cloudflare edge (shipped ranges) and REMOTE_ADDR otherwise, so $ip can 385 // no longer be minted by a rotated header. Bucket on it: before this, every visitor behind the same 386 // Cloudflare edge shared one 120/min bucket and a busy site dropped real beacons as a flood. 387 $bucket_ip = $ip !== '' 337 388 ? $ip 338 389 : (string) filter_var(isset($_SERVER['REMOTE_ADDR']) ? trim(sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']))) : '', FILTER_VALIDATE_IP); 339 $key = 'devdcorev1_beacon_rate_' . md5($bucket_ip) . '_' . (int) floor(time() / 60); 340 $n = (int) get_transient($key); 341 if ($n >= 120) { 390 // One bucket per IP that resets every minute, not one per IP per minute (core audit 2026-10-16): 391 // the old key carried the minute, so without an object cache every IP minted two fresh wp_options 392 // rows a minute that sat expired until WordPress's daily sweep (half of all beacon rows on a fleet 393 // site). Now a returning IP reuses its row, and about one request in fifty removes the expired ones. 394 $key = 'devdcorev1_beacon_rate_' . md5($bucket_ip); 395 $minute = (int) floor(time() / 60); 396 $b = get_transient($key); 397 if (!is_array($b) || !isset($b['m'], $b['n']) || (int) $b['m'] !== $minute) { 398 $b = array('m' => $minute, 'n' => 0); 399 } 400 if ((int) $b['n'] >= 120) { 342 401 return new WP_REST_Response(array('ok' => true), 202); // accepted, dropped 343 402 } 344 set_transient($key, $n + 1, 120); 403 $b['n'] = (int) $b['n'] + 1; 404 set_transient($key, $b, 120); 405 if (!wp_using_ext_object_cache() && 1 === wp_rand(1, 50)) { 406 devdcorev1_beacon_rate_sweep(); 407 } 345 408 346 409 $flag = static function ($k) use ($req) { return $req->get_param($k) ? 1 : 0; }; … … 513 576 if (file_exists(__DIR__ . '/hub-install-selfhost.php')) { 514 577 require_once __DIR__ . '/hub-install-selfhost.php'; 578 require_once __DIR__ . '/hub-telemetry.php'; // opt-in "Help improve DevDome" usage reports, core 1.7.12 515 579 } 516 580 } -
devdome-admin-cleaner/trunk/lib/devdome-core/hub-account.php
r3704110 r3750420 15 15 * identity options are routed to the main site through option filters, which covers every 16 16 * plugin's own get_option() calls without touching them. Subdomain multisite blogs keep their own 17 * host and their own identity. Connection state stays per blog: each blog presses Connect once 18 * and the server treats it as the same site. Core 1.7.0. 17 * host and their own identity, and so does a subdirectory blog mapped to its own domain (core 1.7.11). 18 * Connection state stays per blog: each blog presses Connect once and the server treats it as the 19 * same site. Core 1.7.0. 19 20 */ 20 21 if (!function_exists('devdcorev1_shared_identity')) { 21 22 function devdcorev1_shared_identity() 22 23 { 23 // Three cheap calls, evaluated each time: switch_to_blog() can change the answer within one request.24 returnfunction_exists('is_multisite') && is_multisite()24 // Cheap calls, evaluated each time: switch_to_blog() can change the answer within one request. 25 if (!(function_exists('is_multisite') && is_multisite() 25 26 && function_exists('is_subdomain_install') && !is_subdomain_install() 26 && function_exists('is_main_site') && !is_main_site(); 27 && function_exists('is_main_site') && !is_main_site())) { 28 return false; 29 } 30 // Core 1.7.11: identity = the DOMAIN, so a subsite MAPPED to its own domain is its own site. It shares 31 // the network's token + Site ID only while its host is still the network's host (Codex, Analytics 1.1.5). 32 $blog_host = strtolower((string) wp_parse_url(home_url('/'), PHP_URL_HOST)); 33 $net_host = function_exists('network_home_url') ? strtolower((string) wp_parse_url(network_home_url('/'), PHP_URL_HOST)) : ''; 34 return '' !== $blog_host && $blog_host === $net_host; 27 35 } 28 36 /** pre_option_<identity option>: the main site's value, read once per request. */ -
devdome-admin-cleaner/trunk/lib/devdome-core/hub.php
r3721976 r3750420 454 454 455 455 /** Core's registry-driven Overview (the default v1 hub). */ 456 /* ------------------------- Analytics mention card (core 1.7.13) ------------------------- */ 457 // One dismissible card on a plugin's OWN settings page pointing at the free DevDome Analytics 458 // plugin on WordPress.org (a plugin mention, not an upsell: guideline 11 allows linking our own 459 // products on our own screens). Links only to wp-admin's plugin-install screen; never installs 460 // or activates anything itself. Hidden for good once dismissed. Plugins call 461 // devdcorev1_analytics_card() at the top of their settings page. 462 function devdcorev1_analytics_card_dismiss() 463 { 464 if (empty($_GET['devdcorev1_an_card_dismiss']) || !current_user_can('manage_options')) { 465 return; 466 } 467 check_admin_referer('devdcorev1_an_card'); 468 update_option('devdcorev1_an_card_dismissed', 1, false); 469 wp_safe_redirect(remove_query_arg(array('devdcorev1_an_card_dismiss', '_wpnonce'))); 470 exit; 471 } 472 add_action('admin_init', 'devdcorev1_analytics_card_dismiss'); 473 474 function devdcorev1_analytics_card() 475 { 476 if (!current_user_can('manage_options') || get_option('devdcorev1_an_card_dismissed', 0)) { 477 return; 478 } 479 // Read from the options, not is_plugin_active(): that helper lives in the admin-only plugin helpers file, which the 480 // bundled library never loads itself (wp.org review rule, kept by the suite tests). 481 $an_file = 'devdome-analytics/devdome-analytics.php'; 482 $active = in_array($an_file, (array) get_option('active_plugins', array()), true) 483 || (is_multisite() && array_key_exists($an_file, (array) get_site_option('active_sitewide_plugins', array()))); 484 if ($active) { 485 $btn_url = add_query_arg(array('page' => 'devdome-analytics'), admin_url('admin.php')); 486 $btn_txt = 'Open Analytics'; 487 } elseif (current_user_can('install_plugins')) { 488 // The search results page, not tab=plugin-information: that page is the popup's iframe body and 489 // opened directly it has no admin menu, no admin bar and no way back (core audit 2026-10-16). 490 $btn_url = add_query_arg(array('s' => 'devdome-analytics', 'tab' => 'search', 'type' => 'term'), admin_url('plugin-install.php')); 491 $btn_txt = 'Install Analytics'; 492 } else { 493 $btn_url = 'https://wordpress.org/plugins/devdome-analytics/'; 494 $btn_txt = 'View On WordPress.org'; 495 } 496 $dismiss = wp_nonce_url(add_query_arg('devdcorev1_an_card_dismiss', 1), 'devdcorev1_an_card'); 497 $tools = add_query_arg(array('page' => DEVDCOREV1_TOOLS_MENU_SLUG), admin_url('admin.php')); 498 ?> 499 <style> 500 .ddan-card{display:flex;align-items:center;gap:16px;margin:16px 24px 0;padding:14px 18px;background:#0b1224;color:#fff;border-radius:12px;position:relative;max-width:1000px;box-sizing:border-box} 501 .ddan-ic{flex:0 0 42px;width:42px;height:42px;border-radius:10px;background:#4f46e5;color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:16px;letter-spacing:-.5px} 502 .ddan-body{flex:1 1 auto;min-width:0} 503 .ddan-body strong{display:block;font-size:15px;line-height:1.3;margin:0 0 3px;color:#fff;font-weight:800} 504 .ddan-body span{display:block;font-size:13px;line-height:1.45;color:#fff;font-weight:600} 505 .ddan-body .ddan-key{margin-top:6px;padding-top:6px;border-top:1px solid rgba(255,255,255,.18)} 506 .ddan-body a{color:#c7d2fe;text-decoration:underline;font-weight:700} 507 .ddan-body a:hover{text-decoration:underline} 508 .ddan-btn{flex:0 0 auto;background:#4f46e5;color:#fff;font-weight:700;font-size:13px;padding:9px 16px;border-radius:9px;text-decoration:none;white-space:nowrap} 509 .ddan-btn:hover,.ddan-btn:focus{background:#4338ca;color:#fff} 510 .ddan-x{position:absolute;top:6px;right:10px;color:#c7d2fe;text-decoration:none;font-size:18px;line-height:1} 511 .ddan-x:hover{color:#fff} 512 @media (max-width:782px){.ddan-card{flex-wrap:wrap;margin:12px 10px 0}.ddan-btn{width:100%;text-align:center}} 513 </style> 514 <div class="ddan-card"> 515 <span class="ddan-ic">DD</span> 516 <div class="ddan-body"> 517 <strong>See your real visitors, not the bots. Free up to 50,000 pageviews a month.</strong> 518 <span>DevDome Analytics shows how many real people visit your site and how many hits are bots, with no cookies.</span> 519 <span>More DevDome plugins block bad bots, remove malware, clean unused media, find broken links, manage redirects and affiliate links, tidy the admin and log what users do. <a href="<?php echo esc_url($tools); ?>">See all plugins</a></span> 520 <span class="ddan-key">One DevDome account is the key to everything: connect it once and every plugin gets its dashboard and email alerts.</span> 521 </div> 522 <a class="ddan-btn" href="<?php echo esc_url($btn_url); ?>"><?php echo esc_html($btn_txt); ?></a> 523 <a class="ddan-x" href="<?php echo esc_url($dismiss); ?>" title="Dismiss" aria-label="Dismiss">×</a> 524 </div> 525 <?php 526 } 527 456 528 function devdcorev1_hub_render_default() 457 529 { -
devdome-admin-cleaner/trunk/lib/devdome-core/loader.php
r3721976 r3750420 53 53 } 54 54 55 DEVDCOREV1_Loader::register('1.7.1 0', __DIR__ . '/devdome-core.php');55 DEVDCOREV1_Loader::register('1.7.13', __DIR__ . '/devdome-core.php'); -
devdome-admin-cleaner/trunk/lib/devdome-core/uninstall.php
r3704110 r3750420 42 42 delete_option('devdcorev1_inventory_consent'); 43 43 delete_option('devdcorev1_connection_gen'); 44 // Core audit 2026-10-16: the Analytics mention card dismissal (1.7.13), the update-heal records 45 // (1.7.2) and the per-user "Report a bug" bubble flag (1.7.0) were never swept. 46 delete_option('devdcorev1_an_card_dismissed'); 47 delete_option('devdcorev1_heal_leftovers'); 48 delete_option('devdcorev1_heal_untracked'); 49 delete_metadata('user', 0, 'devdcorev1_bug_hint_seen', '', true); 44 50 // The connection itself (core 1.6.1): identifiers, the site token, the cached account 45 51 // and connection state, and every short-lived connect/beacon transient. 46 foreach (array('devdcorev1_ site_id', 'devdcorev1_site_token', 'devdcorev1_account_id', 'devdcorev1_account_email', 'devdcorev1_account', 'devdcorev1_conn_state', 'devdcorev1_conn_checked', 'devdcorev1_hub_catalog') as $opt) {52 foreach (array('devdcorev1_telemetry', 'devdcorev1_telemetry_salt', 'devdcorev1_site_id', 'devdcorev1_site_token', 'devdcorev1_account_id', 'devdcorev1_account_email', 'devdcorev1_account', 'devdcorev1_conn_state', 'devdcorev1_conn_checked', 'devdcorev1_hub_catalog') as $opt) { 47 53 delete_option($opt); 48 54 } -
devdome-admin-cleaner/trunk/readme.txt
r3721976 r3750420 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 1.0. 97 Stable tag: 1.0.10 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 190 190 191 191 == Changelog == 192 193 = 1.0.10 = 194 195 * A dismissible note at the top of the settings page mentions the free DevDome Analytics plugin, with a button that opens the plugin installer (or the Analytics screen when it is already active). Dismiss hides it for good. 196 * Shared DevDome library 1.7.13: uninstalling the last DevDome plugin also removes the note's dismissal, the update-repair records and the per-user "Report a bug" flag; the visitor-beacon flood guard keeps one short-lived record per address and clears its expired ones itself, and behind Cloudflare it counts per visitor instead of per Cloudflare address. 197 * Shared DevDome library 1.7.12: a Cloudflare visitor address header is trusted only when the connection itself comes from a Cloudflare address, so a forged header from anywhere else is ignored. 198 * Shared DevDome library 1.7.11: on a subdirectory multisite, a site mapped to its own domain is its own site and no longer shares the network's connection. 199 * Deleting the plugin also removes the cached update-check answer it kept in the database. 192 200 193 201 = 1.0.9 = … … 272 280 273 281 * Initial release: one-click admin cleanup with a live clutter score, a notice inbox with snooze/mute/restore (criticals always kept visible), per-widget dashboard cleanup, admin-bar and footer/version hiding, role-based Client Mode, and five ready-made presets. 282 283 == Upgrade Notice == 284 285 = 1.0.10 = 286 Housekeeping release. Nothing changes on the screen. 287 288 = 1.0.9 = 289 Shared DevDome library 1.7.10 and WordPress.org review fixes (escaping, no output buffering). Settings are unaffected. -
devdome-admin-cleaner/trunk/uninstall.php
r3704110 r3750420 44 44 // plugin still installed, so removing this one never orphans the core for the others). 45 45 require_once __DIR__ . '/lib/devdome-core/uninstall.php'; 46 // The suite updater caches its update manifest under a hashed key, per site and (on multisite) network wide. 47 // Clear both spellings, or the cache outlives the plugin. 48 $devdadcl_updater_cache = 'devdome_upd_' . md5('devdome-admin-cleaner'); 49 delete_transient($devdadcl_updater_cache); 50 if (is_multisite()) { 51 delete_site_transient($devdadcl_updater_cache); 52 } 46 53 devdcorev1_uninstall_cleanup('devdome-admin-cleaner/devdome-admin-cleaner.php'); 47 54
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)