PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.1.2
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.1.2
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
cartflows / libraries / cartflows-pointer / class-cartflows-pointer.php

class-cartflows-pointer.php in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 3.1.2, at libraries/cartflows-pointer/class-cartflows-pointer.php

340 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Cartflows Pointer Library.
4 *
5 * A lightweight library for WordPress admin pointers.
6 *
7 * @version 1.0.0
8 * @package cartflows-pointer
9 */
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit; // Exit if accessed directly.
13 }
14
15 if ( ! class_exists( 'Cartflows_Pointer' ) ) {
16
17 /**
18 * Cartflows Pointer
19 */
20 class Cartflows_Pointer {
21
22 /**
23 * Instance
24 *
25 * @var Cartflows_Pointer|null
26 */
27 private static $instance = null;
28
29 /**
30 * Pointer configuration.
31 *
32 * @var array<string, mixed>
33 */
34 private $config = array();
35
36 /**
37 * Library URL.
38 *
39 * @var string
40 */
41 private $library_url = '';
42
43 /**
44 * Library version.
45 *
46 * @var string
47 */
48 private $version = '1.0.0';
49
50 /**
51 * Get Instance
52 *
53 * @return Cartflows_Pointer
54 */
55 public static function get_instance() {
56 if ( null === self::$instance ) {
57 self::$instance = new self();
58 }
59 return self::$instance;
60 }
61
62 /**
63 * Constructor.
64 */
65 private function __construct() {
66 add_action( 'admin_init', array( $this, 'setup' ) );
67 }
68
69 /**
70 * Setup the pointer configuration.
71 *
72 * @return void
73 */
74 public function setup() {
75 /**
76 * Filter to provide pointer configuration.
77 *
78 * @param array $config Pointer configuration array.
79 */
80 $config = apply_filters( 'cartflows_pointer_config', array() );
81
82 if ( empty( $config ) ) {
83 return;
84 }
85
86 $this->library_url = $this->get_library_url( __DIR__ );
87 $this->config = $this->parse_config( $config );
88
89 $this->init_hooks();
90 }
91
92 /**
93 * Get library URL from path.
94 *
95 * @param string $path Library path.
96 * @return string Library URL.
97 */
98 private function get_library_url( $path ) {
99 $content_dir = wp_normalize_path( WP_CONTENT_DIR );
100 $path = wp_normalize_path( $path );
101 return str_replace( $content_dir, content_url(), $path );
102 }
103
104 /**
105 * Parse and validate configuration with defaults.
106 *
107 * @param array<string, mixed> $config Raw configuration.
108 * @return array<string, mixed> Parsed configuration.
109 */
110 private function parse_config( $config ) {
111 $defaults = array(
112 'option_name' => 'cartflows_pointer_data',
113 'title' => '',
114 'content' => '',
115 'button_text' => __( 'Get Started', 'cartflows' ),
116 'button_url' => admin_url(),
117 'dismiss_text' => __( 'Dismiss', 'cartflows' ),
118 'target_selector' => '#menu-plugins',
119 'fallback_target' => '#menu-plugins',
120 'allowed_pages' => array( 'index.php' ),
121 'post_type' => null,
122 'max_posts' => 1,
123 'capability' => 'cartflows_manage_flows_steps',
124 'position' => array(
125 'edge' => 'left',
126 'align' => 'center',
127 ),
128 );
129
130 return wp_parse_args( $config, $defaults );
131 }
132
133 /**
134 * Initialize hooks.
135 *
136 * @return void
137 */
138 private function init_hooks() {
139 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_pointer_assets' ) );
140 add_action( 'wp_ajax_cartflows_pointer_should_show', array( $this, 'ajax_should_show' ) );
141 add_action( 'wp_ajax_cartflows_pointer_dismiss', array( $this, 'ajax_dismiss' ) );
142 add_action( 'wp_ajax_cartflows_pointer_accept', array( $this, 'ajax_accept' ) );
143 }
144
145 /**
146 * Determine if the pointer should be displayed.
147 *
148 * @return bool
149 */
150 private function should_display() {
151 global $pagenow;
152
153 // Check user capability.
154 if ( ! current_user_can( $this->get_capability() ) ) {
155 return false;
156 }
157
158 // Check if already dismissed or accepted.
159 $pointer_data = $this->get_pointer_data();
160 if ( ! empty( $pointer_data['dismissed'] ) || ! empty( $pointer_data['accepted'] ) ) {
161 return false;
162 }
163
164 // Check post count if post_type is specified.
165 if ( ! empty( $this->config['post_type'] ) && is_string( $this->config['post_type'] ) ) {
166 $count = wp_count_posts( $this->config['post_type'] );
167 $post_count = isset( $count->publish ) ? (int) $count->publish : 0;
168 $max_posts = isset( $this->config['max_posts'] ) ? $this->config['max_posts'] : 1;
169
170 if ( $post_count > $max_posts ) {
171 return false;
172 }
173 }
174
175 // Check allowed pages.
176 $allowed_pages = isset( $this->config['allowed_pages'] ) && is_array( $this->config['allowed_pages'] )
177 ? $this->config['allowed_pages']
178 : array();
179
180 if ( ! in_array( $pagenow, $allowed_pages, true ) ) {
181 return false;
182 }
183
184 // Extra check for admin.php?page=cartflows
185 if ( 'admin.php' === $pagenow ) {
186 if ( empty( $_GET['page'] ) || 'cartflows' !== sanitize_key( $_GET['page'] ) ) {
187 return false;
188 }
189 }
190
191 return true;
192 }
193
194 /**
195 * Enqueue pointer assets.
196 *
197 * @return void
198 */
199 public function enqueue_pointer_assets() {
200 if ( ! $this->should_display() ) {
201 return;
202 }
203
204 wp_enqueue_style( 'wp-pointer' );
205 wp_enqueue_script( 'wp-pointer' );
206
207 wp_enqueue_script(
208 'cartflows-pointer',
209 $this->library_url . '/assets/js/cartflows-pointer.js',
210 array( 'wp-pointer', 'jquery' ),
211 $this->version,
212 true
213 );
214
215 wp_localize_script(
216 'cartflows-pointer',
217 'cartflowsPointerData',
218 array(
219 'ajaxurl' => admin_url( 'admin-ajax.php' ),
220 'nonce' => wp_create_nonce( 'cartflows_pointer_nonce' ),
221 'target_selector' => $this->config['target_selector'],
222 'fallback_target' => $this->config['fallback_target'],
223 'position' => $this->config['position'],
224 )
225 );
226 }
227
228 /**
229 * AJAX handler: Check if pointer should be shown.
230 *
231 * @return void
232 */
233 public function ajax_should_show() {
234 // Security: Check capability.
235 if ( ! current_user_can( $this->get_capability() ) ) {
236 wp_send_json_error( array( 'message' => __( 'Unauthorized user.', 'cartflows' ) ), 403 );
237 }
238
239 // Security: Verify nonce.
240 if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'cartflows_pointer_nonce' ) ) {
241 wp_send_json_error( array( 'message' => __( 'Invalid nonce.', 'cartflows' ) ), 403 );
242 }
243
244 wp_send_json(
245 array(
246 'show' => true,
247 'title' => $this->config['title'],
248 'content' => $this->config['content'],
249 'button_text' => $this->config['button_text'],
250 'button_url' => $this->config['button_url'],
251 'dismiss' => $this->config['dismiss_text'],
252 )
253 );
254 }
255
256 /**
257 * AJAX handler: Pointer dismissed.
258 *
259 * @return void
260 */
261 public function ajax_dismiss() {
262 // Security: Check capability.
263 if ( ! current_user_can( $this->get_capability() ) ) {
264 wp_send_json_error( array( 'message' => __( 'Unauthorized user.', 'cartflows' ) ), 403 );
265 }
266
267 // Security: Verify nonce.
268 if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'cartflows_pointer_nonce' ) ) {
269 wp_send_json_error( array( 'message' => __( 'Invalid nonce.', 'cartflows' ) ), 403 );
270 }
271
272 $this->update_pointer_data( 'dismissed', time() );
273 wp_send_json_success();
274 }
275
276 /**
277 * AJAX handler: Pointer CTA accepted.
278 *
279 * @return void
280 */
281 public function ajax_accept() {
282 // Security: Check capability.
283 if ( ! current_user_can( $this->get_capability() ) ) {
284 wp_send_json_error( array( 'message' => __( 'Unauthorized user.', 'cartflows' ) ), 403 );
285 }
286
287 // Security: Verify nonce.
288 if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'cartflows_pointer_nonce' ) ) {
289 wp_send_json_error( array( 'message' => __( 'Invalid nonce.', 'cartflows' ) ), 403 );
290 }
291
292 $this->update_pointer_data( 'accepted', time() );
293 wp_send_json_success();
294 }
295
296 /**
297 * Get capability from config.
298 *
299 * @return string
300 */
301 private function get_capability() {
302 return isset( $this->config['capability'] ) && is_string( $this->config['capability'] )
303 ? $this->config['capability']
304 : 'manage_options';
305 }
306
307 /**
308 * Get pointer data from options.
309 *
310 * @return array<string, mixed>
311 */
312 private function get_pointer_data() {
313 $option_name = isset( $this->config['option_name'] ) && is_string( $this->config['option_name'] )
314 ? $this->config['option_name']
315 : 'cartflows_pointer_data';
316 $data = get_option( $option_name, array() );
317 return is_array( $data ) ? $data : array();
318 }
319
320 /**
321 * Update pointer data in options.
322 *
323 * @param string $key Data key.
324 * @param mixed $value Data value.
325 * @return void
326 */
327 private function update_pointer_data( $key, $value ) {
328 $option_name = isset( $this->config['option_name'] ) && is_string( $this->config['option_name'] )
329 ? $this->config['option_name']
330 : 'cartflows_pointer_data';
331 $data = $this->get_pointer_data();
332 $data[ $key ] = $value;
333 update_option( $option_name, $data );
334 }
335 }
336
337 // Self-initialize.
338 Cartflows_Pointer::get_instance();
339 }
340