<?php
|
namespace JVBase\managers\queue\executors;
|
|
use JVBase\managers\CustomTable;
|
use JVBase\managers\queue\Executor;
|
use JVBase\managers\queue\Operation;
|
use JVBase\managers\queue\Progress;
|
use JVBase\managers\queue\Result;
|
use JVBase\managers\RoleManager;
|
use WP_Error;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
class InvitationExecutor implements Executor
|
{
|
protected CustomTable $table;
|
protected RoleManager $roleManager;
|
protected int $expiryDays = 14;
|
|
public function __construct()
|
{
|
$this->table = CustomTable::for('invitations');
|
$this->roleManager = new RoleManager();
|
}
|
|
public function execute(Operation $operation, Progress $progress): Result
|
{
|
return match($operation->type) {
|
'invitation_create' => $this->processCreation($operation, $progress),
|
'invitation_resend' => $this->processResend($operation, $progress),
|
'invitation_revoke' => $this->processRevoke($operation, $progress),
|
default => Result::fail("Unknown operation type: {$operation->type}")
|
};
|
}
|
|
protected function processCreation(Operation $operation, Progress $progress): Result
|
{
|
$invitations = $operation->requestData['invitations'] ?? [];
|
$userID = $operation->userId;
|
|
$results = [
|
'success' => [],
|
'failed' => []
|
];
|
|
$this->table->startTransaction();
|
|
try {
|
foreach ($invitations as $index => $invite) {
|
$progress->track($index);
|
|
$result = $this->createSingleInvitation(
|
$invite['name'],
|
$invite['email'],
|
$userID,
|
$invite['invited_role'],
|
$invite['to_term'] ?? null,
|
$invite['taxonomy'] ?? null
|
);
|
|
if (is_wp_error($result)) {
|
$results['failed'][] = [
|
'email' => $invite['email'],
|
'name' => $invite['name'],
|
'reason' => $result->get_error_message()
|
];
|
} else {
|
$results['success'][] = $result;
|
}
|
}
|
|
if (!empty($results['success'])) {
|
$this->table->commit();
|
|
// Send emails after successful commit
|
foreach ($results['success'] as $invitation) {
|
$this->sendInvitationEmail($invitation, $userID);
|
}
|
} else {
|
$this->table->rollback();
|
}
|
|
return Result::success($results, [
|
'sent' => count($results['success']),
|
'failed' => count($results['failed'])
|
]);
|
|
} catch (\Exception $e) {
|
$this->table->rollback();
|
return Result::fail($e->getMessage());
|
}
|
}
|
|
protected function createSingleInvitation(
|
string $name,
|
string $email,
|
int $inviterID,
|
string $invitedRole,
|
?int $termID = null,
|
?string $taxonomy = null
|
): WP_Error|array {
|
|
// Check for existing invitation
|
$existing = $this->table->get([
|
'email' => $email,
|
'invited_role' => $invitedRole
|
]);
|
|
$token = wp_generate_password(32, false);
|
$expiresAt = date('Y-m-d H:i:s', strtotime("+{$this->expiryDays} days"));
|
|
if ($existing) {
|
// Update existing
|
$inviters = json_decode($existing->inviters, true) ?: [];
|
|
$inviterExists = false;
|
foreach ($inviters as &$inviter) {
|
if ($inviter['user_id'] == $inviterID) {
|
$inviterExists = true;
|
$inviter['invited_at'] = current_time('mysql');
|
break;
|
}
|
}
|
|
if (!$inviterExists) {
|
$inviters[] = [
|
'user_id' => $inviterID,
|
'invited_at' => current_time('mysql')
|
];
|
}
|
|
$updateData = [
|
'inviters' => json_encode($inviters),
|
'status' => 'pending',
|
'expires_at' => $expiresAt
|
];
|
|
if ($termID && $taxonomy) {
|
$updateData['to_' . $taxonomy] = $termID;
|
}
|
|
if ($existing->status === 'expired') {
|
$updateData['invitation_token'] = $token;
|
} else {
|
$token = $existing->invitation_token;
|
}
|
|
$this->table->update($updateData, ['id' => $existing->id]);
|
$invitationID = $existing->id;
|
|
} else {
|
// Create new
|
$insertData = [
|
'name' => sanitize_text_field($name),
|
'email' => $email,
|
'invitation_token' => $token,
|
'invited_role' => $invitedRole,
|
'status' => 'pending',
|
'inviters' => json_encode([[
|
'user_id' => $inviterID,
|
'invited_at' => current_time('mysql')
|
]]),
|
'expires_at' => $expiresAt
|
];
|
|
if ($termID && $taxonomy) {
|
$insertData['to_' . $taxonomy] = $termID;
|
}
|
|
$invitationID = $this->table->insert($insertData);
|
}
|
|
return [
|
'id' => $invitationID,
|
'token' => $token,
|
'expires_at' => $expiresAt,
|
'to_term' => $termID,
|
'taxonomy' => $taxonomy,
|
'invited_role' => $invitedRole,
|
'email' => $email,
|
'name' => $name
|
];
|
}
|
|
protected function sendInvitationEmail(array $invitation, int $inviterID): void
|
{
|
$terms = [];
|
if ($invitation['to_term'] && $invitation['taxonomy']) {
|
$terms[$invitation['taxonomy']] = $invitation['to_term'];
|
}
|
|
// This would call your email service
|
do_action(
|
BASE . 'send_invitation_email',
|
$invitation['name'],
|
$invitation['email'],
|
$invitation['token'],
|
$inviterID,
|
$terms,
|
$invitation['invited_role']
|
);
|
}
|
|
protected function processResend(Operation $operation, Progress $progress): Result
|
{
|
$invitationID = $operation->requestData['invitation_id'] ?? 0;
|
$userID = $operation->userId;
|
|
if (!$invitationID) {
|
return Result::fail('Invitation ID required');
|
}
|
|
// Get invitation
|
$invitation = $this->table->get(['id' => $invitationID]);
|
|
if (!$invitation) {
|
return Result::fail('Invitation not found');
|
}
|
|
// Verify status
|
if (!in_array($invitation->status, ['pending', 'expired'])) {
|
return Result::fail('Only pending or expired invitations can be resent');
|
}
|
|
// Check if user is an inviter
|
$inviters = json_decode($invitation->inviters, true) ?: [];
|
$isInviter = false;
|
|
foreach ($inviters as &$inviter) {
|
if ($inviter['user_id'] == $userID) {
|
$isInviter = true;
|
$inviter['invited_at'] = current_time('mysql');
|
break;
|
}
|
}
|
|
if (!$isInviter) {
|
return Result::fail('You are not authorized to resend this invitation');
|
}
|
|
// Generate new token and expiry
|
$token = wp_generate_password(32, false);
|
$expiresAt = date('Y-m-d H:i:s', strtotime("+{$this->expiryDays} days"));
|
|
// Update invitation
|
$this->table->update(
|
[
|
'invitation_token' => $token,
|
'status' => 'pending',
|
'expires_at' => $expiresAt,
|
'inviters' => json_encode($inviters)
|
],
|
['id' => $invitation->id]
|
);
|
|
// Build term data for email
|
$terms = [];
|
foreach ($this->roleManager->getInvitableTaxonomies() as $taxonomy) {
|
$column = 'to_' . $taxonomy;
|
if (isset($invitation->$column) && $invitation->$column) {
|
$terms[$taxonomy] = (int) $invitation->$column;
|
}
|
}
|
|
// Send email
|
do_action(
|
BASE . 'send_invitation_email',
|
$invitation->name,
|
$invitation->email,
|
$token,
|
$userID,
|
$terms,
|
$invitation->invited_role
|
);
|
|
return Result::success([
|
'message' => 'Invitation resent successfully',
|
'expires_at' => $expiresAt
|
]);
|
}
|
|
protected function processRevoke(Operation $operation, Progress $progress): Result
|
{
|
$invitationID = $operation->requestData['invitation_id'] ?? 0;
|
$userID = $operation->userId;
|
|
if (!$invitationID) {
|
return Result::fail('Invitation ID required');
|
}
|
|
// Get invitation
|
$invitation = $this->table->get(['id' => $invitationID]);
|
|
if (!$invitation) {
|
return Result::fail('Invitation not found');
|
}
|
|
// Can only revoke pending/expired
|
if (!in_array($invitation->status, ['pending', 'expired'])) {
|
return Result::fail('Only pending or expired invitations can be revoked');
|
}
|
|
// Check if user is an inviter
|
$inviters = json_decode($invitation->inviters, true) ?: [];
|
$isInviter = false;
|
$updatedInviters = [];
|
|
foreach ($inviters as $inviter) {
|
if ($inviter['user_id'] == $userID) {
|
$isInviter = true;
|
} else {
|
$updatedInviters[] = $inviter;
|
}
|
}
|
|
if (!$isInviter) {
|
return Result::fail('You are not authorized to revoke this invitation');
|
}
|
|
// If other inviters remain, just update list
|
if (!empty($updatedInviters)) {
|
$this->table->update(
|
['inviters' => json_encode($updatedInviters)],
|
['id' => $invitation->id]
|
);
|
|
return Result::success([
|
'message' => 'You have been removed from the inviters list',
|
'fully_revoked' => false
|
]);
|
}
|
|
// No inviters left, revoke completely
|
$this->table->update(
|
['status' => 'revoked'],
|
['id' => $invitation->id]
|
);
|
|
// Send revocation email
|
do_action(
|
BASE . 'send_revocation_email',
|
$invitation->email,
|
$invitation->name
|
);
|
|
return Result::success([
|
'message' => 'Invitation revoked successfully',
|
'fully_revoked' => true
|
]);
|
}
|
}
|