PluginProbe
Pixelavo – Server Side Tracking & Pixel + AI Ads Tools / trunk
Pixelavo – Server Side Tracking & Pixel + AI Ads Tools vtrunk
1.5.5 1.5.4 trunk 1.0.0 1.0.1 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.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 All 47 releases
pixelavo / includes / base.php

base.php in Pixelavo – Server Side Tracking & Pixel + AI Ads Tools trunk, at includes/base.php

593 lines 21.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Pixelavo;
4
5 /**
6 * TODO: remove data from other events in the future update (in wp_localize_script)
7 */
8
9 /**
10 * Exit if accessed directly
11 * */
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 /**
17 * Base
18 */
19 final class Base {
20
21 private static $_instance = null;
22
23 /**
24 * Instance
25 *
26 * Initializes a singleton instance
27 *
28 * @return self class
29 */
30 static function instance() {
31 if (is_null( self::$_instance )) {
32 self::$_instance = new self();
33 }
34 return self::$_instance;
35 }
36
37 /**
38 * Base class constructor
39 * @return void
40 */
41 private function __construct() {
42 if (!function_exists('is_plugin_active')) {
43 include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
44 }
45 add_action('plugins_loaded', [$this, 'init']);
46 add_action('in_admin_header', [$this, 'remove_admin_notice'], 1000);
47 add_action('wp_enqueue_scripts', [$this, 'scripts']);
48 add_action('admin_init', [$this, 'show_diagnostic_notice']);
49 add_action('admin_init', [$this, 'show_rating_notice']);
50 add_action('admin_init', [$this, 'show_promo_notice']);
51 add_action('admin_init', [$this, 'ensure_ai_tables_exist']);
52 add_action('admin_init', [$this, 'init_security_notices']);
53 // add_action('admin_head', [$this, 'halloween_notice']); // leave for another offer banner
54 /**
55 * Register Plugin Active Hook
56 */
57 register_activation_hook( PIXELAVO_PL_ROOT, [ $this, 'plugin_activate_hook' ] );
58
59 // Add user login & register hook
60 add_action('wp_login', [$this,'user_login'], 10, 2);
61 add_action('user_register', [$this,'user_register'], 10, 2);
62 }
63
64 /**
65 * Remove all Notices on admin pages.
66 * @return void
67 */
68 function remove_admin_notice(){
69 $current_screen = get_current_screen();
70 $hide_screen = ['toplevel_page_pixelavo', 'update'];
71 if( in_array( $current_screen->id, $hide_screen) ){
72 remove_all_actions('admin_notices');
73 remove_all_actions('all_admin_notices');
74 }
75 }
76
77 /**
78 * init
79 *
80 * Plugins loaded init hook
81 * @return void
82 */
83 function init() {
84
85 /**
86 * Include required files.
87 */
88 $this->include_files();
89
90 /**
91 * Redirect to option page after plugin activate
92 */
93 $this->plugin_redirect_option_page();
94
95 /**
96 * Add plugin setting link to the plugin.
97 */
98 add_filter('plugin_action_links_' . PIXELAVO_PLUGIN_BASE, [$this, 'plugins_setting_links']);
99
100 }
101
102 /**
103 * Include Require Files
104 */
105 function include_files() {
106 require_once (PIXELAVO_PL_PATH . 'includes/helper-functions.php');
107 require_once (PIXELAVO_PL_PATH . 'admin/admin-setting.php');
108 require_once (PIXELAVO_PL_PATH . 'includes/add-pixel.php');
109 require_once (PIXELAVO_PL_PATH . 'includes/modifier.php');
110 if(pixelavo_is_woocommerce_active()) {
111 require_once (PIXELAVO_PL_PATH . 'includes/pixel-events-data.php');
112 if(pixelavo_get_option('product_feed', 'pixelavo_settings') == 'on') {
113 require_once (PIXELAVO_PL_PATH . 'includes/pixel-feed.php');
114 }
115 }
116 if(pixelavo_is_edd_active()) {
117 require_once (PIXELAVO_PL_PATH . 'includes/pixel-edd-events-data.php');
118 if(pixelavo_get_option('edd_product_feed', 'pixelavo_settings') == 'on') {
119 require_once (PIXELAVO_PL_PATH . 'includes/pixel-edd-feed.php');
120 }
121 }
122 if(!empty(pixelavo_get_custom_events())) {
123 require_once (PIXELAVO_PL_PATH . 'includes/pixel-custom-events-data.php');
124 }
125 require_once PIXELAVO_PL_PATH . 'includes/pixel-ajax-events-data.php';
126
127 if( is_admin() ){
128 require_once (PIXELAVO_PL_PATH . 'admin/class-notice-manager.php');
129 require_once (PIXELAVO_PL_PATH . 'admin/class-diagnostic-data.php');
130 require_once (PIXELAVO_PL_PATH . 'admin/class-notices.php');
131 }
132 add_action('wp_footer', [$this, 'localizedEventsData'], 11);
133 }
134
135 function localizedEventsData() {
136 global $pixelavoEventsLocalizedData;
137 if(count($pixelavoEventsLocalizedData) > 0) {
138 wp_localize_script('pixelavo', 'pixelavo_event', $pixelavoEventsLocalizedData);
139 }
140 }
141
142 /**
143 * Scripts
144 *
145 * Enqueue style and scripts for frontend part
146 * @return void
147 */
148 function scripts() {
149 if(pixelavo_pixel_status()) {
150 wp_enqueue_script('pixelavo', PIXELAVO_DIR_URL . 'assets/public/js/main.js', ['jquery'], PIXELAVO_VERSION, true);
151 wp_localize_script('pixelavo', 'pixelavo', [
152 'ajax_url' => admin_url('admin-ajax.php'),
153 'nonce' => wp_create_nonce('pixelavo_ajax_event_nonce'),
154 'other_events' => wp_json_encode(array_merge(pixelavo_get_other_events(), ['data' => pixelavo_get_event_common_data()])),
155 'custom_events' => wp_json_encode(pixelavo_get_custom_events()),
156 'additional_user_info' => apply_filters('pixelavo_additional_user_info', [], [] ),
157 'common_params' => pixelavo_get_event_common_data(),
158 'other_event_options' => [
159 "download_files" => pixelavo_get_option('download_files', 'pixelavo_download_files'),
160 "page_scroll" => pixelavo_get_option('page_scroll', 'pixelavo_page_scroll'),
161 "time_on_page" => pixelavo_get_option('time_on_page', 'pixelavo_time_on_page'),
162 "form" => pixelavo_get_option('form_submission', 'pixelavo_form_submission'),
163 ],
164 'is_active' => [
165 "woo" => pixelavo_is_woocommerce_active(),
166 'edd' => pixelavo_is_edd_active(),
167 'fluent_form' => pixelavo_is_fluentform_active(),
168 'cf7' => pixelavo_is_cf7_active(),
169 'wpforms' => pixelavo_is_wpforms_active(),
170 'ht_contactform' => pixelavo_is_ht_contactform_active(),
171 ],
172 ]);
173 wp_localize_script('pixelavo', 'pixelavo_event', []);
174 }
175 }
176
177 /**
178 * Plugins setting links
179
180 * @param array plugin default action links
181 * @return array plugin action link
182 */
183 function plugins_setting_links($links) {
184 $link = sprintf(
185 '<a href="%1$s">%2$s</a>',
186 admin_url('admin.php?page=pixelavo#/pixels'),
187 __( 'Settings', 'pixelavo' )
188 );
189 array_unshift($links, $link);
190 return $links;
191 }
192
193 /**
194 * Plugin Active Hook
195 * Run when plugin is activated
196 */
197 public function plugin_activate_hook() {
198 // Create database tables for AI features
199 $this->create_ai_tables();
200
201 $events = get_option('pixelavo_events', [
202 'search' => 'on',
203 'view_content' => 'on',
204 'view_category' => 'on',
205 ]);
206 $edd_events = get_option('pixelavo_edd_events', [
207 'edd_search' => 'on',
208 'edd_view_content' => 'on',
209 'edd_view_category' => 'on',
210 ]);
211 $other_events = get_option('pixelavo_edd_events', [
212 'youtube_video' => 'off',
213 'vimeo_video' => 'off',
214 'self_hosted_video' => 'off',
215 'page_scroll' => 'on',
216 'time_on_page' => 'on',
217 'download_files' => 'on',
218 'form_submission' => 'on',
219 'internal_links' => 'off',
220 'external_links' => 'off',
221 ]);
222 $download_files = get_option('pixelavo_download_files', [
223 'download_files' => [
224 "download_files_extensions" => ["pdf", "zip"]
225 ]
226 ]);
227 $time_on_page = get_option('pixelavo_time_on_page', [
228 'time_on_page' => [
229 "time_on_page_value" => "10"
230 ]
231 ]);
232 $page_scroll = get_option('pixelavo_page_scroll', [
233 'page_scroll' => [
234 "page_scroll_value" => "30"
235 ]
236 ]);
237 $form_submission = get_option('pixelavo_form_submission', [
238 'form_submission' => [
239 "fluent_form" => [],
240 "contact_form_7" => [],
241 "wp_forms" => [],
242 "ht_contactform" => [],
243 ]
244 ]);
245 update_option('pixelavo_events', $events);
246 update_option('pixelavo_edd_events', $edd_events);
247 update_option('pixelavo_other_events', $other_events);
248 update_option('pixelavo_download_files', $download_files);
249 update_option('pixelavo_time_on_page', $time_on_page);
250 update_option('pixelavo_page_scroll', $page_scroll);
251 update_option('pixelavo_form_submission', $form_submission);
252
253 /* ViewContent Event Settings */
254 $pixelavo_wc_view_content = get_option('pixelavo_wc_view_content', [
255 'view_content' => [
256 'view_content_delay' => 0,
257 ],
258 ]);
259 update_option('pixelavo_wc_view_content', $pixelavo_wc_view_content);
260 /* Purchase Event Settings */
261 $pixelavo_wc_purchase = get_option('pixelavo_wc_purchase', [
262 'purchase' => [
263 'purchase_event_trigger' => ['on-hold', 'processing'],
264 'purchase_additional_info' => 'off',
265 ],
266 ]);
267 update_option('pixelavo_wc_purchase', $pixelavo_wc_purchase);
268
269 add_option('pixelavo_do_activation_redirect', true);
270 flush_rewrite_rules();
271
272 if ( ! get_option( 'pixelavo_installed' ) ) {
273 update_option( 'pixelavo_installed', time() );
274 }
275 }
276
277 /**
278 * Create AI database tables
279 * @return void
280 */
281 private function create_ai_tables() {
282 // Load DatabaseInstaller class if it exists
283 $installer_class = PIXELAVO_PL_PATH . 'admin/settings-panel/includes/classes/Api/Ai/DatabaseInstaller.php';
284 if (file_exists($installer_class)) {
285 require_once $installer_class;
286 \Pixelavo\Api\Ai\DatabaseInstaller::create_tables();
287 }
288 }
289
290 /**
291 * Ensure AI tables exist on admin init
292 * This handles cases where activation hook might have been missed
293 * @return void
294 */
295 public function ensure_ai_tables_exist() {
296 // Only check once per admin session to avoid performance impact
297 if (get_transient('pixelavo_ai_tables_checked')) {
298 return;
299 }
300
301 $installer_class = PIXELAVO_PL_PATH . 'admin/settings-panel/includes/classes/Api/Ai/DatabaseInstaller.php';
302 if (file_exists($installer_class)) {
303 require_once $installer_class;
304
305 // Check if table exists, create if not
306 if (!\Pixelavo\Api\Ai\DatabaseInstaller::table_exists()) {
307 \Pixelavo\Api\Ai\DatabaseInstaller::create_tables();
308 }
309 }
310
311 // Set transient to avoid repeated checks (expires in 12 hours)
312 set_transient('pixelavo_ai_tables_checked', true, 12 * HOUR_IN_SECONDS);
313 }
314
315 /**
316 * Initialize security notices for AI features
317 * @return void
318 */
319 public function init_security_notices() {
320 $notices_class = PIXELAVO_PL_PATH . 'admin/settings-panel/includes/classes/Api/Ai/SecurityNotices.php';
321 if (file_exists($notices_class)) {
322 require_once $notices_class;
323 \Pixelavo\Api\Ai\SecurityNotices::init();
324
325 // Enqueue JavaScript for notice dismissal
326 add_action('admin_footer', [\Pixelavo\Api\Ai\SecurityNotices::class, 'enqueue_notice_scripts']);
327 }
328 }
329
330 public function halloween_notice() {
331 $plugins = get_plugins();
332 if(!empty($plugins['pixelavo-pro/pixelavo-pro.php'])) {
333 return;
334 }
335 $url = 'https://pixelavo.com/pricing/?utm_source=halloween&utm_medium=dashboard#pixelavo-pricing';
336 $image = PIXELAVO_PL_URL . '/admin/settings-panel/assets/images/banner/halloween-2025.png';
337 \Pixelavo_Notice::set_notice(
338 [
339 'id' => 'pixelavo-halloween-notice',
340 'type' => 'notice',
341 'dismissible' => true,
342 'message_type' => 'banner',
343 'banner' => [
344 'url' => esc_url_raw($url),
345 'image' => '<img src="'.esc_url_raw($image).'" alt="Pixelavo" style="max-width:100%"/>'
346 ],
347 'close_by' => 'transient'
348 ]
349 );
350 }
351
352 /**
353 * User Login
354 * @param string $user_login
355 * @param \WP_User $user
356 * @return void
357 */
358 public function user_login($user_login, $user) {
359 if(pixelavo_other_event_exists('login')) {
360 // Don't double-fire Login when this is part of a fresh registration
361 // (CompleteRegistration already fired in the same request).
362 if($this->auth_event_queued('CompleteRegistration')) {
363 return;
364 }
365 $this->fire_server_auth_event('Login', 'trackCustom', $user);
366 }
367 }
368
369 /**
370 * User Register
371 * @param int $user_id
372 * @param \WP_User $userdata
373 * @return void
374 */
375 public function user_register($user_id, $userdata) {
376 if(pixelavo_other_event_exists('signup')) {
377 $this->fire_server_auth_event('CompleteRegistration', 'track', get_userdata($user_id));
378 }
379 }
380
381 /**
382 * Fire an identity event (Login / CompleteRegistration) server-side.
383 *
384 * The Conversions API call is made HERE, from the authenticated wp_login /
385 * user_register hook — this is the server-side source of truth and cannot be
386 * forged by a client. A short-lived cookie then carries ONLY the event name
387 * + the shared event_id to the browser, which fires the fbq pixel for
388 * deduplication. The cookie is genuinely non-authoritative: forging it can
389 * at most make the visitor's own browser fire an fbq pixel — it never
390 * triggers a CAPI call and carries no credentials.
391 *
392 * @param string $event_name
393 * @param string $track 'track' or 'trackCustom'
394 * @param \WP_User|false $user The authenticated user (for matching data).
395 * @return void
396 */
397 private function fire_server_auth_event($event_name, $track, $user = null) {
398 $event_id = $event_name . '.' . time() . '.' . wp_generate_password(12, false, false);
399
400 // Make the just-authenticated user available for advanced matching —
401 // wp_login / user_register run before WordPress sets the current user.
402 if($user instanceof \WP_User) {
403 wp_set_current_user($user->ID);
404 }
405
406 // Fire the Conversions API now. Identity events are not page-scoped, so
407 // skip page-targeting and use the site home as the source URL (there is
408 // no queried page in this hook). The request is non-blocking, so login
409 // and registration are not slowed.
410 //
411 // Do NOT call pixelavo_get_event_common_data() here — it runs query
412 // conditionals (is_singular/is_home/...) which trigger a "called
413 // incorrectly" notice this early (before the main query). A minimal,
414 // page-independent payload is correct for an identity event anyway.
415 $common = ['event_url' => home_url('/')];
416 pixelavo_run_conversions_api($event_name, $common, $event_id, null, [
417 'event_source_url' => home_url('/'),
418 'skip_page_check' => true,
419 ]);
420
421 // Hand only a browser descriptor to the client for the fbq pixel.
422 $this->queue_browser_auth_event($event_name, $track, $event_id);
423 }
424
425 /**
426 * Store a browser-only descriptor (event name + shared event_id) in the
427 * short-lived cookie so the pixel fires fbq once for deduplication. No PII,
428 * no credentials, not a trust boundary.
429 *
430 * @param string $event_name
431 * @param string $track
432 * @param string $event_id
433 * @return void
434 */
435 private function queue_browser_auth_event($event_name, $track, $event_id) {
436 $queue = [];
437 if(!empty($_COOKIE['pixelavo_srv_evt'])) {
438 $existing = json_decode(wp_unslash($_COOKIE['pixelavo_srv_evt']), true);
439 if(is_array($existing)) {
440 $queue = $existing;
441 }
442 }
443
444 $queue[] = [
445 'name' => $event_name,
446 'track' => ($track === 'track') ? 'track' : 'trackCustom',
447 'id' => $event_id,
448 ];
449
450 $this->set_server_auth_cookie(wp_json_encode($queue));
451 }
452
453 /**
454 * Whether an event of the given name is already in the pending cookie queue.
455 * @param string $name
456 * @return bool
457 */
458 private function auth_event_queued($name) {
459 if(empty($_COOKIE['pixelavo_srv_evt'])) {
460 return false;
461 }
462 $queue = json_decode(wp_unslash($_COOKIE['pixelavo_srv_evt']), true);
463 if(!is_array($queue)) {
464 return false;
465 }
466 foreach($queue as $event) {
467 if(!empty($event['name']) && $event['name'] === $name) {
468 return true;
469 }
470 }
471 return false;
472 }
473
474 /**
475 * Set the short-lived carrier cookie for pending auth events.
476 * httponly is intentionally false: the browser JS must read it. It holds no
477 * secret — only event name + server-generated id.
478 * @param string $value
479 * @return void
480 */
481 private function set_server_auth_cookie($value) {
482 // Reflect immediately so same-request reads (register -> login) see it.
483 $_COOKIE['pixelavo_srv_evt'] = $value;
484 if(headers_sent()) {
485 return;
486 }
487 setcookie('pixelavo_srv_evt', $value, [
488 'expires' => time() + 300,
489 'path' => defined('COOKIEPATH') && COOKIEPATH ? COOKIEPATH : '/',
490 'domain' => defined('COOKIE_DOMAIN') && COOKIE_DOMAIN ? COOKIE_DOMAIN : '',
491 'secure' => is_ssl(),
492 'httponly' => false,
493 'samesite' => 'Lax',
494 ]);
495 }
496
497 /**
498 * After Active the plugin then redirect to option page
499 * @return void
500 */
501 public function plugin_redirect_option_page() {
502 if ( get_option( 'pixelavo_do_activation_redirect', false ) ) {
503 delete_option('pixelavo_do_activation_redirect');
504 if( !isset( $_GET['activate-multi'] ) ){ // phpcs:ignore WordPress.Security.NonceVerification
505 wp_safe_redirect( admin_url("admin.php?page=pixelavo#/pixels") );
506 exit;
507 }
508 }
509 }
510
511
512 function show_diagnostic_notice() {
513 $notice_instance = \Pixelavo_Diagnostic_Data::get_instance();
514 if ( ! $notice_instance->should_show_notice() ) {
515 return;
516 }
517 ob_start();
518 $notice_instance->show_notices();
519 $message = ob_get_clean();
520 if (! empty( $message ) ) {
521 \Pixelavo_Notice::set_notice(
522 [
523 'id' => 'diagnostic-data',
524 'type' => 'success',
525 'dismissible' => false,
526 'message_type' => 'html',
527 'message' => $message,
528 'display_after' => ( 7 * DAY_IN_SECONDS ),
529 'expire_time' => ( 0 * DAY_IN_SECONDS ),
530 'close_by' => 'transient'
531 ]
532 );
533 }
534 }
535
536 function show_rating_notice() {
537 $message = '<div class="pixelavo-review-notice-wrap">
538 <div class="pixelavo-rating-notice-logo">
539 <img src="' . esc_url(PIXELAVO_PL_URL . 'assets/images/logo.png') . '" alt="'.esc_attr__('Pixelavo', 'pixelavo'). '" style="max-width:110px"/>
540 </div>
541 <div class="pixelavo-review-notice-content">
542 <h3>'.esc_html__('Thank you for choosing Pixelavo to manage you plugins!','pixelavo').'</h3>
543 <p>'.esc_html__('Would you mind doing us a huge favor by providing your feedback on WordPress? Your support helps us spread the word and greatly boosts our motivation.','pixelavo').'</p>
544 <div class="pixelavo-review-notice-action">
545 <a href="https://wordpress.org/support/plugin/pixelavo/reviews/" class="pixelavo-review-notice button-primary" target="_blank">'.esc_html__('Ok, you deserve it!','pixelavo').'</a>
546 <a href="#" class="pixelavo-notice-close pixelavo-review-notice"><span class="dashicons dashicons-calendar"></span>'.esc_html__('Maybe Later','pixelavo').'</a>
547 <a href="#" data-already-did="yes" class="pixelavo-notice-close pixelavo-review-notice"><span class="dashicons dashicons-smiley"></span>'.esc_html__('I already did','pixelavo').'</a>
548 </div>
549 </div>
550 </div>';
551 if (! empty( $message ) ) {
552 \Pixelavo_Notice::set_notice(
553 [
554 'id' => 'ratting',
555 'type' => 'info',
556 'dismissible' => true,
557 'message_type' => 'html',
558 'message' => $message,
559 'display_after' => ( 14 * DAY_IN_SECONDS ),
560 'close_by' => 'transient'
561 ]
562 );
563 }
564 }
565
566
567
568 function show_promo_notice() {
569
570 // remove on next update
571 if(!get_option('force_update_pixelavo_notice_info')) {
572 delete_transient('pixelavo_notice_info');
573 update_option('force_update_pixelavo_notice_info', true);
574 }
575
576 $noticeManager = \Pixelavo_Notice_Manager::instance();
577 $notices = $noticeManager->get_notices_info();
578 if(!empty($notices)) {
579 foreach ($notices as $notice) {
580 if(empty($notice['disable'])) {
581 \Pixelavo_Notice::set_notice($notice);
582 }
583 }
584 }
585 }
586
587 }
588
589 /**
590 * Initialize Base Class
591 */
592 Base::instance();
593