From 0afb2c0046b55c123eafb4ab9ee77efa68d12463 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sat, 06 Jun 2026 17:15:31 +0000
Subject: [PATCH] =Starting the Favourites.js setup, converting previous Northeh stuff to new Registrar, fixing up Square.php integration to match

---
 inc/rest/routes/Invitations.php | 1651 ++++++++++------------------------------------------------
 1 files changed, 296 insertions(+), 1,355 deletions(-)

diff --git a/inc/rest/routes/Invitations.php b/inc/rest/routes/Invitations.php
index 5c7638a..1839f44 100644
--- a/inc/rest/routes/Invitations.php
+++ b/inc/rest/routes/Invitations.php
@@ -1,1392 +1,333 @@
 <?php
 namespace JVBase\rest\routes;
 
-use JVBase\JVB;
-use JVBase\rest\RestRouteManager;
-use Exception;
-use JVBase\utility\Features;
+use JVBase\rest\Rest;
+use JVBase\managers\CustomTable;
+use JVBase\rest\Route;
+use WP_REST_Request;
 use WP_REST_Response;
-use WP_Error;
 
 if (!defined('ABSPATH')) {
-    exit; // Exit if accessed directly
+	exit;
 }
-// TODO: Get this to work with the constants setup
-/***
- * WORKFLOW:
- *      1) Verified user (userA) invites user
- *          a) USER EXISTS -> notify user they're already up
- *          b) USER DOESN'T EXIST:
- *              i) check if user exists in invitation table
- *              ii) if they exist, add userA to inviters
- *                  - if status is expired, resent email invite and set status to 'pending'
- *              iii) if they don't exist, add to table
- *              iv) once invited user registers:
- *                  - set status to 'accepted', add new_user_id
- *                  - set user as verified
- *                  - if user was invited to a specific shop, pass user along to that shop
+
+/**
+ * Invitations Route Manager
+ *
+ * Handles user invitations:
+ * - Global invitations (user to user, based on JVB_MEMBERSHIP['can_invite'])
+ * - Term invitations (to ownable content taxonomies with 'invitable' flag)
  */
-class Invitations extends RestRouteManager
+class Invitations extends Rest
 {
-    protected string $tableName;
-    protected array $inviteTypes;
-    protected $wpdb;
-    protected string $prefix;
-    protected array $tableNames;
-    protected int $expiryDays = 14; // Invitations expire after 14 days
+	protected array $inviteConfig;
+	protected CustomTable $table;
 
-    public function __construct()
-    {
-        $this->cache_name = 'invitations';
-        parent::__construct();
-        global $wpdb;
-        $this->inviteTypes = jvbInviteTableTypes();
-        $this->tableNames = jvbInviteTables();
-        $this->wpdb = $wpdb;
-        $this->prefix = $wpdb->prefix;
+	public function __construct()
+	{
+		$this->cacheName = 'invitations';
+		parent::__construct();
 
-        // Add hooks for processing accepted invitations
-        add_action('user_register', [$this, 'checkInvitation'], 10, 1);
+		// Get invitation configuration
+		$this->inviteConfig = JVB()->invitations()->getInviteConfig();
+		$this->table = CustomTable::for('invitations');
 
-
-		add_filter('jvbLoginLabels', [$this, 'modifyLoginLabels'], 10, 2);
-
-
-
-        add_action('jvb_daily_maintenance', [$this, 'cleanupExpiredInvitations']);
-
-        // Add filter for bulk operation handling
-        add_filter(BASE . 'handle_bulk_operation', [ $this, 'processOperation' ], 10, 3);
-    }
-
-    /**
-     * Registers the routes for invitations
-     * @return void
-     */
-    public function registerRoutes():void
-    {
-        register_rest_route($this->namespace, '/invitations', [
-            [
-                'methods'   => 'GET',
-                'callback'  => [$this, 'getInvitations'],
-                'permission_callback'   => [$this, 'checkPermission']
-            ],
-            [
-                'methods'   => 'POST',
-                'callback'  => [$this, 'createInvitationRequest'],
-                'permission_callback'   => [$this, 'checkPermission']
-            ]
-        ]);
-    }
-
-    protected function buildParams(object $request):array
-    {
-        $data = $request->get_params();
-        $role = (array_key_exists('role', $data) && array_key_exists($data['role'], $this->tableNames)) ? $data['role'] : false;
-        $toTerm = (array_key_exists('to_term', $data)) ? (int)$data['to_term'] : false;
-        $taxonomy = (array_key_exists('taxonomy', $data) && in_array($data['taxonomy'], $this->inviteTypes[$role]['to_terms']??[])) ? $data['taxonomy'] : false;
-
-		return [
-			'user'        => (array_key_exists('user', $data)) ? (int)$data['user'] : false,
-			'role'         => $role,
-			'to_term'     => $toTerm,
-			'taxonomy'     => $taxonomy,
-			'status'    => array_key_exists('status', $data) && in_array($data['status'], ['all', 'pending', 'accepted', 'rejected', 'expired', 'revoked']) ? $data['status'] : 'all',
-			'page'        => array_key_exists('page', $data) ? (int)$data['page'] : 1,
-		];
-    }
-    /**
-     * @param object $request the request object
-     *
-     * @return WP_REST_Response
-     */
-    public function getInvitations(object $request): WP_REST_Response
-    {
-        $args = $this->buildParams($request);
-        if ($args['user']) {
-            if (!$this->userCheck($args['user'])) {
-                return new WP_REST_Response([
-                    'success'   => false,
-                    'message'   => 'Looks like you are not who you say you are'
-                ]);
-            }
-            if (!$this->isVerifiedUser($args['user'])) {
-                return new WP_REST_Response([
-                    'success'   => false,
-                    'message'   => 'Sorry, you don\'t have permission to do this.',
-                ]);
-            }
-            return $this->getUserInvitations($args);
-        } elseif ($args['to_term']) {
-            if (!$this->checkTerm($args)) {
-                return new WP_REST_Response([
-                    'success'   => false,
-                    'message'   => 'Looks like this '.$args['taxonomy'].' does not exist'
-                ]);
-            }
-            return $this->getTermInvitations($args);
-        }
-
-        return new WP_REST_Response([
-            'success'   => false,
-            'message'   => 'Invalid request'
-        ]);
-    }
-
-    public function getTermInvitations(array $args):WP_REST_Response
-    {
-        if (!$this->checkTerm($args)) {
-            return new WP_REST_Response([
-                'success'   => false,
-                'message'   => 'Invalid shop'
-            ]);
-        }
-
-        if (!user_can($args['user'], 'manage_'.$args['taxonomy'].'_'.$args['to_term'])) {
-            return new WP_REST_Response([
-                'success'   => false,
-                'message'   => 'You do not have permission to view invitations for this '.$args['taxonomy']
-            ]);
-        }
-
-        $key = $this->cache->generateKey($args);
-
-        $cache = $this->cache->get($key);
-        if ($cache) {
-            return new WP_REST_Response($cache);
-        }
-
-        $per_page = 20;
-
-        $conditions = [];
-        $params = [];
-
-        //Filter by term
-        $conditions[] = "to_{$args['taxonomy']} = %d";
-        $params[] = $args['to_term'];
-
-        if ($args['status'] !== 'all') {
-            $conditions[] = "status = %s";
-            $params[] = $args['status'];
-        }
-
-        $where = !empty($conditions) ? " WHERE " .implode(' AND ', $conditions) : "";
-
-        //Count total for pagination
-        $count_query = "SELECT COUNT(*) FROM {$this->tableNames[$args['role']]} {$where}";
-        $total = $this->wpdb->get_var($this->wpdb->prepare($count_query, $params));
-
-        //Get paginated invitations
-        $offset = ($args['page'] - 1) * $per_page;
-        $query = $count_query." ORDER BY created_at DESC LIMIT %d OFFSET %d";
-
-        //Add pagination
-        $pagination = array_merge($params, [$per_page, $offset]);
-        $invitations = $this->wpdb->get_results($this->wpdb->prepare($query, $pagination));
-
-        $formatted = [];
-        foreach ($invitations as $invitation) {
-            $formatted[] = $this->formatInvitation($invitation);
-        }
-
-        $return = [
-            'invitations'    => $formatted,
-            'total'    => (int)$total,
-            'pages'    => ceil($total /$per_page),
-            'page'    => $args['page'],
-            'per_page'    => $per_page
-        ];
-
-        $this->cache->set($key, $return);
-        return new WP_REST_Response($return);
-    }
-
-    protected function buildInvitationArgs(object $request):array
-    {
-        $data = $request->get_params();
-
-        $user = (array_key_exists('user', $data) && $this->userCheck($data['user'])) ? (int) $data['user'] : false;
-        if (!$user) {
-            return [];
-        }
-        $role = jvbUserRole($user);
-        $args = [
-            'user'          => $user,
-            'role'          => $role,
-            'action'        => (array_key_exists('action', $data) && in_array($data['action'], ['refresh', 'revoke', 'create'])) ? $data['action'] : false,
-			'inviteID'		=> (array_key_exists('refresh', $data)) ? (int) $data['refresh'] : false,
-        ];
-
-        $allowed = $this->inviteTypes[$role];
-        if (count($allowed) > 1) {
-            $inviteAs = (array_key_exists('type', $data) && in_array($data['type'], $allowed)) ? $data['type'] : false;
-        } else {
-            $invitedAs = $allowed[0];
-        }
-
-        if (array_key_exists('invites', $data)) {
-            $invites = [];
-            foreach ($data['invites'] as $invite) {
-                $temp = [
-                    'invited_id'    => (array_key_exists('invited_id', $invite) && $this->userCheck($invite['invited_id'])) ? $invite['invited_id'] : false,
-                    'to_term'       => (array_key_exists('to_term', $invite)) ? (int) $invite['to_term'] : false,
-                    'taxonomy'      => (array_key_exists('taxonomy', $invite) && in_array($invite['taxonomy'], $this->inviteTypes[$role]['to_terms']??[])) ? $invite['taxonomy'] : false,
-                    'invited_name'  => (array_key_exists('name', $invite) && is_string($invite['name'])) ? sanitize_text_field($invite['name']) : false,
-                    'invited_email' => (array_key_exists('email', $invite) && is_email($invite['email'])) ? sanitize_email($invite['email']) : false,
-                ];
-                if ($temp['invited_id'] || ($temp['invited_email'] && $temp['invited_name'])) {
-                    $invites[$invitedAs][] = $data;
-                }
-            }
-            $args['invites'] = $invites;
-        }
-        if (!$invitedAs && !empty($args['invites'])) {
-            unset($args['invites']);
-        }
-
-        return $args;
-    }
-    /**
-     * @param object $request The Request Object
-     *
-     * @return WP_REST_Response
-     */
-    public function createInvitationRequest(object $request):WP_REST_Response
-    {
-        $args = $this->buildInvitationArgs($request);
-
-        $error = '';
-        if (!$args['user']) {
-            $error = 'User ID doesn\'t match up.... are you a bot?';
-        } elseif (Features::forMembership()->has('member_verified') && !user_can($args['user'], 'skip_moderation')) {
-            $error = 'Only verified users can send invitations.';
-        } elseif (!$args['role']) {
-            $error = 'It doesn\'t look like you can invite users.';
-        }
-        if ($error !== '') {
-            return new WP_REST_Response([
-                'success'    => false,
-                'message'    => $error
-            ]);
-        }
-
-        switch ($args['action']) {
-            case 'revoke':
-                return $this->revokeInvite($args);
-            case 'refresh':
-                return $this->resendInvite($args);
-        }
-
-        //Inviting to content taxonomy (ie: shop)
-        $artist = jvbContentFromUser($args['user']);
-        foreach ($args['invites'] as $index => $invite) {
-            if ($invite['to_term'] && $invite['taxonomy']) {
-                if (!$artist[$invite['taxonomy']] || $artist[$invite['taxonomy']['id'] !== $invite['term_id']]) {
-                    $args['invites'][$index]['to_term'] = false;
-                    $args['invites'][$index]['taxonomy'] = false;
-                }
-            }
-        }
-
-        if (!empty($args['invites']??[])) {
-            JVB()->queue()->queueOperation(
-                'invitation_create',
-                $args['user'],
-                [
-                    'invitations'   => $args['invites'],
-                ],
-                [
-                    'count'   => count($args['invites']),
-                    'priority'          => 'high',
-					'chunk_size' => 20,
-					'chunk_key'	=> 'invitations'
-                ]
-            );
-
-            return new WP_REST_Response([
-                'success' => true,
-                'message' => 'Processing ' . count($args['invites']) . ' invitations',
-            ]);
-        }
-        return new WP_REST_Response([
-            'success'    => false,
-            'message'    => 'No invitations sent.'
-        ]);
-    }
-
-    /**
-     * Revoke an invitation
-     *
-     * @params array $args
-     * @return array Response with success or error message
-     */
-    public function revokeInvite(array $args): array
-    {
-        $invitation = $this->getInvitationByUser($args);
-
-        if (!$invitation || is_wp_error($invitation)) {
-            return [
-                'success' => false,
-                'result' => 'Invitation not found'
-            ];
-        }
-
-        // Check if invitation can be revoked (only pending invitations)
-        if ($invitation['status'] !== 'pending' && $invitation['status'] !== 'expired') {
-            return [
-                'success' => true,
-                'result' => 'Only pending or expired invitations can be revoked'
-            ];
-        }
-
-        // Check if the user is one of the inviters
-        $inviters = json_decode($invitation['inviters'], true);
-        $user_is_inviter = false;
-        $updated_inviters = [];
-
-        foreach ($inviters as $inviter) {
-            if (intval($inviter['user_id']) === $args['user']) {
-                $user_is_inviter = true;
-            } else {
-                // Keep other inviters
-                $updated_inviters[] = $inviter;
-            }
-        }
-
-        if (!$user_is_inviter) {
-            return [
-                'success' => false,
-                'return' => 'You are not authorized to revoke this invitation'
-            ];
-        }
-
-        // If there are still other inviters, just update the inviters list
-        if (!empty($updated_inviters)) {
-            $this->wpdb->update(
-                $this->tableNames[$args['role']],
-                [
-                    'inviters' => json_encode($updated_inviters),
-                    'updated_at' => current_time('mysql')
-                ],
-                ['id' => $invitation['id']]
-            );
-
-            return [
-                'success' => true,
-                'result' => 'You have been removed from the inviters list but the invitation is still active with other inviters',
-            ];
-        }
-
-        // If no inviters left, mark the invitation as revoked
-        $this->wpdb->update(
-            $this->tableNames[$args['role']],
-            [
-                'status' => 'revoked',
-                'updated_at' => current_time('mysql')
-            ],
-            ['id' => $invitation['id'] ]
-        );
-
-        $this->sendRevocationEmail($invitation['email'], $invitation['name']);
-
-        return [
-            'success' => true,
-            'result' => 'Invitation has been successfully revoked'
-        ];
-    }
-
-    /**
-     * Resend an expired invitation
-     *
-     * @param array $args Args, as defined in buildInvitationArgs())
-     * @return WP_REST_Response Response with success or error message
-     */
-    public function resendInvite(array $args): WP_REST_Response
-    {
-        $invitation_id = isset($args['inviteID']) ? intval($args['inviteID']) : 0;
-        $user_id = isset($args['user']) ? intval($args['user']) : 0;
-
-        if (!$invitation_id || !$user_id) {
-            return new WP_REST_Response([
-                'success' => false,
-                'message' => 'Missing invitation ID or user ID'
-            ]);
-        }
-
-        // Get the invitation
-        $invitation = $this->wpdb->get_row($this->wpdb->prepare(
-            "SELECT * FROM {$this->tableNames[$args['role']]} WHERE id = %d",
-            $invitation_id
-        ), ARRAY_A);
-
-        if (!$invitation) {
-            return new WP_REST_Response([
-                'success' => false,
-                'message' => 'Invitation not found'
-            ]);
-        }
-
-        // Check if the invitation is expired or pending
-        if (!in_array($invitation['status'], ['expired', 'pending'])) {
-            return new WP_REST_Response([
-                'success' => false,
-                'message' => 'Only expired or pending invitations can be resent'
-            ]);
-        }
-
-        // Check if the user is one of the inviters
-        $inviters = json_decode($invitation['inviters'], true);
-        $user_is_inviter = false;
-
-        foreach ($inviters as &$inviter) {
-            if (intval($inviter['user_id']) === $user_id) {
-                $user_is_inviter = true;
-                // Update the invited_at timestamp for this inviter
-                $inviter['invited_at'] = current_time('mysql');
-                break;
-            }
-        }
-
-        if (!$user_is_inviter) {
-            return new WP_REST_Response([
-                'success' => false,
-                'message' => 'You are not authorized to resend this invitation'
-            ]);
-        }
-
-        // Generate a new token
-        $token = wp_generate_password(32, false);
-
-        // Set new expiration date
-        $expires_at = date('Y-m-d H:i:s', strtotime("+{$this->expiryDays} days"));
-
-        // Update the invitation
-        $this->wpdb->update(
-            $this->tableNames[$args['role']],
-            [
-                'invitation_token' => $token,
-                'status' => 'pending',
-                'expires_at' => $expires_at,
-                'inviters' => json_encode($inviters),
-                'updated_at' => current_time('mysql')
-            ],
-            ['id' => $invitation_id]
-        );
-
-        // Send the invitation email again
-        $name = $invitation['name'];
-        $email = $invitation['email'];
-		$role = $invitation['role'];
-		$terms = $this->getInvitationTerms($invitation, $role);
-
-
-        $result = $this->sendInvitationEmail($name, $email, $token, $user_id, $terms, $role);
-
-        if (!$result) {
-            return new WP_REST_Response([
-                'success' => false,
-                'message' => 'Failed to send invitation email'
-            ]);
-        }
-
-        return new WP_REST_Response([
-            'success' => true,
-            'message' => 'Invitation has been successfully resent',
-            'expires_at' => $expires_at
-        ]);
-    }
-
-	protected function getInvitationTerms(object|array $invitation, string $role) {
-		if (is_object($invitation)) {
-			$invitation = json_decode(json_encode($invitation), true);
-		}
-		$terms = [];
-		foreach ($this->inviteTypes[$role]['to_terms'] as $taxonomy) {
-			$terms[$taxonomy] = $invitation['to_'.$taxonomy];
-		}
-		return $terms;
+		// Cache connections
+		$this->cache
+			->connect('user')
+			->connect('taxonomy');
 	}
 
-    /**
-     * Create or update an invitation
-     * @param string $name Name of person being invited
-     * @param string $email Email of person being invited
-     * @param int $inviter_id User ID of the person inviting
-	 * @param string|false $role
-     * @param int|false $termID Optional shop ID
-	 * @param string|false $taxonomy Optional taxonomy
-     * @param bool $send_email whether to send email right away
-     * @return WP_Error|array
-     *
-     */
-    public function createInvitation(
-        string $name,
-        string $email,
-        int $inviter_id,
-        string|false $role = false,
-        int|false $termID = false,
-        string|false $taxonomy = false,
-        bool $send_email = true
-    ):WP_Error|array {
-        error_log('Creating Invitation with data: '.print_r([
-            'name'      => $name,
-            'email'     => $email,
-            'inviter ID'=> $inviter_id,
-            'termID'    => $termID,
-            'taxonomy'    => $taxonomy,
-            'role'        => $role
-            ], true));
-        // Sanitize and validate email
-        $email = sanitize_email($email);
-        if (!is_email($email)) {
-            error_log('Invalid email');
-            return new WP_Error('invalid_email', 'Invalid email address');
-        }
+	public function registerRoutes(): void
+	{
+		Route::for('invitations')
+			->get([$this, 'getInvitations'])
+			->args([
+				'user' => 'int|required',
+				'to_term' => 'int',
+				'taxonomy' => 'string',
+				'status' => 'string|enum:all,pending,accepted,rejected,expired,revoked|default:all',
+				'page' => 'int|default:1|min:1'
+			])
+			->auth('user')
+			->rateLimit(20)
 
-        // Check if inviter is verified
-        if (Features::forMembership()->has('member_verified') && !$this->isVerifiedUser($inviter_id)) {
-            error_log('Unverified Artist');
-            return new WP_Error('unauthorized', 'Only verified artists can send invitations');
-        }
+			->post([$this, 'createInvitationRequest'])
+			->args([
+				'user' => 'int|required',
+				'action' => 'string|enum:create,revoke,refresh|default:create',
+				'invites' => 'array',
+				'invitation_id' => 'int'
+			])
+			->auth('verified')
+			->rateLimit(10, 300)
+			->register();
+	}
 
-        if ($termID) {
-            // Check if shop exists if specified
-            if ($this->checkTerm(['term_id' => $termID, 'taxonomy' => $taxonomy])) {
-                error_log('Invalid Taxonomy');
-                return new WP_Error('invalid_term', 'The specified term does not exist');
-            }
-        }
+	/**
+	 * Get invitations for a user or term
+	 */
+	public function getInvitations(WP_REST_Request $request): WP_REST_Response
+	{
+		$userID = $request->get_param('user');
+		$termID = $request->get_param('to_term');
+		$taxonomy = $request->get_param('taxonomy');
 
-        if (!$role || !array_key_exists($role, $this->inviteTypes)) {
-            return new WP_Error('invalid_role', 'No role was set');
-        }
-
-        // Check if user already exists
-        $invite = !email_exists($email);
-
-        // Get existing invitation if any
-        $existing = $this->wpdb->get_row($this->wpdb->prepare(
-            "SELECT * FROM {$this->tableNames[$role]} WHERE email = %s",
-            $email
-        ), ARRAY_A);
-
-        // Generate token
-        $token = wp_generate_password(32, false);
-
-        // Set expiration date
-        $expires_at = date('Y-m-d H:i:s', strtotime("+{$this->expiryDays} days"));
-
-        if ($existing) {
-            // Update existing invitation
-            $inviters = json_decode($existing['inviters'], true);
-
-            // Check if this inviter already invited
-            $inviter_exists = false;
-            foreach ($inviters as $inviter) {
-                if ($inviter['user_id'] == $inviter_id) {
-                    $inviter_exists = true;
-                    // Update the invited_at timestamp
-                    $inviter['invited_at'] = current_time('mysql');
-                    break;
-                }
-            }
-
-            if (!$inviter_exists) {
-                // Add new inviter
-                $inviters[] = [
-                    'user_id' => $inviter_id,
-                    'invited_at' => current_time('mysql')
-                ];
-            }
-
-            // Prepare update data
-            $update_data = [
-                'inviters'      => json_encode($inviters),
-                'status'        => 'pending',
-                'expires_at'    => $expires_at,
-                'updated_at'    => current_time('mysql'),
-            ];
-            // Set shop_id if provided and not already set
-			$check = 'to_'.$taxonomy;
-            if ($termID && $existing[$check] !== $termID) {
-                $update_data[$check] = $termID;
-            }
-
-            // If invitation was expired, generate new token
-            if ($existing['status'] === 'expired') {
-                $update_data['invitation_token'] = $token;
-            } else {
-                $token = $existing['invitation_token'];
-            }
-
-            $this->wpdb->update(
-                $this->tableNames[$role],
-                $update_data,
-                ['id' => $existing['id']]
-            );
-
-            $invitation_id = $existing['id'];
-        } else {
-            // Create new invitation
-            $inviters = [[
-                'user_id' => $inviter_id,
-                'invited_at' => current_time('mysql')
-            ]];
-
-            $insert_data = [
-                'name'  => sanitize_text_field($name),
-                'email' => $email,
-                'invitation_token' => $token,
-                'status' => 'pending',
-                'inviters' => json_encode($inviters),
-                'expires_at' => $expires_at,
-                'created_at' => current_time('mysql')
-            ];
-            // Add shop if provided
-            if ($termID) {
-                $insert_data['to_'.$taxonomy] = $termID;
-            }
-
-            $this->wpdb->insert(
-                $this->tableNames[$role],
-                $insert_data
-            );
-
-            $invitation_id = $this->wpdb->insert_id;
-        }
-
-        error_log('On to invitation email send:');
-        // Send invitation email
-        if ($invite && $send_email) {
-            $this->sendInvitationEmail($name, $email, $token, $inviter_id, [$taxonomy => $termID], $role);
-        }
-
-        return [
-            'id' => $invitation_id,
-            'email' => $email,
-            'token' => $token,
-            'expires_at' => $expires_at
-        ];
-    }
-
-    /**
-     * Validate an invitation token
-     * @param string $token the generated token
-     * @param string $email the email of the invited person
-	 * @param string $role the role
-     * @return object $invitation or error
-     */
-    public function validateInvitation(string $token, string $email, string $role):object
-    {
-		if (!array_key_exists($role, $this->inviteTypes)) {
-			return new WP_Error('invalid_role', 'Invalid role type');
+		// Validate user
+		if (get_current_user_id() !== $userID) {
+			return $this->unauthorized('Invalid user');
 		}
-        $table = $this->wpdb->prefix . $this->tableNames[$role];
 
-        // Get invitation by token and email
-        $invitation = $this->wpdb->get_row($this->wpdb->prepare(
-            "SELECT * FROM $table
-             WHERE invitation_token = %s
-             AND email = %s
-             AND status = 'pending'",
-            $token,
-            $email
-        ));
+		$args = [
+			'user' => $userID,
+			'to_term' => $termID,
+			'taxonomy' => $taxonomy ? jvbNoBase($taxonomy) : null,
+			'status' => $request->get_param('status'),
+			'page' => $request->get_param('page')
+		];
 
-        if (!$invitation) {
-            return new WP_Error('invalid_invitation', 'Invalid invitation token or email');
-        }
+		// Check cache
+		$key = $this->cache->generateKey($args);
+		if ($cached = $this->cache->get($key)) {
+			return $this->success($cached);
+		}
 
-        // Check if expired
-        if (strtotime($invitation->expires_at) < time()) {
-            return new WP_Error('expired_invitation', 'This invitation has expired');
-        }
+		// Get appropriate invitations
+		$result = ($args['to_term'] && $args['taxonomy'])
+			? $this->getTermInvitations($args)
+			: $this->getUserInvitations($args);
 
-        return $invitation;
-    }
+		// Cache result
+		$this->cache->set($key, $result);
 
-    /**
-     * Send invitation email to the new artist
-     * @param string $name The invited person's name
-     * @param string $email The invited person's email
-     * @param string $token The randomly generated password
-     * @param int $inviter_id The User ID of the one inviting
-     * @param int|null $shopID The optional shop ID to be invited to
-     * @return bool Whether or not the invitation was sent successfully
-     */
-    protected function sendInvitationEmail(string $name, string $email, string $token, int $inviter_id, array $terms, string|null $role = null):bool
-    {
-        $inviter = get_userdata($inviter_id);
-        $inviter_name = jvbGetUsername($inviter_id);
-        $inviter_name = $inviter_name ?: $inviter->display_name;
+		return $this->success($result);
+	}
 
-		$siteName = get_bloginfo('name');
+	/**
+	 * Get invitations for a specific term
+	 */
+	protected function getTermInvitations(array $args): array
+	{
+		// Check permission
+		if (!JVB()->roles()->isManager($args['user'], $args['to_term'])) {
+			return $this->forbidden('You cannot view invitations for this ' . $args['taxonomy'])->get_data();
+		}
 
-        $subject = apply_filters('jvbInvitationSubject',
-			sprintf(
-				"%s invited you to join %s!",
-				$inviter_name,
-				$siteName
-			),
-			$inviter_name
+		$perPage = 20;
+		$offset = ($args['page'] - 1) * $perPage;
+
+		// Build query
+		$where = ['to_' . $args['taxonomy'] => $args['to_term']];
+		if ($args['status'] !== 'all') {
+			$where['status'] = $args['status'];
+		}
+
+		// Fluent CustomTable usage
+		$total = $this->table
+			->where($where)
+			->countResults();
+
+		$invitations = $this->table
+			->where($where)
+			->orderBy('created_at')
+			->limit($perPage, $offset)
+			->getResults();
+
+		return [
+			'invitations' => array_map([$this, 'formatInvitation'], $invitations),
+			'total' => $total,
+			'pages' => ceil($total / $perPage),
+			'page' => $args['page'],
+			'per_page' => $perPage
+		];
+	}
+
+	/**
+	 * Get invitations sent by a user
+	 */
+	protected function getUserInvitations(array $args): array
+	{
+		$perPage = 20;
+		$offset = ($args['page'] - 1) * $perPage;
+
+		// Use raw query for JSON search
+		$query = "SELECT * FROM {$this->table->getFullTableName()}
+                  WHERE JSON_SEARCH(inviters, 'one', %s, NULL, '$[*].user_id') IS NOT NULL";
+		$params = [$args['user']];
+
+		if ($args['status'] !== 'all') {
+			$query .= " AND status = %s";
+			$params[] = $args['status'];
+		}
+
+		$query .= " ORDER BY created_at DESC LIMIT %d OFFSET %d";
+		$params = array_merge($params, [$perPage, $offset]);
+
+		$invitations = $this->table->queryResults($query, $params);
+
+		// Get count
+		$countQuery = str_replace('SELECT *', 'SELECT COUNT(*)',
+			substr($query, 0, strrpos($query, 'ORDER BY')));
+		$countParams = array_slice($params, 0, -2);
+		$total = (int) $this->table->queryVar($countQuery, $countParams);
+
+		return [
+			'invitations' => array_map([$this, 'formatInvitation'], $invitations),
+			'total' => $total,
+			'pages' => ceil($total / $perPage),
+			'page' => $args['page'],
+			'per_page' => $perPage
+		];
+	}
+
+	/**
+	 * Create invitation request - queues invitations for processing
+	 */
+	public function createInvitationRequest(WP_REST_Request $request): WP_REST_Response
+	{
+		$userID = $request->get_param('user');
+		$action = $request->get_param('action');
+
+		// Validate user
+		if (get_current_user_id() !== $userID) {
+			return $this->unauthorized('Invalid user');
+		}
+
+		// Handle actions
+		return match($action) {
+			'revoke' => $this->revokeInvite($userID, $request->get_params()),
+			'refresh' => $this->resendInvite($userID, $request->get_params()),
+			default => $this->queueInvitations($userID, $request->get_param('invites'))
+		};
+	}
+
+	protected function queueInvitations(int $userID, array $invites): WP_REST_Response
+	{
+		if (empty($invites)) {
+			return $this->error('No invitations provided');
+		}
+
+		// Validate invitations
+		$validated = $this->validateInvitations($userID, $invites);
+
+		if (empty($validated)) {
+			return $this->error('No valid invitations to send');
+		}
+
+		// Queue using fluent interface
+		$op = JVB()->queue()->add(
+			'invitation_create',
+			$userID,
+			['invitations' => $validated],
+			[
+				'priority' => 'high',
+				'chunk_key' => 'invitations',
+				'chunk_size' => 20
+			]
+		);
+		return $this->queued($op['operation_id']);
+	}
+
+	/**
+	 * Validate and sanitize invitation data
+	 */
+	protected function validateInvitations(int $userID, array $invites): array
+	{
+		return JVB()->invitations()->validateInvitations($userID, $invites);
+	}
+
+
+
+
+	/**
+	 * Revoke an invitation
+	 */
+	protected function revokeInvite(int $userID, array $data): WP_REST_Response
+	{
+		$invitationID = (int) ($data['invitation_id'] ?? 0);
+
+		if (!$invitationID) {
+			return $this->error('Invitation ID required');
+		}
+
+		$op = JVB()->queue()->add(
+			'invitation_revoke',
+			$userID,
+			['invitation_id' => $invitationID],
+			['priority' => 'high']
 		);
 
-        $signup_url = add_query_arg([
-            'invite' => $token,
-            'email' => urlencode($email),
-            'name'  => $name,
-			'role'	=> $role
-        ], wp_registration_url());
+		return $this->queued($op['operation_id']);
+	}
+
+	/**
+	 * Resend an invitation
+	 */
+	protected function resendInvite(int $userID, array $data): WP_REST_Response
+	{
+		$invitationID = (int) ($data['invitation_id'] ?? 0);
+
+		if (!$invitationID) {
+			return $this->error('Invitation ID required');
+		}
+
+		$op = JVB()->queue()->add(
+			'invitation_resend',
+			$userID,
+			['invitation_id' => $invitationID],
+			['priority' => 'high']
+		);
+
+		if (is_wp_error($op)) {
+			return $this->error($op->get_error_message());
+		}
+		return $this->queued($op['operation_id']);
+	}
 
 
-        // Get shop name if applicable
-        $toContentTax = [];
-		if (!empty ($terms)) {
-			foreach ($terms as $taxonomy => $termID) {
+
+	/**
+	 * Format invitation for API response
+	 */
+	protected function formatInvitation(object $invitation): array
+	{
+		$inviters = json_decode($invitation->inviters ?? '[]', true) ?: [];
+
+		$formatted = [
+			'id' => (int) $invitation->id,
+			'name' => $invitation->name,
+			'email' => $invitation->email,
+			'invited_role' => $invitation->invited_role,
+			'status' => $invitation->status,
+			'expires_at' => $invitation->expires_at,
+			'accepted_at' => $invitation->accepted_at ?? null,
+			'created_at' => $invitation->created_at,
+			'inviters' => array_map(fn($inviter) => [
+				'id' => (int) $inviter['user_id'],
+				'name' => jvbGetUsername($inviter['user_id']),
+				'invited_at' => $inviter['invited_at']
+			], $inviters)
+		];
+
+		// Add term information if present
+		foreach (JVB()->roles()->getInvitableTaxonomies() as $taxonomy) {
+			$column = 'to_' . $taxonomy;
+			if (isset($invitation->$column) && $invitation->$column) {
+				$termID = (int) $invitation->$column;
 				$term = get_term($termID, BASE . $taxonomy);
+
 				if ($term && !is_wp_error($term)) {
-					$toContentTax[] = sprintf(
-						"<p>%s has also invited you to join %s. You'll be automatically added to this %s when you register.</p>",
-						$inviter_name,
-						$term->name,
-						$taxonomy
-					);
+					$formatted['term'] = [
+						'id' => $termID,
+						'name' => $term->name,
+						'taxonomy' => $taxonomy
+					];
+					break; // Only show first term
 				}
 			}
 		}
-		$toContentTax = implode(' ', $toContentTax);
 
-        $button = JVB()->email()->button($signup_url, 'Join the Scene!');
-        $link = JVB()->email()->link($signup_url);
-        $signature = JVB()->email()->signature();
-
-		$message = sprintf(
-			'<p>Hi %s!</p>
-            <p>%s has invited you to join them on %s.</p>
-
-            <h2>Interested?</h2>
-            <p>Join in by clicking the button below:</p>
-            %s
-            <p>Or by copying and pasting the link below into your browser:</p>
-            %s
-            <div class="divider"></div>
-            %s
-            <p>This invitation expires in %d days.</p>
-            <p>Ink on, %s</p>
-            %s
-            ',
-			$name,
-			$inviter_name,
-			$siteName,
-			$button,
-			$link,
-			$name,
-			$toContentTax,
-			$this->expiryDays,
-			$signature
-		);
-		$message = apply_filters('jvbInvitationMessage',
-			$message,
-			$name,
-			$inviter_name,
-			$role,
-			$termID,
-			$taxonomy,
-			$toContentTax,
-			$this->expiryDays,
-			$button,
-			$link,
-			$signature,
-		);
-
-
-        $success = JVB()->email()->sendEmail($email, $subject, $message);
-
-
-        if (!$success) {
-            // Log the invitation
-            JVB()->error()->log(
-                'invitation_email',
-                'Invitation not sent',
-                [
-                    'email' => $email,
-                    'inviter_id' => $inviter_id,
-                    'token' => $token
-                ],
-                'info'
-            );
-        }
-
-        return $success;
-    }
-
-    /**
-     * Send revocation email notification
-     * @param string $email the invited person's email
-     * @param string $name the invited person's name
-     * @return bool Whether or not the email was sent
-     */
-    protected function sendRevocationEmail(string $email, string $name):bool
-    {
-		$siteName = get_bloginfo('name');
-        $subject = apply_filters(
-			'jvbInvitationRevokedSubject',
-			sprintf(
-				'[%s] Your invitation has been revoked',
-				$siteName
-			)
-		);
-		$content = apply_filters(
-			'jvbInvitationRevokedMessage',
-			sprintf(
-				'<p>Hey %s,</p>
-				<p>This is to let you know that your invitation to join %s has been revoked.</p>
-				<p>If you believe this was done in error, please contact the person who invited you, the site admin, or try registering yourself.</p>',
-				$name,
-				$siteName
-			),
-			$name
-		);
-
-        $success =  JVB()->email()->sendEmail($email, $subject, $content, 'INVITATION REVOKED');
-        if (!$success) {
-            JVB()->error()->log(
-                'invitation_revoke_email',
-                'Invitation not sent',
-                [
-                    'email' => $email,
-                    'name' => $name,
-                ],
-                'info'
-            );
-        }
-        return $success;
-    }
-
-    /**
-     * Verify an invitation token
-     * @param string $token The randomly generated token
-     * @param string $email The invited person's email
-     * @return bool|object False on failure. Invitation object if success
-     */
-    public function verifyInvitation(string $token, string $email, string $role):bool|object
-    {
-        $invitation = $this->wpdb->get_row($this->wpdb->prepare(
-            "SELECT * FROM {$this->tableNames[$role]}
-            WHERE invitation_token = %s AND email = %s AND status = 'pending' AND expires_at > NOW()",
-            $token,
-            $email
-        ));
-
-        if (!$invitation) {
-            return false;
-        }
-
-        return $invitation;
-    }
-
-    /**
-     * Mark an invitation as accepted
-     * @param string $token The randomly generated token
-     * @param string $email The invited person's email
-     * @param int $user_id The invited person's user ID
-     * @return bool whether or not it was successfully accepted
-     */
-    public function acceptInvitation(string $token, string $email, int $user_id):bool
-    {
-		$role = jvbUserRole($user_id);
-        $invitation = $this->verifyInvitation($token, $email, $role);
-
-        if (!$invitation) {
-            return false;
-        }
-
-        // Update invitation status
-        $this->wpdb->update(
-            $this->tableNames[$role],
-            [
-                'status' => 'accepted',
-                'new_user_id' => $user_id,
-                'accepted_at' => current_time('mysql'),
-                'updated_at' => current_time('mysql')
-            ],
-            ['id' => $invitation->id]
-        );
-
-        // Set user role to artist with can_publish=false (needs verification)
-        $user = get_userdata($user_id);
-        // Set the user's verification status
-        $user->add_cap('skip_moderation', true);
-
-        // If there's a shop to add the artist to, do that now
-        if (!empty($invitation->to_shop)) {
-            JVB()->routes('shopInvite')->addArtistToShop($user_id, $invitation->to_shop);
-        }
-
-        // Notify inviters
-        $this->notifyInvitersOfAcceptance($invitation, $user_id);
-
-        return true;
-    }
-
-    /**
-     * Notify all inviters that the invitation was accepted
-     * @param object $invitation The invitation object
-     * @param int $user_id the newly added user id
-     * @return void
-     */
-    protected function notifyInvitersOfAcceptance(object $invitation, int $user_id):void
-    {
-        $inviters = json_decode($invitation->inviters, true);
-        $user_data = get_userdata($user_id);
-
-        foreach ($inviters as $inviter) {
-            JVB()->notification()->addNotification(
-                $inviter['user_id'],
-                'artist_joined',
-                [
-                    'invited_email' => $invitation->email,
-                    'user_id' => $user_id,
-                    'display_name' => $user_data->display_name
-                ]
-            );
-        }
-    }
-
-    /**
-     * Check if a registered user has a pending invitation. Accept invitation if so
-     * @param int $user_id The user ID to check
-     * @return void
-     */
-    public function checkInvitation(int $user_id):void
-    {
-        $user = get_userdata($user_id);
-
-        if (!$user) {
-            return;
-        }
-
-        // Check if there's a token and email in the request
-        $token = isset($_GET['invite']) ? sanitize_text_field($_GET['invite']) : '';
-        $email = isset($_GET['email']) ? sanitize_email($_GET['email']) : '';
-
-        if ($token && $email && $email === $user->user_email) {
-            $this->acceptInvitation($token, $email, $user_id);
-        }
-    }
-
-    /**
-     * Clean up expired invitations
-     * @return void
-     */
-    public function cleanupExpiredInvitations():void
-    {
-        global $wpdb;
-
-        $wpdb->query($wpdb->prepare(
-            "UPDATE {$this->tableName}
-            SET status = 'expired', updated_at = %s
-            WHERE status = 'pending' AND expires_at < NOW()",
-            current_time('mysql')
-        ));
-    }
-
-    /**
-     * Get invitations sent by a specific user
-     * @param array $args built by buildParams()
-     * @return WP_REST_Response
-     */
-    public function getUserInvitations(array $args):WP_REST_Response
-    {
-        if (!$this->checkUser($args['user'])) {
-            return new WP_REST_Response([
-                'success'   => false,
-                'message'   => 'Invalid user'
-            ]);
-        }
-
-        $key = $this->cache->generateKey($args);
-        $cache = $this->cache->get($key);
-        if ($cache) {
-            return new WP_REST_Response($cache);
-        }
-
-        $per_page = 20;
-
-        // Build query conditions
-        $conditions = [];
-        $params = [];
-
-        $conditions[] = "inviters LIKE %s";
-        $params[] = '%"'.$args['user'].'"%';
-
-        // Filter by status
-        if ($args['status'] !== 'all') {
-            $conditions[] = "status = %s";
-            $params[] = $args['status'];
-        }
-
-        $where = !empty($conditions) ? "WHERE " . implode(' AND ', $conditions) : "";
-
-        // Count total invitations for pagination
-        $count_query = "SELECT COUNT(*) FROM {$this->tableNames[$args['role']]} {$where}";
-        $total = $this->wpdb->get_var($this->wpdb->prepare($count_query, $params));
-
-        // Get paginated invitations
-        $offset = ($args['page'] - 1) * $per_page;
-        $query = "SELECT * FROM {$this->tableNames[$args['role']]} {$where} ORDER BY created_at DESC LIMIT %d OFFSET %d";
-
-        // Add pagination parameters
-        $pagination_params = array_merge($params, [$per_page, $offset]);
-        $invitations = $this->wpdb->get_results($this->wpdb->prepare($query, $pagination_params));
-
-        // Format invitations for response
-        $formatted = [];
-        foreach ($invitations as $invitation) {
-            $formatted[] = $this->formatInvitation($invitation);
-        }
-
-        $return = [
-            'invitations' => $formatted,
-            'total' => (int)$total,
-            'pages' => ceil($total / $per_page),
-            'page' => $args['page'],
-            'per_page' => $per_page
-        ];
-
-        $this->cache->set($key, $return);
-
-        return new WP_REST_Response($return);
-    }
-
-    /**
-     * Get a specific invitation by its ID
-     *
-     * @param int $invitationID The invitation ID to fetch
-     * @param string $role
-     * @return array|WP_Error The formatted invitation or an error
-     */
-    protected function getInvitation(int $invitationID, string $role):array|WP_Error
-    {
-        // Validate invitation ID
-        $invitationID = intval($invitationID);
-        if (!$invitationID) {
-            return new WP_Error('invalid_id', 'Invalid invitation ID');
-        }
-
-        // Try to get from cache first
-        $cached = $this->cache->get($invitationID);
-        if ($cached) {
-            return $cached;
-        }
-
-        // Query the database
-        $invitation = $this->wpdb->get_row($this->wpdb->prepare(
-            "SELECT * FROM {$this->tableNames[$role]} WHERE id = %d",
-            $invitationID
-        ));
-
-        // Return error if not found
-        if (!$invitation) {
-            return new WP_Error('not_found', 'Invitation not found');
-        }
-
-        // Format the invitation for response
-        $formatted = $this->formatInvitation($invitation);
-
-        // Cache the result
-        $this->cache->set($invitationID, $formatted);
-
-        return $formatted;
-    }
-
-    /**
-     * Get invitations for a specific user by their email or user ID
-     *
-     * @param int|string $identifier Either user ID or email of the invited person
-     * @param bool $include_token Whether to include the token in the response
-     * @return array|WP_Error The formatted invitations or an error
-     */
-    public function getInvitationByUser(int|string $identifier):array|WP_Error
-    {
-        // Try to get from cache first
-        $cached = $this->cache->get($identifier);
-        if ($cached) {
-            return $cached;
-        }
-        global $wpdb;
-
-        // Determine if we have a user ID or email
-        if (is_numeric($identifier)) {
-            // We have a user ID
-            $userID = intval($identifier);
-
-            // Query by user ID
-            $invitation = $wpdb->get_row($wpdb->prepare(
-                "SELECT * FROM {$this->tableName} WHERE new_user_id = %d",
-                $userID
-            ));
-        } else {
-            // We have an email
-            $email = sanitize_email($identifier);
-            if (!is_email($email)) {
-                return new WP_Error('invalid_email', 'Invalid email address');
-            }
-
-            // Query by email
-            $invitation = $wpdb->get_row($wpdb->prepare(
-                "SELECT * FROM {$this->tableName} WHERE email = %s",
-                $email
-            ));
-        }
-
-        // Return error if not found
-        if (!$invitation) {
-            return new WP_Error('not_found', 'No invitations found for this user');
-        }
-
-        // Format the invitation for response
-        $formattedInvitation = $this->formatInvitation($invitation);
-
-        $this->cache->set($identifier, $formattedInvitation);
-
-        return $formattedInvitation;
-    }
-
-    /**
-     * Format invitation for API response
-     * @param object $invitation The invitation object
-     * @param bool $include_token whether or not to include the token in response
-     * @return array The formatted invitation
-     */
-    protected function formatInvitation(object $invitation, bool $include_token = false):array
-    {
-        // Parse inviters JSON
-        $inviters = json_decode($invitation->inviters ?? '[]', true) ?: [];
-
-        // Format inviters with names
-        $inviter_details = [];
-        foreach ($inviters as $inviter_id) {
-            $inviter_details[] = [
-                'id' => $inviter_id,
-                'name' => jvbGetUsername($inviter_id)
-            ];
-        }
-
-        // Build formatted invitation
-        $formatted = [
-            'id' => $invitation->id,
-            'name'  => $invitation->name,
-            'email' => $invitation->email,
-            'status' => $invitation->status,
-            'expires_at' => $invitation->expires_at,
-            'accepted_at' => $invitation->accepted_at,
-            'created_at' => $invitation->created_at,
-            'updated_at' => $invitation->updated_at,
-            'inviters' => $inviters
-        ];
-
-        // Include shop if assigned
-        if (!empty($invitation->to_shop)) {
-            $shop = get_term($invitation->to_shop, BASE . 'shop');
-            if ($shop && !is_wp_error($shop)) {
-                $formatted['shop'] = [
-                    'id' => $shop->term_id,
-                    'name' => $shop->name
-                ];
-            }
-        }
-
-        // Include token if needed (only for validation)
-        if ($include_token) {
-            $formatted['token'] = $invitation->invitation_token;
-        }
-
-        // Add registration URL for convenience
-        $formatted['registration_url'] = add_query_arg([
-            'token' => $invitation->invitation_token,
-            'email' => urlencode($invitation->email)
-        ], home_url('/register/'));
-
-        return $formatted;
-    }
-
-    /**
-     * @param WP_Error|array $result The WP_Error to replace, if this is the operation type we're looking for
-     * @param object $operation The operation object
-     * @param array $data The data to process
-     * @return WP_Error|array WP_Error or array of processed data
-     *
-     */
-    public function processOperation(WP_Error|array $result, object $operation, array $data):array|WP_Error
-    {
-        switch ($operation->type) {
-            case 'invitation_create':
-                return $this->processInvitations($data, $operation->user_id);
-            case 'invitation_revoke':
-                return $this->revokeInvite(
-                    $data['invited']
-                );
-        }
-        return $result;
-    }
-
-    /**
-     * Process a batch of invitations with transaction support
-     *
-     * @param array $data Array of invitation data ['role' => $invites ]
-     * @param int $user_id User ID of the inviter
-     * @return array Result data with success/failure information
-     */
-    public function processInvitations(array $data, int $user_id):array
-    {
-        if (!$this->checkUser($user_id)) {
-            return [
-                'success'   => false,
-                'result'   => 'Invalid User',
-            ];
-        }
-
-        // Start transaction
-        $this->wpdb->query('START TRANSACTION');
-
-        $results = [
-            'success' => [],
-            'failed' => []
-        ];
-
-        try {
-            foreach ($data as $role => $invitations) {
-                foreach ($invitations as $invite) {
-                    if (!$invite['invited_name'] || !$invite['invited_email']) {
-                        $results['failed'][] = [
-                            'email' => $invite['invited_email'],
-                            'name' => $invite['invited_name'],
-                            'reason' => 'Invalid name or email'
-                        ];
-                        continue;
-                    }
-
-                    if ($invite['to_term'] && !$this->checkTerm($invite)) {
-                        $results['failed'][] = [
-                            'email' => $invite['invited_email'],
-                            'name'  => $invite['invited_name'],
-                            'reason'    => 'Invalid taxonomy to add to'
-                        ];
-                    }
-
-                    // Create invitation (modify your existing method to avoid sending emails yet)
-                    $result = $this->createInvitation($invite['invited_name'], $invite['invited_email'], $user_id, $role, $invite['to_term'], $invite['taxonomy'], false);
-
-                    if (is_wp_error($result)) {
-                        $results['failed'][] = [
-                            'email' => $invite['invited_email'],
-                            'name' => $invite['invited_name'],
-                            'reason' => $result->get_error_message()
-                        ];
-                    } else {
-                        $results['success'][] = [
-                            'email' => $invite['invited_email'],
-                            'name' => $invite['invited_name'],
-                            'id' => $result['id'],
-							'to_term'	=> $invite['to_term'],
-							'taxonomy'	=> $invite['taxonomy'],
-							'role'		=> $role,
-                            'expires_at' => $result['expires_at']
-                        ];
-                    }
-                }
-            }
-
-            // If we've processed at least one invitation successfully, commit
-            if (!empty($results['success'])) {
-                $this->wpdb->query('COMMIT');
-
-                // Now send emails for successful invitations
-                foreach ($results['success'] as $invitation) {
-                    $this->sendInvitationEmail(
-                        $invitation['name'],
-                        $invitation['email'],
-                        $invitation['token'],
-                        $user_id,
-                        [$invitation['taxonomy'] => $invitation['to_term']],
-						$invitation['role']
-                    );
-                }
-            } else {
-                // No successful invitations, roll back
-                $this->wpdb->query('ROLLBACK');
-            }
-
-            return [
-				'success'	=> count($results['success']) > count($results['failed']),
-				'results'	=> $results
-			];
-
-        } catch (Exception $e) {
-            // Handle error and roll back transaction
-            $this->wpdb->query('ROLLBACK');
-
-            JVB()->error()->log(
-                'invitation_create',
-                'Error processing batch invitations: ' . $e->getMessage(),
-                [
-                    'user_id' => $user_id,
-                    'error' => $e->getMessage()
-                ],
-                'error'
-            );
-
-            return [
-                'success' => false,
-                'result' 	=> [
-					'failed' => $invitations,
-                	'error' => $e->getMessage()
-				]
-            ];
-        }
-    }
-
-	public function modifyLoginLabels(array $labels, array $get_params): array
-	{
-		// Only modify if invitation params present
-		if (!array_key_exists('invite', $get_params) || !array_key_exists('email', $get_params)) {
-			return $labels;
-		}
-		$email = sanitize_email($get_params['email']);
-		$token = sanitize_text_field($get_params['invite']);
-		$user = email_exists($email);
-		if (!$user) {
-			return $labels;
-		}
-		$role = jvbUserRole($user);
-		// Get invitation data
-		$data = $this->verifyInvitation(
-			$token,
-			$email,
-			$role,
-		);
-
-		if (!$data) {
-			return $labels;
-		}
-
-		// Build custom message
-		$inviters = json_decode($data->inviters, true);
-		$name = $data->name;
-		$names = array_map(function($inviter) {
-			$artist = jvbContentFromUser((int)$inviter['user_id']);
-			return $artist['name'] ?: $artist['display_name'];
-		}, $inviters);
-
-		$message = count($names) > 1
-			? 'are already here, and have invited you to join in!'
-			: ' is already here, and invited you to join in!';
-
-		// Modify labels
-		$labels['title'] = 'Join the Scene, ' . $data->name;
-		$labels['description'] = [jvbCommaList($names) . ' ' . $message];
-
-		return $labels;
+		return $formatted;
 	}
+
 }

--
Gitblit v1.10.0