Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 155
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Yoast
0.00% covered (danger)
0.00%
0 / 155
0.00% covered (danger)
0.00%
0 / 14
2970
0.00% covered (danger)
0.00%
0 / 1
 register_hooks
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 site_representation_updated
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 social_profiles_updated
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 tracking_updated
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 maybe_push_site_representation_event
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
90
 maybe_push_social_profiles_event
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 push_other_social_profiles
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 map_params_names_to_hiive_names
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 map_failures_to_hiive_names
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 is_param_empty
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_base_url
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 clean_social_profiles_failures
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 database_upgrade
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 option_updated
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
110
1<?php
2
3namespace NewfoldLabs\WP\Module\Data\Listeners;
4
5/**
6 * Monitors Yoast events
7 */
8class Yoast extends Listener {
9
10    /**
11     * Fields to skip when tracking site representation changes
12     *
13     * @var array
14     */
15    private $site_representation_skip_fields = array( 'company_logo_id', 'person_logo_id', 'description' );
16
17    /**
18     * Mapping between Yoast site representation option names and Hiive event tracking names
19     *
20     * @var array
21     */
22    private $site_representation_map = array(
23        'company_or_person'         => 'site_representation',
24        'company_name'              => 'organization_name',
25        'company_logo'              => 'organization_logo',
26        'person_logo'               => 'logo',
27        'company_or_person_user_id' => 'name',
28        'website_name'              => 'website_name',
29    );
30
31    /**
32     * Mapping between Yoast social profiles option names and Hiive event tracking names
33     *
34     * @var array
35     */
36    private $social_profiles_map = array(
37        'facebook_site'     => 'facebook_profile',
38        'twitter_site'      => 'twitter_profile',
39        'other_social_urls' => 'other_profiles',
40    );
41
42    /**
43     * Register the hooks for the listener
44     *
45     * @return void
46     */
47    public function register_hooks() {
48        // First time configuration
49        add_action( 'wpseo_ftc_post_update_site_representation', array( $this, 'site_representation_updated' ), 10, 3 );
50        add_action( 'wpseo_ftc_post_update_social_profiles', array( $this, 'social_profiles_updated' ), 10, 3 );
51        add_action( 'wpseo_ftc_post_update_enable_tracking', array( $this, 'tracking_updated' ), 10, 3 );
52
53        // Upgrade database
54        add_action( 'wpseo_run_upgrade', array( $this, 'database_upgrade' ) );
55
56        // Updated options
57        add_action( 'update_option_wpseo_titles', array( $this, 'option_updated' ), 10, 3 );
58        add_action( 'update_option_wpseo_social', array( $this, 'option_updated' ), 10, 3 );
59        add_action( 'update_option_wpseo', array( $this, 'option_updated' ), 10, 3 );
60        add_action( 'update_option_wpseo_ms', array( $this, 'option_updated' ), 10, 3 );
61    }
62
63    /**
64     * The user just updated their site representation
65     *
66     * @param array $new_values The new values for the options related to the site representation
67     * @param array $old_values The old values for the options related to the site representation
68     * @param array $failures   The failures that occurred during the update
69     *
70     * @return void
71     */
72    public function site_representation_updated( $new_values, $old_values, $failures ) {
73        // All the options are unchanged, opt out
74        if ( $new_values === $old_values ) {
75            return;
76        }
77
78        $mapped_new_values = $this->map_params_names_to_hiive_names( $new_values, $this->site_representation_map, $this->site_representation_skip_fields );
79        $mapped_old_values = $this->map_params_names_to_hiive_names( $old_values, $this->site_representation_map, $this->site_representation_skip_fields );
80        $mapped_failures   = $this->map_failures_to_hiive_names( $failures, $this->site_representation_map, $this->site_representation_skip_fields );
81
82        foreach ( $mapped_new_values as $key => $value ) {
83            $this->maybe_push_site_representation_event( $key, $value, $mapped_old_values[ $key ], \in_array( $key, $mapped_failures ) );
84        }
85    }
86
87    /**
88     * The user just updated their personal profiles
89     *
90     * @param array $new_values The new values for the options related to the site representation
91     * @param array $old_values The old values for the options related to the site representation
92     * @param array $failures   The failures that occurred during the update
93     *
94     * @return void
95     */
96    public function social_profiles_updated( $new_values, $old_values, $failures ) {
97        // Yoast stores only twitter username, and $new_values stores the pre-processed values
98        if ( strpos( $new_values['twitter_site'], 'twitter.com/' ) !== false ) {
99            $new_values['twitter_site'] = ( explode( 'twitter.com/', $new_values['twitter_site'] )[1] );
100        }
101
102        // All the options are unchanged, opt out
103        if ( $new_values === $old_values ) {
104            return;
105        }
106
107        // Remove multiple occurences of other_social_urls;
108        $cleaned_failures = $this->clean_social_profiles_failures( $failures );
109
110        $mapped_values     = $this->map_params_names_to_hiive_names( $new_values, $this->social_profiles_map );
111        $mapped_old_values = $this->map_params_names_to_hiive_names( $old_values, $this->social_profiles_map );
112        $mapped_failures   = $this->map_failures_to_hiive_names( $cleaned_failures, $this->social_profiles_map );
113
114        foreach ( $mapped_values as $key => $value ) {
115            // The option update failed
116            if ( \in_array( $key, $mapped_failures ) ) {
117                $this->push( "failed_$key", array( 'category' => 'ftc_personal_profiles' ) );
118                return;
119            }
120
121            if ( $value !== $mapped_old_values[ $key ] ) {
122                $this->maybe_push_social_profiles_event( $key, $value, $mapped_old_values[ $key ], \in_array( $key, $mapped_failures ) );
123            }
124        }
125    }
126
127    /**
128     * The user updated their tracking preferences
129     *
130     * @param string $new_value The new value for the option related to tracking
131     * @param string $old_value The old value for the option related to tracking
132     * @param bool   $failed    Whether the option update failed
133     *
134     * @return void
135     */
136    public function tracking_updated( $new_value, $old_value, $failed ) {
137        // Option unchanged, opt out
138        if ( $new_value === $old_value ) {
139            return;
140        }
141
142        $failed ? $this->push( 'failed_usage_tracking', array( 'category' => 'ftc_tracking' ) ) : $this->push( 'changed_usage_tracking', array( 'category' => 'ftc_tracking' ) );
143    }
144
145    /**
146     * A method used to (maybe) push a site representation-related event to the queue.
147     *
148     * @param string $key       The option key
149     * @param string $value     The new option value
150     * @param string $old_value The old option value
151     * @param bool   $failure   Whether the option update failed
152     *
153     * @return void
154     */
155    private function maybe_push_site_representation_event( $key, $value, $old_value, $failure ) {
156        $category = 'ftc_site_representation';
157
158        // The option update failed
159        if ( $failure ) {
160            $this->push( "failed_$key", array( 'category' => $category ) );
161            return;
162        }
163
164        // The option value changed
165        if ( $value !== $old_value ) {
166            // The option was set for the first time
167
168            // name is a special case, because it represents the company_or_person_user_id which is initialised to false, and the first time the user saves the site representation step
169            // is set either to 0 if the site represents an organisation, or to an integer > 0 if the site represents a person
170            if ( 'name' === $key ) {
171                if ( false === $old_value && 0 === $value ) {
172                    return;
173                }
174            }
175
176            // Again, name is a special case, because if its old value was 0 and a value different that 0 is being received, it means that the user
177            // switched from organisation to person, and then the person id is being set.
178            // Once the name is assigned an integer > 0, it can never go back to 0, even if the user switches back to organisation
179            // ( it "caches" the last user id that was set)
180            if ( ( $this->is_param_empty( $old_value ) ) || ( 'name' === $key && 0 === $old_value ) ) {
181                $this->push( "set_$key", array( 'category' => $category ) );
182                return;
183            }
184
185            // The option was updated
186            $data = array(
187                'category' => $category,
188                'data'     => array(
189                    'label_key' => $key,
190                    'new_value' => $value,
191                ),
192            );
193
194            $this->push(
195                "changed_$key",
196                $data
197            );
198        }
199    }
200
201    /**
202     * A method used to (maybe) push a social profile-related event to the queue.
203     *
204     * @param string $key       The option key
205     * @param string $value     The new option value
206     * @param string $old_value The old option value
207     * @param bool   $failure   Whether the option update failed
208     *
209     * @return void
210     */
211    private function maybe_push_social_profiles_event( $key, $value, $old_value, $failure ) {
212        $category = 'ftc_personal_profiles';
213
214        // The option update failed
215        if ( $failure ) {
216            $this->push( "failed_$key", array( 'category' => $category ) );
217            return;
218        }
219
220        // The option value changed
221        if ( $value !== $old_value ) {
222            if ( 'other_profiles' === $key ) {
223                $this->push_other_social_profiles( $key, $value, $old_value, $category );
224                return;
225            }
226
227            // The option was set for the first time
228            if ( $this->is_param_empty( $old_value ) ) {
229                $this->push( "set_$key", array( 'category' => $category ) );
230                return;
231            }
232
233            // The option was updated
234            $this->push( "changed_$key", array( 'category' => $category ) );
235        }
236    }
237
238
239    /**
240     * A method used to (maybe) push the other_profiles-related event to the queue.
241     *
242     * @param string $key       The option key (other_profiles)
243     * @param array  $new_value The array of new social profiles
244     * @param array  $old_value The array of old social profiles
245     * @param string $category  The category of the event
246     *
247     * @return void
248     */
249    private function push_other_social_profiles( $key, $new_value, $old_value, $category ) {
250        // The option was set for the first time
251        if ( $this->is_param_empty( $old_value ) ) {
252            $this->push( "set_$key", array( 'category' => $category ) );
253            return;
254        }
255
256        $changed_profiles = \array_map(
257            function ( $value ) {
258                return $this->get_base_url( \wp_unslash( $value ) );
259            },
260            $new_value
261        );
262
263        // The option was updated
264        $data = array(
265            'category' => $category,
266            'data'     => array(
267                'label_key' => $key,
268                'new_value' => $changed_profiles,
269            ),
270        );
271
272        $this->push( 'changed_other_profiles', $data );
273    }
274
275    /**
276     * Maps the param names to the names used for Hiive events tracking.
277     *
278     * @param array $params      The params to map.
279     * @param array $map         The map to use.
280     * @param array $skip_fields The fields to skip.
281     *
282     * @return array The mapped params.
283     */
284    private function map_params_names_to_hiive_names( $params, $map, $skip_fields = array() ) {
285        $mapped_params = array();
286
287        foreach ( $params as $param_name => $param_value ) {
288            if ( in_array( $param_name, $skip_fields, true ) ) {
289                continue;
290            }
291
292            $new_name                   = $map[ $param_name ];
293            $mapped_params[ $new_name ] = $param_value;
294        }
295
296        return $mapped_params;
297    }
298
299    /**
300     * Maps the names of the params which failed the update to the names used for Hiive events tracking.
301     *
302     * @param array $failures    The params names to map.
303     * @param array $map         The map to use.
304     * @param array $skip_fields The fields to skip.
305     *
306     * @return array The mapped params names.
307     */
308    private function map_failures_to_hiive_names( $failures, $map, $skip_fields = array() ) {
309        $mapped_failures = array();
310
311        foreach ( $failures as $failed_field_name ) {
312            if ( in_array( $failed_field_name, $skip_fields, true ) ) {
313                continue;
314            }
315
316            $mapped_failures[] = $map[ $failed_field_name ];
317        }
318
319        return $mapped_failures;
320    }
321
322    /**
323     * Checks whether a param is empty.
324     *
325     * @param mixed $param The param to check.
326     *
327     * @return bool Whether the param is empty.
328     */
329    private function is_param_empty( $param ) {
330        if ( is_array( $param ) ) {
331            return ( count( $param ) === 0 );
332        }
333
334        return ( strlen( $param ) === 0 );
335    }
336
337    /**
338     * Gets the base url of a given url.
339     *
340     * @param string $url The url.
341     *
342     * @return string The base url.
343     */
344    private function get_base_url( $url ) {
345        $parts = \parse_url( $url );
346
347        return $parts['scheme'] . '://' . $parts['host'];
348    }
349
350    /**
351     * Removes multiple occurences of other_social_urls from the failures array
352     *
353     * @param array $failures The failures array
354     *
355     * @return array The cleaned failures array
356     */
357    private function clean_social_profiles_failures( $failures ) {
358        $cleaned_failures             = array();
359        $other_social_profiles_failed = false;
360
361        foreach ( $failures as $failure ) {
362            if ( strpos( $failure, 'other_social_urls' ) === 0 ) {
363                $other_social_profiles_failed = true;
364                continue;
365            }
366            $cleaned_failures[] = $failure;
367        }
368
369        if ( $other_social_profiles_failed ) {
370            $cleaned_failures[] = 'other_social_urls';
371        }
372
373        return $cleaned_failures;
374    }
375
376    /**
377     * Yoast SEO database upgrade.
378     *
379     * @param string|null $previous_version The previous version of Yoast SEO.
380     *
381     * @return void
382     */
383    public function database_upgrade( $previous_version ) {
384
385        if ( ! $previous_version ) {
386            $previous_version = '';
387        }
388
389        $data = array(
390            'category' => 'yoast_event',
391            'data'     => array(
392                'previous_version' => $previous_version,
393                'new_version'      => WPSEO_VERSION,
394            ),
395        );
396
397        $this->push(
398            'database_upgrade',
399            $data
400        );
401    }
402
403    /**
404     * Yoast SEO option updated.
405     *
406     * @param mixed  $old_value The old value.
407     * @param mixed  $new_value The new value.
408     * @param string $option    The option name.
409     *
410     * @return void
411     */
412    public function option_updated( $old_value, $new_value, $option ) {
413
414        $modified_values = array();
415        $original_values = array();
416
417        if ( is_array( $old_value ) && is_array( $new_value ) ) {
418            foreach ( $new_value as $key => $value ) {
419
420                if ( ! array_key_exists( $key, $old_value ) ) {
421                    $modified_values[ $key ] = $value;
422                    continue;
423                }
424                $old_val     = $old_value[ $key ];
425                $has_changed = ( is_array( $value ) || is_object( $value ) )
426                    ? json_encode( $value ) !== json_encode( $old_val )
427                    : $value !== $old_val;
428
429                if ( $has_changed ) {
430                    $modified_values[ $key ] = $value;
431                    $original_values[ $key ] = $old_val;
432                }
433            }
434        } elseif ( $old_value !== $new_value ) {
435            $modified_values = $new_value;
436            $original_values = $old_value;
437        }
438
439        if ( ! empty( $modified_values ) ) {
440            $data = array(
441                'category' => 'yoast_event',
442                'data'     => array(
443                    'old_value' => $original_values,
444                    'new_value' => $modified_values,
445                ),
446            );
447                $this->push(
448                    $option,
449                    $data
450                );
451        }
452    }
453}