sync(); add_action( 'rest_api_init', [$this, 'registerRoutes'] ); } /** * Runs once a day. Alerts are evaluated every time; the digest only when its * interval has elapsed. */ public function run() { Notifications\Alerts::run(); if ( class_exists( '\\DaftPlug\\Rankify\\Features\\Notifications\\CrawlerWatch' ) ) { Notifications\CrawlerWatch::run(); } if ( $this->digestDue() ) { if ( Notifications\Digest::send() ) { update_option( self::LAST_DIGEST, time(), false ); } } } protected function digestDue() { if ( 'on' !== Utils::getSetting( 'notifyDigest' ) ) { return false; } $interval = ( 'monthly' === Utils::getSetting( 'notifyDigestFrequency' ) ? 30 * DAY_IN_SECONDS : 7 * DAY_IN_SECONDS ); $last = (int) get_option( self::LAST_DIGEST, 0 ); // A site that has never had one waits a full interval rather than sending // the moment the setting is switched on — the first digest should cover a // real period, not the four minutes since someone flipped the toggle. if ( 0 === $last ) { update_option( self::LAST_DIGEST, time(), false ); return false; } return time() - $last >= $interval; } /** * The cron exists only while something is actually subscribed to it. * * There is no master switch — each notification is its own toggle — so the * schedule follows whether any of them is on, and disappears again when the * last one is turned off. */ public function sync() { $enabled = false; foreach ( [ 'notifyDigest', 'notifyCritical', 'notify404', 'notifyCrawlerDrop' ] as $setting ) { if ( 'on' === Utils::getSetting( $setting ) ) { $enabled = true; break; } } $scheduled = wp_next_scheduled( self::HOOK ); if ( $enabled && !$scheduled ) { wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', self::HOOK ); return; } if ( !$enabled && $scheduled ) { wp_clear_scheduled_hook( self::HOOK ); } } public static function unschedule() { wp_clear_scheduled_hook( self::HOOK ); } public function registerRoutes() { \DaftPlug\Rankify\Helpers\Utils::registerRestRoute( '/notifications/test', [ 'methods' => 'POST', 'permission_callback' => function () { return current_user_can( 'manage_options' ); }, 'callback' => [$this, 'handleTest'], ] ); } /** * Sends the digest on demand. * * Mail is the one part of this that cannot be verified by reading the code — * SMTP is configured outside the plugin and fails silently more often than * anything else on a WordPress site. Being able to prove delivery in one * click is worth the endpoint. */ public function handleTest( \WP_REST_Request $request ) { if ( !wp_verify_nonce( $request->get_header( 'X-WP-Nonce' ), 'wp_rest' ) ) { return new \WP_Error('invalid_nonce', __( 'Invalid nonce', 'crownify' ), [ 'status' => 403, ]); } $recipient = Notifications\Mailer::recipient(); if ( !is_email( $recipient ) ) { return new \WP_REST_Response([ 'sent' => false, 'message' => __( 'No valid recipient address is set.', 'crownify' ), ], 200); } $failure = ''; $capture = function ( $error ) use(&$failure) { $failure = $error->get_error_message(); }; add_action( 'wp_mail_failed', $capture ); $sent = Notifications\Digest::send( true ); remove_action( 'wp_mail_failed', $capture ); if ( $sent && '' === $failure ) { /* translators: %s: email address. */ $sentMessage = __( 'Sent to %s.', 'crownify' ); return new \WP_REST_Response([ 'sent' => true, 'message' => sprintf( $sentMessage, $recipient ), ], 200); } return new \WP_REST_Response([ 'sent' => false, 'message' => ( '' !== $failure ? $failure : __( 'WordPress could not send the email. Check this site\'s mail configuration.', 'crownify' ) ), ], 200); } }