services = $services; $this->customers = $customers; $this->products = $products; $this->log = $log; } /** * Creates a service record. * * @since 0.1.0 * * @param array $raw Unslashed input. * @param bool $dry_run When true, everything is checked but nothing is saved. * @return array|WP_Error The new record; with a dry run, the cleaned data. */ public function create( array $raw, bool $dry_run = false ) { $clean = ServiceInput::clean( $raw, true ); $errors = $clean['errors'] + $this->relation_errors( $clean['data'] ); if ( $errors ) { return Errors::invalid( $errors ); } if ( $dry_run ) { return $clean['data']; } $id = $this->services->insert( $clean['data'] ); if ( $id < 1 ) { return Errors::failed( __( 'The hosting record could not be saved.', 'hostkhata' ) ); } $this->log->record( 'service.created', $clean['data']['customer_id'], 'service', $id, __( 'Hosting record created.', 'hostkhata' ) ); return $this->services->find( $id ); } /** * Updates a service record. * * @since 0.1.0 * * @param int $id Service id. * @param array $raw Unslashed input. * @return array|WP_Error */ public function update( int $id, array $raw ) { $service = $this->services->find( $id ); if ( null === $service ) { return Errors::not_found( __( 'Hosting record not found.', 'hostkhata' ) ); } // The owner of a record never changes by editing it. unset( $raw['customer_id'], $raw['order_id'], $raw['order_item_id'] ); $clean = ServiceInput::clean( $raw, false ); $errors = $clean['errors'] + $this->relation_errors( $clean['data'] ); if ( $errors ) { return Errors::invalid( $errors ); } if ( array() !== $clean['data'] ) { $this->services->update( $id, $clean['data'] ); } if ( isset( $clean['data']['status'] ) && $clean['data']['status'] !== $service['status'] ) { $this->log->record( 'service.status_changed', $service['customer_id'], 'service', $id, /* translators: 1: old status, 2: new status. */ sprintf( __( 'Hosting status changed from %1$s to %2$s.', 'hostkhata' ), $service['status'], $clean['data']['status'] ), array( 'from' => $service['status'], 'to' => $clean['data']['status'], ) ); } elseif ( array() !== $clean['data'] ) { $this->log->record( 'service.updated', $service['customer_id'], 'service', $id, __( 'Hosting record updated.', 'hostkhata' ), array( 'fields' => array_keys( $clean['data'] ) ) ); } return $this->services->find( $id ); } /** * Deletes a service record that no invoice refers to. Others should be terminated or cancelled instead. * * @since 0.1.0 * * @param int $id Service id. * @return true|WP_Error */ public function delete( int $id ) { $service = $this->services->find( $id ); if ( null === $service ) { return Errors::not_found( __( 'Hosting record not found.', 'hostkhata' ) ); } if ( $this->services->invoice_reference_count( $id ) > 0 ) { return Errors::state( 'hostkhata_in_use', __( 'Invoices refer to this hosting account. Set its status to Terminated or Cancelled instead of deleting it.', 'hostkhata' ) ); } $this->log->record( 'service.deleted', $service['customer_id'], 'service', $id, __( 'Hosting record deleted.', 'hostkhata' ) ); $this->services->delete( $id ); return true; } /** * Checks the parts of the input that refer to other records. * * @param array $data Cleaned data. * @return array */ private function relation_errors( array $data ): array { $errors = array(); if ( ! empty( $data['customer_id'] ) && null === $this->customers->find( $data['customer_id'] ) ) { $errors['customer_id'] = __( 'This customer does not exist.', 'hostkhata' ); } if ( ! empty( $data['product_id'] ) && null === $this->products->find( $data['product_id'] ) ) { $errors['product_id'] = __( 'This package does not exist.', 'hostkhata' ); } return $errors; } }