$fields See Requests::create(). */ public static function submit( array $fields ): string { $id = self::requests()->create( $fields ); ( new ActionSchedulerQueue() )->run_now( $id ); return $id; } /** * Action Scheduler callback. * * @param mixed $id Request UUID. */ public static function run( $id ): void { $runner = self::runner(); if ( null === $runner ) { self::fail_without_storage( (string) $id ); return; } // A generation can take minutes; hosts often stop PHP after 30 seconds. Ignored where disabled. if ( function_exists( 'set_time_limit' ) ) { @set_time_limit( Runner::LEASE_SECONDS ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, Squiz.PHP.DiscouragedFunctions.Discouraged -- Only raised for this background job. Warns where the function is disabled; the job then runs with the host's limit. } $runner->run( (string) $id ); } /** * Action Scheduler callback. * * @param mixed $id Request UUID. */ public static function poll( $id ): void { $runner = self::runner(); if ( null === $runner ) { self::fail_without_storage( (string) $id ); return; } $runner->poll( (string) $id ); } /** * Action Scheduler callback. */ public static function reap(): void { $runner = self::runner(); if ( null !== $runner ) { $runner->reap(); } } /** * Action Scheduler callback: deletes expired photos, results and rows. */ public static function cleanup(): void { $files = Storage::store(); if ( null !== $files ) { ( new Cleanup( self::requests(), $files, Settings::retention(), 'time' ) )->run(); } } /** * Request storage. */ public static function requests(): Requests { global $wpdb; return new WpdbRequests( $wpdb ); } /** * A runner wired to WordPress, or null when there is nowhere to store results. */ private static function runner(): ?Runner { $files = Storage::store(); if ( null === $files ) { return null; } return new Runner( self::requests(), array( Registry::class, 'get' ), new ProductInputBuilder( $files, array( self::class, 'product_image' ), array( TypeResolver::class, 'for_product' ), array( VariantAttributes::class, 'for_variation' ), new Sanitizer() ), $files, new ActionSchedulerQueue(), 'time', array( self::class, 'announce' ) ); } /** * Bytes of the image shown for a product or variation, or null. * * @param int $product_id Product ID. * @param int $variation_id Variation ID, 0 for none. */ public static function product_image( int $product_id, int $variation_id ): ?string { $product = wc_get_product( $variation_id > 0 ? $variation_id : $product_id ); if ( ! $product instanceof \WC_Product ) { return null; } // A variation without its own image returns the parent's. $file = get_attached_file( (int) $product->get_image_id() ); if ( ! is_string( $file ) || '' === $file || ! is_readable( $file ) ) { return null; } $bytes = file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- A local media file; WP_Filesystem can need credentials a background job does not have. return false === $bytes ? null : $bytes; } /** * Turns runner events into actions other code (and Fitzaro Pro) can hook into. * * @param string $event 'completed' or 'failed'. * @param string $id Request UUID. * @param string $code Error code when failed. */ public static function announce( string $event, string $id, string $code ): void { if ( 'completed' === $event ) { /** * Fires when a try-on result is ready. * * @since 1.0.0 * * @param string $id Request UUID. */ do_action( 'fitzaro/job/completed', $id ); return; } /** * Fires when a try-on request fails. * * @since 1.0.0 * * @param string $id Request UUID. * @param string $code Error code (a ProviderError or JobError constant). */ do_action( 'fitzaro/job/failed', $id, $code ); } /** * Fails a request when the storage folder disappeared. * * @param string $id Request UUID. */ private static function fail_without_storage( string $id ): void { if ( self::requests()->fail( $id, JobError::INTERNAL, gmdate( 'Y-m-d H:i:s' ) ) ) { self::announce( 'failed', $id, JobError::INTERNAL ); } } }