cache_name = 'itsme'; parent::__construct(); $this->action = 'itsme'; } public function registerRoutes():void { if (!current_user_can('manage_options')) { return; } register_rest_route($this->namespace, '/myster', [ [ 'methods' => 'GET', 'callback' => [$this, 'getItems'], 'permission_callback' => [$this, 'checkPermission'] ], [ 'methods' => 'POST', 'callback' => [$this, 'updateItems'], 'permission_callback' => [$this, 'checkPermission'] ] ]); register_rest_route($this->namespace, '/admin-action', [ [ 'methods' => 'POST', 'callback' => [$this, 'adminAction'], 'permission_callback' => [$this, 'checkPermission'] ] ]); } /** * @param WP_REST_Request $request The Request object * * @return bool whether or not we can proceed */ public function checkPermission(WP_REST_Request $request):bool { if (!current_user_can('manage_options')) { return false; } $this->verifyNonce($request, 'wp_rest'); if ($this->action!=='') { $this->verifyNonce($request, $this->action . get_current_user_id(), $request->get_header('action_nonce')); } return true; } /** * Handles admin actions from the custom WP Admin pages. * Extended by other managers that register admin subpages * @param WP_REST_Request $request * * @return WP_REST_Response */ public function adminAction(WP_REST_Request $request):WP_REST_Response { error_log('Request Params: '.print_r($request->get_param('action'), true)); return apply_filters( BASE.'admin_action_filter', new WP_REST_Response([ 'success' => false, 'message' => 'No filters found' ]), $request, sanitize_text_field($request->get_param('action')) ); } /** * @param WP_REST_Request $request Request object * * @return WP_REST_Response */ public function updateItems(WP_REST_Request $request):WP_REST_Response { error_log('Received Request: '.print_r($request->get_params(), true)); $content = $request->get_param('content'); if (!$this->checkContent($content, true)) { return new WP_REST_Response([ 'sucess' => false, 'message' => 'Invalid attempt' ]); } $data = $request->get_param('data'); if (empty($data)) { return new WP_REST_Response([ 'success' => true, 'message' => 'Nothing to Update' ]); } $type = match ($content) { 'shop', 'artform', 'type', 'media', 'artstyle', 'arttheme', 'city', 'colour', 'offerfor', 'pstyle', 'style', 'theme' => 'term', default => 'post', }; $errors = []; $success = []; error_log('Data: '.print_r($data, true)); foreach ($data as $ID => $fields) { $ID = (int)$ID; $meta = new MetaManager($ID, $type); $allFields = JVB()->getFields($content); foreach ($fields as $name => $value) { $process = true; if (!array_key_exists($name, $allFields)) { $errors[$ID][$name] = __('Field not found', 'jvb'); $process = false; } error_log('Proceeding as Normal...'); //Switch between config types to extract values for special cases $config = $allFields[$name]; switch ($config['type']) { case 'radio': //Ensure we only chose one option $temp = explode(',', $value); $value = trim($temp[0]); if (!in_array($value, $config['options'])) { $errors[$ID][$name] = __('Invalid Option', 'jvb'); $process = false; } break; case 'set': $temp = explode(',', $value); $value = array_map('trim', $temp); break; case 'repeater': error_log('Repeater: '.print_r($name, true)); $single = false; switch ($name) { case 'keywords': $single = 'keyword'; break; case 'languages': $single = 'language'; break; case 'links': $single = 'url'; break; case 'followers': $single = 'count'; break; } $items = array_keys($config['fields']); //First, separate out any ] if (strpos($value, ']')) { $rows = array_map(function ($item) { return str_replace('[', '', $item); }, explode(']', $value)); array_pop($rows); error_log('Rows: '.print_r($rows, true)); $value = []; foreach ($rows as $index => $row) { $r = array_map('trim', explode(',', $row)); if (count($r) !== count($items)) { $errors[$ID][$name] = __('Not enough fields set. May not save correctly', 'jvb'); } //attempt to save fields $new = []; foreach ($items as $key => $i) { $new[$i] = (array_key_exists($key, $r)) ? $r[$key] : (($i==='checked') ? date('Y-m-d'): ''); } $value[] = $new; } error_log('Processed Repeater Value for sanitizing: '.print_r($value, true)); } elseif ($value === '') { $value = []; } else { if (!$single) { $errors[$ID][$name] = __('Must set single key', 'jvb'); $process = false; } $rows = array_map('trim', explode(',', $value)); $value = []; foreach ($rows as $row) { $new = []; foreach ($items as $i) { $new[$i] = ($i === $single) ? $row : (($i==='checked') ? date('Y-m-d') : ''); } error_log('New Repeater Row Output: '.print_r($new, true)); $value[] = $new; } } break; } switch ($name) { case 'shop': case 'type': case 'city': $terms = array_map('trim', explode(',', $value)); $set = []; foreach ($terms as $term) { $t = get_term_by('name', $value, BASE.$name); if (!$t) { $errors[$ID][$name][] = __($value.' does not exist yet', 'jvb'); } else { $set[] = $t->term_id; } } if (!empty($set)) { $result = wp_set_post_terms($ID, $set, BASE.$name); $result = is_array($result); } break; case 'owner': error_log('Processing Owner Request from Admin Routes...'); $users = array_map('trim', explode(',', $value)); foreach ($users as $user) { $t = jvbGetUserByDisplayName($user)??jvbGetUserByFirstName($user)??false; error_log('Got user: '.print_r($t, true)); if (!$t) { $errors[$ID][$name][] = __($value.' does not exist yet...', 'jvb'); $result = false; } else { $result = JVB()->routes('shop')->setShopOwner($t->ID, $ID, true); } } break; case 'managers': error_log('Processing Manager Request from Admin Routes...'); $users = array_map('trim', explode(',', $value)); foreach ($users as $user) { $t = jvbGetUserByDisplayName($user)??jvbGetUserByFirstName($user)??false; error_log('Got user: '.print_r($t, true)); if (!$t) { $result = $errors[$ID][$name][] = __($value.' does not exist yet...', 'jvb'); } else { $result = JVB()->routes('shop')->setShopManager($t->ID, $ID, true); } } break; case 'location': $value = [ 'address' => $value, 'lat' => '', 'lng' => '', ]; $result = $meta->updateValue($name, $value); break; case 'hours': $temp = []; foreach ($value as $key => $v) { if (strpos($v['days'], '-')) { $temp[$key]['days'] = jvbExpandDayRange($v['days']); $temp[$key]['time_open'] = $v['time_open']; $temp[$key]['time_closes'] = $v['time_closes']; } } $value = $temp; error_log('Final Hours for processing: '.print_r($value, true)); $result = $meta->updateValue($name, $value); break; default: $result = $meta->updateValue($name, $value); break; } //Save the value if ($result) { $success[$ID][] = $name; } else { $errors[$ID][] = [ $name => 'Could not update value' ]; } } } return new WP_REST_Response([ 'success' => true, 'successful' => $success, 'errors' => $errors ]); } /** * @param array $filters * * @return array */ protected function checkFilters(array $filters) { global $karma; $out = []; foreach ($filters as $type => $value) { if (!array_key_exists($type, $karma)) { continue; } $out[$type] = jvbSanitizeIDList($value); } return $out; } /** * @param WP_REST_Request $request The REST Request * * @return array */ protected function buildParams(WP_REST_Request $request):array { $data = $request->get_params(); $this->setMetaType($data['content']); switch ($this->metaType) { case 'post': $key = 'post_type'; break; case 'term': $key = 'taxonomy'; break; case 'user': $key = 'role'; break; default: return []; } global $jvb_everything; return [ $key => (array_key_exists('content', $data) && array_key_exists($data['content'], $jvb_everything)) ? BASE.$data['content'] : BASE.'artist', 'order' => (array_key_exists('order', $data) && in_array(strtolower($data['order']), ['asc', 'desc'])) ? strtolower($data['order']) : 'desc', 'orderby' => (array_key_exists('orderby', $data) && in_array($data['orderby'], ['name', 'date', 'followers', 'karma'])) ? $data['orderby'] : 'name', 'paged' => (array_key_exists('page', $data) && is_numeric($data['page'])) ? (int)$data['page'] : 1, 'filters' => (array_key_exists('filters', $data)) ? $this->checkFilters($data['filters']) : null, ]; } protected function setMetaType(string $type):void { $this->metaType = match (true) { array_key_exists($type, JVB_CONTENT) => 'post', array_key_exists($type, JVB_TAXONOMY) => 'term', array_key_exists($type, JVB_USER) => 'user', default => false, }; } /** * @param WP_REST_Request $request The REST Request * * @return WP_REST_Response */ public function getItems(WP_REST_Request $request):WP_REST_Response { $args = $this->buildParams($request); $key = $this->cache->generateKey($args); $cache = $this->cache->get($key); if ($cache) { return new WP_REST_Response($cache); } $this->content = $request->get_param('content'); $args['posts_per_page'] = 50; $this->fields = jvbGetFields($this->content); // $this->fields = array_filter(JVB()->getFields($data['content']), function($arr){ // return array_key_exists('quickEdit', $arr); // }); $response = match ($this->metaType) { 'post' => $this->getPostItems($args), 'term' => $this->getTermItems($args), 'user' => $this->getUserItems($args), default => [] }; $this->cache->set($key, $response); return new WP_REST_Response($response); } /** * @param array $args the data from buildParams method * * @return array */ protected function getTermItems(array $args):array { if (isJVBContentTax($args['taxonomy'])) { return $this->getContentTypeTaxItems($args); } // Build query arguments $args = array_merge($args, [ 'hide_empty' => false, 'number' => $args['posts_per_page'], 'offset' => ($args['paged'] - 1) * $args['posts_per_page'], 'fields' => 'ids' ]); // Add ordering switch ($args['orderby']) { case 'date': $args['orderby'] = 'id'; // Terms don't have date, so use ID as a proxy break; case 'karma': // Terms should be ordered by meta value $args['orderby'] = 'meta_value_num'; $args['meta_key'] = BASE . 'karma'; break; default: $args['orderby'] = 'name'; break; } $args['order'] = strtoupper($args['order']); // Add any filters if (!empty($args['filters'])) { // Term meta filtering would go here $meta_query = []; foreach ($args['filters'] as $filter_type => $filter_values) { if (!empty($filter_values)) { // Example: filter by parent terms if ($filter_type === 'parent') { $args['parent'] = $filter_values[0]; // Assume single parent filter } else { // For meta-based filters $meta_query[] = [ 'key' => BASE . $filter_type, 'value' => $filter_values, 'compare' => 'IN' ]; } } } if (!empty($meta_query)) { $args['meta_query'] = $meta_query; } } unset($args['filters']); // Get count first for pagination info $count_args = $args; unset($count_args['number']); unset($count_args['offset']); $count_args['fields'] = 'count'; $total_items = get_terms($count_args); // Get the actual terms $term_ids = get_terms($args); // Error handling if (is_wp_error($term_ids)) { return [ 'items' => [], 'has_more' => false, 'total_items' => 0, 'total_pages' => 0, 'error' => $term_ids->get_error_message() ]; } // Format each term $items = array_map([$this, 'formatItem'], $term_ids); return [ 'items' => $items, 'has_more' => ($total_items > ($args['offset'] + $args['number'])), 'total_items' => (int)$total_items, 'total_pages' => ceil($total_items / $args['posts_per_page']) ]; } protected function getUserItems(array $args):array { return []; } /** * @param array $data the $data built by buildParams * * @return array */ protected function getContentTypeTaxItems(array $data):array { global $wpdb; $table_name = $wpdb->prefix . BASE . jvbNoBase($data['taxonomy']); // Start building the query to get just the term_ids with proper ordering $sql_select = "SELECT s.term_id"; $sql_from = " FROM {$table_name} AS s"; $sql_where = " WHERE 1=1"; $sql_orderby = ""; $sql_limit = ""; $params = []; // Add filters if any if (!empty($data['filters'])) { foreach ($data['filters'] as $filter_type => $filter_values) { if ($filter_type === 'city' && !empty($filter_values)) { $placeholders = implode(',', array_fill(0, count($filter_values), '%d')); $sql_where .= $wpdb->prepare(" AND s.city IN ($placeholders)", ...$filter_values); } // Add more filter conditions for other fields as needed } } // Determine ORDER BY clause - here we can use any column from the custom table $valid_order_columns = ['name', 'updated_at', 'established_year', 'karma']; $orderby_column = in_array($data['orderby'], $valid_order_columns) ? $data['orderby'] : 'name'; $sql_orderby = " ORDER BY s." . $orderby_column; // Validate order direction $order_direction = strtoupper($data['order']) === 'DESC' ? 'DESC' : 'ASC'; $sql_orderby .= " " . $order_direction; // Calculate offset for pagination $per_page = absint($data['posts_per_page']); $page = max(1, absint($data['paged'])); $offset = ($page - 1) * $per_page; // Add pagination with prepared statement $sql_limit = $wpdb->prepare(" LIMIT %d OFFSET %d", $per_page, $offset); // Count query (without limit clause) $count_sql = "SELECT COUNT(s.term_id)" . $sql_from . $sql_where; $total_items = $wpdb->get_var($count_sql); // Final query with all components $sql = $sql_select . $sql_from . $sql_where . $sql_orderby . $sql_limit; // Execute the query $shop_term_ids = $wpdb->get_col($sql); // Now get the full term objects for these IDs $items = []; foreach ($shop_term_ids as $shop_term_id) { $items[] = $this->formatItem($shop_term_id); } return [ 'items' => $items, 'has_more' => ($total_items > ($offset + $per_page)), 'total_items' => (int)$total_items, 'total_pages' => ceil($total_items / $per_page) ]; } /** * @param array $args array returned by buildParams * * @return array */ protected function getPostItems(array $args):array { $args['fields'] = 'ids'; $args['post_status'] = ['draft', 'trash', 'publish']; $query = new WP_Query($args); $items = array_map([$this, 'formatItem'], $query->posts); return [ 'items' => $items, 'has_more' => $query->max_num_pages > $args['paged'], 'total_items' => $query->found_posts, 'total_pages' => $query->max_num_pages ]; } /** * @param int $ID the ID of the item to format * * @return array */ protected function formatItem(int $ID):array { $meta = new MetaManager($ID, $this->metaType); $item = [ 'id' => $ID, 'public' => !($this->metaType === 'post') || get_post_status($ID) === 'publish', ]; $hierarchical = false; if ($this->metaType === 'term') { $term = get_term($ID); if ($term) { $hierarchical = is_taxonomy_hierarchical($term->taxonomy); } } foreach ($this->fields as $key => $config) { switch ($key) { case 'type': case 'city': case 'shop': $terms = get_the_terms($ID, BASE.$key); $value = []; if ($terms && !is_wp_error($terms)) { foreach ($terms as $term) { $value[] = htmlspecialchars_decode($term->name); } } $item[$key] = implode(', ', $value); break; default: $item[$key] = $meta->getValue($key); break; } switch ($config['type']) { case 'repeater': if (empty($item[$key]) || !is_array($item[$key])) { $item[$key] = ''; } else { $temp = ''; foreach ($item[$key] as $row) { if (is_array($row)) { // Format each row as [value1,value2,value3] $rowValues = array_values($row); $temp .= '[' . implode(',', $rowValues) . ']'; } else { // Handle simpler cases where rows might not be arrays $temp .= '[' . $row . ']'; } } $item[$key] = $temp; } break; case 'location': $item[$key] = $item[$key]['address']; break; } if ($hierarchical && $key === 'term_name') { $item[$key.'_path'] = JVB()->routes('term')->getTermPath($ID, html_entity_decode($term->name), $term->taxonomy); } } error_log('Item: '.print_r($item, true)); return $item; } }