sites = $sites; $this->store = $store; $this->data = $data; $this->settings = $settings; } /** * Whether the monitor is switched on in Settings. * * @return bool */ public function enabled() { return (bool) $this->settings->get( 'monitor_enabled' ); } /** * The alert store. * * @return AlertStore */ public function store() { return $this->store; } /** * Whether a site has anything the monitor can check. * * @param Profile $site Site. * @return bool */ public static function checkable( Profile $site ) { return ( $site->wants( Profile::SECTION_SEARCH ) && '' !== (string) $site->gsc_site ) || ( ( $site->wants( Profile::SECTION_CONVERSIONS ) || $site->wants( Profile::SECTION_TRAFFIC ) ) && '' !== (string) $site->ga_property ); } /** * Checks one site now. * * @param Profile $site Site. * @param int $now Timestamp. * @return array The site's record. */ public function check( Profile $site, $now = 0 ) { $period = Periods::last_days( 28 ); $detector = new Detector( $this->data, Rules::get( (string) $this->settings->get( 'monitor_sensitivity' ) ) ); try { $result = $detector->detect( $site, $period, $this->store->open_ids( $site->id ) ); } catch ( \Throwable $e ) { // A bug or a removed report costs this site's check, not everyone's. $result = array( 'findings' => array(), 'errors' => array( Detector::SEARCH => __( 'This client couldn\'t be checked because of an unexpected error.', 'turtlereport' ), Detector::ANALYTICS => __( 'This client couldn\'t be checked because of an unexpected error.', 'turtlereport' ), ), 'sources' => array(), ); } return $this->store->reconcile( $site->id, $result, $period, $now ); } /** * Checks every site (the daily background job), longest-unchecked first. * * Stops after a time budget so a big agency doesn't run WP-Cron past its * limit; sites left over are checked first the next day, or when someone * opens the Client Monitor. * * @return int Sites checked. */ public function check_all() { if ( ! $this->enabled() ) { return 0; } $sites = array_values( $this->sites->all() ); $this->store->keep_only( array_map( static function ( $s ) { return $s->id; }, $sites ) ); $checked = array(); foreach ( $sites as $site ) { $checked[ $site->id ] = $this->store->site( $site->id )['checked']; } usort( $sites, static function ( $a, $b ) use ( $checked ) { return $checked[ $a->id ] <=> $checked[ $b->id ]; } ); /** * Filters how many seconds the daily Client Monitor check may run. * * @param int $seconds Default 150. */ $budget = (int) apply_filters( 'turtlereport_attention_time_budget', 150 ); $start = microtime( true ); $n = 0; foreach ( $sites as $site ) { if ( ! self::checkable( $site ) ) { continue; } if ( $n && microtime( true ) - $start > $budget ) { break; } $this->check( $site ); ++$n; } return $n; } /** * Everything the Client Monitor screen shows. * * @param Profile[] $sites Sites to include (all, for administrators). * @return array{alerts:array[],sites:array[],stale:string[],checked:int} */ public function payload( array $sites ) { $alerts = array(); $list = array(); $stale = array(); $checked = 0; $now = time(); foreach ( $sites as $site ) { if ( ! self::checkable( $site ) ) { continue; } $rec = $this->store->site( $site->id ); if ( $rec['checked'] < $now - self::STALE_AFTER ) { $stale[] = $site->id; } $checked = max( $checked, $rec['checked'] ); $list[ $site->id ] = array( 'id' => $site->id, 'name' => $site->display_name(), 'checked' => $rec['checked'], 'errors' => array_values( array_unique( array_map( 'strval', $rec['errors'] ) ) ), ); foreach ( $rec['alerts'] as $alert ) { $alerts[] = Presenter::present( $alert, $site ); } } $order = array( AlertStore::ACTIVE => 0, AlertStore::SNOOZED => 1, AlertStore::HANDLED => 2, AlertStore::RESOLVED => 3, ); usort( $alerts, static function ( $a, $b ) use ( $order ) { $state = $order[ $a['state'] ] <=> $order[ $b['state'] ]; if ( 0 !== $state ) { return $state; } $severity = ( 'high' === $b['severity'] ) <=> ( 'high' === $a['severity'] ); return 0 !== $severity ? $severity : strnatcasecmp( $a['siteName'], $b['siteName'] ); } ); return array( 'alerts' => $alerts, 'sites' => array_values( $list ), 'stale' => $stale, 'checked' => $checked, ); } }