Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
EventService
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 11
1190
0.00% covered (danger)
0.00%
0 / 1
 init
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 send
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 validate_category
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 validate_action
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 validate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
30
 add_timestamp_and_ttl
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
 handle_option_update
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 track_site_classification
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
56
 is_site_classification_option
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 is_primary_type_option
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_secondary_type_option
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace NewfoldLabs\WP\Module\Onboarding\Services;
4
5use NewfoldLabs\WP\Module\Onboarding\Data\Events;
6use NewfoldLabs\WP\Module\Onboarding\Data\Options;
7
8/**
9 * Class for handling analytics events.
10 */
11class EventService {
12
13    /**
14     * Initialize event tracking hooks.
15     */
16    public static function init() {
17        // Hook into option updates to track events when data is saved to database
18        add_action( 'updated_option', array( __CLASS__, 'handle_option_update' ), 10, 3 );
19        add_action( 'added_option', array( __CLASS__, 'handle_option_update' ), 10, 3 );
20    }
21
22    /**
23     * Sends a Hiive Event to the data module API.
24     *
25     * @param array $event The event to send.
26     * @return WP_REST_Response|WP_Error
27     */
28    public static function send( $event ) {
29        $event = self::validate( $event );
30        if ( ! $event ) {
31            return new \WP_Error(
32                'nfd_module_onboarding_error',
33                __( 'Bad event structure/value.', 'wp-module-onboarding' )
34            );
35        }
36
37        // Add timestamp and ttl to specific events
38        $event = self::add_timestamp_and_ttl( $event );
39
40        $event_data_request = new \WP_REST_Request(
41            \WP_REST_Server::CREATABLE,
42            NFD_MODULE_DATA_EVENTS_API
43        );
44        $event_data_request->set_body_params( $event );
45
46        $response = rest_do_request( $event_data_request );
47        if ( $response->is_error() ) {
48            return $response->as_error();
49        }
50
51        return $response;
52    }
53
54    /**
55     * Validates the category of an event.
56     *
57     * @param string $category The category of an event.
58     * @return boolean
59     */
60    public static function validate_category( $category ) {
61        $default_categories = Events::get_category();
62        foreach ( $default_categories as $event_category ) {
63            if ( $event_category === $category ) {
64                return true;
65            }
66        }
67        return false;
68    }
69
70    /**
71     * Validates the action performed in an event.
72     *
73     * @param string $action The action performed in an event.
74     * @return boolean
75     */
76    public static function validate_action( $action ) {
77        $valid_actions = Events::get_valid_actions();
78        if ( ! isset( $valid_actions[ $action ] ) ) {
79            return false;
80        }
81
82        return true;
83    }
84
85    /**
86     * Sanitizes and validates the action and category parameters of an event.
87     *
88     * @param array $event The event to sanitize and validate.
89     * @return array|boolean
90     */
91    public static function validate( $event ) {
92        if ( ! isset( $event['action'] ) || ! self::validate_action( $event['action'] ) ) {
93            return false;
94        }
95
96        if ( ! isset( $event['category'] ) || ! self::validate_category( $event['category'] ) ) {
97            return false;
98        }
99
100        return $event;
101    }
102
103    /**
104     * Adds timestamp and ttl properties to specific events (onboarding_started and onboarding_complete).
105     *
106     * @param array $event The event to enhance.
107     * @return array The enhanced event.
108     */
109    private static function add_timestamp_and_ttl( $event ) {
110        $current_time = time();
111
112        switch ( $event['action'] ) {
113            case 'onboarding_started':
114                // Add timestamp to onboarding_started event
115                $event['data']['timestamp'] = $current_time;
116                break;
117
118            case 'onboarding_complete':
119                // Add timestamp and ttl to onboarding_complete event
120                $event['data']['timestamp'] = $current_time;
121
122                // Use the same completion time that was stored in handle_completed()
123                $completion_time = get_option( Options::get_option_name( 'completed_time' ) );
124                $start_time      = get_option( Options::get_option_name( 'start_time' ) );
125
126                if ( $start_time ) {
127                    if ( $completion_time ) {
128                        // Use stored completion time
129                        $ttl_seconds = $completion_time - $start_time;
130                    } else {
131                        // Fallback to current time if completion_time not found
132                        $ttl_seconds = $current_time - $start_time;
133                    }
134
135                    if ( $ttl_seconds >= 0 ) {
136                        $event['data']['ttl'] = $ttl_seconds;
137                    }
138                }
139                break;
140        }
141
142        return $event;
143    }
144
145    /**
146     * Handle option updates and track relevant events.
147     *
148     * @param string $option The option name.
149     * @param mixed  $old_value The old option value for updated_option, or new value for added_option.
150     * @param mixed  $value The new option value (only for updated_option).
151     */
152    public static function handle_option_update( $option, $old_value, $value = null ) {
153        // Handle added_option hook (only 2 params: option name and value)
154        if ( null === $value ) {
155            $value     = $old_value;
156            $old_value = null;
157        }
158
159        // Track primary and secondary types from site classification
160        if ( self::is_site_classification_option( $option ) ) {
161            self::track_site_classification( $option, $old_value, $value );
162        }
163    }
164
165    /**
166     * Track site classification changes (primary/secondary types).
167     *
168     * @param string $option The option name.
169     * @param mixed  $old_value The old option value.
170     * @param mixed  $new_value The new option value.
171     */
172    private static function track_site_classification( $option, $old_value, $new_value ) {
173        if ( ! is_array( $new_value ) || $new_value === $old_value ) {
174            return;
175        }
176
177        // Track primary type
178        if ( self::is_primary_type_option( $option ) && isset( $new_value['value'] ) ) {
179            self::send(
180                array(
181                    'action'   => 'primary_type_set',
182                    'category' => 'wonder_start',
183                    'data'     => array(
184                        'primary_type' => $new_value['value'],
185                        'source'       => 'database_saved',
186                    ),
187                )
188            );
189        }
190
191        // Track secondary type
192        if ( self::is_secondary_type_option( $option ) && isset( $new_value['value'] ) ) {
193            self::send(
194                array(
195                    'action'   => 'secondary_type_set',
196                    'category' => 'wonder_start',
197                    'data'     => array(
198                        'secondary_type' => $new_value['value'],
199                        'source'         => 'database_saved',
200                    ),
201                )
202            );
203        }
204    }
205
206    /**
207     * Check if option is a site classification option.
208     *
209     * @param string $option The option name.
210     * @return bool
211     */
212    private static function is_site_classification_option( $option ) {
213        return self::is_primary_type_option( $option ) || self::is_secondary_type_option( $option );
214    }
215
216    /**
217     * Check if option is a primary type option.
218     *
219     * @param string $option The option name.
220     * @return bool
221     */
222    private static function is_primary_type_option( $option ) {
223        return strpos( $option, 'nfd_data_site_classification_primary' ) !== false;
224    }
225
226    /**
227     * Check if option is a secondary type option.
228     *
229     * @param string $option The option name.
230     * @return bool
231     */
232    private static function is_secondary_type_option( $option ) {
233        return strpos( $option, 'nfd_data_site_classification_secondary' ) !== false;
234    }
235}