| | |
| | | |
| | | |
| | | use DateTime; |
| | | use JVBase\base\Site; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\managers\CustomTable; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | protected array $tables =[]; |
| | | protected int $expiresAt = 21; //days for request |
| | | protected int $requiredVotes = 3; //Number of votes before a term is approved |
| | | protected bool $hasApproval = false; |
| | | public function __construct() |
| | | { |
| | | $this->defineTables(); |
| | | if (empty($this->tables)) { |
| | | return; |
| | | } |
| | | |
| | | $this->hasApproval = Site::membership() && Site::membership()->has('member_verified'); |
| | | if ($this->hasApproval) { |
| | | add_action('user_register', [$this, 'handleRegistration'], 10, 2); |
| | | } |
| | | |
| | | add_action('jvb_cleanup_expired_approvals', [$this, 'cleanupExpired']); |
| | | } |
| | | |
| | | protected function defineTables():void |
| | |
| | | $types = Registrar::getFeatured('approve_new'); |
| | | foreach ($types as $type) { |
| | | $requests = CustomTable::for("approval_{$type}_requests"); |
| | | $registrar = Registrar::getInstance($type); |
| | | |
| | | $requests->setColumns([ |
| | | 'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT', |
| | |
| | | ]); |
| | | $base = BASE; |
| | | $requests->setConstraints([ |
| | | "CONSTRAINTS `{$base}{$type}_approval_requester` FOREIGN KEY (`user_id`) |
| | | "CONSTRAINT `{$base}{$type}_approval_requester` FOREIGN KEY (`user_id`) |
| | | REFERENCES `{$requests->getUserTable()}` (`ID`) ON DELETE CASCADE", |
| | | "CONSTRAINTS `{$base}{$type}_approval_parent_term` FOREIGN KEY (`parent_id`) |
| | | "CONSTRAINT `{$base}{$type}_approval_parent_term` FOREIGN KEY (`parent_id`) |
| | | REFERENCES `{$requests->getTermTable()}` (`term_id`) ON DELETE CASCADE" |
| | | ]); |
| | | $requests->defineTable(); |
| | |
| | | |
| | | $votes->setConstraints([ |
| | | "CONSTRAINT `{$base}{$type}_user_approval_request` FOREIGN KEY (`request_id`) |
| | | REFERENCES `{$requests->getFullTableName()} (`id`) ON DELETE CASCADE", |
| | | REFERENCES `{$requests->getFullTableName()}` (`id`) ON DELETE CASCADE", |
| | | "CONSTRAINT `{$base}{$type}_user_approval_voter` FOREIGN KEY (`user_id`) |
| | | REFERENCES `{$votes->getUserTable()}` (`ID`) ON DELETE CASCADE" |
| | | ]); |
| | |
| | | ]; |
| | | } |
| | | } |
| | | /** |
| | | * Handler for user registration |
| | | * |
| | | * @param int $user_id New user ID |
| | | * @param object $user the new user object |
| | | * |
| | | * @return void |
| | | */ |
| | | public function handleRegistration(int $user_id, object $user): void |
| | | { |
| | | $registrar = Registrar::getInstance(jvbUserRole($user_id)); |
| | | |
| | | if ($registrar && $registrar->hasFeature('approve_new')) { |
| | | $user->add_cap('skip_moderation', false); |
| | | $this->createApproval($user_id, $registrar->getSlug(), $user->display_name); |
| | | } |
| | | } |
| | | public function canApprove(int $userID, string $type):bool |
| | | { |
| | | $type = jvbNoBase($type); |
| | |
| | | return $this->response(false, 'Invalid vote'); |
| | | } |
| | | |
| | | |
| | | |
| | | $response = $votes->findOrCreate([ |
| | | 'request_id' => $request_id, |
| | | 'user_id' => $userID |
| | |
| | | if (!$response) { |
| | | return $this->response(false, 'Could not store vote for some reason.'); |
| | | } |
| | | $approvers = $request['approved_by']; |
| | | $rejectors = $request['rejected_By']; |
| | | $both = array_merge($approvers, $rejectors); |
| | | |
| | | //See if the user has already voted |
| | | //If the user is changing their vote, proceed. If it's the same, we can bail early. |
| | | if (in_array($userID, $both)) { |
| | | switch ($vote) { |
| | | case 'reject': |
| | | if (in_array($userID, $approvers)) { |
| | | unset($approvers[array_search($userID, $approvers)]); |
| | | } else { |
| | | return $this->response(true, 'You already voted this way.'); |
| | | } |
| | | break; |
| | | case 'approve': |
| | | if (in_array($userID, $rejectors)) { |
| | | unset($rejectors[array_search($userID, $rejectors)]); |
| | | } else { |
| | | return $this->response(true, 'You already voted this way.'); |
| | | } |
| | | } |
| | | } |
| | | |
| | | switch ($vote) { |
| | | case 'reject': |
| | | $rejectors[] = $userID; |
| | | $request['rejections']++; |
| | | break; |
| | | case 'approve': |
| | | $approvers[] = $userID; |
| | | $request['approvals']++; |
| | | break; |
| | | default: |
| | |
| | | } |
| | | $updated = $requests->update([ |
| | | 'rejections'=> $request['rejections'], |
| | | 'approvals' => $request['approvals'] |
| | | 'approvals' => $request['approvals'], |
| | | 'rejected_by' => $rejectors, |
| | | 'approved_by' => $approvers |
| | | ], |
| | | [ |
| | | 'request_id' => $request_id |
| | |
| | | $updatedID = $term['term_id']; |
| | | } |
| | | } elseif ($registrar->getType() === 'user') { |
| | | |
| | | $user = get_userdata($request['user_id']); |
| | | $user->add_cap('skip_moderation', true); |
| | | update_user_meta($request['user_id'], BASE.'verification_date', current_time('mysql')); |
| | | } |
| | | |
| | | $updates = [ |
| | |
| | | ]); |
| | | |
| | | |
| | | JVB()->notification()->addNotification($request['user_id'], $type.'approved', null, 'Your suggestion "'.$request['name'].'" was denied.'); |
| | | JVB()->notification()->notify($request['user_id'], $type.'approved', null, 'Your suggestion "'.$request['name'].'" was denied.'); |
| | | } |
| | | public function finalizeDenial(int $request_id, string $type):void |
| | | { |
| | |
| | | 'request_id' => $request_id |
| | | ]); |
| | | |
| | | JVB()->notification()->addNotification($request['user_id'], $type.'denied', null, 'Your suggestion "'.$request['name'].'" was denied.'); |
| | | JVB()->notification()->notify($request['user_id'], $type.'denied', null, 'Your suggestion "'.$request['name'].'" was denied.'); |
| | | } |
| | | |
| | | public function getApprovalRequests(int $userID, ?string $type = null):array |
| | |
| | | ] |
| | | ]; |
| | | } |
| | | |
| | | /** |
| | | * Create artist approval request |
| | | * |
| | | * @param int $user_id User ID to be approved |
| | | * |
| | | * @return int|false Request ID or false on failure |
| | | */ |
| | | /** |
| | | * Create artist approval request - REFACTORED |
| | | */ |
| | | public function createArtistApprovalRequest(int $user_id): int|false |
| | | { |
| | | $userRole = jvbUserRole($user_id); |
| | | $table = $this->tables[$userRole]['requests']??false; |
| | | if (!$table) { |
| | | return false; |
| | | } |
| | | return $table->transaction(function($table) use ($user_id) { |
| | | // Check for existing request |
| | | $existing = $table->where(['user_id' => $user_id])->first(); |
| | | |
| | | if ($existing) { |
| | | return $existing->id; |
| | | } |
| | | |
| | | $user_data = get_userdata($user_id); |
| | | |
| | | return $table->create([ |
| | | 'user_id' => $user_id, |
| | | 'status' => 'pending', |
| | | 'expires_at' => date('Y-m-d H:i:s', strtotime('+30 days')), |
| | | 'current_approvals' => 0, |
| | | 'current_rejections' => 0, |
| | | 'required_approvals' => 3, // From config |
| | | 'approved_by' => json_encode([]), |
| | | 'rejected_by' => json_encode([]), |
| | | ]); |
| | | }); |
| | | } |
| | | |
| | | public function getRequest(int $requestID, string $type):array|false |
| | | { |
| | | $table = $this->requests($type); |
| | | if (!$table) { |
| | | return false; |
| | | } |
| | | return $table->get(['id' => $requestID])??false; |
| | | } |
| | | public function getVotes(int $requestID, string $type):array|false |
| | | { |
| | | $table = $this->votes($type); |
| | | if (!$table) { |
| | | return false; |
| | | } |
| | | return $table->getMany(['request_id' => $requestID])??false; |
| | | } |
| | | public function cleanupExpired():void |
| | | { |
| | | $now = current_time('mysql'); |
| | | foreach ($this->tables as $type => $tables) { |
| | | $tables['requests']->query( |
| | | "UPDATE {table} |
| | | SET status = 'expired' |
| | | WHERE status = 'pending' |
| | | AND expires_at <%s", |
| | | [$now] |
| | | ); |
| | | } |
| | | } |
| | | } |