diff --git a/config.php b/config.php
index a295365ae1..b74611b018 100644
--- a/config.php
+++ b/config.php
@@ -1,7 +1,7 @@
cap->edit_post, $post_id ) ) {
+ throw new \Exception( __( 'You are not allowed to upload files for this post.', 'directorist' ), 403 );
+ }
+ }
$form_fields = get_term_meta( $directory, 'submission_form_fields', true );
- $field_config = array_values( wp_list_filter( $form_fields['fields'], [ 'field_key' => $field_id ] ) );
- $field_config = current( $field_config );
+ $field_config = [];
+
+ if ( ! empty( $form_fields['fields'] ) && is_array( $form_fields['fields'] ) ) {
+ $field_config = array_values( wp_list_filter( $form_fields['fields'], [ 'field_key' => $field_id ] ) );
+ $field_config = current( $field_config );
+ }
+
+ if ( empty( $field_config ) || ! is_array( $field_config ) || empty( $field_config['field_key'] ) || 'file' !== ( $field_config['type'] ?? '' ) ) {
+ throw new \Exception( __( 'Invalid upload field!', 'directorist' ), 400 );
+ }
+
+ $field_id = sanitize_text_field( $field_config['field_key'] );
+ $fixed_file = ( ! empty( $_FILES[ $field_id . 'async-upload' ] ) ) ? directorist_clean( wp_unslash( $_FILES[ $field_id . 'async-upload' ] ) ) : '';
+
+ if ( empty( $fixed_file ) ) {
+ throw new \Exception( __( 'No file supplied.', 'directorist' ), 400 );
+ }
$file_type = ! empty( $field_config['file_type'] ) ? $field_config['file_type'] : 'image';
$file_size = ! empty( $field_config['file_size'] ) ? $field_config['file_size'] : '2mb';
+ $max_size = wp_convert_hr_to_bytes( $file_size );
+
+ if ( $max_size > 0 && ! empty( $fixed_file['size'] ) && (int) $fixed_file['size'] > $max_size ) {
+ throw new \Exception(
+ sprintf(
+ /* translators: %s: maximum file size */
+ __( 'Uploaded file is larger than the allowed size of %s.', 'directorist' ),
+ size_format( $max_size )
+ ),
+ 400
+ );
+ }
if ( in_array( $file_type, [ '', 'all_types', 'all' ], true ) ) {
$file_types = directorist_get_supported_file_types();
diff --git a/includes/classes/class-formgent.php b/includes/classes/class-formgent.php
index 960c49cff0..5ad3ae1002 100644
--- a/includes/classes/class-formgent.php
+++ b/includes/classes/class-formgent.php
@@ -10,9 +10,18 @@
if ( ! class_exists( 'ATBDP_Formgent' ) ) {
class ATBDP_Formgent
{
+ protected static $hooks_registered = false;
public function __construct() {
+ if ( self::$hooks_registered ) {
+ return;
+ }
+
+ self::$hooks_registered = true;
+
add_action( 'formgent_after_create_form_response_token', [ $this, 'after_create_form_response_token' ], 10, 3 );
add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
+
+ add_filter( 'formgent_email_send_to', [ $this, 'route_email_to_listing_owner' ], 10, 5 );
}
public function after_create_form_response_token( $response_token, $dto, \WP_REST_Request $wp_rest_request ) {
@@ -26,6 +35,161 @@ public function after_create_form_response_token( $response_token, $dto, \WP_RES
$response_repository->add_meta( $dto->get_id(), 'listing_id', absint( $external_data['listing_id'] ) );
}
+ public function route_email_to_listing_owner( $send_to, $email, $response, $form_answers_data, $queue ) {
+ $response_id = is_object( $queue ) && ! empty( $queue->response_id ) ? absint( $queue->response_id ) : 0;
+
+ if ( empty( $response_id ) ) {
+ return $send_to;
+ }
+
+ $listing_id = absint( formgent_response_repository()->get_meta_value( $response_id, 'listing_id' ) );
+
+ if ( empty( $listing_id ) ) {
+ return $send_to;
+ }
+
+ $should_route = ! $this->recipient_matches_form_email_answer( $send_to, $form_answers_data );
+
+ $should_route = (bool) apply_filters(
+ 'directorist_formgent_route_email_to_listing_owner',
+ $should_route,
+ $listing_id,
+ $send_to,
+ $email,
+ $response,
+ $form_answers_data,
+ $queue
+ );
+
+ if ( ! $should_route ) {
+ return $send_to;
+ }
+
+ $recipient = $this->get_listing_owner_email_recipient( $listing_id );
+
+ $recipient = apply_filters(
+ 'directorist_formgent_listing_owner_email_recipient',
+ $recipient,
+ $listing_id,
+ $send_to,
+ $email,
+ $response,
+ $form_answers_data,
+ $queue
+ );
+
+ $listing_owner_recipients = $this->normalize_email_recipients( $recipient );
+
+ if ( empty( $listing_owner_recipients ) ) {
+ return $send_to;
+ }
+
+ $send_to_recipients = $this->normalize_email_recipients( $send_to );
+ $recipients = array_values( array_unique( array_merge( $send_to_recipients, $listing_owner_recipients ) ) );
+
+ return count( $recipients ) > 1 ? $recipients : reset( $recipients );
+ }
+
+ protected function get_listing_owner_email_recipient( $listing_id ) {
+ $post_author_id = absint( get_post_field( 'post_author', $listing_id ) );
+
+ if ( empty( $post_author_id ) ) {
+ return '';
+ }
+
+ $contact_recipient = get_user_meta( $post_author_id, 'directorist_contact_owner_recipient', true );
+ $recipient_type = ! empty( $contact_recipient ) ? $contact_recipient : 'author';
+
+ if ( 'listing_email' === $recipient_type ) {
+ $listing_email = sanitize_email( get_post_meta( $listing_id, '_email', true ) );
+
+ if ( is_email( $listing_email ) ) {
+ return $listing_email;
+ }
+ }
+
+ $user = get_userdata( $post_author_id );
+
+ if ( empty( $user->user_email ) ) {
+ return '';
+ }
+
+ return sanitize_email( $user->user_email );
+ }
+
+ protected function recipient_matches_form_email_answer( $send_to, $form_answers_data ) {
+ $send_to_emails = $this->normalize_email_recipients( $send_to );
+
+ if ( empty( $send_to_emails ) || empty( $form_answers_data ) || ! is_array( $form_answers_data ) ) {
+ return false;
+ }
+
+ foreach ( $form_answers_data as $answer ) {
+ $field_type = '';
+ $value = '';
+
+ if ( is_object( $answer ) ) {
+ if ( method_exists( $answer, 'get_field_type' ) ) {
+ $field_type = $answer->get_field_type();
+ }
+
+ if ( method_exists( $answer, 'get_value' ) ) {
+ $value = $answer->get_value();
+ }
+ } elseif ( is_array( $answer ) ) {
+ $field_type = $answer['field_type'] ?? '';
+ $value = $answer['value'] ?? '';
+ }
+
+ if ( 'email' !== $field_type ) {
+ continue;
+ }
+
+ $answer_emails = $this->normalize_email_recipients( $value );
+
+ if ( array_intersect( $send_to_emails, $answer_emails ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected function normalize_email_recipients( $emails ) {
+ if ( empty( $emails ) ) {
+ return [];
+ }
+
+ if ( is_array( $emails ) ) {
+ $normalized = [];
+
+ foreach ( $emails as $email ) {
+ $normalized = array_merge( $normalized, $this->normalize_email_recipients( $email ) );
+ }
+
+ return array_values( array_unique( $normalized ) );
+ }
+
+ if ( ! is_string( $emails ) ) {
+ return [];
+ }
+
+ preg_match_all( '/[A-Z0-9._%+\-]+@[A-Z0-9.\-]+\.[A-Z]{2,}/i', $emails, $matches );
+ $emails = ! empty( $matches[0] ) ? $matches[0] : preg_split( '/[,;]/', $emails );
+
+ $normalized = [];
+
+ foreach ( $emails as $email ) {
+ $email = sanitize_email( trim( $email ) );
+
+ if ( is_email( $email ) ) {
+ $normalized[] = strtolower( $email );
+ }
+ }
+
+ return array_values( array_unique( $normalized ) );
+ }
+
public function rest_api_init() {
register_rest_route(
'directorist', '/formgent/responses', [
@@ -34,7 +198,7 @@ public function rest_api_init() {
'permission_callback' => [ $this, 'check_permission' ],
]
);
-
+
register_rest_route(
'directorist', '/formgent/responses/kpis', [
'methods' => 'GET',
@@ -42,7 +206,7 @@ public function rest_api_init() {
'permission_callback' => [ $this, 'check_permission' ],
]
);
-
+
register_rest_route(
'directorist', '/formgent/responses', [
'methods' => 'DELETE',
@@ -50,7 +214,7 @@ public function rest_api_init() {
'permission_callback' => [ $this, 'check_permission' ],
]
);
-
+
register_rest_route(
'directorist', '/formgent/responses/read', [
'methods' => 'POST',
@@ -58,7 +222,7 @@ public function rest_api_init() {
'permission_callback' => [ $this, 'check_permission' ],
]
);
-
+
register_rest_route(
'directorist', '/formgent/responses/single', [
'methods' => 'GET',
@@ -76,7 +240,7 @@ public function rest_api_init() {
*/
public function check_permission( $request ) {
$user_id = get_current_user_id();
-
+
// If user ID is 0, try to authenticate from cookies
if ( empty( $user_id ) && isset( $_COOKIE[ LOGGED_IN_COOKIE ] ) ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_validate_auth_cookie() handles sanitization
@@ -86,7 +250,7 @@ public function check_permission( $request ) {
wp_set_current_user( $user_id );
}
}
-
+
return ! empty( $user_id );
}
@@ -120,8 +284,8 @@ public function single_response( $request ) {
$listing_permalink = ATBDP_Permalink::get_listing_permalink( $listing_id, get_the_permalink( $listing_id ) );
}
- return rest_ensure_response(
- [
+ return rest_ensure_response(
+ [
'success' => true,
'response' => $response,
'fields' => $fields,
@@ -174,16 +338,16 @@ public function delete_responses( $request ) {
public function get_responses( $request ) {
$page = absint( $request->get_param( 'page' ) );
$per_page = absint( $request->get_param( 'per_page' ) );
-
+
$query = $this->get_responses_query();
$count_query = clone $query;
-
+
$responses = $query->select( 'response.*', 'post.post_title as listing_title', 'post.post_author as listing_owner' )->with(
'user', function( $query ) {
$query->select( 'ID', 'user_email', 'display_name' );
}
)->pagination( $page, $per_page );
-
+
$responses = array_map(
function( $response ) {
// Handle cases where user might be null (non-logged-in submissions)
@@ -201,7 +365,7 @@ function( $response ) {
return $response;
}, $responses
);
-
+
return [
'total' => $count_query->count(),
'responses' => $responses
@@ -347,11 +511,11 @@ protected function sanitize_response_field_options( array $options ) {
protected function get_responses_query() {
$user_id = get_current_user_id();
-
+
if ( empty( $user_id ) ) {
return Response::query( 'response' )->where( 'response.id', 0 );
}
-
+
return Response::query( 'response' )
->join(
ResponseMeta::get_table_name() . ' as response_meta', function( $join ) {
diff --git a/languages/directorist.pot b/languages/directorist.pot
index a2880d7d68..7cebafe6cd 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-07-13 11:53+0000\n"
+"POT-Creation-Date: 2026-07-16 04:55+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"
@@ -494,7 +494,7 @@ msgstr ""
msgid "Author"
msgstr ""
-#: ../includes/directorist-core-functions.php:61, ../includes/classes/class-formgent.php:197
+#: ../includes/directorist-core-functions.php:61, ../includes/classes/class-formgent.php:361
msgid "Guest"
msgstr ""
@@ -1228,7 +1228,7 @@ msgstr ""
msgid "This field is required."
msgstr ""
-#: ../includes/classes/class-add-listing.php:960, ../includes/classes/class-ajax-handler.php:870, ../includes/classes/class-ajax-handler.php:2035, ../includes/classes/class-custom-post.php:607, ../includes/classes/class-upgrade.php:1182, ../includes/classes/class-upgrade.php:1189, ../includes/classes/class-upgrade.php:1196
+#: ../includes/classes/class-add-listing.php:960, ../includes/classes/class-ajax-handler.php:870, ../includes/classes/class-ajax-handler.php:2083, ../includes/classes/class-custom-post.php:607, ../includes/classes/class-upgrade.php:1182, ../includes/classes/class-upgrade.php:1189, ../includes/classes/class-upgrade.php:1196
msgid "Security check failed."
msgstr ""
@@ -1280,11 +1280,11 @@ msgstr ""
msgid "You are not allowed to export listings."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:405, ../includes/classes/class-ajax-handler.php:1470, ../includes/classes/class-ajax-handler.php:1728, ../includes/classes/class-settings-panel.php:362, ../includes/modules/multi-directory-setup/class-multi-directory-manager.php:418, ../includes/modules/multi-directory-setup/class-multi-directory-manager.php:507, ../includes/modules/multi-directory-setup/class-multi-directory-manager.php:633
+#: ../includes/classes/class-ajax-handler.php:405, ../includes/classes/class-ajax-handler.php:1518, ../includes/classes/class-ajax-handler.php:1776, ../includes/classes/class-settings-panel.php:362, ../includes/modules/multi-directory-setup/class-multi-directory-manager.php:418, ../includes/modules/multi-directory-setup/class-multi-directory-manager.php:507, ../includes/modules/multi-directory-setup/class-multi-directory-manager.php:633
msgid "Something is wrong! Please refresh and retry."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:421, ../includes/classes/class-ajax-handler.php:440, ../includes/classes/class-ajax-handler.php:455, ../includes/classes/class-ajax-handler.php:2039, ../includes/classes/class-custom-post.php:611
+#: ../includes/classes/class-ajax-handler.php:421, ../includes/classes/class-ajax-handler.php:440, ../includes/classes/class-ajax-handler.php:455, ../includes/classes/class-ajax-handler.php:2087, ../includes/classes/class-custom-post.php:611
msgid "Permission denied."
msgstr ""
@@ -1324,7 +1324,7 @@ msgstr ""
msgid "No category selected."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:725, ../includes/classes/class-ajax-handler.php:2046
+#: ../includes/classes/class-ajax-handler.php:725, ../includes/classes/class-ajax-handler.php:2094
msgid "Invalid listing."
msgstr ""
@@ -1344,7 +1344,7 @@ msgstr ""
msgid "Something went wrong, please reload the page"
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:945
+#: ../includes/classes/class-ajax-handler.php:945, ../includes/classes/class-ajax-handler.php:962
msgid "Invalid upload request!"
msgstr ""
@@ -1352,87 +1352,104 @@ msgstr ""
msgid "Invalid directory type!"
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1111, ../includes/classes/class-ajax-handler.php:1153
+#: ../includes/classes/class-ajax-handler.php:971
+msgid "You are not allowed to upload files for this post."
+msgstr ""
+
+#: ../includes/classes/class-ajax-handler.php:984
+msgid "Invalid upload field!"
+msgstr ""
+
+#: ../includes/classes/class-ajax-handler.php:991
+msgid "No file supplied."
+msgstr ""
+
+#. translators: %s: maximum file size
+#: ../includes/classes/class-ajax-handler.php:1002
+msgid "Uploaded file is larger than the allowed size of %s."
+msgstr ""
+
+#: ../includes/classes/class-ajax-handler.php:1159, ../includes/classes/class-ajax-handler.php:1201
msgid "You are not allowed to perform this operation"
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1115, ../includes/classes/class-ajax-handler.php:1140, ../includes/classes/class-ajax-handler.php:1144, ../includes/classes/class-ajax-handler.php:1157
+#: ../includes/classes/class-ajax-handler.php:1163, ../includes/classes/class-ajax-handler.php:1188, ../includes/classes/class-ajax-handler.php:1192, ../includes/classes/class-ajax-handler.php:1205
msgid "Ops! something went wrong. Try again."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1137
+#: ../includes/classes/class-ajax-handler.php:1185
msgid "Profile updated successfully"
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1178
+#: ../includes/classes/class-ajax-handler.php:1226
msgid "Preferences updated successfully."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1269, ../includes/classes/class-ajax-handler.php:1342, ../includes/review/class-email.php:76
+#: ../includes/classes/class-ajax-handler.php:1317, ../includes/classes/class-ajax-handler.php:1390, ../includes/review/class-email.php:76
msgid "[{site_name}] New review at \"{listing_title}\""
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1272
+#: ../includes/classes/class-ajax-handler.php:1320
msgid "Dear User,
A new review at {listing_url}.
"
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1345
+#: ../includes/classes/class-ajax-handler.php:1393
msgid "Dear Administrator,
A new review at {listing_url}.
Name: {sender_name}
Email: {sender_email}"
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1450, ../includes/rest-api/Version1/class-listings-actions-controller.php:250
+#: ../includes/classes/class-ajax-handler.php:1498, ../includes/rest-api/Version1/class-listings-actions-controller.php:250
msgid "{site_name} Report Abuse via \"{listing_title}\""
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1453, ../includes/rest-api/Version1/class-listings-actions-controller.php:253
+#: ../includes/classes/class-ajax-handler.php:1501, ../includes/rest-api/Version1/class-listings-actions-controller.php:253
msgid "Dear Administrator,
This is an email abuse report for a listing at {listing_url}.
Name: {sender_name}
Email: {sender_email}
Message: {message}"
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1480
+#: ../includes/classes/class-ajax-handler.php:1528
msgid "Trying to report invalid listing."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1487
+#: ../includes/classes/class-ajax-handler.php:1535
msgid "Report message cannot be empty."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1495, ../includes/classes/class-ajax-handler.php:1744
+#: ../includes/classes/class-ajax-handler.php:1543, ../includes/classes/class-ajax-handler.php:1792
msgid "Sorry! Please try again."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1500, ../includes/classes/class-ajax-handler.php:1789
+#: ../includes/classes/class-ajax-handler.php:1548, ../includes/classes/class-ajax-handler.php:1837
msgid "Your message sent successfully."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1663
+#: ../includes/classes/class-ajax-handler.php:1711
msgid "{site_name} Contact via {listing_title}"
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1665
+#: ../includes/classes/class-ajax-handler.php:1713
msgid "Dear Administrator,
A listing on your website {site_name} received a message.
Listing URL: {listing_url}
Name: {sender_name}
Email: {sender_email}
Message: {message}
Time: {now}
This is just a copy of the original email and was already sent to the listing owner. You don't have to reply this unless necessary."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1884
+#: ../includes/classes/class-ajax-handler.php:1932
msgid "User not logged in."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:1899, ../includes/classes/class-ajax-handler.php:1939, ../includes/classes/class-ajax-handler.php:1978, ../includes/classes/class-ajax-handler.php:2015
+#: ../includes/classes/class-ajax-handler.php:1947, ../includes/classes/class-ajax-handler.php:1987, ../includes/classes/class-ajax-handler.php:2026, ../includes/classes/class-ajax-handler.php:2063
msgid "Invalid nonce."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:2020
+#: ../includes/classes/class-ajax-handler.php:2068
msgid "No listing ID found."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:2050
+#: ../includes/classes/class-ajax-handler.php:2098
msgid "Only pending listings can be rejected."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:2054
+#: ../includes/classes/class-ajax-handler.php:2102
msgid "Rejection reason is required."
msgstr ""
-#: ../includes/classes/class-ajax-handler.php:2066
+#: ../includes/classes/class-ajax-handler.php:2114
msgid "Could not reject the listing. Please try again."
msgstr ""
@@ -2629,15 +2646,15 @@ msgstr ""
msgid "Featured Listing: %s"
msgstr ""
-#: ../includes/classes/class-formgent.php:97, ../includes/classes/class-formgent.php:103, ../includes/classes/class-formgent.php:140, ../includes/classes/class-formgent.php:148, ../includes/classes/class-formgent.php:164
+#: ../includes/classes/class-formgent.php:261, ../includes/classes/class-formgent.php:267, ../includes/classes/class-formgent.php:304, ../includes/classes/class-formgent.php:312, ../includes/classes/class-formgent.php:328
msgid "Response not found."
msgstr ""
-#: ../includes/classes/class-formgent.php:154
+#: ../includes/classes/class-formgent.php:318
msgid "Response has been marked as read successfully."
msgstr ""
-#: ../includes/classes/class-formgent.php:171
+#: ../includes/classes/class-formgent.php:335
msgid "Response has been deleted successfully."
msgstr ""
@@ -9624,7 +9641,7 @@ msgstr ""
msgid "Nothing found!"
msgstr ""
-#: ../templates/dashboard/tab-fav-listings.php:43, ../templates/listing-form/custom-fields/file.php:100
+#: ../templates/dashboard/tab-fav-listings.php:43, ../templates/listing-form/custom-fields/file.php:107
msgid "Remove"
msgstr ""
@@ -9851,39 +9868,39 @@ msgstr ""
msgid "Author Image"
msgstr ""
-#: ../templates/listing-form/custom-fields/file.php:60
+#: ../templates/listing-form/custom-fields/file.php:66
msgid "Maximum file size: %s"
msgstr ""
-#: ../templates/listing-form/custom-fields/file.php:76
+#: ../templates/listing-form/custom-fields/file.php:82
msgid "Allowed Files"
msgstr ""
-#: ../templates/listing-form/custom-fields/file.php:95
+#: ../templates/listing-form/custom-fields/file.php:102
msgid "Allowed files"
msgstr ""
-#: ../templates/listing-form/custom-fields/file.php:96
+#: ../templates/listing-form/custom-fields/file.php:103
msgid "File size error : You tried to upload a file over %s"
msgstr ""
-#: ../templates/listing-form/custom-fields/file.php:97
+#: ../templates/listing-form/custom-fields/file.php:104
msgid "File type error. Allowed file types: %s"
msgstr ""
-#: ../templates/listing-form/custom-fields/file.php:98
+#: ../templates/listing-form/custom-fields/file.php:105
msgid "You have reached your upload limit of %s files."
msgstr ""
-#: ../templates/listing-form/custom-fields/file.php:99
+#: ../templates/listing-form/custom-fields/file.php:106
msgid "You may only upload %s files with this package, please try again."
msgstr ""
-#: ../templates/listing-form/custom-fields/file.php:101
+#: ../templates/listing-form/custom-fields/file.php:108
msgid "Set"
msgstr ""
-#: ../templates/listing-form/custom-fields/file.php:170, ../templates/listing-form/fields/image_upload.php:19
+#: ../templates/listing-form/custom-fields/file.php:177, ../templates/listing-form/fields/image_upload.php:19
msgid "Select Files"
msgstr ""
diff --git a/readme.txt b/readme.txt
index c81f23f869..3fcb4b70e0 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.8.6
+Stable tag: 8.8.7
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -300,6 +300,17 @@ Directorist comes with an AI-powered directory builder. Use the Create with AI o
== Changelog ==
+= 8.8.7 - Jul 16, 2026 =
+
+**Improved**
+ - FormGent email notifications now include the Directorist listing owner as a recipient. (#2916)
+
+**Security**
+ - Hardened custom file uploads with capability checks, guest token validation, file field validation, and server-side file size enforcement. (#2914)
+
+**Fixed**
+ - PHP warnings in the archive basic search form when configured search fields are unavailable for the current directory type. (#2919)
+
= 8.8.6 - Jul 13, 2026 =
**Improved**
diff --git a/templates/archive/basic-search-form.php b/templates/archive/basic-search-form.php
index f1b208c4ee..2932cce9a4 100644
--- a/templates/archive/basic-search-form.php
+++ b/templates/archive/basic-search-form.php
@@ -2,7 +2,7 @@
/**
* @author wpWax
* @since 7.2.2
- * @version 8.5
+ * @version 8.8.6
*/
use \Directorist\Helper;
@@ -15,7 +15,7 @@