PluginProbe
Filter Everything — WordPress & WooCommerce Filters / trunk
Filter Everything — WordPress & WooCommerce Filters vtrunk
1.9.6 1.9.5 1.9.4 1.9.3 1.9.2.2 1.9.2.1 trunk 1.2.1 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.1 1.4.4 1.4.5 1.4.8 1.4.9 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 1.6.3 All 51 releases
filter-everything / src / Admin / ReviewRequest.php

ReviewRequest.php in Filter Everything — WordPress & WooCommerce Filters trunk, at src/Admin/ReviewRequest.php

173 lines 5.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FilterEverything\Filter;
4
5 if ( ! defined('ABSPATH') ) {
6 exit;
7 }
8
9 /**
10 * Review request popup (free build only).
11 *
12 * Asks a long-term user to rate the plugin on wordpress.org. Shown on the
13 * plugin's own admin pages to users with manage_options, at most once per page
14 * load, when ALL of the following hold:
15 * - at least self::FIRST_DELAY has passed since the install timestamp
16 * (stamped on the first admin_init after install or after updating to the
17 * version that introduced it);
18 * - the site has at least one published Filter Set (the user actually uses
19 * the plugin — we only ask people the plugin has worked for);
20 * - the current admin has not dismissed it, has seen it fewer than
21 * self::MAX_SHOWS times, and is not within a snooze window.
22 *
23 * Every render auto-snoozes the popup for self::SNOOZE, so ignoring it equals
24 * "Maybe later". "Don't show this again" and clicking through to the review
25 * dismiss it permanently. State is kept per admin user (the ask is personal),
26 * the install timestamp per site.
27 */
28 class ReviewRequest
29 {
30 const INSTALL_OPTION = 'flrt_free_installed_at';
31 const USER_META = 'flrt_review_request';
32 const NONCE_ACTION = 'flrt_review_popup';
33
34 const REVIEW_URL = 'https://wordpress.org/support/plugin/filter-everything/reviews/#new-post';
35 const SUPPORT_URL = 'https://wordpress.org/support/plugin/filter-everything/';
36
37 const FIRST_DELAY = 14 * DAY_IN_SECONDS;
38 const SNOOZE = 21 * DAY_IN_SECONDS;
39 const MAX_SHOWS = 3;
40
41 public function __construct()
42 {
43 // The review ask targets the wordpress.org listing — free build only.
44 if ( defined('FLRT_FILTERS_PRO') ) {
45 return;
46 }
47
48 if ( ! is_admin() ) {
49 return;
50 }
51
52 add_action( 'admin_init', [ $this, 'stampInstallTime' ] );
53 add_action( 'admin_footer', [ $this, 'maybeRender' ] );
54 add_action( 'wp_ajax_flrt_review_popup', [ $this, 'ajaxDismiss' ] );
55 }
56
57 /**
58 * Existing installs never recorded an install time — the first admin_init
59 * after the update becomes their reference point, so everybody waits the
60 * same FIRST_DELAY from a known moment.
61 */
62 public function stampInstallTime()
63 {
64 if ( ! get_option( self::INSTALL_OPTION ) ) {
65 add_option( self::INSTALL_OPTION, time() );
66 }
67 }
68
69 public function maybeRender()
70 {
71 if ( ! $this->shouldShow() ) {
72 return;
73 }
74
75 // Count the show and auto-snooze right away: a user who ignores the
76 // popup (navigates on without clicking anything) is treated exactly
77 // like "Maybe later", and reloading pages cannot burn through the
78 // MAX_SHOWS budget within one session.
79 $state = $this->getState();
80 $state['shows'] = (int) $state['shows'] + 1;
81 $state['snoozed_until'] = time() + self::SNOOZE;
82 $this->saveState( $state );
83
84 flrt_include_admin_view( 'review-popup', [
85 'review_nonce' => wp_create_nonce( self::NONCE_ACTION ),
86 ] );
87 }
88
89 public function ajaxDismiss()
90 {
91 check_ajax_referer( self::NONCE_ACTION, 'nonce' );
92
93 if ( ! current_user_can( flrt_plugin_user_caps() ) ) {
94 wp_send_json_error();
95 }
96
97 $mode = isset( $_POST['mode'] ) ? sanitize_key( $_POST['mode'] ) : '';
98 $state = $this->getState();
99
100 if ( $mode === 'later' ) {
101 $state['snoozed_until'] = time() + self::SNOOZE;
102 } elseif ( $mode === 'never' || $mode === 'rated' ) {
103 $state['dismissed'] = true;
104 } else {
105 wp_send_json_error();
106 }
107
108 $this->saveState( $state );
109 wp_send_json_success();
110 }
111
112 private function shouldShow()
113 {
114 if ( ! current_user_can( flrt_plugin_user_caps() ) ) {
115 return false;
116 }
117
118 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
119
120 // The plugin's own admin pages only — same gate as the PRO modal.
121 if ( ! $screen || $screen->post_type !== FLRT_FILTERS_SET_POST_TYPE ) {
122 return false;
123 }
124
125 $installed_at = (int) get_option( self::INSTALL_OPTION );
126
127 if ( ! $installed_at || ( time() - $installed_at ) < self::FIRST_DELAY ) {
128 return false;
129 }
130
131 $state = $this->getState();
132
133 if ( $state['dismissed']
134 || (int) $state['shows'] >= self::MAX_SHOWS
135 || (int) $state['snoozed_until'] > time()
136 ) {
137 return false;
138 }
139
140 // Only ask users the plugin has actually worked for: at least one
141 // published Filter Set.
142 $sets = get_posts( [
143 'post_type' => FLRT_FILTERS_SET_POST_TYPE,
144 'post_status' => 'publish',
145 'numberposts' => 1,
146 'fields' => 'ids',
147 ] );
148
149 return ! empty( $sets );
150 }
151
152 private function getState()
153 {
154 $state = get_user_meta( get_current_user_id(), self::USER_META, true );
155
156 if ( ! is_array( $state ) ) {
157 $state = [];
158 }
159
160 return array_merge(
161 [ 'shows' => 0, 'snoozed_until' => 0, 'dismissed' => false ],
162 $state
163 );
164 }
165
166 private function saveState( array $state )
167 {
168 update_user_meta( get_current_user_id(), self::USER_META, $state );
169 }
170 }
171
172 new ReviewRequest();
173