rules = $rules; $this->service = $service; $this->mailer = $mailer; $this->vars = $vars; $this->customers = $customers; $this->invoices = $invoices; $this->domains = $domains; $this->services = $services; $this->settings = $settings; } /** * {@inheritdoc} */ public function register(): void { add_action( 'init', array( $this, 'schedule' ) ); add_action( self::HOOK, array( $this, 'run' ) ); add_filter( 'hostkhata_cron_hooks', array( $this, 'cron_hooks' ) ); } /** * Schedules the hourly event once. * * @since 0.1.0 * * @return void */ public function schedule(): void { if ( ! wp_next_scheduled( self::HOOK ) ) { wp_schedule_event( time() + 5 * MINUTE_IN_SECONDS, 'hourly', self::HOOK ); } } /** * Registers the hook for unscheduling on deactivation. * * @since 0.1.0 * * @param string[] $hooks Hooks. * @return string[] */ public function cron_hooks( array $hooks ): array { $hooks[] = self::HOOK; return $hooks; } /** * Queues today's reminders. * * @since 0.1.0 * * @param bool $force Ignore the quiet hours (used by "run now" and tests). * @param string|null $today Day to run for, `YYYY-MM-DD`; defaults to today (UTC). * @return array{queued: int, skipped: int} */ public function run( $force = false, $today = null ): array { $result = array( 'queued' => 0, 'skipped' => 0, ); if ( ! $this->settings->get( 'reminders_enabled' ) ) { return $result; } if ( true !== $force && (int) current_time( 'G' ) < (int) $this->settings->get( 'reminder_send_hour' ) ) { return $result; } $this->service->ensure_defaults(); $today = is_string( $today ) && Date::is_valid_date( $today ) ? $today : Date::today_gmt(); foreach ( $this->rules->all( 'active' ) as $rule ) { $day = Date::add_days( $today, -$rule['days_offset'] ); foreach ( $this->targets( $rule['rule_type'], $day ) as $target_id ) { $this->remind( $rule, $target_id, $today, $result ); } } /** This action is documented in src/Domain/Billing/BillingRunner.php. */ do_action( 'hostkhata_cron_ran', 'reminders', $result ); return $result; } /** * Ids of the items that match a rule on a day. * * @param string $type Rule type. * @param string $day The date the items must have. * @return int[] */ private function targets( string $type, string $day ): array { switch ( $type ) { case ReminderService::TYPE_INVOICE: return $this->invoices->ids_due_on( $day ); case ReminderService::TYPE_DOMAIN: return $this->domains->ids_expiring_on( $day ); case ReminderService::TYPE_SERVICE: return $this->services->ids_due_on( $day ); default: return array(); } } /** * Sends one reminder, unless the customer should not get it or it was already sent. * * @param array $rule Rule. * @param int $id Target id. * @param string $today Today. * @param array $result Counters, updated by this method. * @return void */ private function remind( array $rule, int $id, string $today, array &$result ): void { $item = $this->load( $rule['rule_type'], $id ); if ( null === $item ) { return; } $customer = $this->customers->find( (int) $item['customer_id'] ); if ( null === $customer || ! empty( $customer['reminders_muted'] ) || in_array( $customer['status'], array( CustomerStatus::BLOCKED, CustomerStatus::ANONYMISED ), true ) || empty( $customer['user_email'] ) ) { ++$result['skipped']; return; } if ( ! $this->rules->claim( $rule['id'], $rule['rule_type'], $id, $today ) ) { return; } $vars = array_merge( $this->vars->base( $customer ), $this->item_vars( $rule['rule_type'], $item ), array( 'days' => (string) abs( $rule['days_offset'] ) ) ); $name = trim( $customer['first_name'] . ' ' . $customer['last_name'] ); $type = ReminderService::TYPE_INVOICE === $rule['rule_type'] ? 'invoice' : ( ReminderService::TYPE_DOMAIN === $rule['rule_type'] ? 'domain' : 'service' ); if ( $this->mailer->queue( $rule['template_key'], (string) $customer['user_email'], $name, $vars, $type, $id ) > 0 ) { ++$result['queued']; } if ( $rule['send_admin_copy'] || $this->settings->get( 'reminder_admin_copy' ) ) { $this->mailer->queue_to_admin( $rule['template_key'], $vars, $type, $id ); } } /** * Loads the item behind a rule type. * * @param string $type Rule type. * @param int $id Item id. * @return array|null */ private function load( string $type, int $id ): ?array { switch ( $type ) { case ReminderService::TYPE_INVOICE: return $this->invoices->find( $id ); case ReminderService::TYPE_DOMAIN: return $this->domains->find( $id ); case ReminderService::TYPE_SERVICE: return $this->services->find( $id ); default: return null; } } /** * Merge tag values for an item. * * @param string $type Rule type. * @param array $item Item. * @return array */ private function item_vars( string $type, array $item ): array { switch ( $type ) { case ReminderService::TYPE_INVOICE: return $this->vars->invoice( $item ); case ReminderService::TYPE_DOMAIN: return array( 'domain' => (string) $item['domain_name'], 'expires_on' => $this->vars->date( (string) $item['expires_on'] ), ); default: return array( 'domain' => (string) $item['domain'], 'due_date' => $this->vars->date( (string) $item['next_due_date'] ), 'amount' => $this->vars->money( (int) $item['recurring_amount'] ), ); } } }