Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.92% covered (success)
94.92%
56 / 59
100.00% covered (success)
100.00%
5 / 5
CRAP
n/a
0 / 0
wpcom_editor_welcome_guides_meta_key
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
wpcom_editor_welcome_guides_current_editor_scope
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
wpcom_editor_welcome_guides_record_suppressed
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
wpcom_editor_welcome_guides_filter_preferences
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
13
wpcom_editor_welcome_guides_record_preferences
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
12
1<?php
2/**
3 * Once-per-user editor welcome guides: merges the guides WordPress.com records as dismissed
4 * for the user into each site's editor preferences, and reports newly dismissed ones back.
5 *
6 * @package automattic/jetpack-mu-wpcom
7 */
8
9declare( strict_types = 1 );
10
11/**
12 * The current site's editor preferences meta key.
13 *
14 * @return string
15 */
16function wpcom_editor_welcome_guides_meta_key(): string {
17    global $wpdb;
18
19    return $wpdb->get_blog_prefix() . 'persisted_preferences';
20}
21
22/**
23 * The preferences scope of the editor screen being loaded.
24 *
25 * @return string|null The scope, or null when the request is not an editor screen.
26 */
27function wpcom_editor_welcome_guides_current_editor_scope(): ?string {
28    global $pagenow;
29
30    $scopes = array(
31        'post.php'        => 'core/edit-post',
32        'post-new.php'    => 'core/edit-post',
33        'site-editor.php' => 'core/edit-site',
34        'widgets.php'     => 'core/edit-widgets',
35        'customize.php'   => 'core/customize-widgets',
36    );
37
38    return is_string( $pagenow ) ? $scopes[ $pagenow ] ?? null : null;
39}
40
41/**
42 * Records that a guide was kept from opening because the user dismissed it on another site.
43 *
44 * @param string $scope Preferences scope.
45 * @param string $guide Guide key.
46 * @return void
47 */
48function wpcom_editor_welcome_guides_record_suppressed( string $scope, string $guide ): void {
49    // The preferences are read several times per request; count each guide once.
50    static $recorded = array();
51
52    if ( isset( $recorded[ "$scope/$guide" ] ) ) {
53        return;
54    }
55    $recorded[ "$scope/$guide" ] = true;
56
57    Automattic\Jetpack\Jetpack_Mu_Wpcom\Common\wpcom_record_tracks_event(
58        'wpcom_editor_welcome_guide_suppressed',
59        array(
60            'scope'    => $scope,
61            'guide'    => $guide,
62            'platform' => ( new Automattic\Jetpack\Status\Host() )->is_wpcom_simple() ? 'simple' : 'atomic',
63        )
64    );
65}
66
67/**
68 * When the editor preferences are read, marks the guides the user already dismissed
69 * on another site as dismissed on this one too.
70 *
71 * @param mixed  $value    Value another filter already supplied, or null.
72 * @param int    $user_id  User ID.
73 * @param string $meta_key Meta key being read.
74 * @return mixed The adjusted preferences wrapped in an array, or `$value` unchanged.
75 */
76function wpcom_editor_welcome_guides_filter_preferences( $value, $user_id, $meta_key ) {
77    static $is_reading = false;
78
79    if ( null !== $value || $is_reading || wpcom_editor_welcome_guides_meta_key() !== $meta_key ) {
80        return $value;
81    }
82
83    /**
84     * Filters the guides the user has dismissed on any site.
85     *
86     * @param array $dismissed Map of preferences scope to guide keys.
87     * @param int   $user_id   User ID.
88     */
89    $dismissed = apply_filters( 'wpcom_editor_welcome_guides_dismissed', array(), $user_id );
90    if ( empty( $dismissed ) || ! is_array( $dismissed ) ) {
91        return $value;
92    }
93
94    $is_reading  = true;
95    $preferences = get_user_meta( $user_id, $meta_key, true );
96    $is_reading  = false;
97
98    if ( ! is_array( $preferences ) ) {
99        $preferences = array();
100    }
101
102    // Only a guide of the editor being loaded, for the user loading it, would have opened.
103    $editor_scope = (int) $user_id === get_current_user_id() ? wpcom_editor_welcome_guides_current_editor_scope() : null;
104
105    $changed = false;
106    foreach ( $dismissed as $scope => $guides ) {
107        foreach ( (array) $guides as $guide ) {
108            if ( false !== ( $preferences[ $scope ][ $guide ] ?? null ) ) {
109                $preferences[ $scope ][ $guide ] = false;
110                $changed                         = true;
111
112                if ( $scope === $editor_scope ) {
113                    wpcom_editor_welcome_guides_record_suppressed( $scope, (string) $guide );
114                }
115            }
116        }
117    }
118
119    if ( $changed ) {
120        // Newer than the browser's localStorage copy, so the editor uses this one.
121        $preferences['_modified'] = gmdate( 'Y-m-d\TH:i:s.v\Z' );
122    }
123
124    // get_user_meta( $user_id, $meta_key, true ) returns the first element of a filtered array,
125    // so without this wrap the editor would get $preferences[0], which does not exist.
126    return array( $preferences );
127}
128add_filter( 'get_user_metadata', 'wpcom_editor_welcome_guides_filter_preferences', 1, 3 );
129
130/**
131 * Reports the guides turned off in the editor preferences just saved.
132 *
133 * @param int    $meta_id  Meta ID.
134 * @param int    $user_id  User ID.
135 * @param string $meta_key Meta key.
136 * @param mixed  $value    Meta value.
137 * @return void
138 */
139function wpcom_editor_welcome_guides_record_preferences( $meta_id, $user_id, $meta_key, $value ) {
140    if ( wpcom_editor_welcome_guides_meta_key() !== $meta_key || ! is_array( $value ) ) {
141        return;
142    }
143
144    $dismissed = array();
145    foreach ( $value as $scope => $preferences ) {
146        if ( ! is_string( $scope ) || ! str_starts_with( $scope, 'core/' ) || ! is_array( $preferences ) ) {
147            continue;
148        }
149
150        foreach ( $preferences as $key => $preference ) {
151            if ( is_string( $key ) && str_starts_with( $key, 'welcomeGuide' ) && false === $preference ) {
152                $dismissed[ $scope ][] = $key;
153            }
154        }
155    }
156
157    if ( empty( $dismissed ) ) {
158        return;
159    }
160
161    /**
162     * Fires when the user has turned welcome guides off.
163     *
164     * @param int   $user_id   User ID.
165     * @param array $dismissed Map of preferences scope to guide keys.
166     */
167    do_action( 'wpcom_editor_welcome_guides_dismiss', $user_id, $dismissed );
168}
169add_action( 'added_user_meta', 'wpcom_editor_welcome_guides_record_preferences', 10, 4 );
170add_action( 'updated_user_meta', 'wpcom_editor_welcome_guides_record_preferences', 10, 4 );