route = $content; $this->config = $config; $this->cache_name = $content; parent::__construct(); global $wpdb; $this->wpdb = $wpdb; if (jvbCheck('invitable', $this->config)) { foreach ($this->config['for_content'] as $c) { $this->tables[$c] = BASE .'invitations_'.$c; } } $this->action = 'dash-'; add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3); } public function registerRoutes() { register_rest_route($this->namespace, "/{$this->route}", [ [ 'methods' => 'GET', 'callback' => [$this, 'handleGET'], 'permission_callback' => [$this, 'checkPermission'], ], [ 'methods' => 'POST', 'callback' => [$this, 'handlePOST'], 'permission_callback' => [$this, 'checkPermission'], ] ]); if (jvbCheck('invitable', $this->config)) { register_rest_route($this->namespace, "/{$this->route}/members", [ [ 'methods' => 'GET', 'callback' => [$this, 'getInvitations'], 'permission_callback' => [$this, 'checkPermission'], ], [ 'methods' => 'POST', 'callback' => [$this, 'handleActions'], 'permission_callback' => [$this, 'checkPermission'], ] ]); } if (jvbCheck('verify_entry', $this->config)) { register_rest_Route($this->namespace, "/{$this->route}/requests", [ [ 'methods' => 'POST', 'callback' => [$this, 'handleInvite'], 'permission_callback' => [$this, 'checkPermission'] ] ]); } } public function checkPermission(WP_REST_Request $request):bool { if ($this->route === 'options' && !current_user_can('manage_options')) { return false; } elseif (array_key_exists('is_owned_by', $this->config)) { $term = $request->get_param('term_id'); if (!$this->checkTerm([ 'term_id' => $term, 'taxonomy' => $this->route ])) { return false; } if (!current_user_can('manage_'.$this->route.'_'.$term)) { return false; } } return parent::checkPermission($request); } public function handleGET(WP_REST_Request $request):WP_REST_Response { } public function handlePOST(WP_REST_Request $request):WP_REST_Response { $data = $request->get_params(); $user = $data['user']; if (!$this->checkUser($user) || !$this->userCheck($user)) { return new WP_REST_Response([ 'success' => false, 'message' => 'Looks like you may not be who you say you are...' ]); } if ($this->route === 'shop' && !$this->checkTerm([ 'term_id' => $data['shop'], 'taxonomy' => $this->route ])) { return new WP_REST_Response([ 'success' => false, 'message' => 'This shop doesn\'t exist?' ]); } $queue = JVB()->queue(); unset($data['user']); $operationID = $data['id']; unset($data['id']); $queue->queueOperation( $this->route.'_update', $user, $data, [ 'operation_id' => $operationID, 'priority' => 'high', ] ); return new WP_REST_Response([ 'success' => true, 'message' => 'Successfully queued for processing' ]); } public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array { switch ($operation->type) { case $this->route.'_update': return $this->handleUpdateOperation($operation->user_id, $data); default: return $result; } } protected function handleUpdateOperation(int $userID, array $data):WP_Error|array { if ($this->route === 'options') { if (!user_can($userID, 'manage_options')) { return [ 'success' => false, 'error' => 'User cannot change options' ]; } $meta = Meta::forOptions($this->route); } else { $termID = (int) $data['term_id']; if (!user_can($userID, 'manage_'.$this->route.'_'.$termID)) { return [ 'success' => false, 'error' => 'User cannot manage this '.$this->route ]; } $meta = Meta::forTerm($termID); } $results = []; $allowed = array_filter($data, function ($v, $k) { return array_key_exists($v, $this->config['fields']??[]); }, ARRAY_FILTER_USE_BOTH); foreach ($allowed as $name => $value) { if (empty($value)) { $results[] = $meta->delete($name); } else { $results[] = $meta->set($name, $value); } } //Allow plugins & themes to process extra data here //Example: custom image generation logic $results = array_merge($results, apply_filters('jvb_'.$this->route.'_update_extra', [], $data)); return [ 'success' => true, 'message' => $this->route.' successfully updated', 'results' => $results ]; } public function getInvitations(WP_REST_Request $request):WP_REST_Response { $termID = (int) $request->get_param('term_id'); if (!$this->checkTerm(['term_id' => $termID, 'taxonomy' => $this->route])) { return new WP_REST_Response([ 'success' => false, 'message' => 'Invalid shop' ]); } $status = $request->get_param('status') ?? 'all'; $page = $request->get_param('page') ?? 1; $role = $request->get_param('content') ?? $this->config['for_content'][0]; return JVB()->routes('invites')->getTermInvitations(['to_term' => $termID, 'status' => $status, 'page' => $page, 'taxonomy' => $this->route, 'role' => $role]); } public function handleActions(WP_REST_Request $request):WP_REST_Response { $action = $request->get_param('action'); $termID = (int)$request->get_param('term_id'); $userID = (int)$request->get_param('user'); if (!$this->checkTerm(['term_id' => $termID, 'taxonomy' => $this->route]) || !$this->checkUser($userID)) { return new WP_REST_Response([ 'success' => false, 'message' => 'Invalid shop or user' ]); } switch ($action) { case 'add_user': $return = $this->addUserToTerm($userID, $termID); break; case 'remove_user': $return = $this->removeUserFromTerm($userID, $termID); break; case 'add_invitation': $invitation = JVB()->routes('invites')->createInvitation( sanitize_text_field($request->get_param('name')), sanitize_email($request->get_param('email')), $userID, sanitize_text_field($request->get_param('role')) ?? $this->config['for_content'][0], $termID, $this->route ); if (is_wp_error($invitation)) { $return = [ 'success' => false, 'message' => $invitation->get_error_message() ]; }else { $return = [ 'success' => true, 'message' => 'Invitation sent successfully' ]; } break; case 'remove_invitation': $targetUser = sanitize_text_field($request->get_param('target_user')); $return = $this->removeTermInvite($userID, $termID, $targetUser); break; case 'process_request': $request_id = sanitize_text_field($request->get_param('request_id')); $decision = $request->get_param('decision'); $notes = $request->get_param('notes'); $return = $this->processTermRequest($request_id, $decision, $notes, $userID); break; default: $return = [ 'success' => false, 'message' => 'invalid action' ]; } return new WP_REST_Response($return); } public function handleInvite(WP_REST_Request $request):WP_REST_Response { } public function requestEntry(int $userID, string $role, int $termID, string $taxonomy):bool { if (!$this->checkUser($userID) || !$this->checkTerm(['term_id' => $termID, 'taxonomy' => $taxonomy])) { return false; } $tableName = $this->wpdb->prefix . BASE . $role . '_' . $taxonomy . '_requests'; //Check if request already exists $existing = $this->wpdb->get_var($this->wpdb->prepare( "SELECT id FROM {$tableName} WHERE user_id = %d AND term_id = %d", $userID, $termID )); if ($existing) { return false; } //get the user's profile id $profileID = get_user_meta($userID, BASE.'profile_link', true); if (!$profileID) { return false; } $result = $this->wpdb->insert( $tableName, [ 'user_id' => $userID, 'content_id' => $profileID, 'term_id' => $termID, 'status' => 'requested', 'created_date' => current_time('mysql') ] ); if ($result === false) { return false; } $this->notifyManagers($termID, $userID, $profileID); return true; } protected function notifyManagers(int $termID, int $userID, int $profileID):void { if (!$this->checkTerm(['term_id' => $termID, 'taxonomy' => $this->route]) || !$this->checkUser($userID)) { return; } $termMeta = Meta::forTerm($termID); $managers = explode(',', $termMeta->get('managers')); $owner = explode(',', $termMeta->get('owner')); $owners = array_unique(array_merge($managers, $owner)); //Get requester's info $userName = get_the_title($profileID); $termName = get_term($termID, BASE.$this->route)->name; //Notify managers JVB()->notification()->addNotification( $owners, 'entry_request', [ 'profile_id' => $profileID, 'user_name' => $userName, 'user_id' => $userID, 'term_id' => $termID, 'term_name' => $termName ] ); } }