rules = $rules; } /** * Rule types and the email templates each may use. * * @since 0.1.0 * * @return array */ public static function types(): array { return array( self::TYPE_INVOICE => array( 'invoice_reminder', 'invoice_overdue' ), self::TYPE_DOMAIN => array( 'domain_reminder' ), self::TYPE_SERVICE => array( 'service_reminder' ), ); } /** * Creates the default rules once, on a site that has none. * * @since 0.1.0 * * @return void */ public function ensure_defaults(): void { if ( get_option( self::SEEDED_OPTION ) ) { return; } // Set first so two requests at once do not both seed. if ( ! add_option( self::SEEDED_OPTION, '1', '', 'no' ) ) { return; } if ( $this->rules->count() > 0 ) { return; } $defaults = array( array( __( 'Invoice due in 3 days', 'hostkhata' ), self::TYPE_INVOICE, -3, 'invoice_reminder' ), array( __( 'Invoice due today', 'hostkhata' ), self::TYPE_INVOICE, 0, 'invoice_reminder' ), array( __( 'Invoice 3 days overdue', 'hostkhata' ), self::TYPE_INVOICE, 3, 'invoice_overdue' ), array( __( 'Invoice 7 days overdue', 'hostkhata' ), self::TYPE_INVOICE, 7, 'invoice_overdue' ), array( __( 'Domain expires in 30 days', 'hostkhata' ), self::TYPE_DOMAIN, -30, 'domain_reminder' ), array( __( 'Domain expires in 7 days', 'hostkhata' ), self::TYPE_DOMAIN, -7, 'domain_reminder' ), array( __( 'Domain expires tomorrow', 'hostkhata' ), self::TYPE_DOMAIN, -1, 'domain_reminder' ), array( __( 'Hosting renews in 7 days', 'hostkhata' ), self::TYPE_SERVICE, -7, 'service_reminder' ), ); foreach ( $defaults as $index => $rule ) { $this->rules->insert( array( 'name' => $rule[0], 'rule_type' => $rule[1], 'days_offset' => $rule[2], 'template_key' => $rule[3], 'status' => 'active', 'sort_order' => $index, ) ); } } /** * Creates a rule. * * @since 0.1.0 * * @param array $raw Unslashed input. * @return array|WP_Error */ public function create( array $raw ) { $clean = $this->clean( $raw, true ); if ( $clean['errors'] ) { return Errors::invalid( $clean['errors'] ); } $id = $this->rules->insert( $clean['data'] ); return 0 === $id ? Errors::failed( __( 'The rule could not be saved.', 'hostkhata' ) ) : $this->rules->find( $id ); } /** * Updates a rule. * * @since 0.1.0 * * @param int $id Rule id. * @param array $raw Unslashed input. * @return array|WP_Error */ public function update( int $id, array $raw ) { $rule = $this->rules->find( $id ); if ( null === $rule ) { return Errors::not_found( __( 'Reminder rule not found.', 'hostkhata' ) ); } $clean = $this->clean( array_merge( $rule, $raw ), false ); if ( $clean['errors'] ) { return Errors::invalid( $clean['errors'] ); } $this->rules->update( $id, $clean['data'] ); return $this->rules->find( $id ); } /** * Deletes a rule. * * @since 0.1.0 * * @param int $id Rule id. * @return true|WP_Error */ public function delete( int $id ) { if ( null === $this->rules->find( $id ) ) { return Errors::not_found( __( 'Reminder rule not found.', 'hostkhata' ) ); } $this->rules->delete( $id ); return true; } /** * Cleans and validates a rule. * * @param array $raw Input. * @param bool $creating Whether the rule is new. * @return array{data: array, errors: array} */ private function clean( array $raw, bool $creating ): array { $errors = array(); $types = self::types(); $name = Sanitizer::text( $raw['name'] ?? '' ); $type = Sanitizer::pick( $raw, 'rule_type', array_keys( $types ), $creating ? null : '' ); if ( '' === $name ) { $errors['name'] = __( 'Give the rule a name.', 'hostkhata' ); } elseif ( mb_strlen( $name ) > 190 ) { $errors['name'] = __( 'Use at most 190 characters.', 'hostkhata' ); } if ( null === $type || '' === $type ) { $errors['rule_type'] = __( 'Choose what the reminder is about.', 'hostkhata' ); $type = ''; } $offset = $raw['days_offset'] ?? null; if ( is_string( $offset ) && preg_match( '/^-?\d{1,3}$/', $offset ) ) { $offset = (int) $offset; } if ( ! is_int( $offset ) || $offset < -365 || $offset > 365 ) { $errors['days_offset'] = __( 'Enter a number of days between -365 and 365 (negative means before).', 'hostkhata' ); $offset = 0; } $template = Sanitizer::key( $raw['template_key'] ?? '' ); $allowed = '' !== $type ? $types[ $type ] : array(); if ( '' === $template && $allowed ) { $template = $offset > 0 ? end( $allowed ) : reset( $allowed ); } if ( $allowed && ! in_array( $template, $allowed, true ) ) { $errors['template_key'] = __( 'This email cannot be used for this kind of reminder.', 'hostkhata' ); } $channel = Sanitizer::pick( $raw, 'channel', array( 'email' ), 'email' ); if ( null === $channel ) { $errors['channel'] = __( 'Choose email.', 'hostkhata' ); $channel = 'email'; } $status = Sanitizer::pick( $raw, 'status', array( 'active', 'disabled' ), 'active' ); if ( null === $status ) { $errors['status'] = __( 'Choose active or disabled.', 'hostkhata' ); $status = 'active'; } return array( 'data' => array( 'name' => $name, 'rule_type' => $type, 'days_offset' => $offset, 'channel' => $channel, 'template_key' => $template, 'send_admin_copy' => Sanitizer::boolean( $raw['send_admin_copy'] ?? false ) ? 1 : 0, 'status' => (string) $status, 'sort_order' => Sanitizer::absint( $raw['sort_order'] ?? 0 ), ), 'errors' => $errors, ); } }