sites = $sites; $this->runs = $runs; $this->deliveries = $deliveries; } /** * {@inheritDoc} * * @return string */ public function menu_title() { return __( 'Email deliveries', 'turtlereport' ); } /** * {@inheritDoc} * * @return string */ public function capability() { return Capabilities::MANAGE; } /** * Link that opens a run's PDF in the browser. * * @param int $run_id Run ID. * @return string */ public static function pdf_url( $run_id ) { return wp_nonce_url( add_query_arg( array( 'action' => 'turtlereport_view_pdf', 'run' => (int) $run_id, ), admin_url( 'admin-post.php' ) ), Actions\FormActions::nonce_action( 'view_pdf' ) ); } /** * Scheduled reports that need an administrator: waiting for review, held, * failed or partly sent. * * @return int */ public function attention() { if ( ! Schema::ready() ) { return 0; } return $this->runs->count( array( 'status' => array( RunRepository::REVIEW, RunRepository::HELD, RunRepository::FAILED, RunRepository::PARTIAL ), 'kind' => RunRepository::KIND_SCHEDULED, ) ); } /** * {@inheritDoc} * * @return void */ public function render() { // A manager opening this screen retries setup at once instead of waiting out the back-off. if ( ! Schema::ready() && ! Schema::install() ) { View::render( 'deliveries/list', array( 'missing_tables' => true ) ); return; } // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only screen routing and filters. $run_id = isset( $_GET['run'] ) ? absint( $_GET['run'] ) : 0; if ( $run_id ) { $this->render_run( $run_id ); return; } $site_id = isset( $_GET['site'] ) ? sanitize_key( wp_unslash( $_GET['site'] ) ) : ''; $status = isset( $_GET['status'] ) ? sanitize_key( wp_unslash( $_GET['status'] ) ) : ''; $kind = isset( $_GET['kind'] ) ? sanitize_key( wp_unslash( $_GET['kind'] ) ) : ''; $paged = isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1; // phpcs:enable $sites = $this->sites->all(); $filter = array( 'site_id' => isset( $sites[ $site_id ] ) ? $site_id : '', 'status' => in_array( $status, self::statuses(), true ) ? self::status_filter( $status ) : '', 'kind' => in_array( $kind, array( RunRepository::KIND_SCHEDULED, RunRepository::KIND_MANUAL, RunRepository::KIND_TEST ), true ) ? $kind : '', ); $total = $this->runs->count( $filter ); $rows = array(); foreach ( $this->runs->query( array_merge( $filter, array( 'limit' => self::PER_PAGE, 'offset' => ( $paged - 1 ) * self::PER_PAGE, ) ) ) as $run ) { $rows[] = $this->row( $run, $sites ); } View::render( 'deliveries/list', array( 'rows' => $rows, 'sites' => $sites, 'filter' => array( 'site' => $filter['site_id'], 'status' => in_array( $status, self::statuses(), true ) ? $status : '', 'kind' => $filter['kind'], ), 'total' => $total, 'paged' => $paged, 'pages' => (int) ceil( $total / self::PER_PAGE ), 'attention' => array( 'review' => $this->runs->count( array( 'status' => RunRepository::REVIEW ) ), 'held' => $this->runs->count( array( 'status' => RunRepository::HELD ) ), 'failed' => $this->runs->count( array( 'status' => array( RunRepository::FAILED, RunRepository::PARTIAL, RunRepository::RETRYING ), 'kind' => RunRepository::KIND_SCHEDULED, ) ), ), 'cron' => SettingsPage::cron_status(), ) ); } /** * Status filters offered on the list. * * @return string[] */ public static function statuses() { return array( 'attention', RunRepository::REVIEW, RunRepository::HELD, RunRepository::SENT, 'problems', RunRepository::CANCELLED ); } /** * Statuses behind a filter. * * @param string $filter Filter. * @return string[] */ private static function status_filter( $filter ) { switch ( $filter ) { case 'attention': return array( RunRepository::REVIEW, RunRepository::HELD, RunRepository::FAILED, RunRepository::PARTIAL, RunRepository::RETRYING ); case 'problems': return array( RunRepository::FAILED, RunRepository::PARTIAL, RunRepository::RETRYING ); default: return array( $filter ); } } /** * A list row. * * @param array $run Run. * @param array $sites Sites keyed by ID. * @return array */ private function row( array $run, array $sites ) { $site = isset( $sites[ $run['site_id'] ] ) ? $sites[ $run['site_id'] ] : null; $tally = DeliveryRepository::tally( $this->deliveries->for_run( $run['id'] ) ); return array( 'run' => $run, 'site' => $site, 'label' => ReportBuilder::label( RunRepository::period( $run ), isset( $run['meta']['frequency'] ) ? (string) $run['meta']['frequency'] : '' ), 'tally' => $tally, 'url' => self::url( array( 'run' => $run['id'] ) ), // Being prepared or sent right now: can't be deleted (RunRepository::delete()). 'busy' => RunRepository::SENDING === $run['status'] || ( RunRepository::PREPARING === $run['status'] && (int) $run['updated'] > time() - 15 * MINUTE_IN_SECONDS ), ); } /** * One report: checks, highlights, recipients and actions. * * @param int $run_id Run ID. * @return void */ private function render_run( $run_id ) { $run = $this->runs->find( $run_id ); $site = $run ? $this->sites->find( $run['site_id'] ) : null; $rows = $run ? $this->deliveries->for_run( $run['id'] ) : array(); View::render( 'deliveries/run', array( 'run' => $run, 'site' => $site, 'rows' => $rows, 'tally' => DeliveryRepository::tally( $rows ), 'label' => $run ? ReportBuilder::label( RunRepository::period( $run ), isset( $run['meta']['frequency'] ) ? (string) $run['meta']['frequency'] : '' ) : '', 'report_url' => $run && $site ? ReportBuilder::report_url( $site, RunRepository::period( $run ), isset( $run['meta']['compare'] ) ? (string) $run['meta']['compare'] : ReportBuilder::compare_of( RunRepository::period( $run ) ) ) : '', 'back' => self::url(), 'editable' => $run && ! $rows && in_array( $run['status'], array( RunRepository::REVIEW, RunRepository::HELD, RunRepository::PREPARING ), true ), ) ); } /** * {@inheritDoc} * * @return void */ public function enqueue() { $this->enqueue_admin_style(); AdminAssets::enqueue( 'admin', 'turtlereport-admin' ); } }