settings = $settings; $this->factory = $factory; } /** * Register the shortcode, block and styles. * * @return void */ public function register(): void { add_shortcode( 'verifycate_wallet', array( $this, 'shortcode' ) ); add_action( 'init', array( $this, 'registerBlock' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'assets' ) ); } /** * Register the server-rendered wallet block. * * @return void */ public function registerBlock(): void { if ( ! function_exists( 'register_block_type' ) ) { return; } register_block_type( 'verifycate/wallet', array( 'api_version' => '2', 'title' => __( 'Verifycate Wallet', 'verifycate' ), 'category' => 'widgets', 'icon' => 'awards', 'description' => __( 'Show the logged-in learner their Verifycate certificates.', 'verifycate' ), 'attributes' => array( 'title' => array( 'type' => 'string', 'default' => __( 'My certificates', 'verifycate' ), ), 'emptyText' => array( 'type' => 'string', 'default' => __( 'No certificates yet.', 'verifycate' ), ), 'limit' => array( 'type' => 'number', 'default' => 20, ), ), 'render_callback' => array( $this, 'renderBlock' ), ) ); } /** * Register the shared certificate styles. * * @return void */ public function assets(): void { $style_path = VERIFYCATE_DIR . 'assets/wallet.css'; $version = VERIFYCATE_VERSION . ( file_exists( $style_path ) ? '.' . (string) filemtime( $style_path ) : '' ); wp_register_style( 'verifycate-wallet', VERIFYCATE_URL . 'assets/wallet.css', array(), $version ); wp_enqueue_style( 'verifycate-wallet' ); } /** * Shortcode renderer. * * @param array|string $atts Shortcode attributes. * @return string */ public function shortcode( $atts ): string { $atts = shortcode_atts( array( 'title' => __( 'My certificates', 'verifycate' ), 'empty_text' => __( 'No certificates yet.', 'verifycate' ), 'limit' => 20, ), is_array( $atts ) ? $atts : array(), 'verifycate_wallet' ); return $this->render( array( 'title' => (string) $atts['title'], 'empty_text' => (string) $atts['empty_text'], 'limit' => (int) $atts['limit'], ) ); } /** * Block renderer. * * @param array $attributes Block attributes. * @return string */ public function renderBlock( array $attributes ): string { return $this->render( array( 'title' => isset( $attributes['title'] ) ? (string) $attributes['title'] : '', 'empty_text' => isset( $attributes['emptyText'] ) ? (string) $attributes['emptyText'] : '', 'limit' => isset( $attributes['limit'] ) ? (int) $attributes['limit'] : 20, ) ); } /** * Render the wallet. * * @param array $args Wallet arguments. * @return string */ public function render( array $args ): string { $title = isset( $args['title'] ) && '' !== (string) $args['title'] ? (string) $args['title'] : __( 'My certificates', 'verifycate' ); $empty_text = isset( $args['empty_text'] ) && '' !== (string) $args['empty_text'] ? (string) $args['empty_text'] : __( 'No certificates yet.', 'verifycate' ); $limit = isset( $args['limit'] ) ? max( 1, min( 50, (int) $args['limit'] ) ) : 20; if ( ! is_user_logged_in() ) { return sprintf( '

%s

', wp_kses_post( sprintf( /* translators: %s: login link. */ esc_html__( 'Please %s to see your certificates.', 'verifycate' ), '' . esc_html__( 'log in', 'verifycate' ) . '' ) ) ); } if ( ! $this->factory->isConfigured() ) { return ''; } $user = wp_get_current_user(); if ( ! $user instanceof \WP_User || '' === (string) $user->user_email ) { return ''; } $issues = $this->getIssues( (string) $user->user_email ); if ( is_wp_error( $issues ) ) { $message = current_user_can( 'manage_options' ) ? $issues->get_error_message() : __( 'Certificates are temporarily unavailable. Please try again later.', 'verifycate' ); return sprintf( '

%s

', esc_html( $message ) ); } $issues = array_slice( $issues, 0, $limit ); $html = '
'; $html .= '

' . esc_html( $title ) . '

'; if ( $issues === array() ) { $html .= '

' . esc_html( $empty_text ) . '

'; } else { $html .= '
    '; foreach ( $issues as $issue ) { $html .= $this->renderIssue( $issue ); } $html .= '
'; } $html .= '
'; return $html; } /** * Fetch and normalize the learner's issues. * * @param string $email Recipient email. * @return array>|\WP_Error */ private function getIssues( string $email ) { $key = 'verifycate_wallet_' . md5( $this->settings->getWorkspace() . '|' . strtolower( $email ) ); $cached = get_transient( $key ); if ( is_array( $cached ) ) { /** * Cached wallet issues. * * @var array> $cached */ return $cached; } try { $recipient = $this->factory->make()->recipients()->get( $email ); $issues = array(); foreach ( $recipient->getIssues() as $issue ) { if ( $issue->getCertificateStatus() !== 'active' ) { continue; } $issues[] = array( 'id' => $issue->getId(), 'certificate_name' => $issue->getCertificateName(), 'status' => $issue->getStatus(), 'public_id' => $issue->getPublicId(), 'issued_at' => null === $issue->getIssuedAt() ? null : $issue->getIssuedAt()->format( DATE_ATOM ), 'expires_at' => null === $issue->getExpiresAt() ? null : $issue->getExpiresAt()->format( DATE_ATOM ), 'pending_reason' => $issue->getPendingReason(), ); } usort( $issues, static function ( array $left, array $right ): int { return strcmp( (string) ( $right['issued_at'] ?? '' ), (string) ( $left['issued_at'] ?? '' ) ); } ); /** * Filter the issues shown in a learner's wallet. * * @param array> $issues Normalized issues. * @param string $email Recipient email. */ $filtered = apply_filters( 'verifycate_wallet_issues', $issues, $email ); $issues = is_array( $filtered ) ? $filtered : array(); set_transient( $key, $issues, self::TTL ); return $issues; } catch ( ApiException $exception ) { if ( $exception->isNotFound() ) { set_transient( $key, array(), self::TTL ); return array(); } Logger::warning( 'Could not load the learner wallet.', array( 'status' => $exception->getStatusCode(), 'error' => $exception->getMessage(), ) ); } catch ( TransportException $exception ) { Logger::warning( 'Could not reach the API for the learner wallet.', array( 'error' => $exception->getMessage() ) ); } catch ( \Throwable $exception ) { Logger::warning( 'Unexpected error loading the learner wallet.', array( 'error' => $exception->getMessage() ) ); } return new \WP_Error( 'verifycate_wallet', __( 'Certificates are temporarily unavailable. Please try again later.', 'verifycate' ) ); } /** * Render one wallet row. * * @param array $issue Normalized issue. * @return string */ private function renderIssue( array $issue ): string { $name = isset( $issue['certificate_name'] ) ? (string) $issue['certificate_name'] : ''; $status = isset( $issue['status'] ) ? (string) $issue['status'] : 'pending'; $public = isset( $issue['public_id'] ) ? (string) $issue['public_id'] : ''; $meta = array(); if ( ! empty( $issue['issued_at'] ) ) { $timestamp = strtotime( (string) $issue['issued_at'] ); if ( false !== $timestamp ) { /* translators: %s: issue date. */ $meta[] = sprintf( __( 'Issued %s', 'verifycate' ), wp_date( (string) get_option( 'date_format' ), $timestamp ) ); } } if ( 'pending' === $status ) { $meta[] = __( 'Awaiting release', 'verifycate' ); } elseif ( 'expired' === $status ) { $meta[] = __( 'Expired', 'verifycate' ); } else { $meta[] = __( 'Active', 'verifycate' ); } $html = '
  • '; $html .= '
    '; $html .= '

    ' . esc_html( $name ) . '

    '; $html .= '

    ' . esc_html( implode( ' ยท ', $meta ) ) . '

    '; $html .= '
    '; if ( 'active' === $status && '' !== $public ) { $url = rtrim( $this->settings->getPortalUrl(), '/' ) . '/c/' . rawurlencode( $public ); $html .= '' . esc_html__( 'View certificate', 'verifycate' ) . ''; } $html .= '
  • '; return $html; } }