*/ public array $currencies; /** @var int|string */ public $staff_id = 0; public int $location_id = 0; public DateTimeZone $timezone; /** True for report managers; shared viewers only see the cards granted to them. */ public bool $owner; /** Base query for links and the filter form (page plus view parameters). */ public array $base_query; public ReportInsightsRepository $insights; /** @var array */ private array $memo = array(); /** @param array $input Unslashed GET input. */ /** View parameters the report filters read. */ public const QUERY_KEYS = array( 'range', 'date_from', 'date_to', 'currency', 'staff_id', 'location_id' ); public function __construct( array $input, bool $owner, array $base_query, bool $dimension_filters ) { $this->owner = $owner; $this->base_query = $base_query; $this->timezone = DateTimeHelper::business_timezone(); $this->insights = new ReportInsightsRepository(); $this->currencies = ( new \Abientot\Repositories\AppointmentsRepository() )->reporting_currencies(); $requested = is_string( $input['currency'] ?? null ) ? strtoupper( sanitize_text_field( $input['currency'] ) ) : ''; $this->currency = in_array( $requested, $this->currencies, true ) ? $requested : (string) Settings::values()['currency']; $this->period( $input ); if ( $owner && $dimension_filters ) { $this->staff_id = \Abientot\Support\StaffFilter::request( is_scalar( $input['staff_id'] ?? null ) ? (string) $input['staff_id'] : '0' ); $this->location_id = is_scalar( $input['location_id'] ?? null ) && preg_match( '/^[0-9]{1,10}$/D', (string) $input['location_id'] ) ? min( 2147483647, (int) $input['location_id'] ) : 0; } } private function period( array $input ): void { $today = new DateTimeImmutable( 'today', $this->timezone ); $range = is_string( $input['range'] ?? null ) && in_array( $input['range'], self::RANGES, true ) ? $input['range'] : ''; $from = self::date( $input['date_from'] ?? null, $this->timezone ); $to = self::date( $input['date_to'] ?? null, $this->timezone ); if ( '' === $range ) { $range = $from || $to ? 'custom' : 'month'; } switch ( $range ) { case 'today': $from = $today; $to = $today; break; case '7d': $from = $today->modify( '-6 days' ); $to = $today; break; case '30d': $from = $today->modify( '-29 days' ); $to = $today; break; case '90d': $from = $today->modify( '-89 days' ); $to = $today; break; case 'last-month': $from = $today->modify( 'first day of last month' ); $to = $today->modify( 'last day of last month' ); break; case 'year': $from = $today->setDate( (int) $today->format( 'Y' ), 1, 1 ); $to = $today->setDate( (int) $today->format( 'Y' ), 12, 31 ); break; case 'custom': $from = $from ?? ( $to ? $to->modify( 'first day of this month' ) : $today->modify( 'first day of this month' ) ); $to = $to ?? $from->modify( 'last day of this month' ); if ( $to < $from ) { [ $from, $to ] = array( $to, $from ); } if ( (int) $from->diff( $to )->format( '%a' ) + 1 > self::MAX_DAYS ) { $to = $from->modify( '+' . ( self::MAX_DAYS - 1 ) . ' days' ); } break; default: $from = $today->modify( 'first day of this month' ); $to = $today->modify( 'last day of this month' ); } $this->range = $range; $this->date_from = $from->format( 'Y-m-d' ); $this->date_to = $to->format( 'Y-m-d' ); $this->start = $from->setTime( 0, 0 ); $this->end = $to->setTime( 0, 0 )->modify( '+1 day' ); $this->days = (int) $this->start->diff( $to )->format( '%a' ) + 1; $calendar_months = in_array( $range, array( 'month', 'last-month', 'year' ), true ); $this->previous_end = $this->start; $this->previous_start = $calendar_months ? ( 'year' === $range ? $this->start->modify( '-1 year' ) : $this->start->modify( '-1 month' ) ) : $this->start->modify( '-' . $this->days . ' days' ); } private static function date( $value, DateTimeZone $timezone ): ?DateTimeImmutable { if ( ! is_string( $value ) || ! preg_match( '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/D', $value ) ) { return null; } $date = DateTimeImmutable::createFromFormat( '!Y-m-d', $value, $timezone ); return $date && $date->format( 'Y-m-d' ) === $value && $value >= '2000-01-01' && $value <= '9999-12-30' ? $date : null; } public static function utc( DateTimeImmutable $date ): string { return $date->setTimezone( new DateTimeZone( 'UTC' ) )->format( 'Y-m-d H:i:s' ); } /** @return array{start_utc:string,end_utc:string,currency:string,staff_id:int|string,location_id:int} */ public function scope( bool $previous = false ): array { return array( 'start_utc' => self::utc( $previous ? $this->previous_start : $this->start ), 'end_utc' => self::utc( $previous ? $this->previous_end : $this->end ), 'currency' => $this->currency, 'staff_id' => $this->staff_id, 'location_id' => $this->location_id, ); } /** Memoised loader so cards that share data query it once. */ public function data( string $key, callable $loader ) { if ( ! array_key_exists( $key, $this->memo ) ) { $this->memo[ $key ] = $loader(); } return $this->memo[ $key ]; } public function money( int $cents ): string { return Currency::format( $cents, $this->currency ); } /** Money without minor units for axes and compact labels. */ public function money_short( float $cents ): string { return Locale::currency( round( $cents / 100 ), $this->currency, 0 ); } public function local_date( DateTimeImmutable $date, string $format = '' ): string { return Locale::date( '' !== $format ? $format : DateTimeHelper::date_display_format(), $date->getTimestamp(), $this->timezone ); } public function period_label( bool $previous = false ): string { $start = $previous ? $this->previous_start : $this->start; $last = ( $previous ? $this->previous_end : $this->end )->modify( '-1 day' ); if ( $start->format( 'Y-m-d' ) === $last->format( 'Y-m-d' ) ) { return $this->local_date( $start ); } /* translators: 1: start date, 2: end date. */ return sprintf( __( '%1$s through %2$s', 'abientot' ), $this->local_date( $start ), $this->local_date( $last ) ); } /** @param array $args */ public function url( array $args = array() ): string { $query = array_merge( $this->base_query, array( 'range' => $this->range, 'currency' => $this->currency ), $args ); if ( 'custom' === ( $query['range'] ?? '' ) ) { $query += array( 'date_from' => $this->date_from, 'date_to' => $this->date_to ); } if ( $this->owner ) { $query += array_filter( array( 'staff_id' => $this->staff_id ?: null, 'location_id' => $this->location_id ?: null ) ); } return add_query_arg( array_filter( $query, static fn( $value ): bool => null !== $value && '' !== $value ), admin_url( 'admin.php' ) ); } }