PluginProbe
Property Hive / 2.3.1
Property Hive v2.3.1
2.3.1 2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 1.4.61 All 261 releases
← All changes | includes/admin/settings/class-ph-settings-emails.php +381 -122 1.4.482.3.1 View file →
@@ -1,5 +1,8 @@
1 1 <?php
2 +// phpcs:set WordPress.Security.ValidatedSanitizedInput customSanitizingFunctions[] ph_clean
3 +// ph_clean() recursively sanitizes text; presence, shape and unslashing checks remain separate.
4 +
2 5 /**
3 6 * PropertyHive Email Settings
4 7 *
5 8 * @author PropertyHive
@@ -16,8 +19,9 @@
16 19
17 20 /**
18 21 * PH_Settings_Emails.
19 22 */
23 +// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- Legacy public global class PH_Settings_Emails; preserving the existing PH_* class name is required for plugin and extension compatibility.
20 24 class PH_Settings_Emails extends PH_Settings_Page {
21 25
22 26 /**
23 27 * Constructor.
@@ -41,12 +45,26 @@
41 45 */
42 46 public function get_sections() {
43 47 $sections = array(
44 48 '' => __( 'Email Options', 'propertyhive' ),
49 + 'enquiry-auto-responder' => __( 'Enquiry Auto-Responder', 'propertyhive' ),
45 50 );
46 51
47 52 if ( get_option('propertyhive_module_disabled_contacts', '') != 'yes' )
48 53 {
54 + $sections['match'] = __( 'Property Match', 'propertyhive' );
55 + }
56 +
57 + if (
58 + get_option('propertyhive_module_disabled_viewings', '') != 'yes' ||
59 + get_option('propertyhive_module_disabled_appraisals', '') != 'yes'
60 + )
61 + {
62 + $sections['booking-confirmation'] = __( 'Booking Confirmations & Cancellations', 'propertyhive' );
63 + }
64 +
65 + if ( get_option('propertyhive_module_disabled_contacts', '') != 'yes' )
66 + {
49 67 $sections['log'] = __( 'Email Queue', 'propertyhive' );
50 68 }
51 69
52 70 return apply_filters( 'propertyhive_get_sections_' . $this->id, $sections );
@@ -122,12 +140,28 @@
122 140 ),
123 141
124 142 array( 'type' => 'sectionend', 'id' => 'email_template_options' ),
125 143
126 - array( 'title' => __( 'Enquiry Auto Responder Settings', 'propertyhive' ), 'type' => 'title', 'id' => 'enquiry_auto_responder_email_options' ),
144 + );
127 145
146 + $settings = apply_filters( 'propertyhive_email_settings', $settings );
147 +
148 + return apply_filters( 'propertyhive_get_settings_' . $this->id, $settings );
149 + }
150 +
151 + /**
152 + * Get enquiry auto-responder settings array
153 + *
154 + * @return array
155 + */
156 + public function get_enquiry_autoresponder_settings() {
157 +
158 + return apply_filters( 'propertyhive_enquiry_autoresponder_settings', array(
159 +
160 + array( 'title' => __( 'Enquiry Auto-Responder Settings', 'propertyhive' ), 'type' => 'title', 'id' => 'enquiry_auto_responder_email_options' ),
161 +
128 162 array(
129 - 'title' => __( 'Auto Responder Enabled', 'propertyhive' ),
163 + 'title' => __( 'Auto-Responder Enabled', 'propertyhive' ),
130 164 'id' => 'propertyhive_enquiry_auto_responder',
131 165 'type' => 'checkbox',
132 166 'default' => '',
133 167 ),
@@ -132,9 +166,9 @@
132 166 'default' => '',
133 167 ),
134 168
135 169 array(
136 - 'title' => __( 'Auto Responder Email Subject', 'propertyhive' ),
170 + 'title' => __( 'Auto-Responder Email Subject', 'propertyhive' ),
137 171 'id' => 'propertyhive_enquiry_auto_responder_email_subject',
138 172 'type' => 'text',
139 173 'css' => 'min-width:300px;',
140 174 ),
@@ -139,9 +173,9 @@
139 173 'css' => 'min-width:300px;',
140 174 ),
141 175
142 176 array(
143 - 'title' => __( 'Auto Responder Email Body', 'propertyhive' ),
177 + 'title' => __( 'Auto-Responder Email Body', 'propertyhive' ),
144 178 'id' => 'propertyhive_enquiry_auto_responder_email_body',
145 179 'type' => 'textarea',
146 180 'css' => 'min-width:300px; height:110px;',
147 181 ),
@@ -147,17 +181,107 @@
147 181 ),
148 182
149 183 array( 'type' => 'sectionend', 'id' => 'enquiry_auto_responder_email_options' )
150 184
185 + ) ); // End settings
186 + }
187 +
188 + /**
189 + * Get property match settings settings array
190 + *
191 + * @return array
192 + */
193 + public function get_property_match_settings() {
194 +
195 + $settings = array();
196 +
197 + $settings[] = array( 'title' => __( 'Property Match Email Settings', 'propertyhive' ), 'type' => 'title', 'id' => 'applicant_match_email_options' );
198 +
199 + $settings[] = array(
200 + 'title' => __( 'Default Email Subject', 'propertyhive' ),
201 + 'id' => 'propertyhive_property_match_default_email_subject',
202 + 'type' => 'text',
203 + 'css' => 'min-width:300px;',
151 204 );
152 205
153 - if ( get_option('propertyhive_module_disabled_contacts', '') != 'yes' )
206 + $settings[] = array(
207 + 'title' => __( 'Default Email Body', 'propertyhive' ),
208 + 'id' => 'propertyhive_property_match_default_email_body',
209 + 'type' => 'textarea',
210 + 'css' => 'min-width:300px; height:110px;',
211 + );
212 +
213 + $settings[] = array(
214 + 'title' => __( 'Default From Email Address', 'propertyhive' ),
215 + 'id' => 'propertyhive_property_match_default_from',
216 + 'type' => 'select',
217 + 'default' => '',
218 + 'css' => 'min-width:300px;',
219 + 'options' => array(
220 + '' => __( 'User Email Address', 'propertyhive' ),
221 + 'default_from_email' => __( 'Default "From" Email Address', 'propertyhive' ),
222 + ),
223 + 'desc' => '<p>' . __( 'This sets the email address that manual matches will be sent from by default. This can still be edited when you go to send the match.<br>Automatic matches, if enabled, will still be sent from the email address of the office that most of the properties in the match belong to.', 'propertyhive' ) . '</p>',
224 + );
225 +
226 + $options = array();
227 + $args = array(
228 + 'hide_empty' => false,
229 + 'parent' => 0
230 + );
231 + $terms = get_terms( array_merge( wp_parse_args( $args ), array( 'taxonomy' => 'availability' ) ) );
232 +
233 + if ( !empty( $terms ) && !is_wp_error( $terms ) )
234 + {
235 + foreach ($terms as $term)
236 + {
237 + $options[$term->term_id] = $term->name;
238 + }
239 + }
240 + $settings[] = array(
241 + 'title' => __( 'Only Include Properties With Statuses', 'propertyhive' ),
242 + 'id' => 'propertyhive_property_match_statuses',
243 + 'type' => 'multiselect',
244 + 'css' => 'min-width:300px; height:110px;',
245 + 'options' => $options,
246 + 'desc' => '<p>' . __( 'By default, all on market properties will come back in matches when sending properties to applicants. If you wish to only send properties with a certain status you can choose this here. For example, maybe you don\'t want Sold STC properties to be sent. Hold ctrl/cmd whilst clicking to select multiple.<br>This will also affect the Similar Properties included in the Auto Responder above, if applicable.', 'propertyhive' ) . '</p>',
247 + );
248 +
249 + $time_offset = (int) get_option('gmt_offset') * 60 * 60;
250 +
251 + $settings[] = array(
252 + 'title' => __( 'Automatically Send Matching Properties To Applicants', 'propertyhive' ),
253 + 'desc' => __( 'Enabling this setting will mean applicants will automatically get sent properties.<br><br>
254 + - This will only apply to properties added from the moment this option is activated.<br>
255 + - When enabled, this can be disabled on a per-applicant basis by going into their record.<br>
256 + - When sending out lots of emails we recommend using <a href="https://en-gb.wordpress.org/plugins/tags/smtp" target="_blank">a plugin</a> to send them out using SMTP. Your web developer or hosting company should be able to advise on this.', 'propertyhive' ) . ( ( get_option( 'propertyhive_auto_property_match', '' ) == 'yes' && get_option( 'propertyhive_auto_property_match_enabled_date', '' ) != '' ) ? '<br><br>Enabled on ' . gmdate("jS F Y H:i", strtotime(get_option( 'propertyhive_auto_property_match_enabled_date', '' )) + $time_offset) : '' ),
257 + 'id' => 'propertyhive_auto_property_match',
258 + 'type' => 'checkbox',
259 + 'default' => '',
260 + );
261 +
262 + $settings[] = array( 'type' => 'sectionend', 'id' => 'applicant_match_email_options' );
263 +
264 + return apply_filters( 'propertyhive_property_match_settings', $settings ); // End settings
265 + }
266 +
267 + /**
268 + * Get booking confirmation email settings array
269 + *
270 + * @return array
271 + */
272 + public function get_booking_confirmation_settings() {
273 +
274 + $settings = array();
275 +
276 + if ( get_option('propertyhive_module_disabled_viewings', '') != 'yes' )
154 277 {
155 - $settings[] = array( 'title' => __( 'Property Match Email Settings', 'propertyhive' ), 'type' => 'title', 'id' => 'applicant_match_email_options' );
278 + //Applicant
279 + $settings[] = array( 'title' => __( 'Applicant Viewing Booking Confirmations', 'propertyhive' ), 'type' => 'title', 'id' => 'applicant_viewing_booking_confirmation_email_options' );
156 280
157 281 $settings[] = array(
158 282 'title' => __( 'Default Email Subject', 'propertyhive' ),
159 - 'id' => 'propertyhive_property_match_default_email_subject',
283 + 'id' => 'propertyhive_viewing_applicant_booking_confirmation_email_subject',
160 284 'type' => 'text',
161 285 'css' => 'min-width:300px;',
162 286 );
163 287
@@ -162,58 +286,39 @@
162 286 );
163 287
164 288 $settings[] = array(
165 289 'title' => __( 'Default Email Body', 'propertyhive' ),
166 - 'id' => 'propertyhive_property_match_default_email_body',
290 + 'id' => 'propertyhive_viewing_applicant_booking_confirmation_email_body',
167 291 'type' => 'textarea',
168 292 'css' => 'min-width:300px; height:110px;',
169 293 );
170 294
171 - $options = array();
172 - $args = array(
173 - 'hide_empty' => false,
174 - 'parent' => 0
175 - );
176 - $terms = get_terms( 'availability', $args );
177 -
178 - if ( !empty( $terms ) && !is_wp_error( $terms ) )
179 - {
180 - foreach ($terms as $term)
181 - {
182 - $options[$term->term_id] = $term->name;
183 - }
184 - }
295 + $settings[] = array( 'type' => 'sectionend', 'id' => 'applicant_viewing_booking_confirmation_email_options' );
296 +
297 + $settings[] = array( 'title' => __( 'Applicant Viewing Cancellation Notifications', 'propertyhive' ), 'type' => 'title', 'id' => 'applicant_viewing_cancellation_notification_email_options' );
298 +
185 299 $settings[] = array(
186 - 'title' => __( 'Only Include Properties With Statuses', 'propertyhive' ),
187 - 'id' => 'propertyhive_property_match_statuses',
188 - 'type' => 'multiselect',
189 - 'css' => 'min-width:300px; height:110px;',
190 - 'options' => $options,
191 - 'desc' => '<p>' . __( 'By default, all on market properties will come back in matches when sending properties to applicants. If you wish to only send properties with a certain status you can choose this here. For example, maybe you don\'t want Sold STC properties to be sent. Hold ctrl/cmd whilst clicking to select multiple.', 'propertyhive' ) . '</p>',
300 + 'title' => __( 'Default Email Subject', 'propertyhive' ),
301 + 'id' => 'propertyhive_viewing_applicant_cancellation_notification_email_subject',
302 + 'type' => 'text',
303 + 'css' => 'min-width:300px;',
192 304 );
193 305
194 306 $settings[] = array(
195 - 'title' => __( 'Automatically Send Matching Properties To Applicants', 'propertyhive' ),
196 - 'desc' => __( 'Enabling this setting will mean applicants will automatically get sent emailed properties as they\'re added.<br><br>
197 - - This will only apply to properties added from the moment this option is activated.<br>
198 - - When enabled, this can disabled on a per-applicant basis by going into their record<br>
199 - - When sending out lots of emails we recommend using <a href="https://en-gb.wordpress.org/plugins/tags/smtp" target="_blank">a plugin</a> to send them out using SMTP. Your web developer or hosting company should be able to advise on this.', 'propertyhive' ),
200 - 'id' => 'propertyhive_auto_property_match',
201 - 'type' => 'checkbox',
202 - 'default' => '',
307 + 'title' => __( 'Default Email Body', 'propertyhive' ),
308 + 'id' => 'propertyhive_viewing_applicant_cancellation_notification_email_body',
309 + 'type' => 'textarea',
310 + 'css' => 'min-width:300px; height:110px;',
203 311 );
204 312
205 - $settings[] = array( 'type' => 'sectionend', 'id' => 'applicant_match_email_options' );
206 - }
313 + $settings[] = array( 'type' => 'sectionend', 'id' => 'applicant_viewing_cancellation_notification_email_options' );
207 314
208 - if ( get_option('propertyhive_module_disabled_viewings', '') != 'yes' )
209 - {
210 - //Applicant
211 - $settings[] = array( 'title' => __( 'Applicant Viewing Booking Confirmations', 'propertyhive' ), 'type' => 'title', 'id' => 'applicant_viewing_booking_confirmation_email_options' );
315 + // Owner
316 + $settings[] = array( 'title' => __( 'Owner/Landlord Viewing Booking Confirmations', 'propertyhive' ), 'type' => 'title', 'id' => 'owner_viewing_booking_confirmation_email_options' );
212 317
213 318 $settings[] = array(
214 319 'title' => __( 'Default Email Subject', 'propertyhive' ),
215 - 'id' => 'propertyhive_viewing_applicant_booking_confirmation_email_subject',
320 + 'id' => 'propertyhive_viewing_owner_booking_confirmation_email_subject',
216 321 'type' => 'text',
217 322 'css' => 'min-width:300px;',
218 323 );
219 324
@@ -218,21 +323,79 @@
218 323 );
219 324
220 325 $settings[] = array(
221 326 'title' => __( 'Default Email Body', 'propertyhive' ),
222 - 'id' => 'propertyhive_viewing_applicant_booking_confirmation_email_body',
327 + 'id' => 'propertyhive_viewing_owner_booking_confirmation_email_body',
223 328 'type' => 'textarea',
224 329 'css' => 'min-width:300px; height:110px;',
225 330 );
226 331
227 - $settings[] = array( 'type' => 'sectionend', 'id' => 'viewing_booking_confirmation_email_options' );
332 + $settings[] = array( 'type' => 'sectionend', 'id' => 'owner_viewing_booking_confirmation_email_options' );
228 333
334 + $settings[] = array( 'title' => __( 'Owner/Landlord Viewing Cancellation Notifications', 'propertyhive' ), 'type' => 'title', 'id' => 'owner_viewing_cancellation_notification_email_options' );
335 +
336 + $settings[] = array(
337 + 'title' => __( 'Default Email Subject', 'propertyhive' ),
338 + 'id' => 'propertyhive_viewing_owner_cancellation_notification_email_subject',
339 + 'type' => 'text',
340 + 'css' => 'min-width:300px;',
341 + );
342 +
343 + $settings[] = array(
344 + 'title' => __( 'Default Email Body', 'propertyhive' ),
345 + 'id' => 'propertyhive_viewing_owner_cancellation_notification_email_body',
346 + 'type' => 'textarea',
347 + 'css' => 'min-width:300px; height:110px;',
348 + );
349 +
350 + $settings[] = array( 'type' => 'sectionend', 'id' => 'owner_viewing_cancellation_notification_email_options' );
351 +
352 + // Attending Negotiator
353 + $settings[] = array( 'title' => __( 'Attending Negotiator Viewing Booking Confirmations', 'propertyhive' ), 'type' => 'title', 'id' => 'attending_negotiator_viewing_booking_confirmation_email_options' );
354 +
355 + $settings[] = array(
356 + 'title' => __( 'Default Email Subject', 'propertyhive' ),
357 + 'id' => 'propertyhive_viewing_attending_negotiator_booking_confirmation_email_subject',
358 + 'type' => 'text',
359 + 'css' => 'min-width:300px;',
360 + );
361 +
362 + $settings[] = array(
363 + 'title' => __( 'Default Email Body', 'propertyhive' ),
364 + 'id' => 'propertyhive_viewing_attending_negotiator_booking_confirmation_email_body',
365 + 'type' => 'textarea',
366 + 'css' => 'min-width:300px; height:110px;',
367 + );
368 +
369 + $settings[] = array( 'type' => 'sectionend', 'id' => 'attending_negotiator_viewing_booking_confirmation_email_options' );
370 +
371 + $settings[] = array( 'title' => __( 'Attending Negotiator Viewing Cancellation Notifications', 'propertyhive' ), 'type' => 'title', 'id' => 'attending_negotiator_viewing_cancellation_notifications_email_options' );
372 +
373 + $settings[] = array(
374 + 'title' => __( 'Default Email Subject', 'propertyhive' ),
375 + 'id' => 'propertyhive_viewing_attending_negotiator_cancellation_notification_email_subject',
376 + 'type' => 'text',
377 + 'css' => 'min-width:300px;',
378 + );
379 +
380 + $settings[] = array(
381 + 'title' => __( 'Default Email Body', 'propertyhive' ),
382 + 'id' => 'propertyhive_viewing_attending_negotiator_cancellation_notification_email_body',
383 + 'type' => 'textarea',
384 + 'css' => 'min-width:300px; height:110px;',
385 + );
386 +
387 + $settings[] = array( 'type' => 'sectionend', 'id' => 'attending_negotiator_viewing_cancellation_notifications_email_options' );
388 + }
389 +
390 + if ( get_option('propertyhive_module_disabled_appraisals', '') != 'yes' )
391 + {
229 392 // Owner
230 - $settings[] = array( 'title' => __( 'Owner/Landlord Booking Confirmations', 'propertyhive' ), 'type' => 'title', 'id' => 'applicant_viewing_booking_confirmation_email_options' );
393 + $settings[] = array( 'title' => __( 'Owner/Landlord Appraisal Booking Confirmations', 'propertyhive' ), 'type' => 'title', 'id' => 'owner_appraisal_booking_confirmation_email_options' );
231 394
232 395 $settings[] = array(
233 396 'title' => __( 'Default Email Subject', 'propertyhive' ),
234 - 'id' => 'propertyhive_viewing_owner_booking_confirmation_email_subject',
397 + 'id' => 'propertyhive_appraisal_owner_booking_confirmation_email_subject',
235 398 'type' => 'text',
236 399 'css' => 'min-width:300px;',
237 400 );
238 401
@@ -237,19 +400,46 @@
237 400 );
238 401
239 402 $settings[] = array(
240 403 'title' => __( 'Default Email Body', 'propertyhive' ),
241 - 'id' => 'propertyhive_viewing_owner_booking_confirmation_email_body',
404 + 'id' => 'propertyhive_appraisal_owner_booking_confirmation_email_body',
242 405 'type' => 'textarea',
243 406 'css' => 'min-width:300px; height:110px;',
244 407 );
245 408
246 - $settings[] = array( 'type' => 'sectionend', 'id' => 'viewing_booking_confirmation_email_options' );
409 + $settings[] = array( 'type' => 'sectionend', 'id' => 'owner_appraisal_booking_confirmation_email_options' );
247 410 }
248 411
249 - $settings = apply_filters( 'propertyhive_email_settings', $settings );
412 + if ( get_option('propertyhive_module_disabled_appraisals', '') != 'yes' || get_option('propertyhive_module_disabled_viewings', '') != 'yes' )
413 + {
414 + // Owner
415 + $settings[] = array( 'title' => __( 'Booking Confirmations & Cancellations', 'propertyhive' ), 'type' => 'title', 'id' => 'booking_confirmation_email_options' );
250 416
251 - return apply_filters( 'propertyhive_get_settings_' . $this->id, $settings );
417 + $settings[] = array(
418 + 'title' => __( 'Sent From Email Address', 'propertyhive' ),
419 + 'id' => 'propertyhive_confirmations_default_from',
420 + 'type' => 'select',
421 + 'default' => '',
422 + 'css' => 'min-width:300px;',
423 + 'options' => array(
424 + '' => __( 'Default "From" Email Address', 'propertyhive' ),
425 + 'office' => __( 'Office Email Address', 'propertyhive' ),
426 + 'user' => __( 'User Email Address', 'propertyhive' ),
427 + ),
428 + 'desc' => '<p>' . __( 'This sets the email address that booking confirmations and cancellation notifications come from and that will receive the response should someone reply.', 'propertyhive' ) . '</p>',
429 + );
430 +
431 + $settings[] = array(
432 + 'title' => __( 'Customise Emails Before Sending', 'propertyhive' ),
433 + 'id' => 'propertyhive_customise_confirmation_emails',
434 + 'type' => 'checkbox',
435 + 'desc' => 'With this ticked you\'ll be able to customise the subject and body of any emails before they get sent. This can be useful for adding any appraisal/viewing-specific details to the email. If left unticked, the default subject and body set above will be used.'
436 + );
437 +
438 + $settings[] = array( 'type' => 'sectionend', 'id' => 'booking_confirmation_email_options' );
439 + }
440 +
441 + return apply_filters( 'propertyhive_booking_confirmation_settings', $settings ); // End general map settings
252 442 }
253 443
254 444 /**
255 445 * Get email queue settings array
@@ -280,23 +470,25 @@
280 470 public function email_queue_setting()
281 471 {
282 472 global $wpdb, $post;
283 473
284 - $additional_query = '';
285 - $additional_query_string = '';
286 - if ( isset($_GET['date_from']) && sanitize_text_field($_GET['date_from']) != '' )
287 - {
288 - $additional_query_string .= '&date_from=' . sanitize_text_field($_GET['date_from']);
289 - if ( sanitize_text_field($_GET['date_from']) != 'all' )
290 - {
291 - $additional_query .= " AND send_at >= '" . sanitize_text_field($_GET['date_from']) . " 00:00:00' ";
292 - }
474 + $date_from = '';
475 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The email queue date filter is a read-only GET parameter; the separate Run Now action has its own capability and nonce gate.
476 + if ( isset( $_GET['date_from'] ) && is_string( $_GET['date_from'] ) ) {
477 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The email queue date filter is a read-only GET parameter; the separate Run Now action has its own capability and nonce gate.
478 + $date_from = sanitize_text_field( wp_unslash( $_GET['date_from'] ) );
293 479 }
294 - else
295 - {
296 - // Default to 30 days
297 - $additional_query .= " AND send_at >= '" . date("Y-m-d", strtotime('-30 days')) . " 00:00:00' ";
480 + $date = DateTime::createFromFormat( 'Y-m-d', $date_from );
481 + if ( 'all' !== $date_from && ( ! $date || $date->format( 'Y-m-d' ) !== $date_from ) ) {
482 + $date_from = gmdate( 'Y-m-d', strtotime( '-30 days' ) );
298 483 }
484 + $status = '';
485 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The email queue status filter is a read-only GET parameter; the separate Run Now action has its own capability and nonce gate.
486 + if ( isset( $_GET['status'] ) && is_string( $_GET['status'] ) ) {
487 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The email queue status filter is a read-only GET parameter; the separate Run Now action has its own capability and nonce gate.
488 + $status = sanitize_key( wp_unslash( $_GET['status'] ) );
489 + }
490 + $additional_query_string = '&date_from=' . rawurlencode( $date_from );
299 491 ?>
300 492 <tr valign="top">
301 493 <td style="padding:0">
302 494
@@ -314,24 +506,26 @@
314 506 echo 'Whoops. WordPress doesn\'t have the emails automated task scheduled. A quick fix for this is to deactivate, then re-activate the plugin.';
315 507 }
316 508 else
317 509 {
318 - echo __( 'Next scheduled to run at', 'propertyhive' ) . ' ' . date("H:i jS F Y", $next_due);
510 + echo esc_html(__( 'Next scheduled to run at', 'propertyhive' ) . ' ' . wp_date("H:i jS F Y", $next_due));
319 511 }
320 - ?></strong> <a href="<?php echo admin_url('admin.php?page=ph-settings&tab=email&section=log&custom_email_log_cron=propertyhive_process_email_log' ); ?>" class="button">Run Now</a></p>
512 + ?></strong> <a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=ph-settings&tab=email&section=log&custom_email_log_cron=propertyhive_process_email_log' ), 'propertyhive-run-email-job' ) ); ?>" class="button">Run Now</a></p>
321 513
322 514 <br>
323 515
324 516 <ul class="subsubsub">
325 517 <?php
326 - $emails = $wpdb->get_var("
518 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- The email queue is stored in Property Hive's custom table, which has no WP_Query API; this live admin count must reflect asynchronous queue changes immediately.
519 + $emails = $wpdb->get_var("
327 520 SELECT COUNT(*)
328 521 FROM " . $wpdb->prefix . "ph_email_log
329 522 ");
330 523 ?>
331 - <li class="all"><a href="<?php echo admin_url('admin.php?page=ph-settings&tab=email&section=log' . $additional_query_string); ?>"<?php if ( !isset($_GET['status']) || (isset($_GET['status']) && $_GET['status'] == '') ) { echo ' class="current"'; } ?>>All <span class="count">(<?php echo number_format($emails); ?>)</span></a> |</li>
524 + <li class="all"><a href="<?php echo esc_url(admin_url('admin.php?page=ph-settings&tab=email&section=log' . $additional_query_string)); ?>"<?php if ( '' === $status ) { echo ' class="current"'; } ?>>All <span class="count">(<?php echo number_format($emails); ?>)</span></a> |</li>
332 525 <?php
333 - $emails = $wpdb->get_var("
526 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- The email queue is stored in Property Hive's custom table, which has no WP_Query API; this live admin count must reflect asynchronous queue changes immediately.
527 + $emails = $wpdb->get_var("
334 528 SELECT COUNT(*)
335 529 FROM " . $wpdb->prefix . "ph_email_log
336 530 WHERE
337 531 status = ''
@@ -336,11 +530,12 @@
336 530 WHERE
337 531 status = ''
338 532 ");
339 533 ?>
340 - <li class="queued"><a href="<?php echo admin_url('admin.php?page=ph-settings&tab=email&section=log&status=queued' . $additional_query_string); ?>"<?php if ( isset($_GET['status']) && $_GET['status'] == 'queued' ) { echo ' class="current"'; } ?>>Queued <span class="count">(<?php echo number_format($emails); ?>)</span></a> |</li>
534 + <li class="queued"><a href="<?php echo esc_url(admin_url('admin.php?page=ph-settings&tab=email&section=log&status=queued' . $additional_query_string)); ?>"<?php if ( 'queued' === $status ) { echo ' class="current"'; } ?>>Queued <span class="count">(<?php echo number_format($emails); ?>)</span></a> |</li>
341 535 <?php
342 - $emails = $wpdb->get_var("
536 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- The email queue is stored in Property Hive's custom table, which has no WP_Query API; this live admin count must reflect asynchronous queue changes immediately.
537 + $emails = $wpdb->get_var("
343 538 SELECT COUNT(*)
344 539 FROM " . $wpdb->prefix . "ph_email_log
345 540 WHERE
346 541 status IN ('fail1', 'fail2')
@@ -345,11 +540,12 @@
345 540 WHERE
346 541 status IN ('fail1', 'fail2')
347 542 ");
348 543 ?>
349 - <li class="failed"><a href="<?php echo admin_url('admin.php?page=ph-settings&tab=email&section=log&status=failed' . $additional_query_string); ?>"<?php if ( isset($_GET['status']) && $_GET['status'] == 'failed' ) { echo ' class="current"'; } ?>>Failed <span class="count">(<?php echo number_format($emails); ?>)</span></a> |</li>
544 + <li class="failed"><a href="<?php echo esc_url(admin_url('admin.php?page=ph-settings&tab=email&section=log&status=failed' . $additional_query_string)); ?>"<?php if ( 'failed' === $status ) { echo ' class="current"'; } ?>>Failed <span class="count">(<?php echo number_format($emails); ?>)</span></a> |</li>
350 545 <?php
351 - $emails = $wpdb->get_var("
546 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- The email queue is stored in Property Hive's custom table, which has no WP_Query API; this live admin count must reflect asynchronous queue changes immediately.
547 + $emails = $wpdb->get_var("
352 548 SELECT COUNT(*)
353 549 FROM " . $wpdb->prefix . "ph_email_log
354 550 WHERE
355 551 status = 'sent'
@@ -354,9 +550,9 @@
354 550 WHERE
355 551 status = 'sent'
356 552 ");
357 553 ?>
358 - <li class="sent"><a href="<?php echo admin_url('admin.php?page=ph-settings&tab=email&section=log&status=sent' . $additional_query_string); ?>"<?php if ( isset($_GET['status']) && $_GET['status'] == 'sent' ) { echo ' class="current"'; } ?>>Sent <span class="count">(<?php echo number_format($emails); ?>)</span></a></li>
554 + <li class="sent"><a href="<?php echo esc_url(admin_url('admin.php?page=ph-settings&tab=email&section=log&status=sent' . $additional_query_string)); ?>"<?php if ( 'sent' === $status ) { echo ' class="current"'; } ?>>Sent <span class="count">(<?php echo number_format($emails); ?>)</span></a></li>
359 555 </ul>
360 556
361 557 <div class="tablenav top">
362 558 <div class="alignleft actions bulkactions">
@@ -363,14 +559,14 @@
363 559 <div class="alignleft actions">
364 560 <input type="hidden" name="page" value="ph-settings">
365 561 <input type="hidden" name="tab" value="email">
366 562 <input type="hidden" name="section" value="log">
367 - <input type="hidden" name="status" value="<?php echo ( ( isset($_GET['status']) ) ? $_GET['status'] : '' ); ?>">
563 + <input type="hidden" name="status" value="<?php echo esc_attr( $status ); ?>">
368 564 <select name="date_from" id="dropdown_date_from">
369 - <option value="<?php echo date("Y-m-d", strtotime("-7 days")); ?>"<?php if ( isset($_GET['date_from']) && $_GET['date_from'] == date("Y-m-d", strtotime("-7 days")) ) { echo ' selected'; } ?>>Last 7 Days</option>
370 - <option value="<?php echo date("Y-m-d", strtotime("-14 days")); ?>"<?php if ( isset($_GET['date_from']) && $_GET['date_from'] == date("Y-m-d", strtotime("-14 days")) ) { echo ' selected'; } ?>>Last 14 Days</option>
371 - <option value="<?php echo date("Y-m-d", strtotime("-30 days")); ?>"<?php if ( !isset($_GET['date_from']) || ( isset($_GET['date_from']) && $_GET['date_from'] == date("Y-m-d", strtotime("-30 days")) ) ) { echo ' selected'; } ?>>Last 30 Days</option>
372 - <option value="all"<?php if ( isset($_GET['date_from']) && $_GET['date_from'] == 'all' ) { echo ' selected'; } ?>>All Time</option>
565 + <option value="<?php echo esc_attr(gmdate("Y-m-d", strtotime("-7 days"))); ?>"<?php if ( $date_from === gmdate("Y-m-d", strtotime("-7 days")) ) { echo ' selected'; } ?>>Last 7 Days</option>
566 + <option value="<?php echo esc_attr(gmdate("Y-m-d", strtotime("-14 days"))); ?>"<?php if ( $date_from === gmdate("Y-m-d", strtotime("-14 days")) ) { echo ' selected'; } ?>>Last 14 Days</option>
567 + <option value="<?php echo esc_attr(gmdate("Y-m-d", strtotime("-30 days"))); ?>"<?php if ( $date_from === gmdate("Y-m-d", strtotime("-30 days")) ) { echo ' selected'; } ?>>Last 30 Days</option>
568 + <option value="all"<?php if ( 'all' === $date_from ) { echo ' selected'; } ?>>All Time</option>
373 569 </select>
374 570 <input type="submit" name="filter_action" id="post-query-submit" class="button" value="Filter">
375 571
376 572 <script>
@@ -378,9 +574,9 @@
378 574 {
379 575 jQuery('#_wpnonce').remove();
380 576 jQuery('input[name=\'_wp_http_referer\']').remove();
381 577 jQuery('#mainform').attr('method', 'get');
382 - jQuery('#mainform').attr('action', '<?php echo admin_url('admin.php'); ?>');
578 + jQuery('#mainform').attr('action', '<?php echo esc_url(admin_url('admin.php')); ?>');
383 579 });
384 580 </script>
385 581 </div>
386 582 </div>
@@ -388,44 +584,42 @@
388 584
389 585 <table class="ph_email_queue widefat" cellspacing="0">
390 586 <thead>
391 587 <tr>
392 - <th class="date-time"><?php _e( 'Date/Time', 'propertyhive' ); ?></th>
393 - <th class="recipient"><?php _e( 'Recipient', 'propertyhive' ); ?></th>
394 - <th class="subject"><?php _e( 'Subject', 'propertyhive' ); ?></th>
395 - <th class="status"><?php _e( 'Status', 'propertyhive' ); ?></th>
588 + <th class="date-time"><?php echo esc_html(__( 'Date/Time', 'propertyhive' )); ?></th>
589 + <th class="recipient"><?php echo esc_html(__( 'Recipient', 'propertyhive' )); ?></th>
590 + <th class="subject"><?php echo esc_html(__( 'Subject', 'propertyhive' )); ?></th>
591 + <th class="status"><?php echo esc_html(__( 'Status', 'propertyhive' )); ?></th>
396 592 <th class="actions">&nbsp;</th>
397 593 </tr>
398 594 </thead>
399 595 <tbody>
400 596 <?php
401 - $query = "
402 - SELECT
403 - email_id,
404 - contact_id,
405 - to_email_address,
406 - subject,
407 - status,
408 - send_at
409 - FROM " . $wpdb->prefix . "ph_email_log
410 - WHERE
411 - 1=1 ";
412 - if ( isset($_GET['status']) )
413 - {
414 - switch ( $_GET['status'] )
415 - {
416 - case "queued": { $query .= " AND status = '' "; break; }
417 - case "failed": { $query .= " AND status IN ('fail1', 'fail2') "; break; }
418 - case "sent": { $query .= " AND status = 'sent' "; break; }
419 - }
420 - }
421 - $query .= $additional_query;
597 + $query = "SELECT email_id, contact_id, to_email_address, subject, status, send_at
598 + FROM {$wpdb->prefix}ph_email_log
599 + WHERE %s = %s";
600 + $query_args = array( '1', '1' );
601 + if ( 'all' !== $date_from ) {
602 + $query .= ' AND send_at >= %s';
603 + $query_args[] = $date_from . ' 00:00:00';
604 + }
605 + switch ( $status ) {
606 + case 'queued':
607 + $query .= ' AND status = %s';
608 + $query_args[] = '';
609 + break;
610 + case 'failed':
611 + $query .= " AND status IN ('fail1', 'fail2')";
612 + break;
613 + case 'sent':
614 + $query .= ' AND status = %s';
615 + $query_args[] = 'sent';
616 + break;
617 + }
618 + $query .= ' ORDER BY send_at DESC LIMIT 250';
619 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- The email queue is stored in Property Hive's custom table, which has no WP_Query API; this live filtered log must reflect asynchronous queue changes immediately. The query contains only fixed SQL branches and passes all date/status values through $wpdb->prepare().
620 + $emails = $wpdb->get_results( $wpdb->prepare( $query, $query_args ) );
422 621
423 - $query .= " ORDER BY send_at DESC
424 - LIMIT 250
425 - ";
426 - $emails = $wpdb->get_results( $query );
427 -
428 622 if ( is_array($emails) && !empty($emails) )
429 623 {
430 624 foreach ( $emails as $email )
431 625 {
@@ -430,11 +624,11 @@
430 624 foreach ( $emails as $email )
431 625 {
432 626 ?>
433 627 <tr>
434 - <td class="date-time"><?php echo date("jS M Y H:i", strtotime($email->send_at)); ?></td>
435 - <td class="recipient"><?php echo '<a href="' . get_edit_post_link($email->contact_id) . '">' . get_the_title($email->contact_id) . '</a><br>' . $email->to_email_address; ?></td>
436 - <td class="subject"><?php echo $email->subject; ?></td>
628 + <td class="date-time"><?php echo esc_html(gmdate("jS M Y H:i", strtotime($email->send_at))); ?></td>
629 + <td class="recipient"><?php echo '<a href="' . esc_url(get_edit_post_link($email->contact_id)) . '">' . esc_html(get_the_title($email->contact_id)) . '</a><br>' . esc_html($email->to_email_address); ?></td>
630 + <td class="subject"><?php echo esc_html($email->subject); ?></td>
437 631 <td class="status"><?php
438 632 switch ($email->status)
439 633 {
440 634 case "fail1": { echo 'First attempt failed. Will retry'; break; }
@@ -443,9 +637,9 @@
443 637 default: { echo 'Queued'; }
444 638 }
445 639 ?></td>
446 640 <td class="actions">
447 - <a href="<?php echo wp_nonce_url( admin_url('?view_propertyhive_email=' . $email->email_id . '&email_id=' . $email->email_id ), 'view-email' ) ?>" target="_blank" class="button">View Email</a>
641 + <a href="<?php echo esc_url(wp_nonce_url( admin_url('?view_propertyhive_email=' . $email->email_id . '&email_id=' . $email->email_id ), 'view-email' )); ?>" target="_blank" class="button">View Email</a>
448 642 </td>
449 643 </tr>
450 644 <?php
451 645 }
@@ -475,8 +669,12 @@
475 669 if ( $current_section )
476 670 {
477 671 switch ($current_section)
478 672 {
673 + case "enquiry-auto-responder": { $settings = $this->get_enquiry_autoresponder_settings(); break; }
674 + case "match": { $settings = $this->get_property_match_settings(); break; }
675 + case "booking-confirmation": { $settings = $this->get_booking_confirmation_settings(); break; }
676 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Shared admin settings-view state; this global is intentionally used to control the common settings template and is not an arbitrary application global.
479 677 case "log": { $settings = $this->get_email_queue_settings(); $hide_save_button = true; break; }
480 678 default: { die("Unknown setting section"); }
481 679 }
482 680 }
@@ -490,20 +688,81 @@
490 688
491 689 /**
492 690 * Save settings.
493 691 */
494 - public function save() {
495 - PH_Admin_Settings::save_fields( $this->get_settings() );
692 + public function save()
693 + {
694 + if ( ! current_user_can( 'manage_options' ) || ! isset( $_REQUEST['_wpnonce'] ) || ! is_string( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'propertyhive-settings' ) ) {
695 + return;
696 + }
496 697
497 - if ( isset($_POST['propertyhive_auto_property_match']) && $_POST['propertyhive_auto_property_match'] == '1' )
498 - {
499 - update_option( 'propertyhive_auto_property_match_enabled_date', date("Y-m-d H:i:s"), FALSE);
698 + global $current_section;
500 699
501 - wp_schedule_event( time(), 'hourly', 'propertyhive_auto_email_match' ); // Skew it by 30 minutes to reduce conflict with email log processing
700 + if ( $current_section != '' )
701 + {
702 + switch ($current_section)
703 + {
704 + case 'enquiry-auto-responder':
705 + {
706 + $settings = $this->get_enquiry_autoresponder_settings();
707 +
708 + PH_Admin_Settings::save_fields( $settings );
709 + break;
710 + }
711 + case 'match':
712 + {
713 + $settings = $this->get_property_match_settings();
714 +
715 + $previous_auto_property_match = get_option( 'propertyhive_auto_property_match', '' );
716 +
717 + PH_Admin_Settings::save_fields( $settings );
718 +
719 + if ( isset($_POST['propertyhive_auto_property_match']) && $_POST['propertyhive_auto_property_match'] == '1' )
720 + {
721 + if ( $previous_auto_property_match != 'yes' )
722 + {
723 + // it's been activated. Stored in UTC
724 + update_option( 'propertyhive_auto_property_match_enabled_date', gmdate("Y-m-d H:i:s"), FALSE);
725 + }
726 +
727 + $timestamp = wp_next_scheduled( 'propertyhive_auto_email_match' );
728 + wp_unschedule_event($timestamp, 'propertyhive_auto_email_match' );
729 + wp_clear_scheduled_hook('propertyhive_auto_email_match');
730 +
731 + $recurrence = apply_filters( 'propertyhive_auto_email_match_cron_recurrence', 'daily' );
732 + if ( $recurrence != 'hourly' )
733 + {
734 + $timestamp = strtotime( 'tomorrow +2hours' ) - ( (int)get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
735 + }
736 + else
737 + {
738 + $timestamp = strtotime( '+1 hours' );
739 + }
740 + wp_schedule_event(
741 + apply_filters( 'propertyhive_auto_email_match_cron_timestamp', $timestamp ),
742 + $recurrence,
743 + 'propertyhive_auto_email_match'
744 + );
745 + }
746 + else
747 + {
748 + wp_clear_scheduled_hook( 'propertyhive_auto_email_match' );
749 + }
750 + break;
751 + }
752 + case 'booking-confirmation':
753 + {
754 + $settings = $this->get_booking_confirmation_settings();
755 +
756 + PH_Admin_Settings::save_fields( $settings );
757 + break;
758 + }
759 + default: { die("Unknown setting section"); }
760 + }
502 761 }
503 762 else
504 763 {
505 - wp_clear_scheduled_hook( 'propertyhive_auto_email_match' );
764 + PH_Admin_Settings::save_fields( $this->get_settings() );
506 765 }
507 766 }
508 767 }
509 768
@@ -508,5 +767,5 @@
508 767 }
509 768
510 769 endif;
511 770
512 -return new PH_Settings_Emails();
771 +return new PH_Settings_Emails();