Changeset 3780742
- Timestamp:
- 11/02/2026 09:12:28 PM (32 hours ago)
- Location:
- siteward
- Files:
-
- 36 added
- 11 edited
-
tags/0.17.1 (added)
-
tags/0.17.1/admin (added)
-
tags/0.17.1/admin/assets (added)
-
tags/0.17.1/admin/assets/admin.css (added)
-
tags/0.17.1/admin/assets/admin.js (added)
-
tags/0.17.1/admin/class-omnis-admin.php (added)
-
tags/0.17.1/admin/views (added)
-
tags/0.17.1/admin/views/_helpers.php (added)
-
tags/0.17.1/admin/views/add-site.php (added)
-
tags/0.17.1/admin/views/overview.php (added)
-
tags/0.17.1/admin/views/settings.php (added)
-
tags/0.17.1/admin/views/site-detail.php (added)
-
tags/0.17.1/admin/views/updates.php (added)
-
tags/0.17.1/admin/views/vulnerabilities.php (added)
-
tags/0.17.1/includes (added)
-
tags/0.17.1/includes/class-omnis-connection.php (added)
-
tags/0.17.1/includes/class-omnis-cron.php (added)
-
tags/0.17.1/includes/class-omnis-db.php (added)
-
tags/0.17.1/includes/class-omnis-flags.php (added)
-
tags/0.17.1/includes/class-omnis-http.php (added)
-
tags/0.17.1/includes/class-omnis-inventory.php (added)
-
tags/0.17.1/includes/class-omnis-notifications.php (added)
-
tags/0.17.1/includes/class-omnis-settings.php (added)
-
tags/0.17.1/includes/class-omnis-sites.php (added)
-
tags/0.17.1/includes/class-omnis-sync.php (added)
-
tags/0.17.1/includes/class-omnis-tags.php (added)
-
tags/0.17.1/includes/class-omnis-updates.php (added)
-
tags/0.17.1/includes/class-omnis-uptime.php (added)
-
tags/0.17.1/includes/class-omnis-vuln-feed.php (added)
-
tags/0.17.1/includes/class-omnis-vuln.php (added)
-
tags/0.17.1/languages (added)
-
tags/0.17.1/languages/index.php (added)
-
tags/0.17.1/license.txt (added)
-
tags/0.17.1/readme.txt (added)
-
tags/0.17.1/siteward.php (added)
-
tags/0.17.1/uninstall.php (added)
-
trunk/admin/assets/admin.css (modified) (1 diff)
-
trunk/admin/class-omnis-admin.php (modified) (1 diff)
-
trunk/admin/views/settings.php (modified) (1 diff)
-
trunk/admin/views/site-detail.php (modified) (1 diff)
-
trunk/includes/class-omnis-flags.php (modified) (6 diffs)
-
trunk/includes/class-omnis-settings.php (modified) (1 diff)
-
trunk/includes/class-omnis-sync.php (modified) (3 diffs)
-
trunk/includes/class-omnis-uptime.php (modified) (1 diff)
-
trunk/includes/class-omnis-vuln.php (modified) (1 diff)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/siteward.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
siteward/trunk/admin/assets/admin.css
r3757358 r3780742 170 170 .omnis-kv .v.warn { color: #b54708; } 171 171 .omnis-kv .v.bad { color: #b42318; } 172 .omnis-kv .v .warn { color: #b54708; } 173 .omnis-kv .v .bad { color: #b42318; } 172 174 .omnis-kv .v.ok { color: #067647; } 173 175 .omnis-inline-warn { margin-top: 11px; background: #fffaeb; border: 1px solid #fedf89; border-radius: 8px; padding: 8px 10px; font-size: 12px; color: #b54708; } -
siteward/trunk/admin/class-omnis-admin.php
r3757358 r3780742 400 400 'notify_vulnerable' => empty( $in['notify_vulnerable'] ) ? 0 : 1, 401 401 'notify_rollback' => empty( $in['notify_rollback'] ) ? 0 : 1, 402 'vuln_notices' => empty( $in['vuln_notices'] ) ? 0 : 1, 402 403 // 1 to 168 hours: below an hour the feed client's own floor rejects it 403 404 // anyway, and beyond a week a "current" feed is not current. -
siteward/trunk/admin/views/settings.php
r3757358 r3780742 147 147 <p class="section-desc"><?php esc_html_e( 'How often this dashboard fetches the vulnerability data it matches your sites against.', 'siteward' ); ?></p> 148 148 <?php omnis_num_row( 'vuln_interval', __( 'Check every (hours)', 'siteward' ), $settings, '', 1 ); ?> 149 <div class="omnis-row" style="border-top:1px solid #f4f4f5;margin-top:10px"> 150 <label><input type="checkbox" name="vuln_notices" value="1" <?php checked( ! empty( $settings['vuln_notices'] ) ); ?> /> <?php esc_html_e( 'Warn on each site\'s Plugins screen', 'siteward' ); ?> 151 <span class="field-desc"><?php esc_html_e( 'Adds a row under each affected plugin on the site itself, seen only by people who can update plugins there, with the version that fixes it. Sites need Siteward Child 0.9.0 or later. Turn it off if clients log in and should not see these warnings.', 'siteward' ); ?></span> 152 </label> 153 </div> 149 154 <p class="section-desc" style="margin:12px 0 0"> 150 155 <?php -
siteward/trunk/admin/views/site-detail.php
r3747027 r3780742 168 168 <div class="omnis-info-card"> 169 169 <h3>⛭ <?php esc_html_e( 'Environment', 'siteward' ); ?></h3> 170 <div class="omnis-kv"><span class="k"><?php esc_html_e( 'WordPress', 'siteward' ); ?></span><span class="v"><?php echo esc_html( $status && $status->wp_version ? $status->wp_version : '—' ); ?><?php echo ! empty( $core['available'] ) ? ' <span class="warn">· ' . esc_html__( 'update ready', 'siteward' ) . '</span>' : ''; ?></span></div> 171 <div class="omnis-kv"><span class="k"><?php esc_html_e( 'PHP', 'siteward' ); ?></span><span class="v"><?php echo esc_html( $status && $status->php_version ? $status->php_version : '—' ); ?></span></div> 170 <?php 171 $omnis_wp_behind = $status && $status->wp_version ? Omnis_Flags::wp_releases_behind( $status->wp_version, Omnis_Flags::latest_wp( isset( $core['latest'] ) ? (string) $core['latest'] : '' ) ) : 0; 172 $omnis_php_eol = $status && $status->php_version ? Omnis_Flags::php_eol( $status->php_version ) : null; 173 ?> 174 <div class="omnis-kv"><span class="k"><?php esc_html_e( 'WordPress', 'siteward' ); ?></span><span class="v"><?php echo esc_html( $status && $status->wp_version ? $status->wp_version : '—' ); ?><?php echo ! empty( $core['available'] ) ? ' <span class="warn">· ' . esc_html__( 'update ready', 'siteward' ) . '</span>' : ''; ?><?php if ( $omnis_wp_behind >= 2 ) : ?> <span class="bad">· <?php echo esc_html( sprintf( /* translators: %d: number of major WordPress releases */ _n( '%d release behind', '%d releases behind', $omnis_wp_behind, 'siteward' ), $omnis_wp_behind ) ); ?></span><?php endif; ?></span></div> 175 <div class="omnis-kv"><span class="k"><?php esc_html_e( 'PHP', 'siteward' ); ?></span><span class="v"><?php echo esc_html( $status && $status->php_version ? $status->php_version : '—' ); ?> 176 <?php if ( $omnis_php_eol && $omnis_php_eol['ended'] ) : ?> 177 <span class="bad" title="<?php esc_attr_e( 'This PHP version no longer gets security fixes. Ask the host to move the site to a supported version.', 'siteward' ); ?>">· <?php echo '' !== $omnis_php_eol['date'] ? esc_html( sprintf( /* translators: %s: date */ __( 'unsupported since %s', 'siteward' ), date_i18n( get_option( 'date_format' ), strtotime( $omnis_php_eol['date'] ) ) ) ) : esc_html__( 'unsupported', 'siteward' ); ?></span> 178 <?php elseif ( $omnis_php_eol && $omnis_php_eol['days_left'] <= Omnis_Flags::PHP_EOL_SOON_DAYS ) : ?> 179 <span class="warn" title="<?php esc_attr_e( 'After this date this PHP version gets no more security fixes.', 'siteward' ); ?>">· <?php echo esc_html( sprintf( /* translators: %s: date */ __( 'security support ends %s', 'siteward' ), date_i18n( get_option( 'date_format' ), strtotime( $omnis_php_eol['date'] ) ) ) ); ?></span> 180 <?php endif; ?> 181 </span></div> 172 182 <?php if ( $status && ! empty( $status->server_ip ) ) : ?> 173 183 <?php $omnis_ip_public = filter_var( $status->server_ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ); ?> -
siteward/trunk/includes/class-omnis-flags.php
r3708286 r3780742 27 27 const SITE_HEALTH = 'site_health'; 28 28 const VULNERABLE = 'vulnerable'; 29 const OUTDATED_PHP = 'outdated_php'; 30 const OUTDATED_WP = 'outdated_wp'; 31 32 /** 33 * End of security support for each PHP branch, from php.net/supported-versions. 34 * A branch older than the first entry ended long ago; a branch newer than the 35 * last is not judged until it is added here, so add each new branch when it 36 * ships (8.6 / 9.0 expected late 2026, supported until the end of 2030). 37 */ 38 const PHP_EOL = array( 39 '7.4' => '2022-11-28', 40 '8.0' => '2023-11-26', 41 '8.1' => '2025-12-31', 42 '8.2' => '2026-12-31', 43 '8.3' => '2027-12-31', 44 '8.4' => '2028-12-31', 45 '8.5' => '2029-12-31', 46 ); 47 48 /** How soon before a PHP branch's end the site page starts warning, in days. */ 49 const PHP_EOL_SOON_DAYS = 180; 29 50 30 51 /** … … 48 69 self::FAILING_CRON => array( 'label' => __( 'Cron failing', 'siteward' ), 'severity' => 'warning' ), 49 70 self::SITE_HEALTH => array( 'label' => __( 'Site Health issues', 'siteward' ), 'severity' => 'warning' ), 71 self::OUTDATED_PHP => array( 'label' => __( 'Unsupported PHP', 'siteward' ), 'severity' => 'warning' ), 72 self::OUTDATED_WP => array( 'label' => __( 'Outdated WordPress', 'siteward' ), 'severity' => 'warning' ), 50 73 self::UPDATES_AVAILABLE => array( 'label' => __( 'Updates available', 'siteward' ), 'severity' => 'info' ), 51 74 ); … … 178 201 if ( ! empty( $health['site_health']['critical'] ) ) { 179 202 $flags[] = self::SITE_HEALTH; 203 } 204 } 205 206 // PHP past the end of its security support, and WordPress two or more 207 // major releases behind. Both versions come from the site's last sync. 208 if ( ! empty( $snapshot['php_version'] ) ) { 209 $eol = self::php_eol( (string) $snapshot['php_version'] ); 210 if ( $eol && $eol['ended'] ) { 211 $flags[] = self::OUTDATED_PHP; 212 } 213 } 214 if ( ! empty( $snapshot['wp_version'] ) ) { 215 $latest = self::latest_wp( isset( $updates['core']['latest'] ) ? (string) $updates['core']['latest'] : '' ); 216 if ( self::wp_releases_behind( (string) $snapshot['wp_version'], $latest ) >= 2 ) { 217 $flags[] = self::OUTDATED_WP; 180 218 } 181 219 } … … 220 258 self::VULNERABLE => array( 'label' => __( 'Known vulnerability', 'siteward' ), 'flags' => array( self::VULNERABLE ) ), 221 259 self::SLOW_LOAD => array( 'label' => __( 'Slow', 'siteward' ), 'flags' => array( self::SLOW_LOAD ) ), 260 self::OUTDATED_PHP => array( 'label' => __( 'Unsupported PHP', 'siteward' ), 'flags' => array( self::OUTDATED_PHP ) ), 261 self::OUTDATED_WP => array( 'label' => __( 'Outdated WordPress', 'siteward' ), 'flags' => array( self::OUTDATED_WP ) ), 222 262 'healthy' => array( 'label' => __( 'Healthy', 'siteward' ), 'special' => 'healthy' ), 223 263 ); … … 261 301 } 262 302 return $keys; 303 } 304 305 /** 306 * Where a PHP version stands against its branch's end of security support. 307 * 308 * @param string $version e.g. "8.1.33". 309 * @return array|null { branch, date (Y-m-d, '' if before the table), ended, days_left }, 310 * or null when the version cannot be read or is newer than the table. 311 */ 312 public static function php_eol( $version ) { 313 if ( ! preg_match( '/^(\d+)\.(\d+)/', (string) $version, $m ) ) { 314 return null; 315 } 316 $branch = $m[1] . '.' . $m[2]; 317 $first = array_key_first( self::PHP_EOL ); 318 if ( version_compare( $branch, $first, '<' ) ) { 319 return array( 'branch' => $branch, 'date' => '', 'ended' => true, 'days_left' => 0 ); 320 } 321 if ( ! isset( self::PHP_EOL[ $branch ] ) ) { 322 return null; 323 } 324 $date = self::PHP_EOL[ $branch ]; 325 $end = strtotime( $date . ' 23:59:59 UTC' ); 326 return array( 327 'branch' => $branch, 328 'date' => $date, 329 'ended' => time() > $end, 330 'days_left' => max( 0, (int) floor( ( $end - time() ) / DAY_IN_SECONDS ) ), 331 ); 332 } 333 334 /** 335 * The latest WordPress release, as the highest of: the dashboard's own core 336 * update check, the dashboard's own WordPress, the newest version any managed 337 * site runs, and what this site reported. No single source is enough: the 338 * update check can come back empty, and a site on old PHP is only offered the 339 * newest release its PHP can run, which would hide exactly the sites that matter. 340 * 341 * @param string $reported The latest version the site reported, if any. 342 * @return string Version, or '' when unknown. 343 */ 344 public static function latest_wp( $reported = '' ) { 345 static $known = null; 346 if ( null === $known ) { 347 global $wpdb; 348 $candidates = array( (string) get_bloginfo( 'version' ) ); 349 $core = get_site_transient( 'update_core' ); 350 if ( is_object( $core ) && ! empty( $core->offers ) && is_array( $core->offers ) ) { 351 foreach ( $core->offers as $offer ) { 352 if ( is_object( $offer ) && ! empty( $offer->current ) ) { 353 $candidates[] = (string) $offer->current; 354 } 355 } 356 } 357 $table = Omnis_DB::table( Omnis_DB::STATUS ); 358 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- own table name, once per request (static). 359 $fleet = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT wp_version FROM {$table} WHERE wp_version <> %s", '' ) ); 360 $known = self::max_version( array_merge( $candidates, (array) $fleet ) ); 361 } 362 return self::max_version( array( $known, (string) $reported ) ); 363 } 364 365 /** 366 * The highest of a list of version strings ('' when none is valid). Releases 367 * only: one test site on a beta or RC must not make every site look behind. 368 */ 369 private static function max_version( array $versions ) { 370 $max = ''; 371 foreach ( $versions as $v ) { 372 if ( preg_match( '/^\d+\.\d+(\.\d+)?$/', (string) $v ) && ( '' === $max || version_compare( $v, $max, '>' ) ) ) { 373 $max = (string) $v; 374 } 375 } 376 return $max; 377 } 378 379 /** 380 * How many major WordPress releases (6.9, 7.0, 7.1, ...) a version is behind. 381 * 382 * @param string $version Site's version. 383 * @param string $latest Latest version. 384 * @return int 0 when current, ahead or unknown. 385 */ 386 public static function wp_releases_behind( $version, $latest ) { 387 if ( ! preg_match( '/^(\d+)\.(\d+)/', (string) $version, $a ) || ! preg_match( '/^(\d+)\.(\d+)/', (string) $latest, $b ) ) { 388 return 0; 389 } 390 // WordPress majors run x.0 to x.9, so x * 10 + y counts releases. 391 return max( 0, ( (int) $b[1] * 10 + (int) $b[2] ) - ( (int) $a[1] * 10 + (int) $a[2] ) ); 263 392 } 264 393 … … 311 440 'health' => ! empty( $row->health ) ? json_decode( $row->health, true ) : array(), 312 441 'vulns' => ! empty( $row->vulns ) ? json_decode( $row->vulns, true ) : array(), 442 'php_version' => (string) $row->php_version, 443 'wp_version' => (string) $row->wp_version, 313 444 'load_time_ms' => (int) $row->load_time_ms, 314 445 'is_up' => $site && null !== $site->is_up ? (bool) $site->is_up : null, -
siteward/trunk/includes/class-omnis-settings.php
r3757358 r3780742 40 40 'notify_vulnerable' => 1, // alert on newly discovered vulnerabilities 41 41 'notify_rollback' => 1, // alert when an update was rolled back 42 'vuln_notices' => 1, // show known vulnerabilities on each site's Plugins screen (child 0.9.0+) 42 43 'vuln_interval' => 12, // hours between vulnerability feed checks 43 44 ); -
siteward/trunk/includes/class-omnis-sync.php
r3708286 r3780742 45 45 foreach ( $sites as $s ) { 46 46 $by_id[ (int) $s->id ] = $s; 47 $spec = Omnis_Connection::build_request( $s, 'get_status' );47 $spec = Omnis_Connection::build_request( $s, 'get_status', self::status_params( (int) $s->id ) ); 48 48 if ( is_wp_error( $spec ) ) { 49 49 $errored[ (int) $s->id ] = $spec; … … 70 70 71 71 /** 72 * What a status pull hands the site: its known vulnerabilities, for the 73 * warning rows on its own Plugins screen (child 0.9.0+; older children 74 * ignore it). An empty list with on=false tells the child to clear them. 75 * 76 * @param int $site_id Site ID. 77 * @return array 78 */ 79 private static function status_params( $site_id ) { 80 $on = (bool) Omnis_Settings::get( 'vuln_notices', 1 ) && class_exists( 'Omnis_Vuln' ); 81 return array( 82 'vuln_notices' => array( 83 'on' => $on, 84 'items' => $on ? Omnis_Vuln::notices_for_site( $site_id ) : array(), 85 ), 86 ); 87 } 88 89 /** 72 90 * Sync a single site (on-demand path). 73 91 * … … 81 99 } 82 100 83 $spec = Omnis_Connection::build_request( $site, 'get_status' );101 $spec = Omnis_Connection::build_request( $site, 'get_status', self::status_params( (int) $site_id ) ); 84 102 if ( is_wp_error( $spec ) ) { 85 103 self::process_result( $site, $spec ); -
siteward/trunk/includes/class-omnis-uptime.php
r3757358 r3780742 339 339 // otherwise be dropped from the recomputed flags. 340 340 'vulns' => $row && ! empty( $row->vulns ) ? json_decode( $row->vulns, true ) : array(), 341 'php_version' => $row ? (string) $row->php_version : '', 342 'wp_version' => $row ? (string) $row->wp_version : '', 341 343 'load_time_ms' => $is_up ? (int) $resp_ms : ( $row ? (int) $row->load_time_ms : 0 ), 342 344 'is_up' => $is_up, -
siteward/trunk/includes/class-omnis-vuln.php
r3655630 r3780742 467 467 468 468 /** 469 * What a site's own Plugins screen should warn about: one short entry per 470 * affected plugin, sent to the child with each status sync. Informational 471 * advisories are left out, as they are from the flag. The child shows an 472 * entry only while the plugin is still on the version scanned here. 473 * 474 * @param int $site_id Site ID. 475 * @return array List of { file, version, severity, count, fix, unfixable, cve, title, link }. 476 */ 477 public static function notices_for_site( $site_id ) { 478 $real = array_filter( 479 self::for_site( $site_id ), 480 function ( $f ) { 481 return 'plugin' === ( $f['y'] ?? '' ) && '' !== (string) ( $f['f'] ?? '' ) && empty( $f['e']['n'] ); 482 } 483 ); 484 $out = array(); 485 foreach ( self::group( $real ) as $g ) { 486 // The advisory to name: the worst one, preferring one with a CVE. 487 $rank = array_flip( self::SEVERITIES ); 488 $pick = null; 489 $best = PHP_INT_MAX; 490 foreach ( $g['entries'] as $e ) { 491 $r = isset( $rank[ self::severity( $e ) ] ) ? $rank[ self::severity( $e ) ] : 99; 492 if ( null === $pick || $r < $best || ( $r === $best && empty( $pick['c'] ) && ! empty( $e['c'] ) ) ) { 493 $pick = $e; 494 $best = $r; 495 } 496 } 497 $out[] = array( 498 'file' => (string) $g['file'], 499 'version' => (string) $g['version'], 500 'severity' => (string) $g['worst'], 501 'count' => (int) $g['count'], 502 'fix' => (string) $g['fix'], 503 'unfixable' => ! empty( $g['unfixable'] ), 504 'cve' => isset( $pick['c'] ) ? (string) $pick['c'] : '', 505 'title' => isset( $pick['t'] ) ? (string) $pick['t'] : '', 506 'link' => $pick ? Omnis_Vuln_Feed::link( $pick ) : '', 507 ); 508 } 509 return $out; 510 } 511 512 /** 469 513 * A site's findings, already grouped by software (for the site detail page). 470 514 * -
siteward/trunk/readme.txt
r3769325 r3780742 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 0.1 6.27 Stable tag: 0.17.1 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 87 87 88 88 == Changelog == 89 90 = 0.17.1 = 91 * New: known-vulnerability warnings on each site's own Plugins screen. With each sync the dashboard hands a site its matches, and a row appears under each affected plugin with its rating and the version that fixes it, for people who can update plugins there. On by default; switch it off under Settings, Vulnerability feed (for example if clients log in). Needs Siteward Child 0.9.0 or later on the site. 92 * New: Unsupported PHP flag. A site running a PHP version that no longer gets security fixes (8.1 and older today) is flagged, with its own filter on the Sites page. The end-of-support dates come from php.net and are kept in one list in the plugin, so 8.2 is flagged automatically once its support ends on 31 December 2026. 93 * New: the site page shows when a site's PHP version stops getting security fixes, from six months before the date, and how long ago it stopped for older versions. 94 * New: Outdated WordPress flag, for sites two or more major releases behind the latest WordPress, with its own filter. The site page shows how many releases behind it is. 89 95 90 96 = 0.16.2 = -
siteward/trunk/siteward.php
r3769325 r3780742 4 4 * Plugin URI: https://omniswp.com/features/ 5 5 * Description: Monitor and update many WordPress sites from one lightweight, self-hosted dashboard: uptime monitoring with email alerts, one-click and bulk core/plugin/theme updates, and critical-error detection. 6 * Version: 0.1 6.26 * Version: 0.17.1 7 7 * Requires at least: 5.8 8 8 * Tested up to: 7.1 … … 20 20 defined( 'ABSPATH' ) || exit; 21 21 22 define( 'OMNIS_VERSION', '0.1 6.2' );22 define( 'OMNIS_VERSION', '0.17.1' ); 23 23 define( 'OMNIS_DB_VERSION', '10' ); 24 24 define( 'OMNIS_FILE', __FILE__ );
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)