PluginProbe
WP Map Block – Google Maps, OpenStreetMap, Mapbox, Store & Shop Locator, Directory, Listings & Filters / 2.0.2
WP Map Block – Google Maps, OpenStreetMap, Mapbox, Store & Shop Locator, Directory, Listings & Filters v2.0.2
trunk 2.0.1 2.0.2 2.0.3 2.0.4 3.0.0
wp-map-block / includes / Insights.php

Insights.php in WP Map Block – Google Maps, OpenStreetMap, Mapbox, Store & Shop Locator, Directory, Listings & Filters 2.0.2, at includes/Insights.php

485 lines 16.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace WPMapBlock;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 // phpcs:ignoreFile
9
10 class Insights {
11 private static $instances = [];
12
13 private $slug;
14 private $type;
15 private $version;
16 private $api_site_url;
17 private $config = [];
18
19 private function __construct( $api_site, $slug, $type = 'plugin', $version = null, $config = [] ) {
20 $this->api_site_url = untrailingslashit( $api_site );
21 $this->slug = sanitize_title( $slug );
22 $this->type = $type === 'theme' ? 'theme' : 'plugin';
23 $this->version = $version;
24 $default_config = [
25 'logo' => '', // default logo URL
26 'optin_message' => 'Help improve ' . esc_html( $this->slug ) . '! Allow anonymous usage tracking?',
27 'deactivation_message' => 'If you have a moment, please share why you are deactivating:',
28 'deactivation_reasons' => [
29 'no_longer_needed' => [
30 'label' => 'I no longer need the plugin',
31 ],
32 'found_a_better_plugin' => [
33 'label' => 'I found a better plugin',
34 'has_custom_reason' => true,
35 'custom_reason_placeholder' => 'Please share which plugin',
36 ],
37 'couldnt_get_the_plugin_to_work' => [
38 'label' => 'I couldn\'t get the plugin to work',
39 ],
40 'temporary_deactivation' => [
41 'label' => 'It\'s a temporary deactivation',
42 ],
43 'other' => [
44 'label' => 'Other',
45 'has_custom_reason' => true,
46 'custom_reason_placeholder' => 'Please share the reason',
47 ],
48 ],
49 ];
50
51 $this->config = wp_parse_args( $config, $default_config );
52
53 add_action( 'admin_notices', [ $this, 'maybe_show_optin' ] );
54 add_action( 'admin_init', [ $this, 'handle_optin_click' ] );
55 add_action( 'admin_enqueue_scripts', [ $this, 'inject_deactivate_feedback' ] );
56 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_inline_css' ] );
57
58 add_action( 'wp_ajax_insights_deactivate_send', function () {
59 if ( ! wp_verify_nonce( sanitize_text_field( $_POST['security'] ), $this->slug . '-insights-deactivate-nonce' ) ) {
60 wp_send_json_error( 'Invalid nonce.' );
61 }
62
63 $slug = sanitize_text_field( $_POST['slug'] );
64 $reason_key = sanitize_text_field( $_POST['reason_key'] );
65 $reason = sanitize_text_field( $_POST['reason'] );
66 $instance = self::$instances[ $slug ] ?? null;
67
68 if ( ! $instance || empty( $slug ) ) {
69 wp_send_json_error();
70 }
71
72 $instance->send_to_server( 'deactivated', [
73 'deactivation_reason_type' => $reason_key,
74 'deactivation_reason' => $reason,
75 'deactivation_time' => current_time( 'mysql' ),
76 ] );
77 $instance->mark_sent( 'deactivated' );
78
79 wp_send_json_success();
80 } );
81 }
82
83 public static function init( $api_site, $slug, $type = 'plugin', $version = null, $config = [] ) {
84 if ( ! isset( self::$instances[ $slug ] ) ) {
85 self::$instances[ $slug ] = new self( $api_site, $slug, $type, $version, $config );
86 }
87 }
88
89 public function maybe_show_optin() {
90 $screen = get_current_screen();
91 if ( $screen && (in_array( $screen->base, [ 'post', 'post-new' ], true ) || $screen->is_block_editor()) ) {
92 return;
93 }
94
95 if ( $this->is_already_sent( 'optin' ) ) {
96 return;
97 }
98
99 $install_time = get_option('wp_map_block_first_install_time');
100 $current_time = time() + (get_option('gmt_offset') * HOUR_IN_SECONDS);
101 $three_days = 3 * DAY_IN_SECONDS;
102
103 if ( ! $install_time || ! is_numeric( $install_time ) ) {
104 return;
105 }
106
107 if ( ( $current_time - $install_time ) < $three_days ) {
108 return;
109 }
110
111 $optin_key = 'insights_optin_' . $this->slug;
112 $nonce = wp_create_nonce( 'insights_optin_' . $this->slug );
113 $optin_url = esc_url( add_query_arg( [ $optin_key => 'yes', 'security' => $nonce ] ) );
114 $nooptin_url = esc_url( add_query_arg( [ $optin_key => 'no', 'security' => $nonce ] ) );
115
116 echo '<div class="notice notice-info" style="padding:0;border-top:0;border-right:0;border-bottom:0;margin:1rem; display: flex; border-left-color: #008dff;">
117 <div style="padding: 24px; background: #E0F1FF; display:flex; flex-direction: column; justify-content:center; align-item: center;">
118 <img src="' . esc_url( WPMAPBLOCK_ASSETS_URI . 'images/logo.png' ) . '" width="100" alt="logo" style="margin-bottom: 20px;" />
119 <p>Build connections with <br />2000+ Website experts.</p>
120 <a href="https://www.facebook.com/groups/386840187678401" target="_blank" style="color: #008dff;">Join the community</a>
121 </div>
122 <div style="padding: 24px;">
123 <h2 style="margin-top: 0; font-size: 20x; font-weight:bold; color: #1E1E1E;"> 💌 Help Us Improve & Get Exclusive Perks!</h2>
124 <p>
125 We’d love to stay in touch and share helpful updates, tips, and special offers to make the most of <strong>WP Map Block</strong>. Your privacy is our priority. No spam — ever.
126 </p>
127 <ul>
128 <li>�
129 Early access to new features</li>
130 <li>�
131 Helpful guides & tutorials</li>
132 <li>�
133 Occasional discounts</li>
134 </ul>
135 <details style="margin: 10px 0;">
136 <summary style="cursor: pointer; color: #008dff; text-decoration: underline;">What we collect if you opt in?</summary>
137 <div style="margin-top: 8px;">
138 <p>We collect your <strong>site URL, plugin version, WordPress version, PHP version</strong>, and <strong>admin email</strong> to better tailor our updates and improve plugin performance.</p>
139 <p><em>No sensitive or personal data is collected. You can unsubscribe at any time.</em></p>
140 </div>
141 </details>
142 <p style="display: flex;column-gap: 15px; margin-bottom: 0;">
143 <a href="' . esc_url( $optin_url ) . '" class="button-primary" style="background: #008dff; border-color: #008dff;">Yes, keep me updated</a>
144 <a href="' . esc_url( $nooptin_url ) . '" class="button-secondary" style="background: #E0F1FF; color: #008dff; border-color: #E0F1FF;">No thanks</a>
145 </p>
146 </div>
147 </div>';
148 }
149
150 public function handle_optin_click() {
151 $key = 'insights_optin_' . $this->slug;
152 if ( isset( $_GET[ $key ] ) && wp_verify_nonce( sanitize_text_field( $_GET['security'] ), 'insights_optin_' . $this->slug ) ) {
153 $optin = $_GET[ $key ] === 'yes' ? 1 : 0;
154 if($optin){
155 $this->send_to_server( 'optin', [
156 'optin_status' => (int) $optin,
157 'optin_time' => current_time( 'mysql' ),
158 ] );
159 }
160 $this->mark_sent( 'optin' );
161 wp_safe_redirect( remove_query_arg( $key ) );
162 exit;
163 }
164 }
165
166 public function inject_deactivate_feedback( $hook ) {
167 if ( $hook !== 'plugins.php' || $this->is_already_sent( 'deactivated' ) ) {
168 return;
169 }
170
171 wp_enqueue_script( 'jquery-ui-dialog' );
172 wp_enqueue_style( 'jquery-ui-dialog' );
173
174 add_action( 'admin_footer', function () {
175 $slug = esc_js( $this->slug );
176 $ajax_url = esc_url( admin_url( 'admin-ajax.php?action=insights_deactivate_send' ) );
177 ?>
178 <script>
179 jQuery(function ($) {
180 const $link = $('tr[data-slug="<?php echo esc_attr( $slug ); ?>"] .deactivate a');
181 const dialogId = 'insights-deactivate-dialog-<?php echo esc_attr( $slug ); ?>';
182 const formId = 'insights-deactivate-form-<?php echo esc_attr( $slug ); ?>';
183
184 const $dialog = $(`
185 <div id="${dialogId}">
186 <div class="insights-deactivate-dialog__header">
187 <img src="<?php echo esc_url( $this->config['logo'] ); ?>" width="20" />
188 <span>Quick Feedback</span>
189 </div>
190 <form id="${formId}" class="insights-deactivate-form">
191 <input type="hidden" name="security" value="<?php echo esc_attr( wp_create_nonce( $this->slug . '-insights-deactivate-nonce' ) ); ?>" />
192 <input type="hidden" name="slug" value="<?php echo esc_attr( $slug ); ?>" />
193 <div class="insights-deactivate-dialog__description"><?php echo esc_html( $this->config['deactivation_message'] ); ?></div>
194 <ul>
195 <?php foreach ( $this->config['deactivation_reasons'] as $reason_key => $reason ) : ?>
196 <li>
197 <label style="display:block; margin-bottom: 8px;">
198 <input type="radio" name="reason_key" value="<?php echo esc_attr( $reason_key ); ?>" required />
199 <?php echo esc_html( $reason['label'] ); ?>
200 </label>
201 <?php
202 if ( isset( $reason['toggle_text'] ) && ! empty( $reason['toggle_text'] ) ) :
203 ?>
204 <div class="insights-toggle-text" style="color: #F59E0B; display: none; margin-left: 25px;">
205 <?php echo esc_html( $reason['toggle_text'] ); ?>
206 <div>
207 <?php
208 endif;
209 ?>
210
211 <?php
212 if ( isset( $reason['has_custom_reason'] ) && $reason['has_custom_reason'] === true ) :
213 ?>
214 <div class="insights-deactivate-form-reason-message">
215 <input type="text" name="reason" placeholder="<?php echo esc_attr( $reason['custom_reason_placeholder'] ); ?>" required />
216 <div>
217 <?php
218 endif;
219 ?>
220 </li>
221 <?php endforeach; ?>
222 </ul>
223 <div class="insights-deactivate-form__control-button">
224 <button type="submit">Submit & Deactivate</button>
225 <button type="button" data-type="skip">Skip & Deactivate</button>
226 </div>
227 </form>
228 </div>
229 `).appendTo('body').hide();
230
231 $link.on('click', function (e) {
232 e.preventDefault();
233 const href = $(this).attr('href');
234
235 $(`#${dialogId}`).dialog({
236 modal: true,
237 width: 550,
238 dialogClass: 'insights-deactivate-dialog',
239 buttons: [],
240 create: function () {
241 const $thisDialog = $(this);
242 // Hide the default close (X) button
243 $thisDialog.closest('.ui-dialog').find('.ui-dialog-titlebar-close').hide();
244 },
245 close: function () {
246 // Clean up delegated handler
247 $(document).off('click.insightsOverlay');
248 }
249
250 });
251
252 const $form = $(`#${formId}`);
253
254 // Unbind first to prevent multiple bindings
255 $form.off('submit').on('submit', function (event) {
256 event.preventDefault();
257
258 const $submitButton = $(this).find('button[type="submit"]');
259 const originalHtml = $submitButton.html();
260
261 // Add WP spinner
262 $submitButton.prop('disabled', true).html('Deactivating...');
263
264 const formDataArray = $form.serializeArray();
265 const formDataObject = {};
266
267 formDataArray.forEach(field => {
268 if (field.value.trim() !== '') {
269 formDataObject[field.name] = field.value;
270 }
271 });
272
273 $.ajax({
274 url: '<?php echo $ajax_url; ?>',
275 method: 'POST',
276 data: formDataObject,
277 success: function (response) {
278 window.location.href = href;
279 },
280 error: function () {
281 alert('An error occurred. Please try again.');
282 $submitButton.prop('disabled', false).html(originalHtml);
283 }
284 });
285 });
286
287
288 // Skip button closes modal and redirects
289 $form.find('button[data-type="skip"]').off('click').on('click', function () {
290 $(`#${dialogId}`).dialog('close');
291 window.location.href = href;
292 });
293
294 $(document).on('click.insightsOverlay', '.ui-widget-overlay', function () {
295 // Get the visible dialog that is open now
296 const $openDialog = $('.ui-dialog-content:visible');
297 if ($openDialog.length) {
298 $openDialog.dialog('close');
299 }
300 });
301
302 $form.find('input[name="reason_key"]').on('change', function () {
303 const $selected = $(this).closest('li');
304
305 // Hide all custom reason inputs
306 $form.find('.insights-deactivate-form-reason-message').hide().find('input').prop('required', false);
307 // toggle text
308 $form.find('.insights-toggle-text').hide();
309 $selected.find('.insights-toggle-text').show();
310
311 // Show the one under the selected reason, if exists
312 const $customReason = $selected.find('.insights-deactivate-form-reason-message');
313 if ($customReason.length) {
314 $customReason.show().find('input').prop('required', true);
315 }
316 });
317
318 });
319 });
320 </script>
321 <?php
322 });
323
324 }
325
326 private function send_to_server( $track_type, $extra_data = [] ) {
327 if ( $this->is_already_sent( $track_type ) ) {
328 return;
329 }
330
331 $data = [
332 'track_type' => $track_type,
333 'project_type' => $this->type,
334 'project_slug' => $this->slug,
335 'site_url' => site_url(),
336 'admin_email' => get_option( 'admin_email' ),
337 'wp_version' => get_bloginfo( 'version' ),
338 'php_version' => PHP_VERSION,
339 'project_version' => $this->version,
340 'deactivation_reason_type' => '',
341 'deactivation_reason' => '',
342 'deactivation_time' => current_time( 'mysql' ),
343 ];
344
345 if ( $track_type === 'optin' ) {
346 $data['optin_status'] = '';
347 $data['optin_time'] = current_time( 'mysql' );
348 unset( $data['deactivation_reason_type'] );
349 unset( $data['deactivation_reason'] );
350 unset( $data['deactivation_time'] );
351 }
352
353 $payload = \wp_parse_args( $extra_data, $data );
354
355 $api_url = trailingslashit( $this->api_site_url ) . 'wp-json/insightsforwp/v1/track';
356
357 $response = wp_remote_post( $api_url, [
358 'timeout' => 10,
359 'body' => $payload,
360 'sslverify' => false
361 ] );
362
363 if ( is_wp_error( $response ) ) {
364 // Handle error.
365 $error_message = $response->get_error_message();
366 // You can log this or return an error response.
367 error_log( "Request failed: $error_message" );
368 }
369 }
370
371 private function is_already_sent( $type ) {
372 $sent = get_option( 'insightsforwp_sent_data', [] );
373 return ! empty( $sent[ $this->slug ][ $type ] );
374 }
375
376 private function mark_sent( $type ) {
377 $sent = get_option( 'insightsforwp_sent_data', [] );
378 $sent[ $this->slug ][ $type ] = true;
379 update_option( 'insightsforwp_sent_data', $sent, false );
380 }
381
382 public function enqueue_inline_css() {
383 if ( $this->is_already_sent( 'deactivated' ) ) {
384 return;
385 }
386 $css = '
387 .ui-widget-overlay {
388 position: fixed;
389 top: 0;
390 left: 0;
391 width: 100% !important;
392 height: 100% !important;
393 background: rgba(0, 0, 0, 0.5);
394 z-index: 1000;
395 }
396 .insights-deactivate-dialog {
397 display: flex;
398 z-index: 99999;
399 }
400 {{wrapper}} .insights-deactivate-dialog__header {
401 padding: 18px 15px;
402 box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
403 text-align: start;
404 display: flex;
405 }
406 {{wrapper}} .insights-deactivate-dialog__header span {
407 font-size: 15px;
408 text-transform: uppercase;
409 font-weight: bold;
410 padding-inline-start: 5px;
411 color: #515962;
412 }
413 {{wrapper}} .insights-deactivate-dialog__description {
414 font-size: 15px;
415 line-height: 1.4;
416 font-weight: bold;
417 color: #515962;
418 }
419 {{wrapper}} {
420 height: auto;
421 background-color: #ffffff;
422 border-radius: 3px;
423 box-shadow: 2px 8px 23px 3px rgba(0, 0, 0, 0.2);
424 overflow: hidden;
425 }
426
427 {{wrapper}} .insights-deactivate-form {
428 padding: 30px;
429 text-align: start;
430 }
431 {{wrapper}} input[type="radio"] {
432 margin-block: 0;
433 margin-inline: 0 10px;
434 box-shadow: none;
435 }
436 {{wrapper}} label {
437 display: block;
438 font-size: 13px;
439 color: #515962;
440 }
441 {{wrapper}} ul {
442 padding-block-start: 30px;
443 padding-block-end: 15px;
444 }
445 {{wrapper}} .insights-deactivate-form-reason-message {
446 display: none;
447 }
448 {{wrapper}} .insights-deactivate-form-reason-message input {
449 width: 92%;
450 margin-left: 25px;
451 background-color: transparent;
452 color: #515962;
453 padding: 5px;
454 box-shadow: none;
455 }
456 {{wrapper}} .insights-deactivate-form__control-button {
457 display: flex;
458 justify-content: space-between;
459 }
460 {{wrapper}} .insights-deactivate-form__control-button button {
461 font-size: 12px;
462 font-weight: 500;
463 line-height: 1.2;
464 padding: 8px 16px;
465 outline: none;
466 border: none;
467 border-radius: 4px;
468 background-color: white;
469 color: #0c0d0e;
470 transition: all .3s;
471 cursor: pointer;
472 }
473 {{wrapper}} .insights-deactivate-form__control-button button[type="submit"] {
474 background: #008dff;
475 color: #fff;
476 }
477 {{wrapper}} .insights-deactivate-form__control-button button[type="button"] {
478 background: transparent;
479 color: #c4c4c5;
480 }
481 ';
482 wp_add_inline_style( 'wp-admin', str_replace( '{{wrapper}}', '#insights-deactivate-dialog-' . $this->slug, $css ) );
483 }
484 }
485