PluginProbe ʕ •ᴥ•ʔ
ShopEngine Elementor WooCommerce Builder Addon – All in One WooCommerce Solution / 4.9.2
ShopEngine Elementor WooCommerce Builder Addon – All in One WooCommerce Solution v4.9.2
4.9.2 4.9.1 4.9.0 2.0.0 2.1.0 2.2.0 2.2.1 2.2.2 2.3.0 2.4.0 2.5.0 2.5.1 3.0.0 3.1.0 3.1.1 4.0.0 4.0.1 4.1.0 4.1.1 4.2.0 4.2.1 4.3.0 4.3.1 4.4.0 4.5.0 4.5.1 4.6.0 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.6.6 4.6.7 4.6.8 4.6.9 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.7.5 4.7.6 4.7.7 4.7.8 4.7.9 4.8.0 4.8.1 4.8.2 4.8.3 4.8.4 4.8.5 4.8.6 4.8.7 4.8.8 4.8.9 trunk 0.1.2-beta 0.1.3-beta 0.1.4-beta 1.0.0 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.5.0 1.5.1 1.6.0 1.6.1 1.7.0 1.8.0 1.8.1 1.9.0
shopengine / utils / feedback / plugin-unsubscribe.php
shopengine / utils / feedback Last commit date
assets 1 month ago plugin-unsubscribe.php 1 month ago
plugin-unsubscribe.php
517 lines
1 <?php
2
3 /**
4 * Plugin Unsubscribe / Deactivation Feedback Handler
5 *
6 * Renders a feedback modal on plugin deactivation, collects user input,
7 * and sends telemetry to the ShopEngine API.
8 *
9 * @package ShopEngine\Utils\Feedback
10 * @since 1.0.0
11 */
12
13 namespace ShopEngine\Utils\Feedback;
14
15 defined('ABSPATH') || exit;
16
17 class Plugin_Unsubscribe
18 {
19
20 /**
21 * Constructor.
22 *
23 * Registers all admin-side hooks required by this feature.
24 *
25 * @since 1.0.0
26 */
27 public function __construct()
28 {
29 if (! is_admin()) {
30 return;
31 }
32
33 add_action('admin_footer', array($this, 'render_modal'));
34 add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
35
36 add_action('wp_ajax_shopengine_deactivation_feedback', array($this, 'handle_feedback'));
37 }
38
39
40 /**
41 * Enqueue CSS and JS assets for the deactivation feedback modal.
42 *
43 * Passes localised data (nonce, AJAX URL, plugin URL)
44 * to the front-end script via {@see wp_localize_script()}.
45 *
46 * @since 1.0.0
47 *
48 * @return void
49 */
50 public function enqueue_scripts($hook_suffix)
51 {
52 if ('plugins.php' !== $hook_suffix) {
53 return;
54 }
55
56 $plugin_url = \ShopEngine::plugin_url();
57 $version = \ShopEngine::version();
58
59 wp_enqueue_style(
60 'shopengine-deactivation-modal',
61 $plugin_url . 'utils/feedback/assets/css/shopengine-deactivation-modal.css',
62 array(),
63 $version
64 );
65
66 wp_enqueue_script(
67 'shopengine-deactivation-modal',
68 $plugin_url . 'utils/feedback/assets/js/shopengine-deactivation-modal.js',
69 array('jquery'),
70 $version,
71 true
72 );
73
74 wp_localize_script(
75 'shopengine-deactivation-modal',
76 'ShopEngineDeactivation',
77 array(
78 'nonce' => wp_create_nonce('shopengine-deactivation'),
79 'ajaxurl' => admin_url('admin-ajax.php'),
80 'plugin_url' => $plugin_url,
81 )
82 );
83 }
84
85
86 /**
87 * Output the deactivation feedback modal markup in the admin footer.
88 *
89 * @since 1.0.0
90 *
91 * @return void
92 */
93 public function render_modal()
94 {
95 $screen = function_exists('get_current_screen') ? get_current_screen() : null;
96
97 if (! $screen || 'plugins' !== $screen->id) {
98 return;
99 }
100
101 $reasons = $this->get_deactivation_reasons();
102 ?>
103 <div id="shopengine-deactivation-modal" class="shopengine-deactivation-modal">
104 <div class="shopengine-deactivation-content">
105
106 <?php $this->render_modal_header(); ?>
107
108 <div class="shopengine-deactivation-body">
109 <div id="shopengine-deactivation-error-message" class="shopengine-deactivation-error-message" style="display: none;"></div>
110
111 <h2 class="shopengine-deactivation-title">
112 <?php esc_html_e('Before you go, what made you deactivate ShopEngine?', 'shopengine'); ?>
113 </h2>
114
115 <form id="shopengine-deactivation-form" class="shopengine-deactivation-form">
116 <input type="hidden" name="shopengine_nonce" value="<?php echo esc_attr(wp_create_nonce('shopengine-deactivation')); ?>" />
117
118 <div class="shopengine-deactivation-radio-group">
119 <?php foreach ($reasons as $reason) : ?>
120 <?php $this->render_reason_item($reason); ?>
121 <?php endforeach; ?>
122 </div>
123
124 <?php $this->render_modal_footer(); ?>
125
126 </form>
127 </div><!-- .shopengine-deactivation-body -->
128
129 </div><!-- .shopengine-deactivation-content -->
130 </div><!-- #shopengine-deactivation-modal -->
131 <?php
132 }
133
134
135 /**
136 * Handle the AJAX feedback-submission request.
137 *
138 * Verifies the nonce and user capabilities, collects payload data,
139 * then dispatches the data to the remote API via {@see send_feedback_data()}.
140 *
141 * Sends a JSON error response on failure; a JSON success response otherwise.
142 *
143 * @since 1.0.0
144 *
145 * @return void Terminates execution via {@see wp_send_json_error()} or
146 * {@see wp_send_json_success()}.
147 */
148 public function handle_feedback()
149 {
150 $this->verify_request();
151
152 $selected_reason = isset($_POST['reason'])
153 ? sanitize_text_field(wp_unslash($_POST['reason']))
154 : '';
155
156 $data = array(
157 'plugin_slug' => 'shopengine',
158 'plugin_name' => 'ShopEngine',
159 'plugin_version' => \ShopEngine::version(),
160 'user' => array(
161 'email' => $this->get_user_email(),
162 ),
163 'feedback' => array(
164 'reason_key' => isset($_POST['reason_key']) ? sanitize_text_field(wp_unslash($_POST['reason_key'])) : 'other',
165 'reason_label' => isset($_POST['reason_label']) ? sanitize_text_field(wp_unslash($_POST['reason_label'])) : $selected_reason,
166 'message' => isset($_POST['feedback']) ? sanitize_textarea_field(wp_unslash($_POST['feedback'])) : '',
167 ),
168 'usage' => array(
169 'user_type' => $this->get_user_type(),
170 'active_days' => $this->get_days_active(),
171 ),
172 'environment' => array(
173 'multisite_status' => is_multisite(),
174 'wp_version' => get_bloginfo('version'),
175 'php_version' => PHP_VERSION,
176 'elementor_version' => defined('ELEMENTOR_VERSION') ? ELEMENTOR_VERSION : '',
177 'site_url' => get_site_url(),
178 ),
179 );
180
181 $response = $this->send_feedback_data($data);
182
183 if (is_wp_error($response)) {
184 wp_send_json_error(array('message' => $response->get_error_message()));
185 }
186
187 $response_code = (int) wp_remote_retrieve_response_code($response);
188 if ($response_code < 200 || $response_code >= 300) {
189 wp_send_json_error(
190 array(
191 'message' => esc_html__('Failed to submit feedback.', 'shopengine'),
192 'code' => $response_code,
193 )
194 );
195 }
196
197 wp_send_json_success(
198 array('message' => esc_html__('Thank you for your feedback!', 'shopengine'))
199 );
200 }
201
202
203 /**
204 * Output the modal header, including the ShopEngine logo SVG and title.
205 *
206 * @since 1.0.0
207 *
208 * @return void
209 */
210 private function render_modal_header()
211 {
212 ?>
213 <div class="shopengine-deactivation-header">
214 <h2>
215 <svg class="shopengine-deactivation-logo" width="36" height="30" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 62 54">
216 <defs>
217 <style>.mix{isolation:isolate}.mul{mix-blend-mode:multiply;opacity:0.49}</style>
218 <linearGradient id="g1" x1="15.82" y1="18.7" x2="20.26" y2="9.9" gradientUnits="userSpaceOnUse">
219 <stop offset="0" stop-color="#44a6ff"/><stop offset="1" stop-color="#1bc1ff"/>
220 </linearGradient>
221 <linearGradient id="g2" x1="20.35" y1="18.53" x2="19.73" y2="9.69" gradientUnits="userSpaceOnUse">
222 <stop offset="0" stop-color="#000"/><stop offset="0.14" stop-color="#303030"/>
223 <stop offset="0.38" stop-color="#797979"/><stop offset="0.59" stop-color="#b2b2b2"/>
224 <stop offset="0.77" stop-color="#dcdcdc"/><stop offset="0.92" stop-color="#f6f6f6"/>
225 <stop offset="1" stop-color="#fff"/>
226 </linearGradient>
227 <linearGradient id="g3" x1="44.75" y1="17.55" x2="37.88" y2="7.91" gradientUnits="userSpaceOnUse">
228 <stop offset="0" stop-color="#47b2d4"/><stop offset="1" stop-color="#00d5c9"/>
229 </linearGradient>
230 <linearGradient id="g4" x1="43.74" y1="18.56" x2="37.26" y2="7.69" xlink:href="#g2"/>
231 <linearGradient id="g5" x1="0" y1="22.21" x2="58.3" y2="22.21" gradientUnits="userSpaceOnUse">
232 <stop offset="0" stop-color="#f8003c"/><stop offset="1" stop-color="#ff8438"/>
233 </linearGradient>
234 <linearGradient id="g6" x1="18.83" y1="27.07" x2="18.83" y2="46.06" gradientUnits="userSpaceOnUse">
235 <stop offset="0" stop-color="#00d5c9"/><stop offset="1" stop-color="#0099ac"/>
236 </linearGradient>
237 <linearGradient id="g7" x1="56.67" y1="47.89" x2="47.45" y2="39.43" xlink:href="#g5"/>
238 </defs>
239
240 <g class="mix">
241 <!-- Left handle (blue gradient) -->
242 <path fill="url(#g1)" d="M23.86,7.6a3.82,3.82,0,1,0-4.31-3.25,4.59,4.59,0,0,0,.24.9L12.43,17.41h5.7Z"/>
243 <path fill="url(#g2)" class="mul" d="M23.86,7.6a3.82,3.82,0,1,0-4.31-3.25,4.59,4.59,0,0,0,.24.9L12.43,17.41h5.7Z"/>
244
245 <!-- Right handle (teal gradient) -->
246 <path fill="url(#g3)" d="M38.49,5.25a3.82,3.82,0,1,0-5,2.1,4.09,4.09,0,0,0,.91.25l5.73,9.81h5.68Z"/>
247 <path fill="url(#g4)" class="mul" d="M38.49,5.25a3.82,3.82,0,1,0-5,2.1,4.09,4.09,0,0,0,.91.25l5.73,9.81h5.68Z"/>
248
249 <!-- Rim bar (red orange gradient, white circle cutout included) -->
250 <path fill="url(#g5)" d="M4,27H31.86a4.76,4.76,0,0,1,.32-.51,8.19,8.19,0,0,1,1.89-2,5.94,5.94,0,0,1,1.94-1,4.93,4.93,0,0,1,3.77.39,5,5,0,0,1,3.71-3.45l.54-.16,1.12,0h.59l1.12,0,.54.16a5,5,0,0,1,3.72,3.45,4.93,4.93,0,0,1,3.77-.38,5.82,5.82,0,0,1,1.94,1c.25.2.53.44.8.7A4,4,0,0,0,58.3,23V21.43a4,4,0,0,0-4-4H4a4,4,0,0,0-4,4V23a4,4,0,0,0,4,4Zm5.48-3.76a2.19,2.19,0,0,1,2.18-2.19h0a2.19,2.19,0,1,1-2.19,2.19Z"/>
251
252 <!-- Basket body (teal gradient) -->
253 <path fill="url(#g6)" d="M32.45,49a4.7,4.7,0,0,1-1-3.83,6.74,6.74,0,0,1,.17-.75,5,5,0,0,1,.47-1,5,5,0,0,1-1-.3v.14a1.64,1.64,0,0,1-.66,1.24,1.6,1.6,0,0,1-1,.32h-.88A1.62,1.62,0,0,1,27,43.2L27,41.84l-.38-9.56A1.23,1.23,0,0,1,27.79,31h2.54a1.25,1.25,0,0,1,1.22,1l.17-.06a5,5,0,0,1,0-4.56,3.72,3.72,0,0,1,.19-.34h-28L7,45.6a5.84,5.84,0,0,0,5.73,4.83H33.84A11.88,11.88,0,0,1,32.45,49ZM18.91,44.75H17.86A2.23,2.23,0,0,1,15.67,43l-2.22-10.8a1,1,0,0,1,0-.35,1,1,0,0,1,.78-.82l.2,0h2.67a1.63,1.63,0,0,1,1.62,1.45l.43,3.6L20,43.36A1.25,1.25,0,0,1,18.91,44.75Z"/>
254
255 <!-- Gear (dark) -->
256 <path fill="#252525" d="M55.94,40.46c.58,0,1.17-.06,1.76-.08A2.1,2.1,0,0,0,58.84,40a1.79,1.79,0,0,0,.75-1.21.22.22,0,0,1,0-.08,2.6,2.6,0,0,0,.1-.5c0-.26,0-.53,0-.79V37.3c0-.21,0-.42-.06-.62a2.08,2.08,0,0,0-.58-1.31,2,2,0,0,0-1.37-.57l-1.68,0a1.75,1.75,0,0,1-1.58-1.09c0-.12,0-.23,0-.35A1.63,1.63,0,0,1,55,32.08c.39-.39.78-.77,1.15-1.17a1.88,1.88,0,0,0,.34-2.19,4.19,4.19,0,0,0-.31-.5h0A4.87,4.87,0,0,0,54.91,27a2.61,2.61,0,0,0-.9-.48,1.78,1.78,0,0,0-1.73.35,8.28,8.28,0,0,0-.68.64,10.2,10.2,0,0,1-.82.79,1.56,1.56,0,0,1-1.64.21,1.59,1.59,0,0,1-.81-1,3.59,3.59,0,0,1,0-.73c0-.44,0-.89,0-1.33a2,2,0,0,0-1.12-1.78,4.89,4.89,0,0,0-.68-.21l-.08,0H44.53l-.08,0a5,5,0,0,0-.69.21,2,2,0,0,0-1.12,1.78c0,.44,0,.89,0,1.33a4.48,4.48,0,0,1,0,.73,1.6,1.6,0,0,1-.82,1,1.56,1.56,0,0,1-1.64-.21c-.29-.24-.54-.52-.81-.79s-.44-.45-.68-.64a1.79,1.79,0,0,0-1.73-.35A2.52,2.52,0,0,0,36,27a4.91,4.91,0,0,0-1.23,1.26h0a3.21,3.21,0,0,0-.31.5,1.88,1.88,0,0,0,.33,2.19c.37.4.76.78,1.15,1.17a1.63,1.63,0,0,1,.53,1.26,2.11,2.11,0,0,1-.05.35,1.74,1.74,0,0,1-1.58,1.09l-1.68,0a1.91,1.91,0,0,0-1.36.57,2.09,2.09,0,0,0-.59,1.31l-.06.62v.15c0,.26,0,.53.05.79a2.61,2.61,0,0,0,.11.5.22.22,0,0,0,0,.08A1.77,1.77,0,0,0,32.05,40a2.1,2.1,0,0,0,1.14.35c.59,0,1.18,0,1.77.08a1.64,1.64,0,0,1,1.37.83,1.75,1.75,0,0,1-.13,2c-.36.38-.71.78-1.07,1.16a2.24,2.24,0,0,0-.54.85A1.76,1.76,0,0,0,34.85,47a8.2,8.2,0,0,0,1.53,1.47,1.62,1.62,0,0,0,.69.32,1.93,1.93,0,0,0,1.65-.5c.43-.4.85-.82,1.27-1.22a1.56,1.56,0,0,1,.64-.41,1.87,1.87,0,0,1,1.1,0l.05,0a1.8,1.8,0,0,1,.78,1.2,4.07,4.07,0,0,1,0,.61c0,.45,0,.9,0,1.35a1.77,1.77,0,0,0,1.26,1.72,4.51,4.51,0,0,0,1.59.28A4.59,4.59,0,0,0,47,51.64a1.76,1.76,0,0,0,1.25-1.72c0-.45,0-.9,0-1.35a4.08,4.08,0,0,1,0-.61,1.81,1.81,0,0,1,.79-1.2l.05,0a1.55,1.55,0,0,1,.31-.06l-1.62-1.51a8,8,0,0,1-2.41.37,7.91,7.91,0,1,1,7.92-7.91,7.78,7.78,0,0,1-.27,2l1.61,1.5A1.64,1.64,0,0,1,55.94,40.46Z"/>
257
258 <!-- Wrench (red orange gradient) -->
259 <path fill="url(#g7)" d="M57.12,46.39a2,2,0,0,0-.59-.86l-4.26-4.1-1.06-1-.1-.12a.62.62,0,0,1-.08-.55l.12-.47A5.8,5.8,0,0,0,49,32.85a5.38,5.38,0,0,0-3.25-1.1A6.27,6.27,0,0,0,44,32l.13.15,2.19,2.15a2.83,2.83,0,0,1,.5.64,2.58,2.58,0,0,1,.15,2.44A3.6,3.6,0,0,1,46,38.63l0,0a2.73,2.73,0,0,1-2.3.63,2.61,2.61,0,0,1-1.38-.68c-.51-.46-1-.95-1.48-1.43L40,36.38s-.09-.07-.12-.06-.21.1-.22.24c0,.31-.06.63-.07.94a5.5,5.5,0,0,0,.36,2.27,6.15,6.15,0,0,0,2.48,2.92,5.36,5.36,0,0,0,4.16.74c.35-.08.7-.2,1-.29a.84.84,0,0,1,.89.19c.33.32.68.62,1,.94,1.08,1,2.15,2.07,3.24,3.1.42.41.84.81,1.29,1.19A1.52,1.52,0,0,0,55,49a1.77,1.77,0,0,0,1.08-.23,3.12,3.12,0,0,0,.82-.8A1.71,1.71,0,0,0,57.12,46.39ZM55.2,48a1,1,0,0,1-1-.95,1,1,0,0,1,1-1,.93.93,0,0,1,1,1A1,1,0,0,1,55.2,48Z"/>
260 </g>
261 </svg>
262 <span><?php esc_html_e('Quick Feedback', 'shopengine'); ?></span>
263 </h2>
264
265 <button type="button" class="shopengine-deactivation-close" aria-label="<?php esc_attr_e('Close', 'shopengine'); ?>">
266 <span aria-hidden="true">&times;</span>
267 </button>
268 </div><!-- .shopengine-deactivation-header -->
269 <?php
270 }
271
272 /**
273 * Output a single radio-option item inside the feedback form.
274 *
275 * Each item consists of a radio button, its label, hidden key/label inputs,
276 * and an optional follow-up textarea.
277 *
278 * @since 1.0.0
279 *
280 * @param array $reason {
281 * Associative array describing a single deactivation reason.
282 *
283 * @type string $value The radio button value / display label.
284 * @type string $key Programmatic key sent with the AJAX request.
285 * @type string $label Human-readable label sent with the AJAX request.
286 * @type string $placeholder Placeholder text for the follow-up textarea.
287 * }
288 * @return void
289 */
290 private function render_reason_item(array $reason)
291 {
292 $reason_key = esc_attr($reason['key']);
293 ?>
294 <div class="shopengine-deactivation-radio-item" data-reason-key="<?php echo esc_attr($reason['key']); ?>">
295 <label class="shopengine-deactivation-radio-option">
296 <input
297 type="radio"
298 name="reason"
299 value="<?php echo esc_attr($reason['value']); ?>"
300 class="shopengine-form-control-radio">
301 <span><?php echo esc_html($reason['value']); ?></span>
302 </label>
303 <input type="hidden" class="shopengine-reason-key" value="<?php echo esc_attr($reason['key']); ?>" />
304 <input type="hidden" class="shopengine-reason-label" value="<?php echo esc_attr($reason['label']); ?>" />
305 <textarea
306 name="feedback_<?php echo $reason_key; ?>"
307 class="shopengine-deactivation-radio-feedback"
308 placeholder="<?php echo esc_attr($reason['placeholder']); ?>"
309 rows="2"></textarea>
310 </div>
311 <?php
312 }
313
314 /**
315 * Output the modal footer containing the action buttons.
316 *
317 * @since 1.0.0
318 *
319 * @return void
320 */
321 private function render_modal_footer()
322 {
323 ?>
324 <div class="shopengine-deactivation-footer">
325 <button type="button" class="shopengine-btn shopengine-btn-secondary shopengine-deactivation-skip" data-deactivate-link="">
326 <?php esc_html_e('Skip & Deactivate', 'shopengine'); ?>
327 </button>
328 <button type="submit" class="shopengine-btn shopengine-btn-primary shopengine-deactivation-submit">
329 <?php esc_html_e('Submit & Deactivate', 'shopengine'); ?>
330 </button>
331 </div><!-- .shopengine-deactivation-footer -->
332 <?php
333 }
334
335
336 /**
337 * Verify AJAX request nonce and user capabilities.
338 *
339 * Sends a JSON error response and terminates execution if either check fails.
340 *
341 * @since 1.0.0
342 *
343 * @return void
344 */
345 private function verify_request()
346 {
347 $nonce = isset($_POST['shopengine_nonce'])
348 ? sanitize_key(wp_unslash($_POST['shopengine_nonce']))
349 : '';
350
351 if (! wp_verify_nonce($nonce, 'shopengine-deactivation')) {
352 wp_send_json_error(
353 array('message' => esc_html__('Security check failed', 'shopengine'))
354 );
355 }
356
357 if (! current_user_can('manage_options')) {
358 wp_send_json_error(
359 array('message' => esc_html__('Insufficient permissions', 'shopengine'))
360 );
361 }
362 }
363
364
365
366
367 /**
368 * Send collected feedback data to the ShopEngine remote API.
369 *
370 * @since 1.0.0
371 *
372 * @param array $data Associative array of feedback payload data.
373 * @return array|\WP_Error The raw HTTP response array, or a WP_Error on failure.
374 */
375 private function send_feedback_data(array $data)
376 {
377 // Plugin class does not expose an api_url() helper. Use the WPMet public API endpoint.
378 $url = 'https://api.wpmet.com/public/plugin-unsubscribe/';
379 return wp_remote_post(
380 $url,
381 array(
382 'method' => 'POST',
383 'timeout' => 20,
384 'headers' => array(
385 'Content-Type' => 'application/json',
386 ),
387 'body' => wp_json_encode($data),
388 )
389 );
390 }
391
392 /**
393 * Return the number of days the plugin has been active.
394 *
395 * Reads the `shopengine_install_date` option and computes the
396 * difference between that date and the current server time.
397 *
398 * @since 1.0.0
399 *
400 * @return int Number of complete days since installation, or 0 if unknown.
401 */
402 private function get_days_active()
403 {
404 $installed_time = get_option('shopengine_install_date');
405
406 if (! $installed_time) {
407 return 0;
408 }
409
410 $installed_timestamp = strtotime($installed_time);
411 $current_time = current_time('timestamp'); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
412
413 return (int) floor(($current_time - $installed_timestamp) / DAY_IN_SECONDS);
414 }
415
416 /**
417 * Return the current user's license/subscription type.
418 *
419 * Possible return values:
420 * - `'pro_valid'` – Pro plugin is active with a valid licence.
421 * - `'pro'` – Pro plugin is installed but licence is missing or invalid.
422 * - `'free'` – Only the Lite version is installed.
423 *
424 * @since 1.0.0
425 *
426 * @return string One of `'pro_valid'`, `'pro'`, or `'free'`.
427 */
428 private function get_user_type()
429 {
430 if ('pro' !== \ShopEngine::package_type()) {
431 return 'free';
432 }
433
434 return 'valid' === \ShopEngine::license_status() ? 'pro_valid' : 'pro';
435 }
436
437 /**
438 * Return the admin email address stored in plugin options, if available.
439 *
440 * @since 1.0.0
441 *
442 * @return string A sanitized email address, or an empty string when not set.
443 */
444 private function get_user_email()
445 {
446 $options = get_option('shopengine_options', array());
447
448 if (empty($options['settings']['newsletter_email'])) {
449 return '';
450 }
451
452 return sanitize_email($options['settings']['newsletter_email']);
453 }
454
455
456 /**
457 * Return the list of available deactivation reasons shown in the modal.
458 *
459 * Each entry is an associative array with the following keys:
460 * - `value` (string) The user-visible radio-button label.
461 * - `key` (string) The programmatic key sent to the API.
462 * - `label` (string) The human-readable label sent to the API.
463 * - `placeholder` (string) Placeholder text for the follow-up textarea.
464 *
465 * @since 1.0.0
466 *
467 * @return array[] List of reason definition arrays.
468 */
469 private function get_deactivation_reasons()
470 {
471 return array(
472 array(
473 'value' => __('I no longer need the plugin', 'shopengine'),
474 'key' => 'no_longer_needed',
475 'label' => 'I no longer need the plugin',
476 'placeholder' => __('Tell us more...', 'shopengine'),
477 ),
478 array(
479 'value' => __('I found a better plugin', 'shopengine'),
480 'key' => 'found_better_plugin',
481 'label' => 'I found a better plugin',
482 'placeholder' => __('Which plugin are you using instead?', 'shopengine'),
483 ),
484 array(
485 'value' => __("I couldn't get the plugin to work", 'shopengine'),
486 'key' => 'plugin_bug',
487 'label' => "I couldn't get the plugin to work",
488 'placeholder' => __('What specific issue did you face?', 'shopengine'),
489 ),
490 array(
491 'value' => __("It's missing a specific feature", 'shopengine'),
492 'key' => 'missing_feature',
493 'label' => "It's missing a specific feature",
494 'placeholder' => __('What feature do you need?', 'shopengine'),
495 ),
496 array(
497 'value' => __('The plugin affects site performance', 'shopengine'),
498 'key' => 'performance_issue',
499 'label' => 'Slowing down my site',
500 'placeholder' => __('Please share details about the performance issues you experienced...', 'shopengine'),
501 ),
502 array(
503 'value' => __("It's a temporary deactivation", 'shopengine'),
504 'key' => 'temporary_deactivation',
505 'label' => "It's a temporary deactivation",
506 'placeholder' => __('When will you reactivate it?', 'shopengine'),
507 ),
508 array(
509 'value' => __('Other', 'shopengine'),
510 'key' => 'other',
511 'label' => 'Other',
512 'placeholder' => __('Please tell us why...', 'shopengine'),
513 ),
514 );
515 }
516 }
517