Файловый менеджер - Редактировать - /home/tuudkjt/globeasy/wp-includes/ID3/CPT.tar
Назад
CommentsRatingColumn.php 0000777 00000011632 15251237711 0011403 0 ustar 00 <?php namespace Rvx\CPT; use Rvx\CPT\CptHelper; class CommentsRatingColumn { protected $cptHelper; public function __construct() { $this->cptHelper = new CptHelper(); } /** * Add a new column to the comments page for the review rating. * * @param array $columns The existing comment columns. * @return array Modified columns array. */ public function addRatingColumn($columns) { // Find the position of the 'author' column $author_position = \array_search('author', \array_keys($columns)); // Insert the 'rating' column after the 'author' column if ($author_position !== \false) { // Add the 'rating' column after the 'author' column $columns = \array_slice($columns, 0, $author_position + 1, \true) + ['rating' => __('ReviewX Rating', 'reviewx')] + \array_slice($columns, $author_position + 1, null, \true); } return $columns; } /** * Populate the new column with the rating value. * * @param string $column The column name. * @param int $comment_id The ID of the comment. */ public function populateRatingColumn($column, $comment_id) { if ($column === 'rating') { // Get the comment object $comment = get_comment($comment_id); // Check if the comment is a parent comment if ($comment->comment_parent != 0) { return; // Skip if it's a reply (i.e., not a parent comment) } // Get the comment type (WooCommerce product reviews have a type 'review') $comment_type = $comment->comment_type; // Get the post type of the comment $post_type = get_post_type($comment->comment_post_ID); // Define the target post types $enabled_post_types = $this->cptHelper->enabledCPT(); unset($enabled_post_types['product']); // Unset Product // Check if the comment's post type is in the target post types and comment type is 'review' and 'comment' if (\in_array($post_type, $enabled_post_types, \true) && \in_array($comment_type, ['comment', 'review'], \true)) { // Get the rating meta data for the comment (reviews have 'rating' meta key) $rating = \get_comment_meta($comment_id, 'rating', \true); // If rating exists, display stars, otherwise show empty stars (0) if ($rating) { echo $this->getStarsHtml($rating); } else { echo $this->getStarsHtml(0); // Empty stars for no rating } } } } /** * Generate the star HTML for the given rating. * * @param int $rating The rating value (1 to 5). * @return string The HTML for the stars. */ public function getStarsHtml($rating) { $rating = \max(0, \min(5, $rating)); // Ensure the rating is between 0 and 5 $star_size = '22px'; // Customize size if needed $stars = ''; for ($i = 1; $i <= 5; $i++) { if ($rating >= $i) { // Full star $star_symbol = '<span class="dashicons dashicons-star-filled"></span>'; } elseif ($rating >= $i - 0.5) { // Half star $star_symbol = '<span class="dashicons dashicons-star-half"></span>'; } else { // Empty star $star_symbol = '<span class="dashicons dashicons-star-empty"></span>'; } $stars .= $star_symbol; } return "<span class='rvx-stars' title='{$rating}' style='font-size: {$star_size}; color: #f5a623;'>{$stars}</span>"; } /** * Sort comments based on the 'rating' column. * * @param WP_Query $query The WP_Query object. */ public function sortCommentsByRating($query) { // Check if we are in the admin and the correct screen (comments) if (is_admin() && isset($_GET['orderby']) && 'rating' === $_GET['orderby']) { // Sort by rating in ascending or descending order based on the current 'order' parameter $order = isset($_GET['order']) && $_GET['order'] === 'asc' ? 'ASC' : 'DESC'; // Modify the query to order by rating $query->set('meta_key', 'rating'); // Meta key for rating $query->set('orderby', 'meta_value_num'); // Order by numeric value of meta field $query->set('order', $order); // Set the order (ASC or DESC) } } /** * Make the 'rating' column sortable. * * @param array $columns The existing sortable columns. * @return array Modified sortable columns array. */ public function makeRatingColumnSortable($columns) { $columns['rating'] = 'rating'; return $columns; } } CptCommentsLinkMeta.php 0000777 00000001767 15251237711 0011164 0 ustar 00 <?php namespace Rvx\CPT; use Rvx\CPT\CptHelper; class CptCommentsLinkMeta { protected $cptHelper; public function __construct() { $this->cptHelper = new CptHelper(); } /** * Modify the comment count output with custom review logic */ public function replace_total_comments_count($count, $post_id) { // List of post types to target $enabled_post_types = $this->cptHelper->enabledCPT(); unset($enabled_post_types['product']); // Unset Product $post_type = get_post_type($post_id); // Exclude post type if (!isset($enabled_post_types[$post_type])) { return $count; // No changes } $reviewCount = (new \Rvx\CPT\CptReviewsCount())->newCount($post_id); if (!empty($reviewCount[0]) && $reviewCount[0] > 0) { // Return the filtered review count without replies return $reviewCount[0]; } else { return $count; } } } Shared/CptPostHandler.php 0000777 00000031440 15251237711 0011372 0 ustar 00 <?php namespace Rvx\CPT\Shared; use Rvx\Api\ProductApi; use Rvx\CPT\CptHelper; use Rvx\Utilities\Auth\Client; use Rvx\Utilities\TransactionManager; use Rvx\WPDrill\Response; class CptPostHandler { protected $cptHelper; public function __construct() { $this->cptHelper = new CptHelper(); } public function __invoke($post_id, $post, $update) { // Ignore if called during autosave or automatic draft if (\defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || \in_array($post->post_status, ['auto-draft', 'inherit'], \true)) { return; } // Define the target post types $enabled_post_types = $this->cptHelper->enabledCPT(); $post_type = $post->post_type; $post_status = $post->post_status; if (!isset($enabled_post_types[$post_type])) { return; } if ($post_status === 'trash') { return; } // WP Independent Sync: We execute logic, but SaaS failure MUST NOT roll back WP. $is_new_sync = \get_post_meta($post_id, 'rvx_sync_new_status', \true); if (!$is_new_sync) { // Handle new post/product sync $response = $this->createHandler($post_id, $post); if ($response->getStatusCode() === Response::HTTP_OK) { $this->enableCommentsReviews($post_id); \update_post_meta($post_id, 'rvx_sync_new_status', 1, \true); } else { // If creation failed, try update as a fallback sync $response = $this->updateHandler($post_id, $post); if ($response->getStatusCode() === Response::HTTP_OK) { $this->enableCommentsReviews($post_id); \update_post_meta($post_id, 'rvx_sync_new_status', 1, \true); \update_post_meta($post_id, 'rvx_sync_edit_status', 1, \true); } else { \error_log("CptPostHandler Create/Update Sync Failed for ID {$post_id}: " . $response->getBody()); } } } else { // Handle existing post/product update sync $response = $this->updateHandler($post_id, $post); if ($response->getStatusCode() === Response::HTTP_OK) { $this->enableCommentsReviews($post_id); \update_post_meta($post_id, 'rvx_sync_new_status', 1, \true); \update_post_meta($post_id, 'rvx_sync_edit_status', 1, \true); } else { \error_log("CptPostHandler Update Sync Failed for ID {$post_id}: " . $response->getBody()); } } } public function createHandler($post_id, $post) { if ($post->post_type === 'product') { $payload = $this->createProductData($post_id, $post); } else { // Public -> custom post type $payload = $this->createPostData($post_id, $post); } return (new ProductApi())->create($payload); } public function updateHandler($post_id, $post) { if ($post->post_type === 'product') { $payload = $this->updatedProductData($post_id, $post); } else { // Public -> custom post type $payload = $this->updatedPostData($post_id, $post); } $uid = Client::getUid() . '-' . $post_id; return (new ProductApi())->update($payload, $uid); } private function createProductData($product_id, $post) { // Get the product object $product = wc_get_product($product_id); // Initialize variables $product_images = []; // Get the main product image $image_id = $product->get_image_id(); // Featured image ID if ($image_id) { $product_images = wp_get_attachment_image_src($image_id, 'full'); // Full-size image URL } if (empty($product_images)) { $product_images = ''; } // Get the regular price $price = $product->get_regular_price(); if (empty($price)) { $price = 0.0; } // Get the sale price (if available) $discounted_price = $product->get_sale_price(); // If no sale price exists, the discounted price is the regular price if (empty($discounted_price)) { $discounted_price = $price; } return ["wp_id" => $product_id, "title" => isset($post->post_title) ? \htmlspecialchars($post->post_title, \ENT_QUOTES, 'UTF-8') : null, "url" => get_permalink($product_id), "description" => isset($post->short_description) ? \htmlspecialchars($post->short_description, \ENT_QUOTES, 'UTF-8') : null, "price" => $price, "discounted_price" => $discounted_price, "slug" => $post->post_name, "image" => $product_images[0] ?? (string) '', "status" => $this->postStatus($post->post_status), "post_type" => $post->post_type, "total_reviews" => (int) $this->getReviewCount($product_id), "avg_rating" => (float) $this->getAverageRating($product_id), "stars" => $this->getStarCounts($product_id), "category_wp_unique_ids" => $this->getCategoryIds($product_id)]; } private function updatedProductData($product_id, $post) { // Get the product object $product = wc_get_product($product_id); // Initialize variables $product_images = []; // Get the main product image $image_id = $product->get_image_id(); // Featured image ID if ($image_id) { $product_images = wp_get_attachment_image_src($image_id, 'full'); // Full-size image URL } if (empty($product_images)) { $product_images = ''; } // Get the regular price $price = $product->get_regular_price(); if (empty($price)) { $price = 0.0; } // Get the sale price (if available) $discounted_price = $product->get_sale_price(); // If no sale price exists, the discounted price is the regular price if (empty($discounted_price)) { $discounted_price = $price; } return ["wp_id" => $product_id, "title" => isset($post->post_title) ? \htmlspecialchars($post->post_title, \ENT_QUOTES, 'UTF-8') : null, "url" => get_permalink($product_id), "description" => isset($post->post_excerpt) ? \htmlspecialchars($post->post_excerpt, \ENT_QUOTES, 'UTF-8') : null, "price" => (float) $price, "discounted_price" => (float) $discounted_price, "slug" => $post->post_name, "image" => $product_images[0] ?? (string) '', "status" => $this->postStatus($post->post_status), "post_type" => $post->post_type, "total_reviews" => (int) $this->getReviewCount($product_id), "avg_rating" => (float) $this->getAverageRating($product_id), "category_wp_unique_ids" => $this->getCategoryIds($product_id)]; } private function createPostData($post_id, $post) { $image_url = get_the_post_thumbnail_url($post->ID, 'full'); if (empty($image_url)) { $image_url = ''; } // $average_rating calculated via helper in return array $data = ["wp_id" => $post->ID, "title" => isset($post->post_title) ? \htmlspecialchars($post->post_title, \ENT_QUOTES, 'UTF-8') : null, "url" => get_permalink($post->ID), "description" => isset($post->post_excerpt) ? \htmlspecialchars($post->post_excerpt, \ENT_QUOTES, 'UTF-8') : null, "price" => 0, "discounted_price" => 0, "slug" => $post->post_name, "image" => (string) $image_url, "status" => $this->postStatus($post->post_status), "post_type" => get_post_type($post->ID), "total_reviews" => (int) $this->getReviewCount($post->ID), "avg_rating" => (float) $this->getAverageRating($post->ID), "stars" => $this->getStarCounts($post->ID), "category_wp_unique_ids" => $this->getCategoryIds($post_id)]; return $data; } private function updatedPostData($post_id, $post) { $image_url = get_the_post_thumbnail_url($post->ID, 'full'); if (empty($image_url)) { $image_url = ''; } // $average_rating calculated via helper in return array $data = ["wp_id" => $post->ID, "title" => isset($post->post_title) ? \htmlspecialchars($post->post_title, \ENT_QUOTES, 'UTF-8') : null, "url" => get_permalink($post->ID), "description" => isset($post->post_excerpt) ? \htmlspecialchars($post->post_excerpt, \ENT_QUOTES, 'UTF-8') : null, "price" => 0, "discounted_price" => 0, "slug" => $post->post_name, "image" => (string) $image_url, "status" => $this->postStatus($post->post_status), "post_type" => get_post_type($post->ID), "total_reviews" => (int) $this->getReviewCount($post->ID), "avg_rating" => (float) $this->getAverageRating($post->ID), "stars" => $this->getStarCounts($post->ID), "category_wp_unique_ids" => $this->getCategoryIds($post_id)]; return $data; } private function postStatus($status) { switch ($status) { case 'publish': return 1; case 'trash': return 2; default: return 3; } } private function getCategoryIds($post_id) { // Validate the post ID if (empty($post_id)) { \error_log("No valid post/product ID found."); return []; } // Get the post type $post_type = get_post_type($post_id); // Determine the taxonomy: 'product_cat' for products, hierarchical taxonomy for others $taxonomy = $post_type === 'product' ? 'product_cat' : null; if (!$taxonomy) { $taxonomies = get_object_taxonomies($post_type, 'objects'); foreach ($taxonomies as $key => $taxonomy_obj) { if ($taxonomy_obj->hierarchical) { $taxonomy = $key; break; } } } // Retrieve category IDs or assign default if none are found $category_ids = []; if ($taxonomy) { $category_ids = wp_get_post_terms($post_id, $taxonomy, ['fields' => 'ids']); } if (empty($category_ids)) { $category_ids = [0]; } // Append the UID prefix to each category ID $uid = Client::getUid(); $parent_category_ids = []; foreach ($category_ids as $category_id) { $parent_category_ids[] = $uid . '-' . $category_id; } return $parent_category_ids; } private function getStarCounts($post_id) { $check = \get_post_meta($post_id, 'rvx_star_count_1', \true); // Backward compatibility: If meta doesn't exist, calculate it now. if ($check === '' || $check === \false) { \Rvx\CPT\CptAverageRating::update_average_rating($post_id); } return ["one" => (int) \get_post_meta($post_id, 'rvx_star_count_1', \true), "two" => (int) \get_post_meta($post_id, 'rvx_star_count_2', \true), "three" => (int) \get_post_meta($post_id, 'rvx_star_count_3', \true), "four" => (int) \get_post_meta($post_id, 'rvx_star_count_4', \true), "five" => (int) \get_post_meta($post_id, 'rvx_star_count_5', \true)]; } private function enableCommentsReviews($post_id) { global $wpdb; // Validate post ID $post = get_post($post_id); if (!$post) { return \false; } // Ensure the post type supports comments $post_type = $post->post_type; $supports = post_type_supports($post_type, 'comments'); if (!$supports) { // Dynamically add comment support for the post type add_post_type_support($post_type, 'comments'); } // Update the comment status in the database $updated = $wpdb->update( $wpdb->posts, ['comment_status' => 'open'], // Enable comments ['ID' => $post_id], // Match post ID ['%s'], // Data format for `comment_status` ['%d'] ); if ($updated === \false) { return \false; } // Clear WordPress cache for this post clean_post_cache($post_id); return \true; } private function getReviewCount($post_id) { $count = \get_post_meta($post_id, 'rvx_total_reviews', \true); // Backward compatibility: If meta doesn't exist, calculate it now. if ($count === '' || $count === \false) { \Rvx\CPT\CptAverageRating::update_average_rating($post_id); $count = \get_post_meta($post_id, 'rvx_total_reviews', \true); } return (int) $count; } private function getAverageRating($post_id) { $rating = \get_post_meta($post_id, 'rvx_avg_rating', \true); // Backward compatibility: If meta doesn't exist, calculate it now. if ($rating === '' || $rating === \false) { \Rvx\CPT\CptAverageRating::update_average_rating($post_id); $rating = \get_post_meta($post_id, 'rvx_avg_rating', \true); } return (float) ($rating ?: 0.0); } } Shared/CommentsReviewsRowActionRemover.php 0000777 00000002745 15251237711 0015026 0 ustar 00 <?php namespace Rvx\CPT\Shared; use Rvx\CPT\CptHelper; class CommentsReviewsRowActionRemover { protected $cptHelper; public function __construct() { $this->cptHelper = new CptHelper(); } /** * Remove specific row actions from comments and reviews based on the post type and comment type. * * @param array $actions The array of actions for the comment row. * @param WP_Comment $comment The comment object. * @return array The modified actions array. */ public function removeCommentsReviewsRowActions($actions, $comment) { // List of post types to target (include product) $enabled_post_types = $this->cptHelper->enabledCPT(); // Get the post type of the comment $post_type = get_post_type($comment->comment_post_ID); // Get the comment type (WooCommerce product reviews have a type 'review') $comment_type = $comment->comment_type; // List of actions to remove $actions_to_remove = ['reply', 'quickedit', 'edit']; // If the post type matches and the comment type is 'comment' or 'review', remove the specified actions if (\in_array($post_type, $enabled_post_types, \true) && \in_array($comment_type, ['comment', 'review'], \true)) { foreach ($actions_to_remove as $action) { if (isset($actions[$action])) { unset($actions[$action]); } } } return $actions; } } Shared/CommentsReviewsFilter.php 0000777 00000004741 15251237711 0013004 0 ustar 00 <?php namespace Rvx\CPT\Shared; use Rvx\CPT\CptHelper; use Rvx\Utilities\Auth\Client; /** * Filters comments/reviews from the WP Comments admin page for post types * that are enabled in ReviewX. */ class CommentsReviewsFilter { protected $cptHelper; public function __construct() { $this->cptHelper = new CptHelper(); } /** * Filter comments query to exclude comments for ReviewX-enabled post types. * This hides reviews/comments from the WP Comments admin page. * * @param WP_Comment_Query $query The comment query object. */ public function filterCommentsForReviewxPostTypes($query) { // Only execute after sync is completed if (!Client::getSync()) { return; } // Only filter on the admin comments page if (!is_admin()) { return; } // Check if we're on the comments admin page global $pagenow; if ($pagenow !== 'edit-comments.php') { return; } // Get enabled post types from ReviewX $enabled_post_types = $this->cptHelper->enabledCPT(); if (empty($enabled_post_types)) { return; } // Get all post IDs that belong to enabled post types $post_ids_to_exclude = $this->getPostIdsForPostTypes($enabled_post_types); if (empty($post_ids_to_exclude)) { return; } // Get current post__not_in value and merge with our exclusions $current_post_not_in = $query->query_vars['post__not_in'] ?? []; if (!\is_array($current_post_not_in)) { $current_post_not_in = []; } $query->query_vars['post__not_in'] = \array_merge($current_post_not_in, $post_ids_to_exclude); } /** * Get all post IDs for the given post types. * * @param array $post_types Array of post type slugs. * @return array Array of post IDs. */ protected function getPostIdsForPostTypes($post_types) { global $wpdb; if (empty($post_types)) { return []; } // Build placeholders for IN clause $placeholders = \implode(', ', \array_fill(0, \count($post_types), '%s')); // Prepare query to get post IDs for the enabled post types $query = $wpdb->prepare("SELECT ID FROM {$wpdb->posts} WHERE post_type IN ({$placeholders})", \array_values($post_types)); $post_ids = $wpdb->get_col($query); return \array_map('intval', $post_ids); } } Shared/PostsRatingColumn.php 0000777 00000011643 15251237711 0012136 0 ustar 00 <?php namespace Rvx\CPT\Shared; use Rvx\CPT\CptHelper; class PostsRatingColumn { protected $cptHelper; public function __construct() { $this->cptHelper = new CptHelper(); } public function addColumn() { $enabled_post_types = $this->cptHelper->enabledCPT(); // Loop through each post type and hook into the actions/filters dynamically foreach ($enabled_post_types as $post_type) { // Hook into the columns filter for each post type add_filter("manage_edit-{$post_type}_columns", function ($columns) use($post_type) { return $this->addRatingColumn($columns, $post_type); }); add_action("manage_{$post_type}_posts_custom_column", [$this, 'populateRatingColumn'], 10, 2); // if($post_type !== 'product'){ // add_filter("manage_edit-{$post_type}_sortable_columns", [$this, 'makeRatingColumnSortable']); // add_action('pre_get_posts', [$this, 'sortPostsByRating']); // } } } /** * Add a new column to the post type list for the review rating. * * @param array $columns The existing post columns. * @return array Modified columns array. */ public function addRatingColumn($columns, $post_type) { // For 'product' post type, add rating column after the title column if ($post_type === 'product') { // Find the position of the 'author' column $price_position = \array_search('price', \array_keys($columns)); // Insert the 'rating' column after the 'price' column if ($price_position !== \false) { // Add the 'rating' column after the 'author' column $columns = \array_slice($columns, 0, $price_position + 1, \true) + ['rating' => __('ReviewX Rating', 'reviewx')] + \array_slice($columns, $price_position + 1, null, \true); } } else { // For other post types, find the position of the 'author' column $author_position = \array_search('author', \array_keys($columns)); if ($author_position !== \false) { $columns = \array_slice($columns, 0, $author_position + 1, \true) + ['rating' => __('ReviewX Rating', 'reviewx')] + \array_slice($columns, $author_position + 1, null, \true); } } return $columns; } /** * Populate the rating column with the post's rating. * * @param string $column The column name. * @param int $post_id The ID of the post. */ public function populateRatingColumn($column, $post_id) { if ($column === 'rating') { $post_type = get_post_type($post_id); $meta_key = 'product' === $post_type ? '_wc_average_rating' : 'rvx_avg_rating'; $rating = \get_post_meta($post_id, $meta_key, \true); echo $this->getStarsHtml($rating ? $rating : 0); // Default to 0 if no rating } } /** * Generate the HTML for the stars based on the rating value. * * @param int $rating The rating value (1-5). * @return string The HTML output for the stars. */ public function getStarsHtml($rating) { $rating = \max(0, \min(5, $rating)); // Ensure the rating is between 0 and 5 $star_size = '22px'; // Customize size if needed $stars = ''; for ($i = 1; $i <= 5; $i++) { if ($rating >= $i) { // Full star $star_symbol = '<span class="dashicons dashicons-star-filled"></span>'; } elseif ($rating >= $i - 0.5) { // Half star $star_symbol = '<span class="dashicons dashicons-star-half"></span>'; } else { // Empty star $star_symbol = '<span class="dashicons dashicons-star-empty"></span>'; } $stars .= $star_symbol; } return "<span class='rvx-stars' title='{$rating}' style='font-size: {$star_size}; color: #f5a623;'>{$stars}</span>"; } /** * Make the rating column sortable. * * @param array $columns The existing sortable columns. * @return array Modified sortable columns array. */ public function makeRatingColumnSortable($columns) { $columns['rating'] = 'rating'; return $columns; } /** * Sort posts by the rating column. * * @param WP_Query $query The WP_Query object. */ public function sortPostsByRating($query) { if (is_admin() && isset($_GET['orderby']) && 'rating' === $_GET['orderby']) { $order = isset($_GET['order']) && $_GET['order'] === 'asc' ? 'ASC' : 'DESC'; $meta_key = get_post_type() === 'product' ? '_wc_average_rating' : 'rvx_avg_rating'; $query->set('meta_key', $meta_key); $query->set('orderby', 'meta_value_num'); $query->set('order', $order); } } } Shared/CommentsReviewsMetaBoxRemover.php 0000777 00000001611 15251237711 0014447 0 ustar 00 <?php namespace Rvx\CPT\Shared; use Rvx\CPT\CptHelper; class CommentsReviewsMetaBoxRemover { protected $cptHelper; public function __construct() { $this->cptHelper = new CptHelper(); } /** * Removes the comments meta box from all custom post types and WooCommerce products. */ public function removeCommentsReviewsMetaBox() { $enabled_post_types = $this->cptHelper->enabledCPT(); foreach ($enabled_post_types as $post_type) { // Remove the "Allow Comments" meta box remove_meta_box('commentstatusdiv', $post_type, 'normal'); remove_meta_box('commentstatusdiv', $post_type, 'side'); // Remove the "Comments" meta box displaying existing comments remove_meta_box('commentsdiv', $post_type, 'normal'); remove_meta_box('commentsdiv', $post_type, 'side'); } } } Shared/CommentEditBlockHandler.php 0000777 00000020356 15251237711 0013165 0 ustar 00 <?php namespace Rvx\CPT\Shared; use Rvx\CPT\CptHelper; use Rvx\Utilities\Auth\Client; /** * Blocks editing of comments/reviews for post types that are managed by ReviewX. * This applies to both WP Comment Edit page and WooCommerce Review Edit page. */ class CommentEditBlockHandler { protected $cptHelper; public function __construct() { $this->cptHelper = new CptHelper(); } /** * Check if we should block comment editing. * * @return bool */ public function shouldBlockEdit() : bool { // Get enabled post types from ReviewX $enabled_post_types = $this->cptHelper->enabledCPT(); if (empty($enabled_post_types)) { return \false; } return \true; } /** * Check if we're on the comment edit page. * * @return bool */ public function isCommentEditPage() : bool { global $pagenow; // WP Comment Edit page: comment.php?action=editcomment&c={id} if ($pagenow === 'comment.php') { $action = isset($_GET['action']) ? sanitize_text_field($_GET['action']) : ''; return $action === 'editcomment'; } return \false; } /** * Get the comment being edited. * * @return \WP_Comment|null */ public function getEditingComment() : ?\WP_Comment { if (!$this->isCommentEditPage()) { return null; } $comment_id = isset($_GET['c']) ? absint($_GET['c']) : 0; if (!$comment_id) { return null; } return get_comment($comment_id); } /** * Check if the comment belongs to a ReviewX-enabled post type. * * @param \WP_Comment $comment The comment object. * @return bool */ public function isReviewxEnabledComment($comment) : bool { if (!$comment || !$comment->comment_post_ID) { return \false; } $post_type = get_post_type($comment->comment_post_ID); $enabled_post_types = $this->cptHelper->enabledCPT(); return isset($enabled_post_types[$post_type]); } /** * Display a notice on the comment edit page that editing is blocked. */ public function displayEditBlockedNotice() : void { if (!is_admin()) { return; } if (!$this->shouldBlockEdit()) { return; } if (!$this->isCommentEditPage()) { return; } $comment = $this->getEditingComment(); if (!$comment || !$this->isReviewxEnabledComment($comment)) { return; } $reviewx_reviews_url = admin_url('admin.php?page=reviewx_reviews'); $post_type = get_post_type($comment->comment_post_ID); $post_type_label = \ucfirst($post_type); // Use admin_footer to inject HTML that won't be affected by page load scripts add_action('admin_footer', function () use($reviewx_reviews_url, $post_type_label) { ?> <style> /* Position the content area as relative for absolute positioning */ #wpbody-content { position: relative !important; } /* Overlay covers only the content area */ #rvx-edit-blocked-overlay { position: absolute !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important; min-height: 100vh; background: rgba(255,255,255,0.98) !important; z-index: 99 !important; display: flex !important; align-items: center !important; justify-content: center !important; visibility: visible !important; opacity: 1 !important; } </style> <script> document.addEventListener('DOMContentLoaded', function() { var container = document.getElementById('wpbody-content'); if (!container) return; var overlay = document.createElement('div'); overlay.id = 'rvx-edit-blocked-overlay'; overlay.innerHTML = ` <div style=" background: #fff; border: 1px solid #c3c4c7; border-left: 4px solid #f59e0b; padding: 30px 40px; max-width: 500px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); border-radius: 4px; text-align: center; "> <span class="dashicons dashicons-lock" style="font-size: 48px; color: #f59e0b; margin-bottom: 15px; display: block;"></span> <h2 style="margin: 0 0 15px 0; color: #1e293b; font-size: 20px;"> <?php esc_html_e('Review Editing Disabled', 'reviewx'); ?> </h2> <p style="font-size: 14px; color: #475569; margin: 0 0 20px 0; line-height: 1.6;"> <?php \printf(esc_html__('This %s review is managed by ReviewX. To edit this review, please use the ReviewX Reviews page.', 'reviewx'), esc_html($post_type_label)); ?> </p> <a href="<?php echo esc_url($reviewx_reviews_url); ?>" class="button button-primary button-hero" style="background-color: #6366f1; border-color: #6366f1; font-size: 14px;"> <?php esc_html_e('Go to ReviewX Reviews', 'reviewx'); ?> </a> <br><br> <a href="<?php echo esc_url(admin_url('edit-comments.php')); ?>" style="color: #666; font-size: 13px; text-decoration: none;"> <?php esc_html_e('← Back to Comments', 'reviewx'); ?> </a> </div> `; container.appendChild(overlay); }); </script> <?php }); } /** * Redirect to ReviewX reviews page if trying to edit a ReviewX-managed comment. * This is an alternative approach using JavaScript for better UX. */ public function maybeRedirectFromEditPage() : void { if (!is_admin()) { return; } if (!$this->shouldBlockEdit()) { return; } if (!$this->isCommentEditPage()) { return; } $comment = $this->getEditingComment(); if (!$comment || !$this->isReviewxEnabledComment($comment)) { return; } // Add inline script to disable form submission add_action('admin_footer', function () { ?> <script> document.addEventListener('DOMContentLoaded', function() { // Disable all form submissions on this page var forms = document.querySelectorAll('form'); forms.forEach(function(form) { form.addEventListener('submit', function(e) { e.preventDefault(); alert('<?php esc_html_e('This review is managed by ReviewX. Please edit it from the ReviewX Reviews page.', 'reviewx'); ?>'); return false; }); }); // Disable submit buttons var submitButtons = document.querySelectorAll('input[type="submit"], button[type="submit"]'); submitButtons.forEach(function(btn) { btn.disabled = true; btn.style.opacity = '0.5'; btn.style.cursor = 'not-allowed'; }); }); </script> <?php }); } } Shared/WooReviewsRedirectHandler.php 0000777 00000012123 15251237711 0013566 0 ustar 00 <?php namespace Rvx\CPT\Shared; use Rvx\CPT\CptHelper; use Rvx\Utilities\Auth\Client; /** * Handles WooCommerce Reviews page redirection notice and hides reviews * when product post type is managed by ReviewX. */ class WooReviewsRedirectHandler { protected $cptHelper; public function __construct() { $this->cptHelper = new CptHelper(); } /** * Check if WooCommerce product reviews should be hidden. * * @return bool */ public function shouldHideWooReviews() : bool { // Check if WooCommerce is active if (!\class_exists('WooCommerce')) { return \false; } // Check if sync is completed if (!Client::getSync()) { return \false; } // Check if product post type is enabled in ReviewX $enabled_post_types = $this->cptHelper->enabledCPT(); return isset($enabled_post_types['product']); } /** * Check if we're on the WooCommerce Reviews page. * WooCommerce Reviews page: edit.php?post_type=product&page=product-reviews * Legacy Comments page: edit-comments.php?post_type=product or edit-comments.php?comment_type=review * * @return bool */ public function isWooReviewsPage() : bool { global $pagenow; // Check for WooCommerce product-reviews page (Products -> Reviews) // URL: edit.php?post_type=product&page=product-reviews if ($pagenow === 'edit.php') { $post_type = isset($_GET['post_type']) ? sanitize_text_field($_GET['post_type']) : ''; $page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : ''; if ($post_type === 'product' && $page === 'product-reviews') { return \true; } } // Also check legacy edit-comments.php page if ($pagenow === 'edit-comments.php') { $post_type = isset($_GET['post_type']) ? sanitize_text_field($_GET['post_type']) : ''; $comment_type = isset($_GET['comment_type']) ? sanitize_text_field($_GET['comment_type']) : ''; if ($post_type === 'product' || $comment_type === 'review') { return \true; } } return \false; } /** * Display a notice on the WooCommerce Reviews page redirecting users to ReviewX. */ public function displayRedirectNotice() : void { if (!is_admin()) { return; } if (!$this->shouldHideWooReviews()) { return; } if (!$this->isWooReviewsPage()) { return; } $reviewx_reviews_url = admin_url('admin.php?page=reviewx_reviews'); ?> <div class="notice notice-info" style="padding: 15px; margin: 20px 0; border-left-color: #6366f1;"> <h3 style="margin: 0 0 10px 0; color: #1e293b;"> <span class="dashicons dashicons-star-filled" style="color: #6366f1; margin-right: 5px;"></span> <?php esc_html_e('Reviews Managed by ReviewX', 'reviewx'); ?> </h3> <p style="font-size: 14px; color: #475569; margin: 0 0 15px 0;"> <?php esc_html_e('Product reviews are now managed by ReviewX. You can view, manage, and respond to all product reviews from the ReviewX Reviews page.', 'reviewx'); ?> </p> <a href="<?php echo esc_url($reviewx_reviews_url); ?>" class="button button-primary" style="background-color: #6366f1; border-color: #6366f1;"> <span class="dashicons dashicons-arrow-right-alt" style="margin-top: 4px;"></span> <?php esc_html_e('Go to ReviewX Reviews', 'reviewx'); ?> </a> </div> <style> /* Hide the reviews table when ReviewX is managing reviews */ .wp-list-table.comments, .wp-list-table.reviews, .tablenav, .subsubsub, .search-box, #comments-form > p:first-child, .woocommerce-reviews-table, .wc-admin-review-activity-card, #the-comment-list, .comment-ays, form#comments-form { display: none !important; } </style> <?php } /** * Filter WooCommerce product reviews from the comments query using comments_clauses. * This is the proper WordPress filter for modifying comment queries. * * @param array $clauses A compacted array of comment query clauses. * @param \WP_Comment_Query $comment_query The WP_Comment_Query instance. * @return array Modified clauses array. */ public function filterWooReviewsClauses($clauses, $comment_query) : array { if (!is_admin()) { return $clauses; } if (!$this->shouldHideWooReviews()) { return $clauses; } if (!$this->isWooReviewsPage()) { return $clauses; } // Return no results by adding an impossible WHERE condition $clauses['where'] .= ' AND 1=0 '; return $clauses; } } CptRichSchemaHandler.php 0000777 00000011774 15251237711 0011255 0 ustar 00 <?php namespace Rvx\CPT; use Rvx\Services\SettingService; use WP_Post; /** * Handles rich schema generation for non-product post types (CPTs, pages, posts, etc.). */ class CptRichSchemaHandler { /** * Build structured data for a given post. * * @param array $markup Existing markup (unused). * @param WP_Post $post Post object. * @return array */ public function schemaHandler($markup, $post) : array { // Guard conditions. if (is_admin() || empty($post) || !isset($post->ID)) { return $markup; } $postType = get_post_type($post); if (empty($postType) || $postType === 'product') { return $markup; // Skip WooCommerce products. } // Temporarily remove Divi filter $divi_callback = 'et_theme_builder_wc_set_review_metadata'; $divi_removed = \false; if (\function_exists('has_filter')) { $priority = \has_filter('get_comment_metadata', $divi_callback); if ($priority !== \false) { remove_filter('get_comment_metadata', $divi_callback, (int) $priority); $divi_removed = \true; } } // Define schema type mappings. $schemaTypeMap = ['post' => 'BlogPosting', 'page' => 'Article', 'job' => 'JobPosting', 'job_listing' => 'JobPosting', 'book' => 'Book', 'movie' => 'Movie', 'event' => 'Event', 'recipe' => 'Recipe', 'course' => 'Course', 'season' => 'CreativeWorkSeason', 'series' => 'CreativeWorkSeries', 'software' => 'SoftwareApplication', 'application' => 'SoftwareApplication', 'app' => 'SoftwareApplication', 'music' => 'MusicRecording', 'game' => 'Game', 'howto' => 'HowTo', 'episode' => 'Episode', 'business' => 'LocalBusiness']; // Do not use 'CreativeWork' as a fallback for reviews, use 'BlogPosting' or 'Article' instead. $schemaType = $schemaTypeMap[$postType] ?? 'Article'; $reviewableTypes = ['Book', 'Movie', 'Recipe', 'Course', 'CreativeWorkSeason', 'CreativeWorkSeries', 'SoftwareApplication', 'MusicRecording', 'MediaObject', 'Game', 'HowTo', 'Episode', 'LocalBusiness']; // Start with the base markup for the main item. $markup = ['@context' => 'https://schema.org/', '@type' => $schemaType, 'name' => $post->post_title, 'url' => get_permalink($post)]; // Only attach review data to supported types. if (\in_array($schemaType, $reviewableTypes, \true)) { $reviews = get_comments(['post_id' => $post->ID, 'status' => 'approve', 'type__in' => ['comment', 'review']]); if (!empty($reviews)) { $reviewCount = 0; $averageRating = 0.0; $reviewItems = []; foreach ($reviews as $review) { if (!empty($review->comment_parent)) { continue; // Skip replies. } $rating = \get_comment_meta($review->comment_ID, 'rating', \true); $ratingValue = $rating !== '' ? (float) $rating : null; if ($ratingValue !== null && $ratingValue > 0) { $reviewCount++; $averageRating += $ratingValue; $reviewItems[] = ['@type' => 'Review', 'author' => ['@type' => 'Person', 'name' => $review->comment_author], 'reviewRating' => ['@type' => 'Rating', 'ratingValue' => $ratingValue], 'datePublished' => get_comment_date('c', $review), 'reviewBody' => $review->comment_content]; } } if ($reviewCount > 0 && $averageRating > 0) { $trueAverage = \round($averageRating / $reviewCount, 1); // Nest AggregateRating and individual Reviews correctly. $markup['aggregateRating'] = ['@type' => 'AggregateRating', 'ratingValue' => $trueAverage, 'reviewCount' => $reviewCount]; $markup['review'] = $reviewItems; } } } // Restore Divi filter. if ($divi_removed) { add_filter('get_comment_metadata', $divi_callback, $priority ?? 10, 4); } return $markup; } /** * Outputs the schema in the page head for all eligible non-product post types. */ public static function addCustomRichSchema() : void { // Bail early if not on frontend or not singular. if (is_admin() || !is_singular() || \function_exists('is_product') && \is_product()) { return; } global $post; if (empty($post) || !isset($post->ID)) { return; } $handler = new self(); $markup = $handler->schemaHandler([], $post); if (!empty($markup)) { echo "\n<!-- ReviewX Rich Schema for {$post->post_type} -->\n"; echo '<script type="application/ld+json">' . wp_json_encode($markup, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES) . '</script>'; echo "\n<!-- /ReviewX Rich Schema -->\n"; } } } CptReviewsCount.php 0000777 00000001634 15251237711 0010400 0 ustar 00 <?php namespace Rvx\CPT; class CptReviewsCount { public function newCount($post_id) { // Fetch all approved comments (reviews) for the post $reviews = get_comments(['post_id' => $post_id, 'status' => 'approve', 'type' => 'comment']); // Initialize review count $totalCount = 0; $reviewCount = 0; if (!empty($reviews)) { foreach ($reviews as $review) { $totalCount++; // Skip replies if ($review->comment_parent > 0) { continue; // Skip replies } $reviewCount++; } } // If no reviews exist, fall back to original comments count if ($reviewCount === 0) { return $totalCount; // Return original count if no reviews } return [$reviewCount, $totalCount]; } } CptHelper.php 0000777 00000006226 15251237711 0007164 0 ustar 00 <?php namespace Rvx\CPT; use Rvx\Rest\Controllers\CptController; use Rvx\Utilities\Auth\Client; class CptHelper { public function enabledCPT() : array { if (!Client::getSync()) { return []; } // Retrieve settings $data = \get_option('_rvx_cpt_settings'); // Default enabled post types $enabled_post_types = ['product' => 'product']; // Validate data before processing if (\is_array($data) && isset($data['reviews']) && \is_array($data['reviews'])) { foreach ($data['reviews'] as $review) { if (isset($review['status'], $review['post_type']) && $review['status'] === 'Enabled' && post_type_exists($review['post_type']) && $review['post_type'] !== 'page') { $enabled_post_types[\strtolower($review['post_type'])] = \strtolower($review['post_type']); } } } else { return []; } return \array_unique($enabled_post_types); } public function usedCPT($param = 'all') { if (!Client::getSync()) { return []; } $data = (new CptController())->customPostTypes($param); if (!isset($data) || !\is_array($data)) { return []; } // Transform the data $formattedData = []; foreach ($data as $item) { if (isset($item['slug'])) { $formattedData[$item['slug']] = $item['slug']; } } return $formattedData; } public function usedCPTOnSync($param = 'all') { $data = (new CptController())->customPostTypesOnSync($param); if (!isset($data) || !\is_array($data)) { return []; } // Transform the data $formattedData = []; foreach ($data as $item) { if (isset($item['slug'])) { $formattedData[$item['slug']] = $item['slug']; } } return $formattedData; } public function cptSettings() : array { if (!Client::getSync()) { return []; } // Retrieve settings with default as an empty array $data = \get_option('_rvx_cpt_settings', []); // Ensure the data is always an array return \is_array($data) ? $data : []; } public function cptSettingsOnSync() : array { // Retrieve settings with default as an empty array $data = \get_option('_rvx_cpt_settings', []); // Ensure the data is always an array return \is_array($data) ? $data : []; } public function getPublicCptList() { $args = array('public' => \true, '_builtin' => \false); $post_types = get_post_types($args, 'objects'); $result = array(); if (!empty($post_types)) { foreach ($post_types as $post_type) { if ($post_type->name !== 'product') { $result[] = array('name' => \ucfirst($post_type->labels->name), 'slug' => \strtolower($post_type->name)); } } } // Add post type $result[] = array('name' => 'Post', 'slug' => 'post'); return $result; } } CptAverageRating.php 0000777 00000011705 15251237711 0010462 0 ustar 00 <?php namespace Rvx\CPT; use WP_Post; use Rvx\CPT\CptHelper; class CptAverageRating { /** * Initialize or update the rvx_avg_rating meta key for a post. * add_action comment_post */ public static function handle_comment_rating($comment_id, $comment_approved, $comment = null) { if ($comment_approved !== 1) { return; } if ($comment === null) { $comment = get_comment($comment_id); } if (!$comment) { return; } $post_id = $comment->comment_post_ID; // Update the average rating for the post. self::update_average_rating($post_id); } /** * Handle when a comment's status is updated. * * @param int $comment_id The comment ID. * @param string $status The new status of the comment. */ public static function handle_comment_status_change($comment_id, $status) { $comment = get_comment($comment_id); if (!$comment) { return; // Exit if the comment doesn't exist. } $post_id = $comment->comment_post_ID; // Update the average rating for the post. self::update_average_rating($post_id); } /** * Calculate and update the rvx_avg_rating meta key for a given post. * * @param int $post_id ID of the post. */ public static function update_average_rating($post_id) { // Check the post type. $post_type = get_post_type($post_id); $enabled_post_types = (new CptHelper())->usedCPT('used'); if (!isset($enabled_post_types[$post_type]) && $post_type !== 'product') { return; } global $wpdb; // Fetch all approved comment ratings for the post, only for parent comments. $ratings = $wpdb->get_col($wpdb->prepare("SELECT cm.meta_value \n FROM {$wpdb->commentmeta} AS cm\n INNER JOIN {$wpdb->comments} AS c\n ON cm.comment_id = c.comment_ID\n WHERE cm.meta_key = 'rating'\n AND c.comment_post_ID = %d\n AND c.comment_approved = '1'\n AND c.comment_parent = 0", $post_id)); $average_rating = \get_post_meta($post_id, 'rvx_avg_rating', \true); if (empty($average_rating)) { $average_rating = 0.0; } if (!empty($ratings)) { // Calculate the count and average rating using only parent comments. $count = \count($ratings); $average_rating = \round(\array_sum($ratings) / $count, 2); // Calculate individual star counts $starCounts = \array_count_values(\array_map('intval', $ratings)); // Store the average rating and count as post meta \update_post_meta($post_id, 'rvx_avg_rating', (float) $average_rating); \update_post_meta($post_id, 'rating', (float) $average_rating); \update_post_meta($post_id, 'rvx_total_reviews', (int) $count); // Store individual star counts for ($i = 1; $i <= 5; $i++) { \update_post_meta($post_id, "rvx_star_count_{$i}", (int) ($starCounts[$i] ?? 0)); } if ($post_type === 'product') { \update_post_meta($post_id, '_wc_average_rating', (float) $average_rating); \update_post_meta($post_id, '_wc_review_count', (int) $count); } } else { // No ratings found, set the meta keys to 0. \update_post_meta($post_id, 'rvx_avg_rating', (float) 0.0); \update_post_meta($post_id, 'rating', (float) 0.0); \update_post_meta($post_id, 'rvx_total_reviews', 0); for ($i = 1; $i <= 5; $i++) { \update_post_meta($post_id, "rvx_star_count_{$i}", 0); } if ($post_type === 'product') { \update_post_meta($post_id, '_wc_average_rating', (float) 0.0); \update_post_meta($post_id, '_wc_review_count', 0); } } } /** * Hook into the post save action to add the rvx_avg_rating meta key. * * @param int $post_id The post ID. * @param WP_Post $post The post object. */ public static function rvx_avg_rating_on_save($post_id, $post) { // Ensure we are not triggering on autosave if (\defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } // Check the post type. $post_type = get_post_type($post_id); $enabled_post_types = (new CptHelper())->usedCPT('used'); if (!isset($enabled_post_types[$post_type]) && $post_type !== 'product') { return; } // Check if the rvx_avg_rating key already exists if (!\get_post_meta($post_id, 'rvx_avg_rating', \true)) { // Add the rvx_avg_rating meta key with an initial value (0.00) \update_post_meta($post_id, 'rvx_avg_rating', (float) 0.0); \update_post_meta($post_id, 'rating', (float) 0.0); } } }
| ver. 1.6 |
Github
|
.
| PHP 8.1.34 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка