PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.2.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.2.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / includes / admin / insights.php

insights.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at includes/admin/insights.php

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