Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
61.76% covered (warning)
61.76%
21 / 34
80.00% covered (warning)
80.00%
8 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
Monitor
65.62% covered (warning)
65.62%
21 / 32
80.00% covered (warning)
80.00%
8 / 10
28.74
0.00% covered (danger)
0.00%
0 / 1
 init
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 add_standalone_module
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 activate_module
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 delete_all_notifications_copies
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 delete_notifications_copy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 notifications_copy_option_name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 save_notifications_copy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 set_notifications
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 user_receives_notifications
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 get_last_downtime
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Downtime monitoring, which WordPress.com runs for connected sites with the `monitor` module on.
4 *
5 * @package automattic/jetpack-monitor
6 */
7
8namespace Automattic\Jetpack\Monitor;
9
10use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11use Jetpack_IXR_Client;
12use Jetpack_Options;
13use WP_Error;
14
15if ( ! defined( 'ABSPATH' ) ) {
16    exit( 0 );
17}
18
19/**
20 * Keeps the `monitor` module's per-user email alerts in step with WordPress.com.
21 */
22class Monitor {
23
24    const PACKAGE_VERSION = '0.1.0';
25
26    /**
27     * Module slug.
28     *
29     * @var string
30     */
31    const MODULE = 'monitor';
32
33    /**
34     * Prefix of the per-user option holding a local copy of that user's email choice.
35     *
36     * @var string
37     */
38    const NOTIFICATIONS_OPTION_PREFIX = 'monitor_receive_notifications';
39
40    /**
41     * Wire the hooks, and make the module available to plugins other than Jetpack. Safe to call more than once.
42     *
43     * @return void
44     */
45    public static function init() {
46        add_filter( 'jetpack_get_available_standalone_modules', array( __CLASS__, 'add_standalone_module' ) );
47        add_action( 'jetpack_activate_module_monitor', array( __CLASS__, 'activate_module' ) );
48        add_action( 'jetpack_unlinked_user', array( __CLASS__, 'delete_notifications_copy' ) );
49        add_action( 'deleted_user', array( __CLASS__, 'delete_notifications_copy' ) );
50        add_action( 'jetpack_site_before_disconnected', array( __CLASS__, 'delete_all_notifications_copies' ) );
51    }
52
53    /**
54     * Add `monitor` to the modules a standalone plugin can switch on.
55     *
56     * @param string[] $modules Module slugs.
57     * @return string[]
58     */
59    public static function add_standalone_module( $modules ) {
60        return array_unique( array_merge( (array) $modules, array( self::MODULE ) ) );
61    }
62
63    /**
64     * Subscribe the user turning Monitor on to its emails, when they have a WordPress.com account linked.
65     *
66     * @return void
67     */
68    public static function activate_module() {
69        if ( ( new Connection_Manager() )->is_user_connected() ) {
70            // A failure doesn't block activation: dropping any stale copy makes the next settings read ask WordPress.com.
71            if ( is_wp_error( self::set_notifications( true ) ) ) {
72                self::delete_notifications_copy( get_current_user_id() );
73            }
74        }
75    }
76
77    /**
78     * Forget every linked user's saved email choice, while a site disconnect can still say who was linked.
79     *
80     * @return void
81     */
82    public static function delete_all_notifications_copies() {
83        $user_tokens = Jetpack_Options::get_option( 'user_tokens' );
84        foreach ( array_keys( is_array( $user_tokens ) ? $user_tokens : array() ) as $user_id ) {
85            self::delete_notifications_copy( $user_id );
86        }
87    }
88
89    /**
90     * Forget a user's saved email choice, which belongs to the WordPress.com account they had linked.
91     *
92     * @param int $user_id User ID.
93     * @return void
94     */
95    public static function delete_notifications_copy( $user_id ) {
96        delete_option( self::notifications_copy_option_name( $user_id ) );
97    }
98
99    /**
100     * Name of the option holding a user's local copy of their email choice.
101     *
102     * @param int $user_id User ID.
103     * @return string
104     */
105    public static function notifications_copy_option_name( $user_id ) {
106        return self::NOTIFICATIONS_OPTION_PREFIX . (int) $user_id;
107    }
108
109    /**
110     * Save the current user's email choice locally.
111     *
112     * @param bool $enabled Whether the current user gets the emails.
113     * @return void
114     */
115    public static function save_notifications_copy( $enabled ) {
116        // An integer, because update_option() won't create an option whose value is `false`.
117        update_option( self::notifications_copy_option_name( get_current_user_id() ), (int) (bool) $enabled );
118    }
119
120    /**
121     * Subscribe or unsubscribe the current user from the emails on WordPress.com, and save the choice locally.
122     *
123     * @param bool $enabled Whether to send the emails.
124     * @return true|WP_Error
125     */
126    public static function set_notifications( $enabled ) {
127        $xml = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
128        $xml->query( 'jetpack.monitor.setNotifications', (bool) $enabled );
129
130        if ( $xml->isError() ) {
131            return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage(), array( 'status' => 400 ) );
132        }
133
134        self::save_notifications_copy( $enabled );
135        return true;
136    }
137
138    /**
139     * Ask WordPress.com whether the current user gets the emails.
140     *
141     * @return mixed|WP_Error WordPress.com's answer.
142     */
143    public static function user_receives_notifications() {
144        $xml = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
145        $xml->query( 'jetpack.monitor.isUserInNotifications' );
146
147        if ( $xml->isError() ) {
148            return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage(), array( 'status' => 400 ) );
149        }
150        return $xml->getResponse();
151    }
152
153    /**
154     * When the site last went down, also saved in the `monitor_last_downtime` transient for ten minutes.
155     *
156     * @return string|WP_Error Date in `Y-m-d H:i:s` format.
157     */
158    public static function get_last_downtime() {
159        $xml = new Jetpack_IXR_Client();
160        $xml->query( 'jetpack.monitor.getLastDowntime' );
161
162        if ( $xml->isError() ) {
163            return new WP_Error( 'monitor-downtime', $xml->getErrorMessage() );
164        }
165
166        set_transient( 'monitor_last_downtime', $xml->getResponse(), 10 * MINUTE_IN_SECONDS );
167        return $xml->getResponse();
168    }
169}