diff --git a/config.php b/config.php index 863dafcae8..6247368312 100644 --- a/config.php +++ b/config.php @@ -1,7 +1,7 @@ absint( $listing_id ), + 'type' => 'review', + 'status' => 'all', + 'orderby' => 'comment_date_gmt', + 'order' => 'ASC', + ] + ); + + if ( empty( $comments ) ) { + return []; + } + + $reviews = []; + foreach ( $comments as $comment ) { + $reviews[] = [ + 'review_id' => (int) $comment->comment_ID, + 'parent_review_id' => (int) $comment->comment_parent, + 'user_id' => (int) $comment->user_id, + 'author' => self::escape_data( $comment->comment_author ), + 'email' => self::escape_data( $comment->comment_author_email ), + 'url' => esc_url_raw( $comment->comment_author_url ), + 'content' => self::escape_data( $comment->comment_content ), + 'rating' => (float) get_comment_meta( $comment->comment_ID, 'rating', true ), + 'status' => self::prepare_review_status_for_export( $comment->comment_approved ), + 'date' => $comment->comment_date, + 'date_gmt' => $comment->comment_date_gmt, + 'meta' => self::get_listing_review_meta_data( $comment, $listing_id ), + 'advanced_review' => self::get_listing_advanced_review_data( $comment, $listing_id ), + ]; + } + + return apply_filters( 'directorist_listings_export_reviews_data', $reviews, $listing_id ); + } + + public static function get_listing_review_meta_data( $comment, $listing_id = 0 ) { + $meta = get_comment_meta( $comment->comment_ID ); + + foreach ( $meta as $key => $values ) { + $meta[ $key ] = array_map( 'maybe_unserialize', $values ); + } + + return apply_filters( 'directorist_listings_export_review_meta', $meta, $comment, $listing_id ); + } + + public static function get_listing_advanced_review_data( $comment, $listing_id = 0 ) { + global $wpdb; + + $table = self::get_advanced_review_table_name(); + + if ( ! self::advanced_review_table_exists( $table ) ) { + return []; + } + + $rows = $wpdb->get_results( + $wpdb->prepare( + "SELECT criteria_key, rating FROM {$table} WHERE comment_ID = %d AND listing_id = %d ORDER BY id ASC", + $comment->comment_ID, + absint( $listing_id ) + ), + ARRAY_A + ); + + if ( empty( $rows ) ) { + return []; + } + + $advanced_review = []; + $criteria_labels = self::get_advanced_review_criteria_labels( $listing_id ); + + foreach ( $rows as $row ) { + $criteria_key = (string) $row['criteria_key']; + $advanced_review[] = [ + 'criteria_key' => self::escape_data( $criteria_key ), + 'criteria_label' => ! empty( $criteria_labels[ $criteria_key ] ) ? self::escape_data( $criteria_labels[ $criteria_key ] ) : '', + 'rating' => (float) $row['rating'], + ]; + } + + return apply_filters( 'directorist_listings_export_advanced_review_data', $advanced_review, $comment, $listing_id ); + } + + protected static function get_advanced_review_table_name() { + global $wpdb; + + return $wpdb->prefix . 'directorist_advanced_reviews'; + } + + protected static function advanced_review_table_exists( $table ) { + global $wpdb; + + return $table === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ); + } + + protected static function get_advanced_review_criteria_labels( $listing_id = 0 ) { + if ( ! function_exists( 'directorist_get_directory_meta' ) || ! function_exists( 'directorist_get_listings_directory_type' ) ) { + return []; + } + + $contents = directorist_get_directory_meta( directorist_get_listings_directory_type( $listing_id ), 'single_listings_contents' ); + + if ( empty( $contents['fields']['review_criteria']['criterias'] ) || ! is_array( $contents['fields']['review_criteria']['criterias'] ) ) { + return []; + } + + $labels = []; + foreach ( $contents['fields']['review_criteria']['criterias'] as $criteria ) { + if ( ! isset( $criteria['id'], $criteria['value'] ) ) { + continue; + } + + $labels[ (string) $criteria['id'] ] = $criteria['value']; + } + + return $labels; + } + + protected static function prepare_review_status_for_export( $status ) { + if ( '1' === (string) $status ) { + return 'approve'; + } + + if ( '0' === (string) $status ) { + return 'hold'; + } + + return (string) $status; + } + /** * Escape a string to be used in a CSV context * diff --git a/includes/classes/class-tools.php b/includes/classes/class-tools.php index 33264c361b..e9209e30a5 100644 --- a/includes/classes/class-tools.php +++ b/includes/classes/class-tools.php @@ -260,6 +260,11 @@ public function handle_import_listings() { $metas = ! empty( $_POST['meta'] ) ? directorist_clean( wp_unslash( $_POST['meta'] ) ) : []; $tax_inputs = ! empty( $_POST['tax_input'] ) ? directorist_clean( wp_unslash( $_POST['tax_input'] ) ) : []; $publish_date = ! empty( $metas['publish_date'] ) ? directorist_clean( $metas['publish_date'] ) : ''; + $reviews = ! empty( $metas['reviews'] ) ? directorist_clean( $metas['reviews'] ) : ''; + + if ( isset( $metas['reviews'] ) ) { + unset( $metas['reviews'] ); + } $total_items = $importer->get_total_items(); @@ -464,6 +469,14 @@ public function handle_import_listings() { } } + if ( $reviews && ! empty( $post[ $reviews ] ) ) { + $imported_reviews = $this->import_listing_reviews( $post_id, $post[ $reviews ] ); + + if ( $imported_reviews ) { + $processed_logs[] = sprintf( '[%d->%d]: %d reviews imported.', $position, $post_id, $imported_reviews ); + } + } + /** * Fire this event once a listing is successfully imported from CSV. * @@ -523,6 +536,275 @@ public function maybe_create_term( $term, $taxonomy ) { return null; } + public function import_listing_reviews( $listing_id, $reviews_data ) { + $reviews = $this->parse_listing_reviews_data( $reviews_data ); + + if ( empty( $reviews ) || ! is_array( $reviews ) ) { + return 0; + } + + $imported = 0; + $review_id_map = []; + + foreach ( $reviews as $review ) { + if ( ! is_array( $review ) ) { + continue; + } + + $content = ! empty( $review['content'] ) ? static::sanitize_textarea( $review['content'] ) : ''; + $rating = isset( $review['rating'] ) ? (float) $review['rating'] : 0; + + if ( '' === $content && $rating <= 0 ) { + continue; + } + + $old_parent_id = ! empty( $review['parent_review_id'] ) ? absint( $review['parent_review_id'] ) : 0; + $comment_id = wp_insert_comment( + [ + 'comment_post_ID' => absint( $listing_id ), + 'comment_author' => ! empty( $review['author'] ) ? static::sanitize_text( $review['author'] ) : '', + 'comment_author_email' => ! empty( $review['email'] ) && is_email( $review['email'] ) ? sanitize_email( $review['email'] ) : '', + 'comment_author_url' => ! empty( $review['url'] ) ? esc_url_raw( $review['url'] ) : '', + 'comment_content' => $content, + 'comment_type' => 'review', + 'comment_approved' => $this->prepare_review_status_for_import( $review['status'] ?? 'approve' ), + 'comment_parent' => ! empty( $review_id_map[ $old_parent_id ] ) ? $review_id_map[ $old_parent_id ] : 0, + 'comment_date' => $this->prepare_review_date_for_import( $review['date'] ?? '' ), + 'comment_date_gmt' => $this->prepare_review_date_for_import( $review['date_gmt'] ?? '', true ), + 'user_id' => ! empty( $review['user_id'] ) ? absint( $review['user_id'] ) : 0, + ] + ); + + if ( ! $comment_id ) { + continue; + } + + if ( $rating > 0 ) { + $rating = number_format( max( 0, min( 5, $rating ) ), 2, '.', '' ); + + if ( class_exists( 'Directorist\Review\Comment_Meta' ) ) { + \Directorist\Review\Comment_Meta::set_rating( $comment_id, $rating ); + } else { + update_comment_meta( $comment_id, 'rating', $rating ); + } + } + + if ( ! empty( $review['meta'] ) && is_array( $review['meta'] ) ) { + $this->import_listing_review_meta( $comment_id, $review['meta'], $review, $listing_id ); + } + + if ( ! empty( $review['advanced_review'] ) && is_array( $review['advanced_review'] ) ) { + $this->import_listing_advanced_review_data( $comment_id, $review['advanced_review'], $review, $listing_id ); + } + + if ( ! empty( $review['review_id'] ) ) { + $review_id_map[ absint( $review['review_id'] ) ] = $comment_id; + } + + $imported++; + } + + if ( $imported && class_exists( 'Directorist\Review\Comment' ) ) { + \Directorist\Review\Comment::clear_transients( $listing_id ); + } + + return $imported; + } + + protected function import_listing_review_meta( $comment_id, $meta, $review, $listing_id ) { + $meta = apply_filters( 'directorist_listings_import_review_meta', $meta, $review, $comment_id, $listing_id ); + + if ( empty( $meta ) || ! is_array( $meta ) ) { + return; + } + + foreach ( $meta as $key => $values ) { + $key = sanitize_key( $key ); + + if ( ! $key || 'rating' === $key ) { + continue; + } + + if ( ! is_array( $values ) ) { + $values = [ $values ]; + } + + delete_comment_meta( $comment_id, $key ); + + foreach ( $values as $value ) { + add_comment_meta( $comment_id, $key, maybe_unserialize( $value ) ); + } + } + } + + protected function import_listing_advanced_review_data( $comment_id, $advanced_review, $review, $listing_id ) { + global $wpdb; + + $table = $this->get_advanced_review_table_name(); + + if ( ! $this->advanced_review_table_exists( $table ) ) { + return 0; + } + + $advanced_review = apply_filters( 'directorist_listings_import_advanced_review_data', $advanced_review, $review, $comment_id, $listing_id ); + + if ( empty( $advanced_review ) || ! is_array( $advanced_review ) ) { + return 0; + } + + $wpdb->delete( + $table, + [ + 'comment_ID' => absint( $comment_id ), + ], + [ + '%d', + ] + ); + + $imported = 0; + $now = current_time( 'mysql' ); + + foreach ( $advanced_review as $criteria ) { + if ( ! isset( $criteria['rating'] ) ) { + continue; + } + + $criteria_key = ''; + + if ( ! empty( $criteria['criteria_label'] ) ) { + $criteria_key = $this->get_advanced_review_criteria_key_by_label( $criteria['criteria_label'], $listing_id ); + } + + if ( '' === $criteria_key && ! empty( $criteria['criteria_key'] ) ) { + $criteria_key = sanitize_text_field( wp_unslash( $criteria['criteria_key'] ) ); + } + + $rating = max( 0, min( 5, (float) $criteria['rating'] ) ); + + if ( '' === $criteria_key || $rating <= 0 ) { + continue; + } + + $inserted = $wpdb->insert( + $table, + [ + 'comment_ID' => absint( $comment_id ), + 'listing_id' => absint( $listing_id ), + 'criteria_key' => $criteria_key, + 'rating' => $rating, + 'created_at' => $now, + 'updated_at' => $now, + ], + [ + '%d', + '%d', + '%s', + '%f', + '%s', + '%s', + ] + ); + + if ( $inserted ) { + $imported++; + } + } + + return $imported; + } + + protected function get_advanced_review_table_name() { + global $wpdb; + + return $wpdb->prefix . 'directorist_advanced_reviews'; + } + + protected function get_advanced_review_criteria_key_by_label( $criteria_label, $listing_id = 0 ) { + if ( ! function_exists( 'directorist_get_directory_meta' ) || ! function_exists( 'directorist_get_listings_directory_type' ) ) { + return ''; + } + + $contents = directorist_get_directory_meta( directorist_get_listings_directory_type( $listing_id ), 'single_listings_contents' ); + + if ( empty( $contents['fields']['review_criteria']['criterias'] ) || ! is_array( $contents['fields']['review_criteria']['criterias'] ) ) { + return ''; + } + + $criteria_label = sanitize_text_field( wp_unslash( $criteria_label ) ); + + foreach ( $contents['fields']['review_criteria']['criterias'] as $criteria ) { + if ( ! isset( $criteria['id'], $criteria['value'] ) ) { + continue; + } + + if ( $criteria_label === $criteria['value'] ) { + return (string) $criteria['id']; + } + } + + return ''; + } + + protected function advanced_review_table_exists( $table ) { + global $wpdb; + + return $table === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ); + } + + protected function parse_listing_reviews_data( $reviews_data ) { + $reviews_data = trim( self::unescape_data( $reviews_data ) ); + + if ( '' === $reviews_data ) { + return []; + } + + $decoded_data = base64_decode( $reviews_data, true ); + if ( false !== $decoded_data ) { + $decoded_reviews = json_decode( $decoded_data, true ); + + if ( is_array( $decoded_reviews ) ) { + return $decoded_reviews; + } + } + + $decoded_reviews = json_decode( $reviews_data, true ); + if ( is_array( $decoded_reviews ) ) { + return $decoded_reviews; + } + + $decoded_reviews = json_decode( str_replace( "'", '"', $reviews_data ), true ); + if ( is_array( $decoded_reviews ) ) { + return $decoded_reviews; + } + + return []; + } + + protected function prepare_review_status_for_import( $status ) { + $status = sanitize_key( $status ); + + if ( in_array( $status, [ '1', 'approve', 'approved' ], true ) ) { + return '1'; + } + + if ( in_array( $status, [ 'spam', 'trash' ], true ) ) { + return $status; + } + + return '0'; + } + + protected function prepare_review_date_for_import( $date, $gmt = false ) { + $timestamp = strtotime( $date ); + + if ( ! $timestamp ) { + return current_time( 'mysql', $gmt ); + } + + return date( 'Y-m-d H:i:s', $timestamp ); + } + // maybe_unserialize_csv_string public function maybe_unserialize_csv_string( $data ) { if ( ! is_string( $data ) ) { @@ -647,6 +929,7 @@ public function setup_importable_fields( $directory_id = 0 ) { $this->importable_fields[ 'publish_date' ] = esc_html__( 'Publish Date', 'directorist' ); $this->importable_fields[ 'listing_status' ] = esc_html__( 'Listing Status', 'directorist' ); + $this->importable_fields[ 'reviews' ] = esc_html__( 'Reviews', 'directorist' ); foreach ( $fields as $field ) { $field_key = ! empty( $field['field_key'] ) ? $field['field_key'] : ''; diff --git a/includes/fields/class-directorist-fields.php b/includes/fields/class-directorist-fields.php index 8a4c2d7666..b44b340c6b 100644 --- a/includes/fields/class-directorist-fields.php +++ b/includes/fields/class-directorist-fields.php @@ -110,7 +110,7 @@ public static function translate_key_to_field( $type ) { 'dhotels-number' => 'number', 'dhotels-area' => 'number', 'dlawyers_age' => 'number', - 'dlawyers_gender' => 'number', + 'dlawyers_gender' => 'radio', 'drealestate-number' => 'number', 'drealestate-area' => 'number', 'dclassified-bullet-list' => 'textarea', diff --git a/languages/directorist.pot b/languages/directorist.pot index 1746a72b1b..e50813ab0c 100644 --- a/languages/directorist.pot +++ b/languages/directorist.pot @@ -6,7 +6,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2026-05-18 10:55+0000\n" +"POT-Creation-Date: 2026-05-20 05:04+0000\n" "X-Poedit-Basepath: ..\n" "X-Poedit-KeywordsList: __;_e;_ex:1,2c;_n:1,2;_n_noop:1,2;_nx:1,2,4c;_nx_noop:1,2,3c;_x:1,2c;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n" "X-Poedit-SearchPath-0: .\n" @@ -1023,7 +1023,7 @@ msgstr "" msgid "Filter By Icon Pack" msgstr "" -#: ../includes/asset-loader/localized_data.php:288, ../includes/classes/class-tools.php:751 +#: ../includes/asset-loader/localized_data.php:288, ../includes/classes/class-tools.php:1034 msgid "Done" msgstr "" @@ -2788,7 +2788,7 @@ msgstr "" msgid "Start Building Directory" msgstr "" -#: ../includes/classes/class-settings-panel.php:138, ../includes/classes/class-tools.php:745, ../views/admin-templates/post-types-manager/all-listing-types.php:282, ../views/admin-templates/post-types-manager/all-listing-types.php:300 +#: ../includes/classes/class-settings-panel.php:138, ../includes/classes/class-tools.php:1028, ../views/admin-templates/post-types-manager/all-listing-types.php:282, ../views/admin-templates/post-types-manager/all-listing-types.php:300 msgid "Import" msgstr "" @@ -5179,43 +5179,47 @@ msgstr "" msgid "Please select a valid CSV or TXT file." msgstr "" -#: ../includes/classes/class-tools.php:269 +#: ../includes/classes/class-tools.php:274 msgid "No data found" msgstr "" -#: ../includes/classes/class-tools.php:648 +#: ../includes/classes/class-tools.php:930 msgid "Publish Date" msgstr "" -#: ../includes/classes/class-tools.php:649 +#: ../includes/classes/class-tools.php:931 msgid "Listing Status" msgstr "" -#: ../includes/classes/class-tools.php:663, ../includes/elementor/all-listing.php:192, ../includes/elementor/category.php:93, ../includes/elementor/location.php:92, ../includes/elementor/search-result.php:106, ../includes/elementor/tag.php:92 +#: ../includes/classes/class-tools.php:932, ../includes/review/class-admin.php:68, ../includes/review/class-admin.php:82, ../includes/review/class-admin.php:83 +msgid "Reviews" +msgstr "" + +#: ../includes/classes/class-tools.php:946, ../includes/elementor/all-listing.php:192, ../includes/elementor/category.php:93, ../includes/elementor/location.php:92, ../includes/elementor/search-result.php:106, ../includes/elementor/tag.php:92 msgid "Price" msgstr "" -#: ../includes/classes/class-tools.php:664, ../includes/modules/multi-directory-setup/builder-preset-fields.php:281 +#: ../includes/classes/class-tools.php:947, ../includes/modules/multi-directory-setup/builder-preset-fields.php:281 msgid "Price Range" msgstr "" -#: ../includes/classes/class-tools.php:668 +#: ../includes/classes/class-tools.php:951 msgid "Map Latitude" msgstr "" -#: ../includes/classes/class-tools.php:669 +#: ../includes/classes/class-tools.php:952 msgid "Map Longitude" msgstr "" -#: ../includes/classes/class-tools.php:670 +#: ../includes/classes/class-tools.php:953 msgid "Hide Map" msgstr "" -#: ../includes/classes/class-tools.php:733, ../views/admin-templates/import-export/body-templates/step-one.php:17 +#: ../includes/classes/class-tools.php:1016, ../views/admin-templates/import-export/body-templates/step-one.php:17 msgid "Upload CSV File" msgstr "" -#: ../includes/classes/class-tools.php:739 +#: ../includes/classes/class-tools.php:1022 msgid "Column Mapping" msgstr "" @@ -6333,10 +6337,6 @@ msgstr "" msgid "Review" msgstr "" -#: ../includes/review/class-admin.php:68, ../includes/review/class-admin.php:82, ../includes/review/class-admin.php:83 -msgid "Reviews" -msgstr "" - #: ../includes/review/class-admin.php:139 msgid "Review Data" msgstr "" diff --git a/readme.txt b/readme.txt index d57fea617e..4c4ee49ca9 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Tags: business directory, listings, classifieds, directory plugin, directory Requires at least: 4.6 Tested up to: 7.0 Requires PHP: 7.0 -Stable tag: 8.7.2 +Stable tag: 8.7.3 License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -300,6 +300,11 @@ Directorist comes with an AI-powered directory builder. Use the Create with AI o == Changelog == += 8.7.3 - May 20, 2026 = + +**Improved** + - Listing import/export to include listing reviews and Advanced Review criteria data in the existing CSV flow. (#2862) + = 8.7.2 - May 19, 2026 = **Improved**