reset(); $notice = __( 'Template reset to the built-in default.', 'unsupervised-schedular' ); } else { $this->save(); $notice = __( 'Payment due email template saved.', 'unsupervised-schedular' ); } } $subject = $this->template->subject(); $body = $this->template->body(); $itemLine = $this->template->itemLine(); $tokens = PaymentDueEmailTemplate::tokens(); $itemTokens = PaymentDueEmailTemplate::itemTokens(); $previewNonce = wp_create_nonce( 'wp_rest' ); $previewUrl = rest_url( 'us-scheduler/v1/payment-email/preview' ); // Server-render the initial preview from sample values so the panel is // populated before any JavaScript runs (and if it never does). $preview = self::renderSample( $this->template, $subject, $body, $itemLine ); include USC_PLUGIN_DIR . 'templates/admin/payment-email.php'; } /** * Render the given template text against a fixed set of sample values, using * the real mailer so the preview matches a genuine scan exactly. The passed * subject/body/item-line override the stored ones so an unsaved edit can be * previewed. * * @return array{subject: string, body: string} */ public static function renderSample( PaymentDueEmailTemplate $stored, string $subject, string $body, string $itemLine ): array { // A throwaway template returning the supplied (possibly unsaved) text. $draft = new class( $subject, $body, $itemLine ) extends PaymentDueEmailTemplate { public function __construct( private string $draftSubject, private string $draftBody, private string $draftItemLine, ) {} public function subject(): string { return '' !== $this->draftSubject ? $this->draftSubject : self::defaultSubject(); } public function body(): string { return '' !== $this->draftBody ? $this->draftBody : self::defaultBody(); } public function itemLine(): string { return '' !== $this->draftItemLine ? $this->draftItemLine : self::defaultItemLine(); } }; $mailer = new PaymentDueMailer( $draft ); $tokens = $mailer->buildTokens( self::sampleStudentName(), self::sampleItems(), self::sampleReference(), self::sampleCredit() ); return [ 'subject' => $draft->renderSubject( $tokens ), 'body' => $draft->renderBody( $tokens ), ]; } public static function sampleStudentName(): string { return __( 'Alex Student', 'unsupervised-schedular' ); } public static function sampleReference(): string { return 'REF12345'; } public static function sampleCredit(): float { return 20.0; } /** * The sample charges the preview is rendered against — two lessons on * different dates so the {items} block and grand total are both exercised. * * @return list */ public static function sampleItems(): array { return [ [ 'label' => __( 'Piano lesson', 'unsupervised-schedular' ), 'amount' => 35.0, 'currency' => 'CAD', 'due_date' => '2026-07-15', 'etransfer_email' => 'pay@studio.test', ], [ 'label' => __( 'Guitar lesson', 'unsupervised-schedular' ), 'amount' => 40.0, 'currency' => 'CAD', 'due_date' => '2026-07-22', 'etransfer_email' => 'pay@studio.test', ], ]; } private function save(): void { // Nonce is verified by the caller (renderPage) before this method runs. // phpcs:disable WordPress.Security.NonceVerification.Missing $this->template->saveSubject( sanitize_text_field( Val::string( wp_unslash( $_POST['subject'] ?? '' ) ) ) ); $this->template->saveBody( sanitize_textarea_field( Val::string( wp_unslash( $_POST['body'] ?? '' ) ) ) ); $this->template->saveItemLine( sanitize_text_field( Val::string( wp_unslash( $_POST['item_line'] ?? '' ) ) ) ); // phpcs:enable WordPress.Security.NonceVerification.Missing } private function reset(): void { $this->template->saveSubject( '' ); $this->template->saveBody( '' ); $this->template->saveItemLine( '' ); } }