PluginProbe ʕ •ᴥ•ʔ
Hostinger Reach – AI-Powered Email Marketing for WordPress / 1.7.0
Hostinger Reach – AI-Powered Email Marketing for WordPress v1.7.0
1.7.2 1.7.1 1.7.0 1.6.0 1.5.9 1.5.8 1.5.7 1.5.6 1.5.5 1.5.4 1.5.3 1.5.2 1.5.1 1.5.0 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 trunk 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6
hostinger-reach / vendor / hostinger / hostinger-wp-surveys / src / SurveyManager.php
hostinger-reach / vendor / hostinger / hostinger-wp-surveys / src Last commit date
Ajax.php 3 months ago Assets.php 1 month ago Loader.php 1 year ago Rest.php 1 year ago SurveyLoader.php 1 year ago SurveyManager.php 1 month ago
SurveyManager.php
460 lines
1 <?php
2
3 namespace Hostinger\Surveys;
4
5 use Hostinger\WpHelper\Config;
6 use Hostinger\WpHelper\Utils as Helper;
7
8 defined('ABSPATH') || exit;
9
10 class SurveyManager
11 {
12 public const CACHE_THREE_HOURS = 3600 * 3;
13 public const TIME_24_HOURS = 86400;
14 public const CLIENT_SURVEY_IDENTIFIER = 'customer_satisfaction_score';
15 public const CLIENT_WOO_COMPLETED_ACTIONS = 'woocommerce_task_list_tracked_completed_tasks';
16 public const SUBMITTED_SURVEY_TRANSIENT = 'submitted_survey_transient';
17 public const IS_CLIENT_ELIGIBLE_TRANSIENT_RESPONSE = 'client_eligibility_transient_response';
18 public const IS_CLIENT_ELIGIBLE_TRANSIENT_REQUEST = 'survey_questions_transient_request';
19 public const LOCATION_SLUG = 'location';
20 public const HIDE_SURVEY_TRANSIENT = 'hts_hide_survey';
21
22 private Config $configHandler;
23 private Helper $helper;
24 private Rest $surveysRest;
25
26 public function __construct(
27 Helper $helper,
28 Config $configHandler,
29 Rest $surveysRest
30 ) {
31 $this->helper = $helper;
32 $this->configHandler = $configHandler;
33 $this->surveysRest = $surveysRest;
34 }
35
36 /**
37 * Get WooCommerce admin pages paths
38 *
39 * @return array Array of WooCommerce admin page paths
40 */
41 public function getWooCommercePages(): array
42 {
43 $admin_path = parse_url(admin_url(), PHP_URL_PATH);
44
45 $pages = [
46 'admin.php?page=wc-admin',
47 'edit.php?post_type=shop_order',
48 'admin.php?page=wc-admin&path=/customers',
49 'edit.php?post_type=shop_coupon&legacy_coupon_menu=1',
50 'admin.php?page=wc-admin&path=/marketing',
51 'admin.php?page=wc-reports',
52 'admin.php?page=wc-orders',
53 'admin.php?page=wc-settings',
54 'admin.php?page=wc-status',
55 'admin.php?page=wc-admin&path=/extensions',
56 'edit.php?post_type=product',
57 'post-new.php?post_type=product',
58 'edit.php?post_type=product&page=product-reviews',
59 'edit.php?post_type=product&page=product_attributes',
60 'edit-tags.php?taxonomy=product_cat&post_type=product',
61 'edit-tags.php?taxonomy=product_tag&post_type=product',
62 'admin.php?page=wc-admin&path=/analytics/overview',
63 'admin.php?page=wc-admin',
64 ];
65
66 $woocommerce_pages = [];
67 foreach ($pages as $page) {
68 $woocommerce_pages[] = $admin_path . $page;
69 }
70
71 return $woocommerce_pages;
72 }
73
74 /**
75 * Check if current page is a WooCommerce admin page
76 *
77 * @return bool True if current page is a WooCommerce admin page
78 */
79 public function isWoocommerceAdminPage(): bool
80 {
81 if (! isset($_SERVER['REQUEST_URI'])) {
82 return false;
83 }
84
85 $currentUri = sanitize_text_field($_SERVER['REQUEST_URI']);
86
87 if (defined('DOING_AJAX') && \DOING_AJAX) {
88 return false;
89 }
90
91 if (isset($currentUri) && strpos($currentUri, '/wp-json/') !== false) {
92 return false;
93 }
94
95 $woocommercePages = $this->getWooCommercePages();
96
97 foreach ($woocommercePages as $page) {
98 if (strpos($currentUri, $page) !== false) {
99 return true;
100 }
101 }
102
103 return false;
104 }
105
106 public function isClientEligible(): bool
107 {
108 $transientRequestKey = self::IS_CLIENT_ELIGIBLE_TRANSIENT_REQUEST;
109 $transientResponseKey = self::IS_CLIENT_ELIGIBLE_TRANSIENT_RESPONSE;
110 $cachedEligibility = get_transient($transientResponseKey);
111
112 // Check if a request is already in progress
113 if (get_transient('hts_eligible_request')) {
114 return false;
115 }
116
117 // Check if transient response exists
118 if ($cachedEligibility && ( $cachedEligibility === 'eligible' || $cachedEligibility === 'not_eligible' )) {
119 return $cachedEligibility === 'eligible';
120 }
121
122 // Attempt to set transient request
123 if (! $this->helper->checkTransientEligibility($transientRequestKey, self::CACHE_THREE_HOURS)) {
124 return false;
125 }
126
127 try {
128 // Set transient flag to indicate request in progress
129 set_transient('hts_eligible_request', 'in_progress', 60);
130
131 $isEligible = $this->surveysRest->isClientEligible();
132
133 // Clear the request transient flag
134 delete_transient('hts_eligible_request');
135
136 if ($isEligible) {
137 set_transient($transientResponseKey, 'eligible', self::CACHE_THREE_HOURS);
138
139 return $isEligible;
140 }
141
142 set_transient($transientResponseKey, 'not_eligible', self::CACHE_THREE_HOURS);
143
144 return false;
145 } catch (\Exception $exception) {
146 $this->helper->errorLog('Error checking eligibility: ' . $exception->getMessage());
147
148 return false;
149 }
150 }
151
152 public function submitSurveyAnswers(array $answers, string $surveyId, string $surveyLocation, int $is_mobile = 0): void
153 {
154 $data = [
155 'identifier' => self::CLIENT_SURVEY_IDENTIFIER,
156 'is_mobile' => $is_mobile,
157 'answers' => [
158 [
159 'question_slug' => self::LOCATION_SLUG,
160 'answer' => $surveyLocation,
161 ]
162 ],
163 ];
164
165 $answers = $this->addUserAnswers($data, $answers);
166
167 $success = $this->surveysRest->submitSurveyData($answers);
168
169 set_transient(self::SUBMITTED_SURVEY_TRANSIENT, true, self::TIME_24_HOURS);
170
171 if ($success) {
172 delete_transient(self::IS_CLIENT_ELIGIBLE_TRANSIENT_RESPONSE);
173 $settingKey = $surveyId . '_survey_completed';
174 update_option('hostinger_' . $settingKey, true);
175 }
176
177 wp_send_json('Survey completed');
178
179 if (has_action('litespeed_purge_all')) {
180 do_action('litespeed_purge_all');
181 }
182 }
183
184 public function getSpecifiedSurvey(array $activeSurvey): array
185 {
186 $specifiedSurveyQuestions = [
187 'pages' => [],
188 'showQuestionNumbers' => 'off',
189 'showTOC' => false,
190 'pageNextText' => __('Next', 'hostinger-wp-surveys'),
191 'pagePrevText' => __('Previous', 'hostinger-wp-surveys'),
192 'completeText' => __('Submit', 'hostinger-wp-surveys'),
193 'completedHtml' => __('Thank you for completing the survey !', 'hostinger-wp-surveys'),
194 'requiredText' => '*',
195 ];
196
197 foreach ($activeSurvey['questions'] as $question) {
198 $element = [
199 'type' => $question['type'],
200 'name' => $question['slug'],
201 'title' => $question['question'],
202 'requiredErrorText' => __('Response required.', 'hostinger-wp-surveys'),
203 ];
204
205 if ($question['slug'] == 'comment') {
206 $element['maxLength'] = 250;
207 }
208
209 if ($question['slug'] == 'score') {
210 $element['rateMin'] = '1';
211 $element['rateMax'] = '10';
212 $element['minRateDescription'] = __('Poor', 'hostinger-wp-surveys');
213 $element['maxRateDescription'] = __('Excellent', 'hostinger-wp-surveys');
214 }
215
216 if ($question['isRequired']) {
217 $element['isRequired'] = true;
218 }
219
220 $questionData = [
221 'name' => $question['slug'],
222 'elements' => [ $element ],
223 ];
224
225 $specifiedSurveyQuestions['pages'][] = $questionData;
226 }
227
228 return $specifiedSurveyQuestions;
229 }
230
231 public static function getHostingerSurveys(): array
232 {
233 return apply_filters('hostinger_add_surveys', []);
234 }
235
236 public function generateSurveyHtml(): string
237 {
238 $allSurveys = SurveyManager::getHostingerSurveys();
239
240 if (empty($allSurveys)) {
241 return '';
242 }
243
244 $activeSurvey = $this->getHighestPrioritySurvey($allSurveys);
245 ob_start();
246 ?>
247 <div class="hts-survey-wrapper hts-survey"
248 data-survey-id="<?php echo esc_attr($activeSurvey['id']) ?>"
249 data-location="<?php echo esc_attr($activeSurvey['location']) ?>"
250 data-surveys="<?php echo esc_attr(json_encode($this->getSpecifiedSurvey($activeSurvey))) ?>">
251 <div class="close-btn">
252 <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 24 24">
253 <path d="M 4.9902344 3.9902344 A 1.0001 1.0001 0 0 0 4.2929688 5.7070312 L 10.585938 12 L 4.2929688 18.292969 A 1.0001 1.0001 0 1 0 5.7070312 19.707031 L 12 13.414062 L 18.292969 19.707031 A 1.0001 1.0001 0 1 0 19.707031 18.292969 L 13.414062 12 L 19.707031 5.7070312 A 1.0001 1.0001 0 0 0 18.980469 3.9902344 A 1.0001 1.0001 0 0 0 18.292969 4.2929688 L 12 10.585938 L 5.7070312 4.2929688 A 1.0001 1.0001 0 0 0 4.9902344 3.9902344 z"></path>
254 </svg>
255 </div>
256 <div id="hostinger-feedback-survey" class="hostinger-feedback-survey"></div>
257 <div id="hts-questionsLeft">
258 <span id="hts-currentQuestion">1</span>
259 <?php
260 echo esc_html(
261 __(
262 'Question',
263 'hostinger-wp-surveys'
264 )
265 );
266 ?>
267 <?php echo esc_html(__('of ', 'hostinger-wp-surveys')); ?>
268 <span id="hts-allQuestions"></span>
269 </div>
270 </div>
271 <?php if (isset($activeSurvey['review_url'])) { ?>
272 <div id="hts-survey-review"
273 class="hts-survey-wrapper"
274 data-review-min-required-score="<?php echo esc_attr($activeSurvey['review_min_required_score']) ?>">
275 <div class="close-btn">
276 <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 24 24">
277 <path d="M 4.9902344 3.9902344 A 1.0001 1.0001 0 0 0 4.2929688 5.7070312 L 10.585938 12 L 4.2929688 18.292969 A 1.0001 1.0001 0 1 0 5.7070312 19.707031 L 12 13.414062 L 18.292969 19.707031 A 1.0001 1.0001 0 1 0 19.707031 18.292969 L 13.414062 12 L 19.707031 5.7070312 A 1.0001 1.0001 0 0 0 18.980469 3.9902344 A 1.0001 1.0001 0 0 0 18.292969 4.2929688 L 12 10.585938 L 5.7070312 4.2929688 A 1.0001 1.0001 0 0 0 4.9902344 3.9902344 z"></path>
278 </svg>
279 </div>
280 <div class="hostinger-feedback-survey">
281 <div class="sd-question__header">
282 <h5><?php echo esc_html(__('Rate this plugin on WordPress.org', 'hostinger-wp-surveys')); ?></h5>
283 </div>
284 <div class="hts-survey-stars">
285 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
286 <path fill-rule="evenodd"
287 clip-rule="evenodd"
288 d="M12 17.27L18.18 21L16.54 13.97L22 9.24L14.81 8.63L12 2L9.19 8.63L2 9.24L7.46 13.97L5.82 21L12 17.27Z"
289 fill="#673DE6"/>
290 </svg>
291 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
292 <path fill-rule="evenodd"
293 clip-rule="evenodd"
294 d="M12 17.27L18.18 21L16.54 13.97L22 9.24L14.81 8.63L12 2L9.19 8.63L2 9.24L7.46 13.97L5.82 21L12 17.27Z"
295 fill="#673DE6"/>
296 </svg>
297 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
298 <path fill-rule="evenodd"
299 clip-rule="evenodd"
300 d="M12 17.27L18.18 21L16.54 13.97L22 9.24L14.81 8.63L12 2L9.19 8.63L2 9.24L7.46 13.97L5.82 21L12 17.27Z"
301 fill="#673DE6"/>
302 </svg>
303 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
304 <path fill-rule="evenodd"
305 clip-rule="evenodd"
306 d="M12 17.27L18.18 21L16.54 13.97L22 9.24L14.81 8.63L12 2L9.19 8.63L2 9.24L7.46 13.97L5.82 21L12 17.27Z"
307 fill="#673DE6"/>
308 </svg>
309 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
310 <path fill-rule="evenodd"
311 clip-rule="evenodd"
312 d="M12 17.27L18.18 21L16.54 13.97L22 9.24L14.81 8.63L12 2L9.19 8.63L2 9.24L7.46 13.97L5.82 21L12 17.27Z"
313 fill="#673DE6"/>
314 </svg>
315 </div>
316 <div class="hts-survey-footer">
317 <a href="<?php echo esc_url($activeSurvey['review_url']); ?>" target="_blank" class="hts-submit-review sd-btn"><?php echo esc_html(__('Add review', 'hostinger-wp-surveys')); ?></a>
318 </div>
319 </div>
320 </div>
321 <?php } ?>
322 <?php
323 return ob_get_clean();
324 }
325
326
327 public function defaultWoocommerceSurveyCompleted(): bool
328 {
329 $completedActions = get_option(self::CLIENT_WOO_COMPLETED_ACTIONS, []);
330 $requiredCompletedActions = [ 'products', 'payments' ];
331
332 return empty(array_diff($requiredCompletedActions, $completedActions));
333 }
334
335 public function getOldestProductDate(): int
336 {
337 global $wpdb;
338
339 $getProductDate = $wpdb->prepare(
340 "
341 SELECT MIN(post_date)
342 FROM {$wpdb->posts}
343 WHERE post_type = %s
344 AND post_status = %s
345 ",
346 'product',
347 'publish'
348 );
349
350 $oldestProductDate = $wpdb->get_var($getProductDate);
351
352 if ($oldestProductDate !== null) {
353 $oldestProductDateTimestamp = strtotime($oldestProductDate);
354
355 if ($oldestProductDateTimestamp !== false) {
356 return $oldestProductDateTimestamp;
357 }
358 }
359
360 return strtotime('-1 year');
361 }
362
363 public function isTimeElapsed(string $firstLoginAt, int $timeInSeconds): bool
364 {
365 $currentTime = time();
366 $timeElapsed = $currentTime - $timeInSeconds;
367
368 return $timeElapsed >= $firstLoginAt;
369 }
370
371 public function activeSurveyHtml(): void
372 {
373 if (! did_action('activeSurvey')) {
374 $survey_html = $this->generateSurveyHtml('');
375 echo $survey_html;
376 do_action('activeSurvey');
377 }
378 }
379
380 /**
381 * @param string $surveyId The unique ID for this survey
382 * @param string $scoreQuestion The question for the score step
383 * @param string $commentQuestion The question for the comment step
384 * @param string $location The location of the survey
385 * @param int $priority The priority of the survey. The bigger the number, the higher the priority
386 * @param string|null $review_url Optional. The url to review the plugin
387 * @param int $review_min_required_score The minimum rating in the score step to show the Review step
388 * @return array
389 */
390 public static function addSurvey(string $surveyId, string $scoreQuestion, string $commentQuestion, string $location, int $priority, string $review_url = null, int $review_min_required_score = 7): array
391 {
392 $surveyData = [
393 'id' => $surveyId,
394 'questions' => [
395 [
396 'type' => 'rating',
397 'slug' => 'score',
398 'question' => $scoreQuestion,
399 'isRequired' => true
400 ],
401 [
402 'type' => 'comment',
403 'slug' => 'comment',
404 'question' => $commentQuestion,
405 'isRequired' => false
406 ],
407 ],
408 'location' => $location,
409 'priority' => $priority,
410 'review_url' => $review_url,
411 'review_min_required_score' => $review_min_required_score,
412 ];
413
414 $surveys[] = $surveyData;
415
416 return $surveys;
417 }
418
419 public function getHighestPrioritySurvey(array $allSurveys): array
420 {
421 $surveys = array_merge([], ...$allSurveys);
422
423 return array_reduce($surveys, function ($highestPrioritySurvey, $currentSurvey) {
424 return ( ! isset($highestPrioritySurvey['priority']) ||
425 ( isset($currentSurvey['priority']) && $currentSurvey['priority'] > $highestPrioritySurvey['priority'] ) )
426 ? $currentSurvey : $highestPrioritySurvey;
427 }, []);
428 }
429
430 /**
431 * Checks if a specific survey is not completed
432 * @param string $surveyId
433 * @return bool
434 */
435 public function isSurveyNotCompleted(string $surveyId): bool
436 {
437 $optionKey = 'hostinger_' . $surveyId . '_survey_completed';
438 $notCompleted = ! get_option($optionKey, '');
439 return $notCompleted;
440 }
441
442 private function addUserAnswers(array $data, array $answers): array
443 {
444 foreach ($answers as $answerSlug => $answer) {
445 $data['answers'][] = [
446 'question_slug' => $answerSlug,
447 'answer' => $answer,
448 ];
449 }
450
451 return $data;
452 }
453
454 public function isSurveyHidden(): bool
455 {
456 $surveyVisibility = get_transient(self::HIDE_SURVEY_TRANSIENT);
457 return $surveyVisibility !== false;
458 }
459 }
460