mailer = $mailer; $this->runs = $runs; $this->deliveries = $deliveries; $this->log = $log; $this->recipients = $recipients; $this->locks = $locks; } /** * Sends a run's email to every recipient that is due. * * @param Profile $site Site. * @param array $run Run. * @param int $now Unix time. * @return array Run afterwards. */ public function send_pending( Profile $site, array $run, $now ) { $rows = $this->deliveries->for_run( $run['id'] ); $due = array_filter( $rows, static function ( $r ) use ( $now ) { return in_array( $r['status'], array( DeliveryRepository::PENDING, DeliveryRepository::RETRYING ), true ) && $r['next_attempt'] <= $now; } ); $message = null; $errors = array(); try { foreach ( $due as $row ) { $this->locks->renew_lock( $site->id ); $claimed = $this->deliveries->claim( $row['id'], $now ); if ( ! $claimed ) { continue; // Another process has it. } if ( null === $message ) { $message = $this->mailer->compose( $site, $run, RunRepository::KIND_TEST === $run['kind'] ); } $this->locks->renew_lock( $site->id ); $result = $this->mailer->deliver( $message, $row['recipient'] ); if ( is_wp_error( $result ) ) { $data = $result->get_error_data(); $errors[] = $result->get_error_message(); $this->deliveries->record( $row, false, $result->get_error_message(), $now, ! empty( $data['permanent'] ) || RunRepository::KIND_TEST === $run['kind'] ? false : null, $claimed ); } else { $this->deliveries->record( $row, true, '', $now, null, $claimed ); } } } finally { if ( null !== $message ) { $this->mailer->cleanup( $message ); } } $meta = (array) $run['meta']; if ( $message ) { $meta['pdf'] = empty( $site->delivery['attach_pdf'] ) ? 'off' : ( $message['attachments'] ? 'attached' : 'failed' ); if ( '' !== $message['pdf_error'] ) { $meta['pdf_error'] = $message['pdf_error']; } $meta['link'] = '' !== $message['share_url']; } $tally = DeliveryRepository::tally( $this->deliveries->for_run( $run['id'] ) ); $status = DeliveryRepository::run_status( $tally ); $this->runs->update( $run['id'], array( 'status' => $status, 'sent_at' => $tally[ DeliveryRepository::ACCEPTED ] ? ( $run['sent_at'] ? $run['sent_at'] : $now ) : $run['sent_at'], 'meta' => $meta, ) ); if ( RunRepository::KIND_SCHEDULED === $run['kind'] && $message ) { $fields = array( 'last_count' => $tally[ DeliveryRepository::ACCEPTED ], 'last_error' => $errors ? sprintf( /* translators: 1: number of failed recipients, 2: total recipients, 3: error. */ __( '%1$d of %2$d recipients failed: %3$s', 'turtlereport' ), $tally[ DeliveryRepository::FAILED ] + $tally[ DeliveryRepository::RETRYING ], array_sum( $tally ), $errors[0] ) : '', ); if ( $tally[ DeliveryRepository::ACCEPTED ] ) { $fields['last_sent'] = $now; } $this->log->update( $site->id, $fields ); } return $this->runs->find( $run['id'] ); } /** * Fallback when the history tables can't be created: sends to everyone once * and records how many were really accepted. * * @param Profile $site Site. * @param int $now Unix time. * @param int $slot Slot. * @return void */ public function send_without_history( Profile $site, $now, $slot ) { $to = $this->recipients->addresses( $site ); $period = Schedule::period( $site->email, wp_date( 'Y-m-d', $slot ) ); $prep = $this->mailer->builder()->prepare( $site, $period ); $run = ReportMailer::virtual_run( $site, $period, $prep ); $run['kind'] = RunRepository::KIND_SCHEDULED; // Without history there is no review queue or hold to come back to: never send instead. if ( Options::MODE_REVIEW === $site->delivery['mode'] ) { $this->log->update( $site->id, array( 'last_error' => __( 'Not sent: this report needs review, but the delivery history tables could not be set up. Fix the database setup shown on the Email deliveries screen.', 'turtlereport' ) ) ); return; } if ( Readiness::BLOCKED === $prep['readiness']['level'] && Options::INCOMPLETE_HOLD === $site->delivery['incomplete'] ) { $this->log->update( $site->id, array( 'last_error' => __( 'Held: the report was incomplete, and the delivery history tables needed to review it could not be set up.', 'turtlereport' ) ) ); return; } if ( ! $to ) { $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; } $message = $this->mailer->compose( $site, $run ); $ok = 0; $first = ''; try { foreach ( $to as $address ) { $this->locks->renew_lock( $site->id ); $r = $this->mailer->deliver( $message, $address ); if ( is_wp_error( $r ) ) { $first = '' !== $first ? $first : $r->get_error_message(); } else { ++$ok; } } } finally { $this->mailer->cleanup( $message ); } $count = count( $to ); $fields = array( 'last_count' => $ok, /* translators: 1: failed count, 2: total, 3: error. */ 'last_error' => $ok === $count ? '' : sprintf( __( '%1$d of %2$d recipients failed: %3$s', 'turtlereport' ), $count - $ok, $count, $first ), ); if ( $ok ) { $fields['last_sent'] = $now; } $this->log->update( $site->id, $fields ); } }