sites = $sites; $this->mailer = $mailer; $this->log = $log; $this->recipients = $recipients; $this->runs = $runs; $this->deliveries = $deliveries; $this->notifier = $notifier; $this->sender = $sender; } /** * Moves a run forward: build and check it, wait for review, hold it, or send. * * Safe to call again at any point; it carries on where the run is. * * @param int $run_id Run ID. * @param int $now Unix time. * @param array $opts force (send even if blocked), approved (a reviewer approved it), rebuild (fetch the numbers again). * @return array|null The run afterwards. */ public function process( $run_id, $now = null, array $opts = array() ) { $now = null === $now ? time() : (int) $now; $opts = array_merge( array( 'force' => false, 'approved' => false, 'rebuild' => false, ), $opts ); $run = $this->runs->find( $run_id ); if ( ! $run || in_array( $run['status'], array( RunRepository::SENT, RunRepository::CANCELLED ), true ) ) { return $run; } $site = $this->sites->find( $run['site_id'] ); if ( ! $site ) { $this->runs->update( $run['id'], array( 'status' => RunRepository::CANCELLED ) ); return $this->runs->find( $run['id'] ); } $meta = (array) $run['meta']; try { // Build (or rebuild) the numbers. Once recipients exist, they're never rebuilt, so a retry sends what the others got. $started = (bool) $this->deliveries->for_run( $run['id'] ); // An approved run keeps the numbers the reviewer saw. $refresh = ! $opts['approved'] && ( $opts['rebuild'] || in_array( $run['status'], array( RunRepository::HELD, RunRepository::PREPARING ), true ) ); if ( ! $started && ( null === $run['snapshot'] || $refresh ) ) { $prep = $this->mailer->builder()->prepare( $site, RunRepository::period( $run ), null, wp_date( 'Y-m-d', $now ) ); $fields = array( 'snapshot' => array( 'data' => $prep['data'], 'full' => $prep['full'], ), 'readiness' => $prep['readiness']['level'], 'checks' => $prep['readiness'], 'attempts' => (int) $run['attempts'] + 1, ); // Reviewer edits win over fresh automatic insights. if ( empty( $run['reviewed_at'] ) && empty( $meta['insights_edited'] ) ) { $fields['insights'] = $prep['insights']; } $this->runs->update( $run['id'], $fields ); $run = $this->runs->find( $run['id'] ); } if ( ! $started && ! $opts['approved'] && empty( $run['reviewed_at'] ) && Options::MODE_REVIEW === $site->delivery['mode'] && RunRepository::KIND_SCHEDULED === $run['kind'] ) { $this->runs->update( $run['id'], array( 'status' => RunRepository::REVIEW, 'next_check' => 0, ) ); $run = $this->runs->find( $run['id'] ); $this->notify( $site, $run, 'review' ); return $run; } $blocked = Readiness::BLOCKED === $run['readiness']; if ( ! $started && $blocked && ! $opts['force'] && Options::INCOMPLETE_HOLD === $site->delivery['incomplete'] ) { $temporary = ! empty( $run['checks']['temporary'] ); $again = $temporary && (int) $run['attempts'] < self::HOLD_CHECKS; $this->runs->update( $run['id'], array( 'status' => RunRepository::HELD, 'next_check' => $again ? $now + HOUR_IN_SECONDS : 0, ) ); $run = $this->runs->find( $run['id'] ); if ( ! $again ) { $this->notify( $site, $run, 'held' ); $this->log->update( $site->id, array( 'last_error' => __( 'Held: the report was incomplete. Review it on the Email deliveries screen.', 'turtlereport' ) ) ); } return $run; } if ( ! $started ) { $to = $this->recipients->addresses( $site ); if ( ! $to ) { $this->runs->update( $run['id'], array( 'status' => RunRepository::FAILED, 'meta' => array_merge( $meta, array( 'error' => __( 'Nobody to send to: no one can view this site yet and no other people or addresses are chosen.', 'turtlereport' ) ) ), ) ); $this->log->update( $site->id, array( 'last_error' => __( 'Nobody to send to: no one can view this site yet and no other people or addresses are chosen.', 'turtlereport' ), 'last_count' => 0, ) ); return $this->runs->find( $run['id'] ); } $this->deliveries->ensure( $run['id'], $site->id, $to ); } $this->runs->transition( $run['id'], array( RunRepository::PREPARING, RunRepository::HELD, RunRepository::REVIEW, RunRepository::RETRYING, RunRepository::PARTIAL, RunRepository::FAILED, RunRepository::SENDING ), RunRepository::SENDING ); return $this->sender->send_pending( $site, $this->runs->find( $run['id'] ), $now ); } catch ( \Throwable $e ) { // A bug or a fatal problem in one report must not stop the queue. $this->runs->update( $run['id'], array( 'meta' => array_merge( $meta, array( 'error' => ErrorText::from( $e, 500 ) ) ), ) ); /* translators: %s: error message. */ $this->log->update( $site->id, array( 'last_error' => sprintf( __( 'The report couldn\'t be prepared: %s', 'turtlereport' ), ErrorText::from( $e ) ) ) ); return $this->runs->find( $run['id'] ); } } /** * Tells administrators once per run that it needs them. * * @param Profile $site Site. * @param array $run Run. * @param string $why review or held. * @return void */ private function notify( Profile $site, array $run, $why ) { $meta = (array) $run['meta']; $flag = 'notified_' . $why; if ( ! empty( $meta[ $flag ] ) || ! $this->notifier ) { return; } $this->notifier->notify( $site, $run, $why ); $meta[ $flag ] = time(); $this->runs->update( $run['id'], array( 'meta' => $meta ) ); } }