PluginProbe
Claspo – Popups, Spin the Wheel & Email Capture / 1.2.0
Claspo – Popups, Spin the Wheel & Email Capture v1.2.0
trunk 1.0.0 1.0.1 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.2.0
claspo / claspo.php

claspo.php in Claspo – Popups, Spin the Wheel & Email Capture 1.2.0, at claspo.php

401 lines 14.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Plugin Name: Claspo - Popups, Spin the Wheel & Email Capture
5 * Description: Grow your email list and increase sales! Use the Claspo Popup Maker plugin to create pop-up windows, Spin the Wheel, Exit Intent, and Lead Gen forms.
6 * Version: 1.2.0
7 * Author: Claspo Popup Builder team
8 * Author URI: https://www.claspo.io
9 * License: GPL-2.0+
10 * WC requires at least: 9.0
11 * WC tested up to: 10.5.2
12 */
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 const CLASPO_GET_SCRIPT_URL = 'https://script.claspo.io/site-script/v1/site/script/';
19 const CLASPO_EVENT_URL = 'https://script.claspo.io/site-script/v1/event';
20 const CLASPO_STATE_TRANSIENT = 'claspo_connect_state_';
21 const CLASPO_STATE_TTL = 30 * MINUTE_IN_SECONDS;
22
23 add_action( 'admin_menu', 'claspo_add_admin_menu' );
24 add_action( 'admin_post_claspo_save_script', 'claspo_save_script' );
25 add_action( 'admin_init', 'claspo_check_script_id' );
26 add_action( 'admin_enqueue_scripts', 'claspo_enqueue_admin_scripts' );
27 add_action( 'rest_api_init', 'claspo_register_rest_routes' );
28
29 add_action( 'before_woocommerce_init', function () {
30 if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
31 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
32 }
33 } );
34
35 function claspo_add_admin_menu() {
36 $claspo_script_id = get_option('claspo_script_id');
37 $menu_title = 'Claspo';
38
39 // Add badge if the script ID is not set
40 if (!$claspo_script_id) {
41 $menu_title .= ' <span class="awaiting-mod update-plugins count-1"><span class="pending-count">1</span></span>';
42 }
43
44
45 // add_options_page( 'Claspo', 'Claspo', 'manage_options', 'claspo_script_plugin', 'claspo_options_page' );
46 add_menu_page( 'Claspo', $menu_title, 'manage_options', 'claspo_script_plugin', 'claspo_options_page', plugin_dir_url( __FILE__ ) . 'img/claspo_logo.png');
47 }
48
49 function claspo_check_script_id() {
50 if ( ! current_user_can( 'manage_options' ) ) {
51 return;
52 }
53
54 if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'claspo_script_plugin' ) {
55 return;
56 }
57
58 $has_script_id_param = isset( $_GET['script_id'] ) && ! empty( $_GET['script_id'] );
59 $has_state_param = isset( $_GET['claspo_state'] ) && ! empty( $_GET['claspo_state'] );
60
61 if ( ! $has_script_id_param && ! $has_state_param ) {
62 return;
63 }
64
65 // If the plugin is already activated (e.g. background POST already succeeded, or a previous manual save),
66 // strip the query string so the user lands on a clean plugin page instead of seeing the form again.
67 if ( get_option( 'claspo_script_id' ) ) {
68 wp_safe_redirect( admin_url( 'admin.php?page=claspo_script_plugin' ) );
69 exit;
70 }
71
72 // Not yet activated: stash the incoming script_id so templates/form.php can prefill the input.
73 // The actual save still requires an explicit click on the Connect button, which is protected by a
74 // nonce and capability check in claspo_save_script(). Real validity of the ID is determined by
75 // the subsequent call to Claspo's API, which is a stronger guarantee than any local format check.
76 if ( $has_script_id_param ) {
77 $GLOBALS['claspo_prefill_script_id'] = sanitize_text_field( wp_unslash( $_GET['script_id'] ) );
78 }
79 }
80
81 function claspo_enqueue_admin_scripts( $hook ) {
82 if ( $hook != 'toplevel_page_claspo_script_plugin' ) {
83 return;
84 }
85
86 wp_enqueue_style( 'claspo-admin-style', plugin_dir_url( __FILE__ ) . 'css/main.css' );
87 wp_enqueue_script( 'claspo-admin-script', plugin_dir_url( __FILE__ ) . 'js/main2.js', array(), false, true );
88 }
89
90 function claspo_options_page() {
91 $script_code = get_option( 'claspo_script_code' );
92 $error_message = get_transient( 'claspo_api_error' );
93 $success_message = get_transient( 'claspo_success_message' );
94
95 if ( $success_message && $script_code ) {
96 $claspo_verified = get_transient( 'claspo_script_verified' );
97 include plugin_dir_path( __FILE__ ) . 'templates/success.php';
98 delete_transient( 'claspo_success_message' );
99 delete_transient( 'claspo_script_verified' );
100 } /*elseif ( $error_message ) {
101 include plugin_dir_path( __FILE__ ) . 'templates/error.php';
102 delete_transient( 'claspo_api_error' );
103 }*/ elseif ( ! $script_code || $error_message) {
104 include plugin_dir_path( __FILE__ ) . 'templates/form.php';
105
106 if ( $error_message ) {
107 delete_transient( 'claspo_api_error' );
108 }
109 } else {
110 include plugin_dir_path( __FILE__ ) . 'templates/main.php';
111 }
112 }
113
114 function claspo_save_script() {
115 if ( ! isset( $_POST['claspo_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['claspo_nonce'] ) ), 'claspo_save_script' ) ) {
116 wp_die( 'Security check failed', 'Security Error', array( 'response' => 403 ) );
117 }
118
119 if ( ! current_user_can( 'manage_options' ) ) {
120 wp_die( 'You do not have sufficient permissions to access this page.', 'Permission Error', array( 'response' => 403 ) );
121 }
122
123 if ( isset( $_POST['claspo_script_id'] ) ) {
124 $script_id = sanitize_text_field( wp_unslash( $_POST['claspo_script_id'] ) );
125 claspo_apply_script_id( $script_id );
126 }
127
128 wp_safe_redirect( admin_url( 'admin.php?page=claspo_script_plugin' ) );
129 exit;
130 }
131
132 /**
133 * Fetch the script from Claspo by its ID and, on success, persist it locally,
134 * clear caches, and perform the installation self-check ping.
135 *
136 * Shared by the manual Connect form and the background REST handshake so both
137 * entry points stay behaviourally identical. The actual validity of the ID is
138 * determined by Claspo's API response, not by any local format check.
139 *
140 * @param string $script_id
141 * @return true|WP_Error True on success; WP_Error with code `api_error` or
142 * `empty_body` otherwise. Errors also populate the
143 * `claspo_api_error` transient for UI display.
144 */
145 function claspo_apply_script_id( $script_id ) {
146 $response = wp_remote_get( CLASPO_GET_SCRIPT_URL . rawurlencode( $script_id ) );
147
148 if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
149 if ( is_wp_error( $response ) ) {
150 $error_message = $response->get_error_message();
151 } else {
152 $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
153 $error_message = $response_body['errorMessage'] ?? 'Invalid response from API';
154 }
155
156 set_transient( 'claspo_api_error', $error_message, 30 );
157 return new WP_Error( 'api_error', $error_message );
158 }
159
160 $body = wp_remote_retrieve_body( $response );
161
162 if ( empty( $body ) ) {
163 set_transient( 'claspo_api_error', 'Invalid response from API', 30 );
164 return new WP_Error( 'empty_body', 'Invalid response from API' );
165 }
166
167 update_option( 'claspo_script_id', $script_id );
168 update_option( 'claspo_script_code', $body );
169 set_transient( 'claspo_success_message', true, 30 );
170 delete_transient( 'claspo_api_error' );
171
172 claspo_clear_cache();
173
174 $verified = claspo_verify_and_ping( $script_id );
175 set_transient( 'claspo_script_verified', $verified, 30 );
176
177 return true;
178 }
179
180 function claspo_register_rest_routes() {
181 register_rest_route(
182 'claspo/v1',
183 '/connect',
184 array(
185 'methods' => 'POST',
186 'callback' => 'claspo_rest_connect',
187 'permission_callback' => '__return_true',
188 )
189 );
190 }
191
192 /**
193 * Background handshake endpoint called by Claspo right after registration.
194 *
195 * Authentication is based solely on a short-lived one-time `state` token that
196 * was generated by this site and passed to Claspo at registration time. The
197 * token is atomically consumed via delete_transient() return value, so two
198 * concurrent requests cannot both succeed.
199 *
200 * Responses are intentionally opaque: 200 on success, 400 on any failure,
201 * with no details about which step failed. This prevents the endpoint from
202 * being used as an enumeration or probing oracle.
203 *
204 * @param WP_REST_Request $request
205 * @return WP_REST_Response
206 */
207 function claspo_rest_connect( WP_REST_Request $request ) {
208 $params = json_decode( (string) $request->get_body(), true );
209 if ( ! is_array( $params ) ) {
210 return claspo_rest_connect_error();
211 }
212
213 $state = isset( $params['state'] ) ? sanitize_text_field( (string) $params['state'] ) : '';
214 $script_id = isset( $params['script_id'] ) ? sanitize_text_field( (string) $params['script_id'] ) : '';
215
216 // The state token we generate has a known shape (wp_generate_password, 32 chars, [A-Za-z0-9]).
217 if ( $state === '' || ! preg_match( '/^[A-Za-z0-9]{16,128}$/', $state ) ) {
218 return claspo_rest_connect_error();
219 }
220
221 if ( $script_id === '' ) {
222 return claspo_rest_connect_error();
223 }
224
225 // Atomically consume the state: delete_transient() returns true only if it
226 // actually existed. This closes the race window between validation and use.
227 if ( ! delete_transient( CLASPO_STATE_TRANSIENT . $state ) ) {
228 return claspo_rest_connect_error();
229 }
230
231 // If a script is already connected, do not overwrite it from a public endpoint.
232 if ( get_option( 'claspo_script_id' ) ) {
233 return claspo_rest_connect_error();
234 }
235
236 // Real validation of the script_id happens inside claspo_apply_script_id() via the
237 // call to Claspo's API — if the ID is not valid, the API returns a non-200 and we bail.
238 $result = claspo_apply_script_id( $script_id );
239 if ( is_wp_error( $result ) ) {
240 return claspo_rest_connect_error();
241 }
242
243 return new WP_REST_Response( array( 'status' => 'ok' ), 200 );
244 }
245
246 function claspo_rest_connect_error() {
247 return new WP_REST_Response( array( 'error' => 'invalid_request' ), 400 );
248 }
249
250 add_action( 'wp_footer', 'claspo_add_claspo_script' );
251
252 function claspo_add_claspo_script() {
253 // Реєструємо пустий скрипт
254 wp_register_script('claspo-script', false);
255 wp_enqueue_script('claspo-script');
256
257 // Отримуємо скрипт з бази дани�
258
259 $script_code = get_option( 'claspo_script_code' );
260
261 // Видаляємо теги <script> з коду
262 $script_code = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '$1', $script_code);
263
264 // Додаємо скрипт без тегів <script>, якщо він існує
265 if ( $script_code ) {
266 wp_add_inline_script('claspo-script', $script_code);
267 }
268 }
269
270 add_action( 'admin_init', 'claspo_register_settings' );
271 function claspo_register_settings() {
272 register_setting( 'claspo_options_group', 'claspo_script_id', array(
273 'sanitize_callback' => 'sanitize_text_field',
274 ) );
275 }
276
277 // Додаємо функцію для редіректу після активації плагіну
278 function claspo_plugin_activate() {
279 // Зберігаємо змінну, щоб перевірити чи був плагін щойно активований
280 add_option('claspo_plugin_activated', true);
281 }
282
283 // Реєструємо функцію активації
284 register_activation_hook(__FILE__, 'claspo_plugin_activate');
285
286 // Перевіряємо чи плагін був щойно активований, і виконуємо редірект
287 function claspo_plugin_redirect() {
288 if (get_option('claspo_plugin_activated', false)) {
289 delete_option('claspo_plugin_activated');
290 wp_safe_redirect(admin_url('admin.php?page=claspo_script_plugin'));
291 exit;
292 }
293 }
294
295 // Додаємо дію для виконання редіректу після ініціалізації адміністративної частини
296 add_action('admin_init', 'claspo_plugin_redirect');
297
298
299 function claspo_clear_cache() {
300 try {
301 global $wp_fastest_cache;
302 // if W3 Total Cache is being used, clear the cache
303 if (function_exists('w3tc_flush_all')) {
304 w3tc_flush_all();
305 }
306 /* if WP Super Cache is being used, clear the cache */
307 if (function_exists('wp_cache_clean_cache')) {
308 global $file_prefix;
309 if (function_exists('get_supercache_dir')) {
310 get_supercache_dir();
311 }
312 wp_cache_clean_cache($file_prefix);
313 }
314
315 if (method_exists('WpFastestCache', 'deleteCache') && !empty($wp_fastest_cache)) {
316 $wp_fastest_cache->deleteCache();
317 }
318 if (function_exists('rocket_clean_domain')) {
319 rocket_clean_domain();
320 // Preload cache.
321 if (function_exists('run_rocket_sitemap_preload')) {
322 run_rocket_sitemap_preload();
323 }
324 }
325
326 if (class_exists("autoptimizeCache") && method_exists("autoptimizeCache", "clearall")) {
327 autoptimizeCache::clearall();
328 }
329
330 if (class_exists("LiteSpeed_Cache_API") && method_exists("LiteSpeed_Cache_API", "purge_all")) {
331 LiteSpeed_Cache_API::purge_all();
332 }
333
334 if (class_exists('\Hummingbird\Core\Utils')) {
335 $modules = \Hummingbird\Core\Utils::get_active_cache_modules();
336 foreach ($modules as $module => $name) {
337 $mod = \Hummingbird\Core\Utils::get_module($module);
338
339 if ($mod->is_active()) {
340 if ('minify' === $module) {
341 $mod->clear_files();
342 } else {
343 $mod->clear_cache();
344 }
345 }
346 }
347 }
348 } catch (Exception $e) {
349 // do nothing
350 }
351 }
352
353
354 function claspo_verify_and_ping( $script_id ) {
355 try {
356 $home_response = wp_remote_get( home_url( '/' ) . '?claspo_nocache=' . time(), array(
357 'timeout' => 15,
358 'sslverify' => false,
359 ) );
360
361 if ( is_wp_error( $home_response ) ) {
362 return false;
363 }
364
365 $html = wp_remote_retrieve_body( $home_response );
366
367 if ( empty( $html ) || stripos( $html, $script_id ) === false ) {
368 return false;
369 }
370
371 $site_url = home_url( '/' );
372 $site_host = wp_parse_url( $site_url, PHP_URL_HOST );
373
374 $payload = array(
375 'scriptVersion' => 'latest',
376 'orgId' => null,
377 'siteId' => null,
378 'guid' => $script_id,
379 'url' => $site_url,
380 'message' => 'SCRIPT_INITIAL_LOAD',
381 'log_level' => 'INFO',
382 'data' => wp_json_encode( array(
383 'site' => $site_host,
384 'SCRIPT_INITIAL_LOAD' => 0,
385 ) ),
386 );
387
388 $ping = wp_remote_post( CLASPO_EVENT_URL, array(
389 'timeout' => 10,
390 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
391 'body' => wp_json_encode( $payload ),
392 ) );
393
394 return ! is_wp_error( $ping ) && wp_remote_retrieve_response_code( $ping ) < 400;
395 } catch ( Exception $e ) {
396 return false;
397 }
398 }
399
400
401 ?>