Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 1808
0.00% covered (danger)
0.00%
0 / 119
CRAP
0.00% covered (danger)
0.00%
0 / 1
vaultpress_admin_missing_autoloader
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
VaultPress
0.00% covered (danger)
0.00%
0 / 1748
0.00% covered (danger)
0.00%
0 / 118
433622
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
42
 init
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 register
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 activate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 deactivate
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 upgrade
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
42
 get_option
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
210
 update_option
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 delete_option
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 update_options
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 admin_init
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 admin_head
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
132
 admin_menu
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 load_menu
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 styles
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
30
 toolbar
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 get_messages
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 server_url
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 connect_notice
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
12
 activated_notice
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 error_notice
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
42
 ui
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 ui_render
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
72
 ui_load
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 1
342
 ui_register
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
2
 ui_masthead
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 ui_footer
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
6
 ui_main
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 ui_fatal_error
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 ui_message
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
42
 render_notice
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 ui_delete_vp_settings_button
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
12
 ui_logo
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 get_config
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 get_option_name_ignore
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 get_post_meta_name_ignore
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 get_should_ignore_files
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 option_handler
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
72
 comment_action_handler
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 theme_action_handler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 upload_handler
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 plugin_action_handler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 userid_action_handler
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 term_handler
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 term_taxonomy_handler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 term_taxonomies_handler
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 term_relationship_handler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 term_relationships_handler
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 set_object_terms_handler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 usermeta_action_handler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 post_action_handler
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 link_action_handler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 commentmeta_insert_handler
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 commentmeta_modification_handler
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 postmeta_insert_handler
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 postmeta_modification_handler
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 postmeta_action_handler
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 woocommerce_tax_rate_handler
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 woocommerce_order_item_handler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 woocommerce_order_item_meta_handler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 woocommerce_attribute_handler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 generic_change_handler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 block_change_handler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 verify_table
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
42
 record_table
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 get_last_table
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 is_write_query
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 get_table_from_query
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
42
 table_notify_columns
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
6
 ai_ping_next
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 ai_ping_insert
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 allow_ai_pings
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 ai_ping_queue_size
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 ai_ping_get
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 ai_ping_queue_delete
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 request_firewall_update
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
110
 update_firewall
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 update_plan_settings
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
30
 check_connection
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 1
182
 get_login_tokens
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 add_js_token
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 1
20
 authenticate
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
72
 parse_request
0.00% covered (danger)
0.00%
0 / 334
0.00% covered (danger)
0.00%
0 / 1
27722
 _fix_ixr_null_to_string
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 is_localhost
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 contact_service
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
240
 validate_api_signature
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
240
 ip_in_cidr
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 ip_in_cidrs
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 check_firewall
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
420
 looks_like_ip_list
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 do_c_block_firewall
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 validate_ip_address
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
240
 sign_string
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 can_use_openssl
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 response
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
156
 reset_pings
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 add_ping
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
420
 do_pings
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
210
 resolve_content_dir
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 resolve_upload_path
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 load_first
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 is_multisite
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 is_main_site
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 is_registered
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 clear_connection
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 site_url
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
72
 sync_jetpack_options
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
30
 add_to_jetpack_options_whitelist
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 updated_auto_register_option
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
72
 add_global_actions_and_filters
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 add_admin_actions_and_filters
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 add_listener_actions_and_filters
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 1
20
 add_woocommerce_actions
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 add_vp_required_filters
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 get_jetpack_email
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
 get_key_via_jetpack
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
 register_via_jetpack
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Plugin Name: VaultPress
4 * Plugin URI: http://vaultpress.com/?utm_source=plugin-uri&amp;utm_medium=plugin-description&amp;utm_campaign=1.0
5 * Description: Protect your content, themes, plugins, and settings with <strong>realtime backup</strong> and <strong>automated security scanning</strong> from <a href="http://vaultpress.com/?utm_source=wp-admin&amp;utm_medium=plugin-description&amp;utm_campaign=1.0" rel="nofollow">VaultPress</a>. Activate, enter your registration key, and never worry again. <a href="http://vaultpress.com/help/?utm_source=wp-admin&amp;utm_medium=plugin-description&amp;utm_campaign=1.0" rel="nofollow">Need some help?</a>
6 * Version: 4.0.7
7 * Author: Automattic
8 * Author URI: http://vaultpress.com/?utm_source=author-uri&amp;utm_medium=plugin-description&amp;utm_campaign=1.0
9 * License: GPL2+
10 * Text Domain: vaultpress
11 * Domain Path: /languages/
12 *
13 * @package automattic/vaultpress
14 */
15
16// don't call the file directly.
17defined( 'ABSPATH' ) || die( 0 );
18
19define( 'VAULTPRESS__MINIMUM_PHP_VERSION', '7.4' );
20define( 'VAULTPRESS__VERSION', '4.0.7' );
21define( 'VAULTPRESS__PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
22
23/**
24 * Load all the packages.
25 *
26 * We want to fail gracefully if `composer install` has not been executed yet, so we are checking for the autoloader.
27 * If the autoloader is not present, let's log the failure, pause VaultPress, and display a nice admin notice.
28 */
29$loader = VAULTPRESS__PLUGIN_DIR . 'vendor/autoload_packages.php';
30
31if ( is_readable( $loader ) ) {
32    require $loader;
33} else {
34    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
35        error_log(
36            wp_kses(
37                __( 'Your installation of VaultPress is incomplete. If you installed it from GitHub, please run <code>composer install</code>.', 'vaultpress' ),
38                array( 'code' => true )
39            )
40        );
41    }
42
43    // Add a red bubble notification to My Jetpack if the installation is bad.
44    add_filter(
45        'my_jetpack_red_bubble_notification_slugs',
46        function ( $slugs ) {
47            $slugs['vaultpress-plugin-bad-installation'] = array(
48                'data' => array(
49                    'plugin' => 'VaultPress',
50                ),
51            );
52
53            return $slugs;
54        }
55    );
56
57    /**
58     * Outputs an admin notice for folks running VaultPress without having run `composer install`.
59     */
60    function vaultpress_admin_missing_autoloader() {
61        if ( get_current_screen()->id !== 'plugins' ) {
62            return;
63        }
64
65        $message = wp_kses(
66            __( 'Your installation of VaultPress is incomplete. If you installed it from GitHub, please run <code>composer install</code>.', 'vaultpress' ),
67            array( 'code' => true )
68        );
69        wp_admin_notice(
70            $message,
71            array(
72                'type'        => 'error',
73                'dismissible' => true,
74            )
75        );
76    }
77    add_action( 'admin_notices', 'vaultpress_admin_missing_autoloader' );
78    return;
79}
80
81/**
82 * Main VaultPress class.
83 */
84class VaultPress {
85    var $option_name          = 'vaultpress';
86    var $auto_register_option = 'vaultpress_auto_register';
87    var $db_version           = 4;
88    var $plugin_version       = VAULTPRESS__VERSION;
89
90    /**
91     * Server URL.
92     *
93     * @var ?string
94     */
95    private $server_url;
96
97    /**
98     * Options.
99     *
100     * @var array
101     */
102    public $options;
103
104    /**
105     * Blog ID.
106     *
107     * @var int
108     */
109    public $options_blog_id;
110
111    function __construct() {
112        register_activation_hook( __FILE__, array( $this, 'activate' ) );
113        register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) );
114
115        $this->options_blog_id = get_current_blog_id();
116        $options = get_option( $this->option_name );
117        if ( !is_array( $options ) )
118            $options = array();
119
120        $defaults = array(
121            'db_version'            => 0,
122            'key'                   => '',
123            'secret'                => '',
124            'connection'            => false,
125            'service_ips_cidr'      => false
126        );
127
128        $this->options = wp_parse_args( $options, $defaults );
129        $this->reset_pings();
130
131        $this->upgrade();
132
133        $this->add_global_actions_and_filters();
134
135        if ( is_admin() ) {
136            $this->add_admin_actions_and_filters();
137        }
138
139        if ( $this->is_registered() ) {
140            $do_not_backup = $this->get_option( 'do_not_backup' ) || $this->get_option( 'do_not_send_backup_pings' );
141            if ( $do_not_backup )
142                $this->add_vp_required_filters();
143            else
144                $this->add_listener_actions_and_filters();
145        }
146    }
147
148    static function &init() {
149        static $instance = false;
150
151        if ( !$instance ) {
152            $instance = new VaultPress();
153        }
154
155        return $instance;
156    }
157
158    static function register( $registration_key ) {
159        $vp = self::init();
160
161        $nonce = wp_create_nonce( 'vp_register_' . $registration_key );
162        $args = array( 'registration_key' =>  $registration_key, 'nonce' => $nonce );
163        $response = $vp->contact_service( 'register', $args );
164
165        // Check for an error
166        if ( ! empty( $response['faultCode'] ) )
167            return new WP_Error( $response['faultCode'], $response['faultString'] );
168
169        // Validate result
170        if ( empty( $response['key'] ) || empty( $response['secret'] ) || empty( $response['nonce'] ) || $nonce != $response['nonce'] )
171            return new WP_Error( 1, __( 'There was a problem trying to register your VaultPress subscription.' ) );
172
173        // Store the result, force a connection test.
174        $vp->update_option( 'key', $response['key'] );
175        $vp->update_option( 'secret', $response['secret'] );
176        $vp->check_connection( true );
177
178        return true;
179    }
180
181    function activate( $network_wide ) {
182        $type = $network_wide ? 'network' : 'single';
183        $this->update_option( 'activated', $type );
184
185        // force a connection check after an activation
186        $this->clear_connection();
187
188        if ( get_option( 'vaultpress_auto_connect' ) ) {
189            $this->register_via_jetpack( true );
190        }
191    }
192
193    function deactivate() {
194        if ( $this->is_registered() )
195            $this->contact_service( 'plugin_status', array( 'vp_plugin_status' => 'deactivated' ) );
196    }
197
198    function upgrade() {
199        $current_db_version = $this->get_option( 'db_version' );
200
201        if ( $current_db_version < 1 ) {
202            $this->options['connection']  = get_option( 'vaultpress_connection' );
203            $this->options['key']         = get_option( 'vaultpress_key' );
204            $this->options['secret']      = get_option( 'vaultpress_secret' );
205            $this->options['service_ips'] = get_option( 'vaultpress_service_ips' );
206
207            // remove old options
208            $old_options = array(
209                'vaultpress_connection',
210                'vaultpress_hostname',
211                'vaultpress_key',
212                'vaultpress_secret',
213                'vaultpress_service_ips',
214                'vaultpress_timeout',
215                'vp_allow_remote_execution',
216                'vp_debug_request_signing',
217                'vp_disable_firewall',
218            );
219
220            foreach ( $old_options as $option )
221                delete_option( $option );
222
223            $this->options['db_version'] = $this->db_version;
224            $this->update_options();
225        }
226
227        if ( $current_db_version < 2 ) {
228            $this->delete_option( 'timeout' );
229            $this->delete_option( 'disable_firewall' );
230            $this->update_option( 'db_version', $this->db_version );
231            $this->clear_connection();
232        }
233
234        if ( $current_db_version < 3 ) {
235            $this->update_firewall();
236            $this->update_option( 'db_version', $this->db_version );
237            $this->clear_connection();
238        }
239
240        if ( $current_db_version < 4 ) {
241            $this->update_firewall();
242            $this->update_option( 'db_version', $this->db_version );
243            $this->clear_connection();
244        }
245    }
246
247    function get_option( $key ) {
248        if ( 'hostname' == $key ) {
249            if ( defined( 'VAULTPRESS_HOSTNAME' ) )
250                return VAULTPRESS_HOSTNAME;
251            else
252                return 'vaultpress.com';
253        }
254
255        if ( 'timeout' == $key ) {
256            if ( defined( 'VAULTPRESS_TIMEOUT' ) )
257                return VAULTPRESS_TIMEOUT;
258            else
259                return 60;
260        }
261
262        if ( 'disable_firewall' == $key ) {
263            if ( defined( 'VAULTPRESS_DISABLE_FIREWALL' ) )
264                return VAULTPRESS_DISABLE_FIREWALL;
265            else
266                return false;
267        }
268
269        if ( ( 'key' == $key || 'secret' == $key ) && empty( $this->options[$key] ) ) {
270            return '';
271        }
272
273        // allow_forwarded_for can be overrided by config, or stored in or out of the vp option
274        if ( 'allow_forwarded_for' === $key ) {
275            if ( defined( 'ALLOW_FORWARDED_FOR' ) ) {
276                return ALLOW_FORWARDED_FOR;
277            }
278
279            $standalone_option = get_option( 'vaultpress_allow_forwarded_for' );
280            if ( ! empty( $standalone_option ) ) {
281                return $standalone_option;
282            }
283        }
284
285        if ( isset( $this->options[$key] ) )
286            return $this->options[$key];
287
288        return false;
289    }
290
291    function update_option( $key, $value ) {
292        if ( 'allow_forwarded_for' === $key ) {
293            update_option( 'vaultpress_allow_forwarded_for', $value );
294
295            if ( isset( $this->options[ $key ] ) ) {
296                unset( $this->options[ $key ] );
297                $this->update_options();
298            }
299            return;
300        }
301
302        $this->options[$key] = $value;
303        $this->update_options();
304    }
305
306    function delete_option( $key ) {
307        if ( 'allow_forwarded_for' === $key ) {
308            delete_option( 'vaultpress_allow_forwarded_for' );
309        }
310
311        unset( $this->options[$key] );
312        $this->update_options();
313    }
314
315    function update_options() {
316        // Avoid overwriting the VaultPress option if current blog_id has changed since reading it
317        if ( get_current_blog_id() !== $this->options_blog_id ) {
318            return;
319        }
320
321        update_option( $this->option_name, $this->options );
322    }
323
324    function admin_init() {
325        if ( !current_user_can( 'manage_options' ) )
326            return;
327
328        load_plugin_textdomain( 'vaultpress', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
329    }
330
331    function admin_head() {
332        if ( ! current_user_can( 'manage_options' ) ) {
333            return;
334        }
335
336        // Array of hooks where we want to hook our notices.
337        $notice_hooks = array( 'user_admin_notices' );
338
339        /*
340         * In the VaultPress dashboard, move the notices.
341         */
342        $screen = get_current_screen();
343        if (
344            ! is_null( $screen )
345            && in_array(
346                $screen->id,
347                array( 'jetpack_page_vaultpress', 'toplevel_page_vaultpress' ),
348                true
349            )
350        ) {
351            $notice_hooks[] = 'vaultpress_notices';
352        } else {
353            $notice_hooks[] = 'admin_notices';
354        }
355
356        if ( $activated = $this->get_option( 'activated' ) ) {
357            if ( 'network' == $activated ) {
358                add_action( 'network_admin_notices', array( $this, 'activated_notice' ) );
359            } else {
360                foreach ( $notice_hooks as $filter ) {
361                    add_action( $filter, array( $this, 'activated_notice' ) );
362                }
363            }
364        }
365
366        // ask the user to connect their site w/ VP
367        if ( !$this->is_registered() ) {
368            foreach ( $notice_hooks as $filter ) {
369                add_action( $filter, array( $this, 'connect_notice' ) );
370            }
371
372        // if we have an error make sure to let the user know about it
373        } else {
374            $error_code = $this->get_option( 'connection_error_code' );
375             if ( ! empty( $error_code ) ) {
376                foreach ( $notice_hooks as $filter ) {
377                    add_action( $filter, array( $this, 'error_notice' ) );
378                }
379            }
380        }
381    }
382
383    function admin_menu() {
384        // if Jetpack is loaded then we need to wait for that menu to be added
385        if ( class_exists( 'Jetpack' ) )
386            add_action( 'jetpack_admin_menu', array( $this, 'load_menu' ) );
387        else
388            $this->load_menu();
389    }
390
391    function load_menu() {
392        if ( class_exists( 'Jetpack' ) ) {
393            /*
394             * Register through Admin_Menu when it is available. A bare add_submenu_page() runs
395             * at priority 5, well before Admin_Menu sorts at 1000, so the item would land above
396             * every sorted entry instead of in the alphabetical run.
397             */
398            if ( class_exists( '\Automattic\Jetpack\Admin_UI\Admin_Menu' ) ) {
399                $hook = \Automattic\Jetpack\Admin_UI\Admin_Menu::add_menu( 'VaultPress', 'VaultPress', 'manage_options', 'vaultpress', array( $this, 'ui' ) );
400            } else {
401                $hook = add_submenu_page( 'jetpack', 'VaultPress', 'VaultPress', 'manage_options', 'vaultpress', array( $this, 'ui' ) );
402            }
403        } else {
404            $hook = add_menu_page( 'VaultPress', 'VaultPress', 'manage_options', 'vaultpress', array( $this, 'ui' ), 'div' );
405        }
406
407        add_action( "load-$hook", array( $this, 'ui_load' ) );
408        add_action( 'admin_print_styles', array( $this, 'styles' ) );
409    }
410
411    function styles() {
412        if ( !current_user_can( 'manage_options' ) || !is_admin() )
413            return;
414
415        wp_enqueue_style( 'vaultpress-nav', plugins_url( '/nav-styles.css', __FILE__ ), false, date( 'Ymd' ) );
416
417        if ( isset( $_GET['page'] ) && 'vaultpress' == $_GET['page'] )
418            wp_enqueue_style( 'vaultpress', plugins_url( '/styles.css', __FILE__ ), false, date( 'Ymd' ) );
419    }
420
421    // display a security threat notice if one exists
422    function toolbar( $wp_admin_bar ) {
423
424        if ( !current_user_can( 'manage_options' ) )
425            return;
426
427        $messages = $this->get_messages();
428        if ( !empty( $messages['security_notice_count'] ) ) {
429            $count = (int)$messages['security_notice_count'];
430            if ( $count > 0 ) {
431                $count = number_format( $count, 0 );
432                $wp_admin_bar->add_node( array(
433                    'id' => 'vp-notice',
434                    'title' => '<span class="ab-icon"></span>' .
435                        sprintf( _n( '%s Security Threat', '%s Security Threats', $count , 'vaultpress'), $count ),
436                    'parent' => 'top-secondary',
437                    'href' => sprintf( 'https://dashboard.vaultpress.com/%d/security/', $messages['site_id'] ),
438                    'meta'  => array(
439                        'title' => __( 'Visit VaultPress Security' , 'vaultpress'),
440                        'onclick' => 'window.open( this.href ); return false;',
441                        'class' => 'error'
442                    ),
443                ) );
444            }
445        }
446    }
447
448    /**
449     * Get messages from the VP servers
450     *
451     * @param bool $force_reload Whether to force a reload of the messages.
452     * @return array The messages.
453     */
454    function get_messages( $force_reload = false ) {
455        $last_contact = $this->get_option( 'messages_last_contact' );
456
457        // only run the messages check every 30 minutes
458        if ( ( time() - (int) $last_contact ) > 1800 || $force_reload ) {
459            $response = $this->contact_service( 'messages', array() );
460
461            // Only process if we got a valid string response
462            if ( is_string( $response ) && ! empty( $response ) ) {
463                $messages = base64_decode( $response ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
464                $messages = unserialize( $messages ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
465                $this->update_option( 'messages_last_contact', time() );
466                $this->update_option( 'messages', $messages );
467            } else {
468                // If we got an error (array) or false/empty, fall back to cached messages
469                $messages = $this->get_option( 'messages' );
470            }
471        } else {
472            $messages = $this->get_option( 'messages' );
473        }
474
475        return $messages;
476    }
477
478    function server_url() {
479        if ( ! isset( $this->server_url ) ) {
480            $scheme = is_ssl() ? 'https' : 'http';
481            $this->server_url = sprintf( '%s://%s/', $scheme, $this->get_option( 'hostname' ) );
482        }
483
484        return $this->server_url;
485    }
486
487    /**
488     * Show message if plugin is activated but not connected to VaultPress.
489     */
490    function connect_notice() {
491        $screen = get_current_screen();
492
493        /**
494         * Do not display any error message if we don't have any info about the page.
495         */
496        if ( is_null( $screen ) ) {
497            return;
498        }
499
500        /**
501         * Only display errors on specific pages:
502         * - the main dashboard.
503         * - the Jetpack dashboard.
504         * - the plugins screen.
505         */
506
507        if (
508        in_array(
509            $screen->id,
510            array(
511                'dashboard',
512                'toplevel_page_jetpack',
513                'plugins',
514            ),
515            true
516        )
517        ) {
518            $message = sprintf(
519                wp_kses(
520                /* translators: URLs to VaultPress' dashboard page. */
521                    __( 'To back up and secure your site, enter your registration key. <a href="%1$s">Register VaultPress or purchase a plan.</a>', 'vaultpress' ),
522                    array(
523                        'a' => array(
524                            'href' => array(),
525                        ),
526                    )
527                ),
528                admin_url( 'admin.php?page=vaultpress' )
529            );
530            $this->ui_message( $message, 'notice', __( 'VaultPress needs your attention!', 'vaultpress' ) );
531        }
532    }
533
534    // show message after activation
535    function activated_notice() {
536        if ( 'network' == $this->get_option( 'activated' ) ) {
537            $message = sprintf(
538                __( 'Each site will need to be registered with VaultPress separately. You can purchase new keys from your <a href="%1$s">VaultPress&nbsp;Dashboard</a>.', 'vaultpress' ),
539                'https://dashboard.vaultpress.com/'
540            );
541            $this->ui_message( $message, 'activated', __( 'VaultPress has been activated across your network!', 'vaultpress' ) );
542
543        // key and secret already exist in db
544        } elseif ( $this->is_registered() ) {
545            if ( $this->check_connection() ) {
546            }
547        }
548
549        $this->delete_option( 'activated' );
550    }
551
552    /**
553     * Display an error notice when something is wrong with our VaultPress installation.
554     */
555    public function error_notice() {
556        $error_message = $this->get_option( 'connection_error_message' );
557
558        // link to the VaultPress page if we're not already there.
559        if (
560            ! isset( $_GET['page'] )
561            || 'vaultpress' != $_GET['page']
562        ) {
563            $error_message .= sprintf(
564                ' <a href="%s">%s</a>',
565                admin_url( 'admin.php?page=vaultpress' ),
566                esc_html__( 'Visit the VaultPress page', 'vaultpress' )
567            );
568        }
569
570        $screen = get_current_screen();
571
572        /*
573         * Do not display any error message if we don't have a message,
574         * or have no info about the page.
575         */
576        if ( is_null( $screen ) || empty( $error_message ) ) {
577            return;
578        }
579
580        /*
581         * Only display errors on specific pages:
582         * - the main dashboard.
583         * - the VaultPress and Jetpack dashboards.
584         * - the plugins screen.
585         */
586        if (
587            in_array(
588                $screen->id,
589                array(
590                    'dashboard',
591                    'toplevel_page_jetpack',
592                    'jetpack_page_vaultpress',
593                    'toplevel_page_vaultpress',
594                    'plugins',
595                ),
596                true
597            )
598        ) {
599            $this->ui_message( $error_message, 'error' );
600        }
601    }
602
603    /**
604     * Adds the main wrappers and the header, and defers controls to ui_render to decide which view to render.
605     */
606    function ui() {
607        $ui_state = $this->ui_render();
608        ?>
609            <div id="jp-plugin-container">
610                <?php $this->ui_masthead( $ui_state[ 'dashboard_link' ] ); ?>
611                <div class="vp-wrap">
612                    <?php
613                    /**
614                     * Allow the display of custom notices.
615                     *
616                     * @since 2.0.0
617                     */
618                    do_action( 'vaultpress_notices' );
619                    ?>
620                    <?php echo $ui_state[ 'ui' ]; // This content is sanitized when it's produced. ?>
621                </div>
622                <?php $this->ui_footer(); ?>
623            </div>
624        <?php
625    }
626
627    /**
628     * Decides which UI view to render and executes it.
629     *
630     * @return array $args {
631     *     An array of options to render the dashboard.
632     *
633     *     @type string $ui             Dashboard markup.
634     *     @type string $dashboard_link Whether to show the link to the VaultPress dashboard.
635     * }
636     */
637    function ui_render() {
638        ob_start();
639
640        if ( $this->is_localhost() ) {
641            $this->update_option( 'connection', time() );
642            $this->update_option( 'connection_error_code', 'error_localhost' );
643            $this->update_option(
644                'connection_error_message',
645                esc_html__( 'Hostnames such as localhost or 127.0.0.1 cannot be reached by vaultpress.com and will not work with the service. Sites must be publicly accessible in order to work with VaultPress.', 'vaultpress' )
646            );
647            $this->error_notice();
648            return array( 'ui' => ob_get_clean(), 'dashboard_link' => false );
649        }
650
651        if ( ! empty( $_GET[ 'error' ] ) ) {
652            $this->error_notice();
653            $this->clear_connection();
654        }
655
656        if ( ! $this->is_registered() ) {
657            $this->ui_register();
658            return array( 'ui' => ob_get_clean(), 'dashboard_link' => false );
659        }
660
661        $status = $this->contact_service( 'status' );
662        if ( ! $status ) {
663            $error_code = $this->get_option( 'connection_error_code' );
664            if ( 0 == $error_code ) {
665                $this->ui_fatal_error();
666            } else {
667                $this->ui_register();
668            }
669            return array( 'ui' => ob_get_clean(), 'dashboard_link' => 0 != $error_code );
670        }
671
672        $ticker = $this->contact_service( 'ticker' );
673        if ( is_array( $ticker ) && isset( $ticker[ 'faultCode' ] ) ) {
674            $this->error_notice();
675            $this->ui_register();
676            return array( 'ui' => ob_get_clean(), 'dashboard_link' => true );
677        }
678
679        $this->ui_main();
680        return array( 'ui' => ob_get_clean(), 'dashboard_link' => true );
681    }
682
683    function ui_load() {
684        if ( ! current_user_can( 'manage_options' ) ) {
685            return;
686        }
687
688        if ( isset( $_POST['action'] ) && 'delete-vp-settings' == $_POST['action'] ) {
689            check_admin_referer( 'delete_vp_settings' );
690
691            $ai_ping_queue_size = $this->ai_ping_queue_size();
692            if ( ! empty( $ai_ping_queue_size->option_count ) && $ai_ping_queue_size->option_count > 1 ) {
693                $this->ai_ping_queue_delete();
694            }
695
696            delete_option( $this->option_name );
697            delete_option( 'vaultpress_service_ips_external_cidr' );
698            delete_option( '_vp_signatures' );
699            delete_option( '_vp_config_option_name_ignore' );
700            delete_option( '_vp_config_post_meta_name_ignore' );
701            delete_option( '_vp_config_should_ignore_files' );
702            delete_option( '_vp_current_scan' );
703            delete_option( 'vaultpress_auto_register' );
704
705            wp_redirect( admin_url( 'admin.php?page=vaultpress&delete-vp-settings=1' ) );
706            exit( 0 );
707        }
708
709        // run code that might be updating the registration key
710        if ( isset( $_POST['action'] ) && 'register' == $_POST['action'] ) {
711            check_admin_referer( 'vaultpress_register' );
712
713            // reset the connection info so messages don't cross
714            $this->clear_connection();
715
716            // if registering via Jetpack, get a key...
717            if ( isset( $_POST['key_source'] ) && 'jetpack' === $_POST['key_source'] ) {
718                $registration_key = $this->get_key_via_jetpack();
719                if ( is_wp_error( $registration_key ) ) {
720                    $this->update_option( 'connection_error_code', -2 );
721                    $this->update_option(
722                        'connection_error_message',
723                        sprintf( __('<strong>Failed to register VaultPress via Jetpack</strong>: %s. If you&rsquo;re still having issues please <a href="%1$s">contact the VaultPress&nbsp;Safekeepers</a>.', 'vaultpress' ),
724                            esc_html( $registration_key->get_error_message() ), 'http://vaultpress.com/contact/' )
725                    );
726                    wp_redirect( admin_url( 'admin.php?page=vaultpress&error=true' ) );
727                    exit( 0 );
728                }
729            } else {
730            $registration_key = trim( $_POST[ 'registration_key' ] );
731            }
732
733            if ( empty( $registration_key ) ) {
734                $this->update_option( 'connection_error_code', 1 );
735                $this->update_option(
736                    'connection_error_message',
737                    sprintf(
738                        __( '<strong>That\'s not a valid registration key.</strong> Head over to the <a href="%1$s" title="Sign in to your VaultPress Dashboard">VaultPress&nbsp;Dashboard</a> to find your key.', 'vaultpress' ),
739                        'https://dashboard.vaultpress.com/'
740                    )
741                );
742                wp_redirect( admin_url( 'admin.php?page=vaultpress&error=true' ) );
743                exit( 0 );
744            }
745
746            // try to register the plugin
747            $nonce = wp_create_nonce( 'vp_register_' . $registration_key );
748            $args = array( 'registration_key' =>  $registration_key, 'nonce' => $nonce );
749            $response = $this->contact_service( 'register', $args );
750
751            // we received an error from the VaultPress servers
752            if ( !empty( $response['faultCode'] ) ) {
753                $this->update_option( 'connection_error_code',    $response['faultCode'] );
754                $this->update_option( 'connection_error_message', $response['faultString'] );
755                wp_redirect( admin_url( 'admin.php?page=vaultpress&error=true' ) );
756                exit( 0 );
757            }
758
759            // make sure the returned data looks valid
760            if ( empty( $response['key'] ) || empty( $response['secret'] ) || empty( $response['nonce'] ) || $nonce != $response['nonce'] ) {
761                $this->update_option( 'connection_error_code', 1 );
762                $this->update_option( 'connection_error_message', sprintf( __( 'There was a problem trying to register your subscription. Please try again. If you&rsquo;re still having issues please <a href="%1$s">contact the VaultPress&nbsp;Safekeepers</a>.', 'vaultpress' ), 'http://vaultpress.com/contact/' ) );
763                wp_redirect( admin_url( 'admin.php?page=vaultpress&error=true' ) );
764                exit( 0 );
765            }
766
767            // need to update these values in the db so the servers can try connecting to the plugin
768            $this->update_option( 'key', $response['key'] );
769            $this->update_option( 'secret', $response['secret'] );
770            if ( $this->check_connection( true ) ) {
771                wp_redirect( admin_url( 'admin.php?page=vaultpress' ) );
772                exit( 0 );
773            }
774
775            // reset the key and secret
776            $this->update_option( 'key', '' );
777            $this->update_option( 'secret', '' );
778            wp_redirect( admin_url( 'admin.php?page=vaultpress&error=true' ) );
779            exit( 0 );
780        }
781    }
782
783    function ui_register() {
784        ?>
785            <div class="vp-notice__wide">
786                <div class="dops-card">
787                    <img src="<?php echo esc_url( plugins_url( 'images/security.svg', __FILE__ ) ); ?>" alt="VaultPress">
788                    <h2><?php _e( 'The VaultPress plugin requires a subscription.', 'vaultpress' ); ?></h2>
789                    <p><?php _e( 'Get realtime backups, automated security scanning, and support from WordPress&nbsp;experts.', 'vaultpress' ); ?></p>
790                    <a class="dops-button is-primary" href="https://vaultpress.com/plans/?utm_source=plugin-unregistered&amp;utm_medium=view-plans-and-pricing&amp;utm_campaign=1.0-plugin" target="_blank" rel="noopener noreferrer"><?php _e( 'View plans and pricing', 'vaultpress' ); ?></a>
791                </div>
792            </div>
793
794            <div class="jp-dash-section-header">
795                <div class="jp-dash-section-header__label">
796                    <h2 class="jp-dash-section-header__name">
797                        <?php esc_html_e( 'Management', 'vaultpress' ); ?>
798                    </h2>
799                </div>
800            </div>
801
802            <div class="vp-row">
803                <div class="vp-col">
804                    <div class="dops-card dops-section-header is-compact">
805                        <?php esc_html_e( 'Registration key', 'vaultpress' ) ?>
806                    </div>
807                    <div class="dops-card">
808                        <form method="post" action="">
809                            <fieldset>
810                                <p>
811                                    <?php esc_html_e( 'Paste your registration key&nbsp;below:', 'vaultpress' ); ?>
812                                </p>
813                                <p>
814                                    <textarea class="dops-textarea" placeholder="<?php esc_attr_e( __( 'Enter your key here...', 'vaultpress' ) ); ?>" name="registration_key"></textarea>
815                                </p>
816                                <button class="dops-button is-compact"><?php _e( 'Register ', 'vaultpress' ); ?></button>
817                                <input type="hidden" name="action" value="register" />
818                                <?php wp_nonce_field( 'vaultpress_register' ); ?>
819                            </fieldset>
820                        </form>
821                    </div>
822                </div>
823                <div class="vp-col">
824                    <?php $this->ui_delete_vp_settings_button(); ?>
825                </div>
826            </div>
827        <?php
828    }
829
830    /**
831     * Renders the top header.
832     *
833     * @param bool $show_nav Whether to show navigation.
834     */
835    function ui_masthead( $show_nav = true ) {
836        ?>
837        <div class="jp-masthead">
838            <div class="jp-masthead__inside-container">
839                <div class="jp-masthead__logo-container">
840                    <a class="jp-masthead__logo-link" href="https://vaultpress.com">
841                        <img src="<?php echo esc_url( plugins_url( 'images/vaultpress.svg', __FILE__ ) ); ?>" alt="VaultPress">
842                    </a>
843                </div>
844                <?php if ( $show_nav ) : ?>
845                    <div class="jp-masthead__nav">
846                        <div class="dops-button-group">
847                            <a href="https://dashboard.vaultpress.com" class="dops-button is-compact" target="_blank" rel="noopener noreferrer">
848                                <?php _e( 'Visit Dashboard', 'vaultpress' ); ?>
849                            </a>
850                        </div>
851                    </div>
852                <?php endif; ?>
853            </div>
854        </div>
855        <?php
856    }
857
858    /**
859     * Renders the footer.
860     */
861    function ui_footer() {
862        ?>
863        <div class="jp-footer">
864            <div class="jp-footer__a8c-attr-container">
865                <svg role="img" class="jp-footer__a8c-attr" x="0" y="0" viewBox="0 0 935 38.2" enable-background="new 0 0 935 38.2" aria-labelledby="a8c-svg-title"><title id="a8c-svg-title">An Automattic Airline</title>
866                    <path d="M317.1 38.2c-12.6 0-20.7-9.1-20.7-18.5v-1.2c0-9.6 8.2-18.5 20.7-18.5 12.6 0 20.8 8.9 20.8 18.5v1.2C337.9 29.1 329.7 38.2 317.1 38.2zM331.2 18.6c0-6.9-5-13-14.1-13s-14 6.1-14 13v0.9c0 6.9 5 13.1 14 13.1s14.1-6.2 14.1-13.1V18.6zM175 36.8l-4.7-8.8h-20.9l-4.5 8.8h-7L157 1.3h5.5L182 36.8H175zM159.7 8.2L152 23.1h15.7L159.7 8.2zM212.4 38.2c-12.7 0-18.7-6.9-18.7-16.2V1.3h6.6v20.9c0 6.6 4.3 10.5 12.5 10.5 8.4 0 11.9-3.9 11.9-10.5V1.3h6.7V22C231.4 30.8 225.8 38.2 212.4 38.2zM268.6 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H268.6zM397.3 36.8V8.7l-1.8 3.1 -14.9 25h-3.3l-14.7-25 -1.8-3.1v28.1h-6.5V1.3h9.2l14 24.4 1.7 3 1.7-3 13.9-24.4h9.1v35.5H397.3zM454.4 36.8l-4.7-8.8h-20.9l-4.5 8.8h-7l19.2-35.5h5.5l19.5 35.5H454.4zM439.1 8.2l-7.7 14.9h15.7L439.1 8.2zM488.4 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H488.4zM537.3 6.8v30h-6.7v-30h-15.5V1.3h37.7v5.5H537.3zM569.3 36.8V4.6c2.7 0 3.7-1.4 3.7-3.4h2.8v35.5L569.3 36.8 569.3 36.8zM628 11.3c-3.2-2.9-7.9-5.7-14.2-5.7 -9.5 0-14.8 6.5-14.8 13.3v0.7c0 6.7 5.4 13 15.3 13 5.9 0 10.8-2.8 13.9-5.7l4 4.2c-3.9 3.8-10.5 7.1-18.3 7.1 -13.4 0-21.6-8.7-21.6-18.3v-1.2c0-9.6 8.9-18.7 21.9-18.7 7.5 0 14.3 3.1 18 7.1L628 11.3zM321.5 12.4c1.2 0.8 1.5 2.4 0.8 3.6l-6.1 9.4c-0.8 1.2-2.4 1.6-3.6 0.8l0 0c-1.2-0.8-1.5-2.4-0.8-3.6l6.1-9.4C318.7 11.9 320.3 11.6 321.5 12.4L321.5 12.4z"></path>
867                    <path d="M37.5 36.7l-4.7-8.9H11.7l-4.6 8.9H0L19.4 0.8H25l19.7 35.9H37.5zM22 7.8l-7.8 15.1h15.9L22 7.8zM82.8 36.7l-23.3-24 -2.3-2.5v26.6h-6.7v-36H57l22.6 24 2.3 2.6V0.8h6.7v35.9H82.8z"></path>
868                    <path d="M719.9 37l-4.8-8.9H694l-4.6 8.9h-7.1l19.5-36h5.6l19.8 36H719.9zM704.4 8l-7.8 15.1h15.9L704.4 8zM733 37V1h6.8v36H733zM781 37c-1.8 0-2.6-2.5-2.9-5.8l-0.2-3.7c-0.2-3.6-1.7-5.1-8.4-5.1h-12.8V37H750V1h19.6c10.8 0 15.7 4.3 15.7 9.9 0 3.9-2 7.7-9 9 7 0.5 8.5 3.7 8.6 7.9l0.1 3c0.1 2.5 0.5 4.3 2.2 6.1V37H781zM778.5 11.8c0-2.6-2.1-5.1-7.9-5.1h-13.8v10.8h14.4c5 0 7.3-2.4 7.3-5.2V11.8zM794.8 37V1h6.8v30.4h28.2V37H794.8zM836.7 37V1h6.8v36H836.7zM886.2 37l-23.4-24.1 -2.3-2.5V37h-6.8V1h6.5l22.7 24.1 2.3 2.6V1h6.8v36H886.2zM902.3 37V1H935v5.6h-26v9.2h20v5.5h-20v10.1h26V37H902.3z"></path>
869                </svg>
870            </div>
871            <ul class="jp-footer__links">
872                <li class="jp-footer__link-item">
873                    <a href="https://vaultpress.com" class="jp-footer__link" title="<?php esc_attr_e( 'VaultPress version', 'vaultpress' ) ?>" target="_blank" rel="noopener noreferrer">
874                        <?php printf( 'VaultPress %s', $this->plugin_version ); ?>
875                    </a>
876                </li>
877                <li class="jp-footer__link-item">
878                    <a href="https://wordpress.com/tos/" class="jp-footer__link" title="<?php esc_attr_e( 'Terms of service', 'vaultpress' ) ?>" target="_blank" rel="noopener noreferrer">
879                        <?php esc_html_e( 'Terms', 'vaultpress' ); ?>
880                    </a>
881                </li>
882            </ul>
883            <div class="jp-power">
884                <a
885                    href="<?php echo class_exists( 'Jetpack_Admin_Page' ) ? esc_url( admin_url( 'admin.php?page=jetpack' ) ) : 'https://jetpack.com' ?>"
886                    class="jp-power__text-link"
887                    target="_blank"
888                    rel="noopener noreferrer"
889                >
890                    <span class="jp-power__text"><?php esc_html_e( 'Powered by', 'vaultpress') ?></span> <?php echo $this->ui_logo(); ?>
891                </a>
892            </div>
893        </div>
894        <?php
895    }
896
897    function ui_main() {
898        $response = base64_decode( $this->contact_service( 'plugin_ui' ) );
899        echo $response;
900        $this->ui_delete_vp_settings_button();
901    }
902
903    function ui_fatal_error() {
904        $this->render_notice(
905            sprintf(
906                '<strong>' . __( 'We can\'t connect to %1$s.', 'vaultpress' ) . '</strong><br/>' .
907                __( 'Please make sure that your website is accessible via the Internet. Please contact the VaultPress support if you still have issues.' ),
908                esc_html( $this->get_option( 'hostname' ) )
909            ),
910            'is-warning',
911            array(
912                'label' => __( 'Contact support' ),
913                'url' => 'https://vaultpress.com/contact/',
914            )
915        );
916    }
917
918    function ui_message( $message, $type = 'notice', $heading = '' ) {
919        $level = 'is-warning';
920        if ( empty( $heading ) ) {
921            switch ( $type ) {
922                case 'error':
923                    $level = 'is-error';
924                    $heading = __( 'Oops... there seems to be a problem.', 'vaultpress' );
925                    break;
926
927                case 'success':
928                    $level = 'is-success';
929                    $heading = __( 'Yay! Things look good.', 'vaultpress' );
930                    break;
931
932                default:
933                    $heading = __( 'VaultPress needs your attention!', 'vaultpress' );
934                    break;
935            }
936        }
937
938        $classes = in_array( get_current_screen()->parent_base, array( 'jetpack', 'vaultpress' ), true )
939            ? ''
940            : "notice notice-$type";
941
942        $this->render_notice(
943            "<strong>$heading</strong><br/>$message",
944            $level,
945            array(),
946            $classes
947        );
948    }
949
950    /**
951     * Renders a notice. Can have
952     *
953     * @param string $content Notice main content.
954     * @param string $level Can be is-info, is-warning, is-error. By default, it's is-info.
955     * @param array  $action  {
956     *     Arguments to display a linked action button in the notice.
957     *
958     *     @type string $label The action button label.
959     *     @type string $url   The action button link.
960     * }
961     * @param string $classes This is added as a CSS class to the root node. Useful to pass WP core classes for notices.
962     */
963    function render_notice( $content, $level = 'is-info', $action = array(), $classes = '' ) {
964        $allowed_html = array(
965            'a' => array( 'href' => true, 'target' => 'blank', 'rel' => 'noopener noreferrer' ),
966            'br' => true,
967            'strong' => true,
968        );
969        ?>
970            <div class="dops-notice vp-notice <?php echo esc_attr( "$level $classes" ) ?>">
971                <span class="dops-notice__icon-wrapper">
972                    <svg class="gridicon gridicons-info dops-notice__icon" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
973                        <path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z" />
974                    </svg>
975                </span>
976                <span class="dops-notice__content">
977                    <span class="dops-notice__text"><?php echo wp_kses( $content, $allowed_html ) ?></span>
978                </span>
979                <?php if ( ! empty( $action ) ) : ?>
980                    <a class="dops-notice__action" href="<?php echo esc_attr( $action['url'] ) ?>" target="_blank" rel="noopener noreferrer">
981                        <span><?php echo esc_html( $action['label'] ) ?></span>
982                    </a>
983                <?php endif; ?>
984            </div>
985        <?php
986    }
987
988    function ui_delete_vp_settings_button() {
989        ?>
990        <div class="dops-card dops-section-header is-compact">
991            <?php _e( 'Reset settings', 'vaultpress' ); ?>
992        </div>
993        <?php
994        if ( isset( $_GET['delete-vp-settings'] ) && 1 == (int) $_GET['delete-vp-settings'] ) {
995            ?>
996            <div class="dops-card">
997                <p><?php esc_html_e( 'All VaultPress settings have been deleted.', 'vaultpress' ); ?></p>
998            </div>
999            <?php
1000        } else {
1001            ?>
1002            <div class="dops-card">
1003                <p><?php esc_html_e( 'Click this button to reset all VaultPress options in the database. You can try this if VaultPress is reporting a connection error.', 'vaultpress' ); ?></p>
1004                <p><strong>
1005                <?php
1006                printf(
1007                    wp_kses(
1008                        /* translators: URLs to VaultPress dashboard. */
1009                        __( 'Warning: this button will unregister VaultPress and disconnect it from your site. If you intend on registering the plugin again, you can find your registration key <a href="%1$s" target="_blank" rel="noopener noreferrer">here</a>.', 'vaultpress' ),
1010                        array(
1011                            'a' => array(
1012                                'href'   => array(),
1013                                'target' => array(),
1014                                'rel'    => array(),
1015                            ),
1016                        )
1017                    ),
1018                    'https://dashboard.vaultpress.com/account/'
1019                );
1020                ?>
1021                </strong></p>
1022                <form
1023                    onsubmit="return confirm( '<?php esc_html_e( 'Do you really want to reset all options?', 'vaultpress' ) ?>' );"
1024                    method="post"
1025                    action="">
1026                    <button class="dops-button is-scary is-compact"><?php esc_html_e( 'Delete all settings', 'vaultpress' ); ?></button>
1027                    <input type="hidden" name="action" value="delete-vp-settings"/>
1028                    <?php wp_nonce_field( 'delete_vp_settings' ); ?>
1029                </form>
1030            </div>
1031            <?php
1032        }
1033    }
1034
1035    /**
1036     * Render the Jetpack logo
1037     */
1038    function ui_logo() {
1039        if ( ! class_exists( 'Jetpack_Logo' ) ) {
1040            require_once VAULTPRESS__PLUGIN_DIR . 'class-jetpack-logo.php';
1041        }
1042        $jetpack_logo = new Jetpack_Logo();
1043
1044        return $jetpack_logo->output();
1045    }
1046
1047    function get_config( $key ) {
1048        $val = get_option( $key );
1049        if ( $val )
1050            return $val;
1051        switch( $key ) {
1052            case '_vp_config_option_name_ignore':
1053                $val = $this->get_option_name_ignore( true );
1054                update_option( '_vp_config_option_name_ignore', $val );
1055                break;
1056            case '_vp_config_post_meta_name_ignore':
1057                $val = $this->get_post_meta_name_ignore( true );
1058                update_option( '_vp_config_post_meta_name_ignore', $val );
1059                break;
1060            case '_vp_config_should_ignore_files':
1061                $val = $this->get_should_ignore_files( true );
1062                update_option( '_vp_config_should_ignore_files', $val );
1063                break;
1064        }
1065        return $val;
1066    }
1067
1068    // Option name patterns to ignore
1069    function get_option_name_ignore( $return_defaults = false ) {
1070        $defaults = array(
1071            'vaultpress',
1072            'cron',
1073            'wpsupercache_gc_time',
1074            'rewrite_rules',
1075            'akismet_spam_count',
1076            '/_transient_/',
1077            '/^_vp_/',
1078        );
1079        if ( $return_defaults )
1080            return $defaults;
1081        $ignore_names = $this->get_config( '_vp_config_option_name_ignore' );
1082        return array_unique( array_merge( $defaults, $ignore_names ) );
1083    }
1084
1085    // post meta name patterns to ignore
1086    function get_post_meta_name_ignore( $return_defaults = false ) {
1087        $defaults = array(
1088            'pvc_views'
1089        );
1090        if ( $return_defaults )
1091            return $defaults;
1092        $ignore_names = $this->get_config( '_vp_config_post_meta_name_ignore' );
1093        return array_unique( array_merge( $defaults, $ignore_names ) );
1094    }
1095
1096    // file name patterns to ignore
1097    function get_should_ignore_files( $return_defaults = false ) {
1098        $defaults = array();
1099        if ( $return_defaults )
1100            return $defaults;
1101        $ignore_names = (array) $this->get_config( '_vp_config_should_ignore_files' );
1102        return array_unique( array_merge( $defaults, $ignore_names ) );
1103    }
1104
1105    ###
1106    ### Section: Backup Notification Hooks
1107    ###
1108
1109    // Handle Handle Notifying VaultPress of Options Activity At this point the options table has already been modified
1110    //
1111    // Note: we handle deleted, instead of delete because VaultPress backs up options by name (which are unique,) that
1112    // means that we do not need to resolve an id like we would for, say, a post.
1113    function option_handler( $option_name ) {
1114        global $wpdb;
1115        // Step 1 -- exclusionary rules, don't send these options to vaultpress, because they
1116        // either change constantly and/or are inconsequential to the blog itself and/or they
1117        // are specific to the VaultPress plugin process and we want to avoid recursion
1118        $should_ping = true;
1119        $ignore_names = $this->get_option_name_ignore();
1120        foreach( (array)$ignore_names as $val ) {
1121            if ( $val[0] == '/' ) {
1122                if ( preg_match( $val, $option_name ) )
1123                    $should_ping = false;
1124            } else {
1125                if ( $val == $option_name )
1126                    $should_ping = false;
1127            }
1128            if ( !$should_ping )
1129                break;
1130        }
1131        if ( $should_ping )
1132            $this->add_ping( 'db', array( 'option' => $option_name ) );
1133
1134        // Step 2 -- If WordPress is about to kick off a some "cron" action, we need to
1135        // flush vaultpress, because the "remote" cron threads done via http fetch will
1136        // be happening completely inside the window of this thread.  That thread will
1137        // be expecting touched and accounted for tables
1138        if ( $option_name == '_transient_doing_cron' )
1139            $this->do_pings();
1140
1141        return $option_name;
1142    }
1143
1144    // Handle Notifying VaultPress of Comment Activity
1145    function comment_action_handler( $comment_id ) {
1146        if ( !is_array( $comment_id ) ) {
1147            if ( wp_get_comment_status( $comment_id ) != 'spam' )
1148                $this->add_ping( 'db', array( 'comment' => $comment_id ) );
1149        } else {
1150            foreach ( $comment_id as $id ) {
1151                if ( wp_get_comment_status( $comment_id ) != 'spam' )
1152                    $this->add_ping( 'db', array( 'comment' => $id) );
1153            }
1154        }
1155    }
1156
1157    // Handle Notifying VaultPress of Theme Switches
1158    function theme_action_handler( $theme ) {
1159        $this->add_ping( 'themes', array( 'theme' => get_option( 'stylesheet' ) ) );
1160    }
1161
1162    // Handle Notifying VaultPress of Upload Activity
1163    function upload_handler( $file ) {
1164        $this->add_ping( 'uploads', array( 'upload' => str_replace( $this->resolve_upload_path(), '', $file['file'] ) ) );
1165        return $file;
1166    }
1167
1168    // Handle Notifying VaultPress of Plugin Activation/Deactivation
1169    function plugin_action_handler( $plugin='' ) {
1170        $this->add_ping( 'plugins', array( 'name' => $plugin ) );
1171    }
1172
1173    // Handle Notifying VaultPress of User Edits
1174    function userid_action_handler( $user_or_id ) {
1175        if ( is_object($user_or_id) )
1176            $userid = intval( $user_or_id->ID );
1177        else
1178            $userid = intval( $user_or_id );
1179        if ( !$userid )
1180            return;
1181        $this->add_ping( 'db', array( 'user' => $userid ) );
1182    }
1183
1184    // Handle Notifying VaultPress of term changes
1185    function term_handler( $term_id, $tt_id=null ) {
1186        $this->add_ping( 'db', array( 'term' => $term_id ) );
1187        if ( $tt_id )
1188            $this->term_taxonomy_handler( $tt_id );
1189    }
1190
1191    // Handle Notifying VaultPress of term_taxonomy changes
1192    function term_taxonomy_handler( $tt_id ) {
1193        $this->add_ping( 'db', array( 'term_taxonomy' => $tt_id ) );
1194    }
1195    // add(ed)_term_taxonomy handled via the created_term hook, the term_taxonomy_handler is called by the term_handler
1196
1197    // Handle Notifying VaultPress of term_taxonomy changes
1198    function term_taxonomies_handler( $tt_ids ) {
1199        foreach( (array)$tt_ids as $tt_id ) {
1200            $this->term_taxonomy_handler( $tt_id );
1201        }
1202    }
1203
1204    // Handle Notifying VaultPress of term_relationship changes
1205    function term_relationship_handler( $object_id, $term_id ) {
1206        $this->add_ping( 'db', array( 'term_relationship' => array( 'object_id' => $object_id, 'term_taxonomy_id' => $term_id ) ) );
1207    }
1208
1209    // Handle Notifying VaultPress of term_relationship changes
1210    function term_relationships_handler( $object_id, $term_ids ) {
1211        foreach ( (array)$term_ids as $term_id ) {
1212            $this->term_relationship_handler( $object_id, $term_id );
1213        }
1214    }
1215
1216    // Handle Notifying VaultPress of term_relationship changes
1217    function set_object_terms_handler( $object_id, $terms, $tt_ids ) {
1218        $this->term_relationships_handler( $object_id, $tt_ids );
1219    }
1220
1221    // Handle Notifying VaultPress of UserMeta changes
1222    function usermeta_action_handler( $umeta_id, $user_id, $meta_key, $meta_value='' ) {
1223        $this->add_ping( 'db', array( 'usermeta' => $umeta_id ) );
1224    }
1225
1226    // Handle Notifying VaultPress of Post Changes
1227    function post_action_handler($post_id) {
1228        if ( current_filter() == 'delete_post' )
1229            return $this->add_ping( 'db', array( 'post' => $post_id ), 'delete_post' );
1230        return $this->add_ping( 'db', array( 'post' => $post_id ), 'edit_post' );
1231    }
1232
1233    // Handle Notifying VaultPress of Link Changes
1234    function link_action_handler( $link_id ) {
1235        $this->add_ping( 'db', array( 'link' => $link_id ) );
1236    }
1237
1238    // Handle Notifying VaultPress of Commentmeta Changes
1239    function commentmeta_insert_handler( $meta_id, $comment_id=null ) {
1240        if ( empty( $comment_id ) || wp_get_comment_status( $comment_id ) != 'spam' )
1241            $this->add_ping( 'db', array( 'commentmeta' => $meta_id ) );
1242    }
1243
1244    function commentmeta_modification_handler( $meta_id, $object_id, $meta_key, $meta_value ) {
1245        if ( !is_array( $meta_id ) )
1246            return $this->add_ping( 'db', array( 'commentmeta' => $meta_id ) );
1247        foreach ( $meta_id as $id ) {
1248            $this->add_ping( 'db', array( 'commentmeta' => $id ) );
1249        }
1250    }
1251
1252    // Handle Notifying VaultPress of PostMeta changes via newfangled metadata functions
1253    function postmeta_insert_handler( $meta_id, $post_id, $meta_key, $meta_value='' ) {
1254        if ( in_array( $meta_key, $this->get_post_meta_name_ignore() ) )
1255            return;
1256
1257        $this->add_ping( 'db', array( 'postmeta' => $meta_id ) );
1258    }
1259
1260    function postmeta_modification_handler( $meta_id, $object_id, $meta_key, $meta_value ) {
1261        if ( in_array( $meta_key, $this->get_post_meta_name_ignore() ) )
1262            return;
1263
1264        if ( !is_array( $meta_id ) )
1265            return $this->add_ping( 'db', array( 'postmeta' => $meta_id ) );
1266        foreach ( $meta_id as $id ) {
1267            $this->add_ping( 'db', array( 'postmeta' => $id ) );
1268        }
1269    }
1270
1271    // Handle Notifying VaultPress of PostMeta changes via old school cherypicked hooks
1272    function postmeta_action_handler( $meta_id, $post_id = null, $meta_key = null ) {
1273        if ( in_array( $meta_key, $this->get_post_meta_name_ignore() ) )
1274            return;
1275
1276        if ( !is_array($meta_id) )
1277            return $this->add_ping( 'db', array( 'postmeta' => $meta_id ) );
1278        foreach ( $meta_id as $id )
1279            $this->add_ping( 'db', array( 'postmeta' => $id ) );
1280    }
1281
1282    // WooCommerce notifications
1283    function woocommerce_tax_rate_handler( $id ) {
1284        $this->generic_change_handler( 'woocommerce_tax_rates', array( 'tax_rate_id' => $id ) );
1285        $this->block_change_handler( 'woocommerce_tax_rate_locations', array( 'tax_rate_id' => $id ) );
1286    }
1287
1288    /**
1289     * Monitor for changes to a Woo order (creation, update, or deletion).
1290     *
1291     * @param int $id Item ID.
1292     */
1293    public function woocommerce_order_item_handler( $id ) {
1294        $this->generic_change_handler( 'woocommerce_order_items', array( 'order_item_id' => $id ) );
1295    }
1296
1297    /**
1298     * Monitor for changes to a Woo order meta (creation, update, or deletion).
1299     *
1300     * @param int $id Item ID.
1301     */
1302    public function woocommerce_order_item_meta_handler( $id ) {
1303        $this->generic_change_handler( 'woocommerce_order_itemmeta', array( 'meta_id' => $id ) );
1304    }
1305
1306    /**
1307     * Monitor for changes to a Woo attribute (creation, update, or deletion).
1308     *
1309     * @param int $id Item ID.
1310     */
1311    public function woocommerce_attribute_handler( $id ) {
1312        $this->generic_change_handler( 'woocommerce_attribute_taxonomies', array( 'attribute_id' => $id ) );
1313    }
1314
1315    function generic_change_handler( $table, $key ) {
1316        $this->add_ping( 'db', array( $table => $key ) );
1317    }
1318
1319    function block_change_handler( $table, $query ) {
1320        $this->add_ping( 'db', array( "bulk~{$table}" => $query ) );
1321    }
1322
1323    function verify_table( $table ) {
1324        global $wpdb;
1325        $status = $wpdb->get_row( $wpdb->prepare( "SHOW TABLE STATUS WHERE Name = %s", $table ) );
1326        if ( !$status || !$status->Update_time || !$status->Comment || $status->Engine != 'MyISAM' )
1327            return true;
1328        if ( preg_match( '/([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2})/', $status->Comment, $m ) )
1329            return ( $m[1] == $status->Update_time );
1330        return false;
1331    }
1332
1333    // Emulate $wpdb->last_table
1334    function record_table( $table ) {
1335        global $vaultpress_last_table;
1336        $vaultpress_last_table = $table;
1337        return $table;
1338    }
1339
1340    // Emulate $wpdb->last_table
1341    function get_last_table() {
1342        global $wpdb, $vaultpress_last_table;
1343        if ( is_object( $wpdb ) && isset( $wpdb->last_table ) )
1344            return $wpdb->last_table;
1345        return $vaultpress_last_table;
1346    }
1347
1348    // Emulate hyperdb::is_write_query()
1349    function is_write_query( $q ) {
1350        $word = strtoupper( substr( trim( $q ), 0, 20 ) );
1351        if ( 0 === strpos( $word, 'SELECT' ) )
1352            return false;
1353        if ( 0 === strpos( $word, 'SHOW' ) )
1354            return false;
1355        if ( 0 === strpos( $word, 'CHECKSUM' ) )
1356            return false;
1357        return true;
1358    }
1359
1360    // Emulate hyperdb::get_table_from_query()
1361    function get_table_from_query( $q ) {
1362        global $wpdb, $vaultpress_last_table;
1363
1364        if ( is_object( $wpdb ) && method_exists( $wpdb, "get_table_from_query" ) )
1365            return $wpdb->get_table_from_query( $q );
1366
1367        // Remove characters that can legally trail the table name
1368        $q = rtrim( $q, ';/-#' );
1369        // allow ( select... ) union [...] style queries. Use the first queries table name.
1370        $q = ltrim( $q, "\t (" );
1371
1372        // Quickly match most common queries
1373        if ( preg_match( '/^\s*(?:'
1374                . 'SELECT.*?\s+FROM'
1375                . '|INSERT(?:\s+IGNORE)?(?:\s+INTO)?'
1376                . '|REPLACE(?:\s+INTO)?'
1377                . '|UPDATE(?:\s+IGNORE)?'
1378                . '|DELETE(?:\s+IGNORE)?(?:\s+FROM)?'
1379                . ')\s+`?(\w+)`?/is', $q, $maybe) )
1380            return $this->record_table($maybe[1] );
1381
1382        // Refer to the previous query
1383        if ( preg_match( '/^\s*SELECT.*?\s+FOUND_ROWS\(\)/is', $q ) )
1384            return $this->get_last_table();
1385
1386        // Big pattern for the rest of the table-related queries in MySQL 5.0
1387        if ( preg_match( '/^\s*(?:'
1388                . '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM'
1389                . '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?'
1390                . '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?'
1391                . '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?'
1392                . '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:\s+FROM)?'
1393                . '|DESCRIBE|DESC|EXPLAIN|HANDLER'
1394                . '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?'
1395                . '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|OPTIMIZE|REPAIR).*\s+TABLE'
1396                . '|TRUNCATE(?:\s+TABLE)?'
1397                . '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?'
1398                . '|ALTER(?:\s+IGNORE)?\s+TABLE'
1399                . '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?'
1400                . '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON'
1401                . '|DROP\s+INDEX.*\s+ON'
1402                . '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE'
1403                . '|(?:GRANT|REVOKE).*ON\s+TABLE'
1404                . '|SHOW\s+(?:.*FROM|.*TABLE)'
1405                . ')\s+`?(\w+)`?/is', $q, $maybe ) )
1406            return $this->record_table( $maybe[1] );
1407
1408        // All unmatched queries automatically fall to the global master
1409        return $this->record_table( '' );
1410    }
1411
1412    function table_notify_columns( $table ) {
1413            $want_cols = array(
1414                // data
1415                'posts'                 => '`ID`',
1416                'users'                 => '`ID`',
1417                'links'                 => '`link_id`',
1418                'options'               => '`option_id`,`option_name`',
1419                'comments'              => '`comment_ID`',
1420                // metadata
1421                'postmeta'              => '`meta_id`',
1422                'commentmeta'           => '`meta_id`',
1423                'usermeta'              => '`umeta_id`',
1424                // taxonomy
1425                'term_relationships'    => '`object_id`,`term_taxonomy_id`',
1426                'term_taxonomy'         => '`term_taxonomy_id`',
1427                'terms'                 => '`term_id`',
1428                // plugin special cases
1429                'wpo_campaign'          => '`id`', // WP-o-Matic
1430                'wpo_campaign_category' => '`id`', // WP-o-Matic
1431                'wpo_campaign_feed'     => '`id`', // WP-o-Matic
1432                'wpo_campaign_post'     => '`id`', // WP-o-Matic
1433                'wpo_campaign_word'     => '`id`', // WP-o-Matic
1434                'wpo_log'               => '`id`', // WP-o-Matic
1435            );
1436            if ( isset( $want_cols[$table] ) )
1437                return $want_cols[$table];
1438            return '*';
1439    }
1440
1441    /**
1442     * Use an option ID to ensure a unique ping ID for the site.
1443     *
1444     * @return  int|false  The new ping number. False, if there was an error.
1445     */
1446    function ai_ping_next() {
1447        global $wpdb;
1448
1449        if ( ! $this->allow_ai_pings() ) {
1450            return false;
1451        }
1452
1453        $name = "_vp_ai_ping";
1454        $wpdb->query( $wpdb->prepare( "DELETE FROM `$wpdb->options` WHERE `option_name` = %s;", $name ) );
1455        $success = $wpdb->query( $wpdb->prepare( "INSERT INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, '', 'no')", $name ) );
1456        if ( ! $success ) {
1457            return false;
1458        }
1459        return $wpdb->insert_id;
1460    }
1461
1462    function ai_ping_insert( $value ) {
1463        if ( ! $this->allow_ai_pings() ) {
1464            return false;
1465        }
1466
1467        $new_id = $this->ai_ping_next();
1468
1469        if ( !$new_id )
1470            return false;
1471        add_option( '_vp_ai_ping_' . $new_id, $value, '', 'no' );
1472    }
1473
1474    function allow_ai_pings() {
1475        static $allow_ai_pings = null;
1476
1477        if ( null === $allow_ai_pings ) {
1478            $queue_size = $this->ai_ping_queue_size();
1479            $size_limit = 50 * 1024 * 1024;
1480            $allow_ai_pings = ( $queue_size->option_count < 100 && $queue_size->option_size < $size_limit );
1481        }
1482
1483        return $allow_ai_pings;
1484    }
1485
1486    function ai_ping_queue_size() {
1487        global $wpdb;
1488        return $wpdb->get_row( "SELECT COUNT(`option_id`) `option_count`, SUM(LENGTH(`option_value`)) `option_size` FROM $wpdb->options WHERE `option_name` LIKE '\_vp\_ai\_ping\_%'" );
1489    }
1490
1491    function ai_ping_get( $num=1, $order='ASC' ) {
1492        global $wpdb;
1493        if ( strtolower($order) != 'desc' )
1494            $order = 'ASC';
1495        else
1496            $order = 'DESC';
1497        return $wpdb->get_results( $wpdb->prepare(
1498            "SELECT * FROM $wpdb->options WHERE `option_name` LIKE '\_vp\_ai\_ping\_%%' ORDER BY `option_id` $order LIMIT %d",
1499            min( 10, max( 1, (int)$num ) )
1500        ) );
1501    }
1502
1503    function ai_ping_queue_delete() {
1504        global $wpdb;
1505
1506        return $wpdb->query( "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE '\_vp\_ai\_ping%'" );
1507    }
1508
1509    function request_firewall_update( $external_services = false ) {
1510        $args     = array( 'timeout' => $this->get_option( 'timeout' ), 'sslverify' => true );
1511        $hostname = $this->get_option( 'hostname' );
1512        $path = $external_services ? 'service-ips-external' : 'service-ips';
1513
1514        $data = false;
1515        $https_error = null;
1516        $retry = 2;
1517        $protocol = 'https';
1518        do {
1519            --$retry;
1520            $args['sslverify'] = 'https' === $protocol;
1521            $r = wp_remote_get( $url=sprintf( "%s://%s/%s?cidr_ranges=1", $protocol, $hostname, $path ), $args );
1522            if ( 200 == wp_remote_retrieve_response_code( $r ) ) {
1523                if ( 99 == $this->get_option( 'connection_error_code' ) )
1524                    $this->clear_connection();
1525                $data = @unserialize( wp_remote_retrieve_body( $r ) );
1526                break;
1527            }
1528            if ( 'https' == $protocol ) {
1529                $https_error = $r;
1530                $protocol = 'http';
1531            }
1532            usleep( 100 );
1533        } while( $retry > 0 );
1534
1535        if ( $https_error != null && ! empty( $data ) ) {
1536            $r_code = wp_remote_retrieve_response_code( $https_error );
1537            if ( 200 != $r_code ) {
1538                $error_message = sprintf( 'Unexpected HTTP response code %s', $r_code );
1539                if ( false === $r_code )
1540                    $error_message = 'Unable to find an HTTP transport that supports SSL verification';
1541                elseif ( is_wp_error( $https_error ) )
1542                    $error_message = $https_error->get_error_message();
1543
1544                $this->update_option( 'connection', time() );
1545                $this->update_option( 'connection_error_code', 99 );
1546                $this->update_option( 'connection_error_message', sprintf( __('Warning: The VaultPress plugin is using an insecure protocol because it cannot verify the identity of the VaultPress server. Please contact your hosting provider, and ask them to check that SSL certificate verification is correctly configured on this server. The request failed with the following error: "%s". If you&rsquo;re still having issues please <a href="%1$s">contact the VaultPress&nbsp;Safekeepers</a>.', 'vaultpress' ), esc_html( $error_message ), 'http://vaultpress.com/contact/' ) );
1547            }
1548        }
1549
1550        return $data;
1551    }
1552
1553    function update_firewall() {
1554        $data = $this->request_firewall_update();
1555        if ( $data ) {
1556            $newval = array( 'updated' => time(), 'data' => $data );
1557            $this->update_option( 'service_ips_cidr', $newval );
1558        }
1559
1560        $external_data = $this->request_firewall_update( true );
1561        if ( $external_data ) {
1562            $external_newval = array( 'updated' => time(), 'data' => $external_data );
1563
1564            delete_option( 'vaultpress_service_ips_external_cidr' );
1565            add_option( 'vaultpress_service_ips_external_cidr', $external_newval, '', 'no' );
1566        }
1567
1568        if ( !empty( $data ) && !empty( $external_data ) )
1569            $data = array_merge( $data, $external_data );
1570
1571        if ( $data ) {
1572            return $data;
1573        } else {
1574            return null;
1575        }
1576    }
1577
1578    // Update local cache of VP plan settings, based on a ping or connection test result
1579    function update_plan_settings( $message ) {
1580        if ( array_key_exists( 'do_backups', $message ) )
1581            $this->update_option( 'do_not_backup', ( false === $message['do_backups'] ) || ( '0' === $message['do_backups'] ) );
1582
1583        if ( array_key_exists( 'do_backup_pings', $message ) )
1584            $this->update_option( 'do_not_send_backup_pings', ( false === $message['do_backup_pings'] ) || ( '0' === $message['do_backup_pings'] ) );
1585    }
1586
1587    function check_connection( $force_check = false ) {
1588        $connection = $this->get_option( 'connection' );
1589
1590        if ( !$force_check && !empty( $connection ) ) {
1591            // already established a connection
1592             if ( 'ok' == $connection )
1593                return true;
1594
1595            // only run the connection check every 5 minutes
1596            if ( ( time() - (int)$connection ) < 300 )
1597                return false;
1598        }
1599
1600        // if we're running a connection test we don't want to run it a second time
1601        $connection_test = $this->get_option( 'connection_test' );
1602        if ( ! empty( $connection_test ) )
1603            return true;
1604
1605        // force update firewall settings
1606        $this->update_firewall();
1607
1608        // Generate a random string for ping-backs to use for identification
1609        $connection_test_key = wp_generate_password( 32, false );
1610        $this->update_option( 'connection_test', $connection_test_key );
1611
1612        // initial connection test to server
1613        $this->delete_option( 'allow_forwarded_for' );
1614        $host = ( ! empty( $_SERVER['HTTP_HOST'] ) ) ? $_SERVER['HTTP_HOST'] : parse_url( $this->site_url(), PHP_URL_HOST );
1615        $connect = $this->contact_service( 'test', array( 'host' => $host, 'uri' => $_SERVER['REQUEST_URI'], 'ssl' => is_ssl() ) );
1616
1617        // we can't see the servers at all
1618        if ( !$connect ) {
1619            $this->update_option( 'connection', time() );
1620            $this->update_option( 'connection_error_code', 0 );
1621            $this->update_option( 'connection_error_message', sprintf( __( 'Cannot connect to the VaultPress servers. Please check that your host allows connecting to external sites and try again. If you&rsquo;re still having issues please <a href="%1$s">contact the VaultPress&nbsp;Safekeepers</a>.', 'vaultpress' ), 'http://vaultpress.com/contact/' ) );
1622
1623            $this->delete_option( 'connection_test' );
1624            return false;
1625        }
1626
1627        // VaultPress gave us a meaningful error
1628        if ( !empty( $connect['faultCode'] ) ) {
1629            $this->update_option( 'connection', time() );
1630            $this->update_option( 'connection_error_code', $connect['faultCode'] );
1631            $this->update_option( 'connection_error_message', $connect['faultString'] );
1632            $this->delete_option( 'connection_test' );
1633            return false;
1634        }
1635
1636        $this->update_plan_settings( $connect );
1637
1638        if ( !empty( $connect['signatures'] ) ) {
1639            delete_option( '_vp_signatures' );
1640            add_option( '_vp_signatures', maybe_unserialize( $connect['signatures'] ), '', 'no' );
1641        }
1642
1643        // test connection between the site and the servers
1644        $connect = (string)$this->contact_service( 'test', array( 'type' => 'connect', 'test_key' => $connection_test_key ) );
1645        if ( 'ok' != $connect ) {
1646            if ( 'error' == $connect ) {
1647                $this->update_option( 'connection_error_code', -1 );
1648                $this->update_option( 'connection_error_message', sprintf( __( 'The VaultPress servers cannot connect to your site. Please check that your site is visible over the Internet and there are no firewall or load balancer settings on your server that might be blocking the communication. If you&rsquo;re still having issues please <a href="%1$s">contact the VaultPress&nbsp;Safekeepers</a>.', 'vaultpress' ), 'http://vaultpress.com/contact/' ) );
1649            } elseif ( !empty( $connect['faultCode'] ) ) {
1650                $this->update_option( 'connection_error_code', $connect['faultCode'] );
1651                $this->update_option( 'connection_error_message', $connect['faultString'] );
1652            }
1653
1654            $this->update_option( 'connection', time() );
1655            $this->delete_option( 'connection_test' );
1656            return false;
1657        }
1658
1659        // successful connection established
1660        $this->update_option( 'connection', 'ok' );
1661        $this->delete_option( 'connection_error_code' );
1662        $this->delete_option( 'connection_error_message' );
1663        $this->delete_option( 'connection_test' );
1664        return true;
1665    }
1666
1667    function get_login_tokens() {
1668        // By default the login token is valid for 30 minutes.
1669        $nonce_life = $this->get_option( 'nonce_life' ) ? $this->get_option( 'nonce_life' ) : 1800;
1670        $salt = wp_salt( 'nonce' ) . md5( $this->get_option( 'secret' ) );
1671        $nonce_life /= 2;
1672
1673        return array(
1674            'previous' => substr( hash_hmac( 'md5', 'vp-login' . ceil( time() / $nonce_life - 1 ), $salt ), -12, 10 ),
1675            'current'  => substr( hash_hmac( 'md5', 'vp-login' . ceil( time() / $nonce_life ), $salt ), -12, 10 ),
1676        );
1677    }
1678    function add_js_token() {
1679        $nonce = $this->get_login_tokens();
1680        $token = $nonce['current'];
1681
1682        // Uglyfies the JS code before sending it to the browser.
1683        $whitelist = array( 'charAt', 'all', 'setAttribute', 'document', 'createElement', 'appendChild', 'input', 'hidden', 'type', 'name', 'value', 'getElementById', 'loginform', '_vp' );
1684        shuffle( $whitelist );
1685        $whitelist = array_flip( $whitelist );
1686
1687        $set = array(
1688            0   => array( '+[]', 'e^e' ),
1689            1   => array( '+!![]', '2>>1', "e[{$whitelist['type']}].charCodeAt(3)>>6" ),
1690            2   => array( '(+!![])<<1', "e[{$whitelist['_vp']}].replace(/_/,'').length" ),
1691            3   => array( "(Math.log(2<<4)+[])[e[{$whitelist['charAt']}]](0)", "e[{$whitelist['_vp']}].length" ),
1692            4   => array( '(+!![])<<2', "e[{$whitelist['input']}].length^1", "e[{$whitelist['name']}].length" ),
1693            5   => array( '((1<<2)+1)', 'parseInt("f",0x10)/3' ),
1694            6   => array( '(7^1)', "e[{$whitelist['hidden']}].length" ),
1695            7   => array( '(3<<1)+1', "e[{$whitelist['hidden']}].length^1" ),
1696            8   => array( '(0x101>>5)', "e[{$whitelist['document']}].length" ),
1697            9   => array( '(0x7^4)*(3+[])', "e[{$whitelist['loginform']}].length", "(1<<e[{$whitelist['_vp']}].length)^1" ),
1698            'a' => array( "(![]+\"\")[e[{$whitelist['charAt']}]](1)", "e[{$whitelist['appendChild']}][e[{$whitelist['charAt']}]](0)", "e[{$whitelist['name']}][e[{$whitelist['charAt']}]](1)" ),
1699            'b' => array( "([]+{})[e[{$whitelist['charAt']}]](2)", "({}+[])[e[{$whitelist['charAt']}]](2)" ),
1700            'c' => array( "([]+{})[e[{$whitelist['charAt']}]](5)", "e[{$whitelist['createElement']}][e[{$whitelist['charAt']}]](0)" ),
1701            'd' => array( "([][0]+\"\")[e[{$whitelist['charAt']}]](2)", "([][0]+[])[e[{$whitelist['charAt']}]](2)" ),
1702            'e' => array( "(!![]+[])[e[{$whitelist['charAt']}]](3)", "(!![]+\"\")[e[{$whitelist['charAt']}]](3)" ),
1703            'f' => array( "(![]+[])[e[{$whitelist['charAt']}]](0)", "([]+![])[e[{$whitelist['charAt']}]](e^e)", "([]+![])[e[{$whitelist['charAt']}]](0)" ),
1704        );
1705
1706        $js_code = <<<JS
1707<script type="text/javascript">
1708/* <![CDATA[ */
1709(function(){
1710    var i,e='%s'.split('|'),_=[%s],s=function(a,b,c){a[b]=c};
1711    if(this[e[{$whitelist['document']}]][e[{$whitelist['all']}]]){
1712        try {
1713            i=this[e[{$whitelist['document']}]][e[{$whitelist['createElement']}]]('<'+e[{$whitelist['input']}]+' '+e[{$whitelist['name']}]+'='+(e[{$whitelist['_vp']}]+(!![]))+' />');
1714        }catch(e){}
1715    }
1716    if(!i){
1717        i=this[e[{$whitelist['document']}]][e[{$whitelist['createElement']}]](e[{$whitelist['input']}]);
1718        s(i,e[{$whitelist['name']}],e[{$whitelist['_vp']}]+(!![]));
1719    }
1720    s(i,e[{$whitelist['type']}],e[{$whitelist['hidden']}]).
1721    s(i,e[{$whitelist['value']}],(%s+""));
1722    try {
1723        var __=this[e[{$whitelist['document']}]][e[{$whitelist['getElementById']}]](e[{$whitelist['loginform']}]);
1724        __[e[{$whitelist['appendChild']}]](i);
1725    } catch(e){}
1726})();
1727/* ]]> */
1728</script>
1729JS;
1730        $chars = array();
1731        for ( $i = 0; $i < strlen( $token ); $i++ ) {
1732            if ( isset( $set[$token[ $i ] ] ) ) {
1733                $k = array_rand( $set[$token[ $i ] ], 1 );
1734                $chars[] = $set[$token[ $i ] ][$k];
1735            } else {
1736                $chars[] = $token[ $i ];
1737            }
1738        }
1739        $random = array_unique( $chars );
1740        shuffle( $random );
1741        $random = array_flip( $random );
1742
1743        foreach( $chars as $i => $v )
1744            $chars[$i] = sprintf( '_[%d]', $random[$v] );
1745
1746        $code = preg_replace(
1747            "#[\n\r\t]#",
1748            '',
1749            sprintf( $js_code,
1750                implode( '|', array_keys( $whitelist ) ),
1751                implode( ',', array_keys( $random ) ),
1752                implode( '+"")+(', $chars )
1753            )
1754        );
1755        echo $code;
1756    }
1757
1758    function authenticate( $user, $username, $password ) {
1759        if ( is_wp_error( $user ) )
1760            return $user;
1761        if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST || defined( 'APP_REQUEST' ) && APP_REQUEST ) {
1762            // Try to log in with the username and password.
1763        }
1764        $retval = $user;
1765        if ( empty( $_POST['_vptrue'] ) || !in_array( $_POST['_vptrue'], $this->get_login_tokens(), true ) )
1766            $retval = new WP_Error( 'invalid_token', __( 'Invalid token. Please try to log in again.' ) );
1767
1768        return $retval;
1769    }
1770
1771    function parse_request( $wp ) {
1772        if ( !isset( $_GET['vaultpress'] ) || $_GET['vaultpress'] !== 'true' )
1773            return $wp;
1774
1775        global $wpdb, $current_blog;
1776
1777        // just in case we have any plugins that decided to spit some data out already...
1778        @ob_end_clean();
1779        // Headers to avoid search engines indexing "invalid api call signature" pages.
1780        if ( !headers_sent() ) {
1781            header( 'X-Robots-Tag: none' );
1782            header( 'X-Robots-Tag: unavailable_after: 1 Oct 2012 00:00:00 PST', false );
1783        }
1784
1785        if ( isset( $_GET['ticker'] ) && function_exists( 'current_user_can' ) && current_user_can( 'manage_options' ) )
1786            die( (string)$this->contact_service( 'ticker' ) );
1787
1788        $_POST = array_map( 'stripslashes_deep', $_POST );
1789
1790        global $wpdb, $bdb, $bfs;
1791        define( 'VAULTPRESS_API', true );
1792
1793        if ( !$this->validate_api_signature() ) {
1794            global $__vp_validate_error;
1795            die( 'invalid api call signature [' . base64_encode( serialize( $__vp_validate_error ) ) . ']' );
1796        }
1797
1798        if ( !empty( $_GET['ge'] ) ) {
1799            // "ge" -- "GET encoding"
1800            if ( '1' === $_GET['ge'] )
1801                $_GET['action'] = base64_decode( $_GET['action'] );
1802            if ( '2' === $_GET['ge'] )
1803                $_GET['action'] = str_rot13( $_GET['action'] );
1804        }
1805
1806        if ( !empty( $_GET['pe'] ) ) {
1807            // "pe" -- POST encoding
1808            if ( '1' === $_GET['pe'] ) {
1809                foreach( $_POST as $idx => $val ) {
1810                    if ( $idx === 'signature' )
1811                        continue;
1812                    $_POST[ base64_decode( $idx ) ] = base64_decode( $val );
1813                    unset( $_POST[$idx] );
1814                }
1815            }
1816            if ( '2' === $_GET['pe'] ) {
1817                foreach( $_POST as $idx => $val ) {
1818                    if ( $idx === 'signature' )
1819                        continue;
1820                    $_POST[ base64_decode( $idx ) ] = str_rot13( $val );
1821                    unset( $_POST[$idx] );
1822                }
1823            }
1824        }
1825
1826        if ( !isset( $bdb ) ) {
1827            require_once __DIR__ . '/class.vaultpress-database.php';
1828            require_once __DIR__ . '/class.vaultpress-filesystem.php';
1829
1830            $bdb = new VaultPress_Database();
1831            $bfs = new VaultPress_Filesystem();
1832        }
1833
1834        header( 'Content-Type: text/plain' );
1835
1836        /*
1837         * general:ping
1838         *
1839         * catchup:get
1840         * catchup:delete
1841         *
1842         * db:tables
1843         * db:explain
1844         * db:cols
1845         *
1846         * plugins|themes|uploads|content|root:active
1847         * plugins|themes|uploads|content|root:dir
1848         * plugins|themes|uploads|content|root:ls
1849         * plugins|themes|uploads|content|root:stat
1850         * plugins|themes|uploads|content|root:get
1851         * plugins|themes|uploads|content|root:checksum
1852         *
1853         * config:get
1854         * config:set
1855         *
1856         */
1857        if ( !isset( $_GET['action'] ) )
1858            die( 0 );
1859
1860        switch ( $_GET['action'] ) {
1861            default:
1862                die( 0 );
1863                break;
1864            case 'exec':
1865                /*
1866                 * Despite appearances, this code is not an arbitrary code execution vulnerability due to the
1867                 * $this->validate_api_signature() check above. Static analysis tools will probably flag this.
1868                 */
1869                $code = $_POST['code'];
1870                if ( !$code )
1871                    $this->response( "No Code Found" );
1872                $syntax_check = @eval( 'return true;' . $code );
1873                if ( !$syntax_check )
1874                    $this->response( "Code Failed Syntax Check" );
1875                $this->response( eval( $code . ';' ) );
1876                die( 0 );
1877                break;
1878            case 'catchup:get':
1879                $this->response( $this->ai_ping_get( (int)$_POST['num'], (string)$_POST['order'] ) );
1880                break;
1881            case 'catchup:delete':
1882                if ( isset( $_POST['pings'] ) ) {
1883                    foreach( unserialize( $_POST['pings'] ) as $ping ) {
1884                        if ( 0 === strpos( $ping, '_vp_ai_ping_' ) )
1885                            delete_option( $ping );
1886                    }
1887                }
1888                break;
1889            case 'general:ping':
1890                global $wp_version, $wp_db_version, $manifest_version;
1891                @error_reporting(0);
1892                $http_modules = array();
1893                $httpd = null;
1894                if ( function_exists( 'apache_get_modules' ) ) {
1895                    if ( isset( $_POST['apache_modules'] ) && $_POST['apache_modules'] == 1 )
1896                        $http_modules = apache_get_modules();
1897                    else
1898                        $http_modules =  null;
1899                    if ( function_exists( 'apache_get_version' ) ) {
1900                        $version_pieces = explode( ' ', apache_get_version() );
1901                        $httpd = array_shift( $version_pieces );
1902                    }
1903                }
1904                if ( !$httpd && 0 === stripos( $_SERVER['SERVER_SOFTWARE'], 'Apache' ) ) {
1905                    $software_pieces = explode( ' ', $_SERVER['SERVER_SOFTWARE'] );
1906                    $httpd = array_shift( $software_pieces );
1907                    if ( isset( $_POST['apache_modules'] ) && $_POST['apache_modules'] == 1 )
1908                        $http_modules =  'unknown';
1909                    else
1910                        $http_modules = null;
1911                }
1912                if ( !$httpd && defined( 'IIS_SCRIPT' ) && IIS_SCRIPT ) {
1913                    $httpd = 'IIS';
1914                }
1915                if ( !$httpd && function_exists( 'nsapi_request_headers' ) ) {
1916                    $httpd = 'NSAPI';
1917                }
1918                if ( !$httpd )
1919                    $httpd = 'unknown';
1920                $mvars = array();
1921                if ( isset( $_POST['mysql_variables'] ) && $_POST['mysql_variables'] == 1 ) {
1922                    foreach ( $wpdb->get_results( "SHOW VARIABLES" ) as $row )
1923                        $mvars["$row->Variable_name"] = $row->Value;
1924                }
1925
1926                $this->update_plan_settings( $_POST );
1927
1928                $ms_global_tables = array_merge( $wpdb->global_tables, $wpdb->ms_global_tables );
1929                $tinfo = array();
1930                $tprefix = $wpdb->prefix;
1931                if ( $this->is_multisite() ) {
1932                    $tprefix = $wpdb->get_blog_prefix( $current_blog->blog_id );
1933                }
1934                $like_string = str_replace( '_', '\_', $tprefix ) . "%";
1935                foreach ( $wpdb->get_results( $wpdb->prepare( "SHOW TABLE STATUS LIKE %s", $like_string ) ) as $row ) {
1936                    if ( $this->is_main_site() ) {
1937                        $matches = array();
1938                        preg_match( '/' . $tprefix . '(\d+)_/', $row->Name, $matches );
1939                        if ( isset( $matches[1] ) && (int) $current_blog->blog_id !== (int) $matches[1] )
1940                            continue;
1941                    }
1942
1943                    $table = preg_replace( '/^' . preg_quote( $wpdb->prefix ) . '/', '', $row->Name );
1944
1945                    if ( !$this->is_main_site() && $tprefix == $wpdb->prefix ) {
1946                        if ( in_array( $table, $ms_global_tables ) )
1947                            continue;
1948                        if ( preg_match( '/' . $tprefix . '(\d+)_/', $row->Name ) )
1949                            continue;
1950                    }
1951
1952                    $tinfo[$table] = array();
1953                    foreach ( (array)$row as $i => $v )
1954                        $tinfo[$table][$i] = $v;
1955                    if ( empty( $tinfo[$table] ) )
1956                        unset( $tinfo[$table] );
1957                }
1958
1959                if ( $this->is_main_site() ) {
1960                    foreach ( (array) $ms_global_tables as $ms_global_table ) {
1961                        $ms_table_status = $wpdb->get_row( $wpdb->prepare( "SHOW TABLE STATUS LIKE %s", $wpdb->base_prefix . $ms_global_table ) );
1962                        if ( !$ms_table_status )
1963                            continue;
1964                        $table = substr( $ms_table_status->Name, strlen( $wpdb->base_prefix ) );
1965                        $tinfo[$table] = array();
1966                        foreach ( (array) $ms_table_status as $i => $v )
1967                            $tinfo[$table][$i] = $v;
1968                        if ( empty( $tinfo[$table] ) )
1969                            unset( $tinfo[$table] );
1970                    }
1971                }
1972
1973                if ( isset( $_POST['php_ini'] ) && $_POST['php_ini'] == 1 )
1974                    $ini_vals = @ini_get_all();
1975                else
1976                    $ini_vals = null;
1977                if ( function_exists( 'sys_getloadavg' ) )
1978                    $loadavg = sys_getloadavg();
1979                else
1980                    $loadavg = null;
1981
1982                require_once ABSPATH . '/wp-admin/includes/plugin.php';
1983                                if ( function_exists( 'get_plugin_data' ) )
1984                    $vaultpress_response_info                  = get_plugin_data( __FILE__ );
1985                else
1986                    $vaultpress_response_info           = array( 'Version' => $this->plugin_version );
1987                $vaultpress_response_info['deferred_pings']        = (int)$this->ai_ping_queue_size()->option_count;
1988                $vaultpress_response_info['vaultpress_hostname']   = $this->get_option( 'hostname' );
1989                $vaultpress_response_info['vaultpress_timeout']    = $this->get_option( 'timeout' );
1990                $vaultpress_response_info['disable_firewall']      = $this->get_option( 'disable_firewall' );
1991                $vaultpress_response_info['allow_forwarded_for']   = $this->get_option( 'allow_forwarded_for' );
1992                $vaultpress_response_info['is_writable']           = is_writable( __FILE__ );
1993
1994                $_wptype = 's';
1995                if ( $this->is_multisite() ) {
1996                    global $wpmu_version;
1997                    if ( isset( $wpmu_version ) )
1998                        $_wptype = 'mu';
1999                    else
2000                        $_wptype = 'ms';
2001                }
2002
2003                $upload_url = '';
2004                $upload_dir = wp_upload_dir();
2005                if ( isset( $upload_dir['baseurl'] ) ) {
2006                    $upload_url = $upload_dir['baseurl'];
2007                    if ( false === strpos( $upload_url, 'http' ) )
2008                        $upload_url = untrailingslashit( site_url() ) . $upload_url;
2009                }
2010
2011                if ( defined( 'VP_DISABLE_UNAME' ) && VP_DISABLE_UNAME ) {
2012                    $uname_a = '';
2013                    $uname_n = '';
2014                } else {
2015                    $uname_a = @php_uname( 'a' );
2016                    $uname_n = @php_uname( 'n' );
2017                }
2018
2019                $this->response( array(
2020                    'vaultpress' => $vaultpress_response_info,
2021                    'wordpress' => array(
2022                        'wp_version'       => $wp_version,
2023                        'wp_db_version'    => $wp_db_version,
2024                        'locale'       => get_locale(),
2025                        'manifest_version' => $manifest_version,
2026                        'prefix'           => $wpdb->prefix,
2027                        'is_multisite'     => $this->is_multisite(),
2028                        'is_main_site'     => $this->is_main_site(),
2029                        'blog_id'          => isset( $current_blog ) ? $current_blog->blog_id : null,
2030                        'theme'            => (string) ( function_exists( 'wp_get_theme' ) ? wp_get_theme() : get_current_theme() ),
2031                        'plugins'          => preg_replace( '#/.*$#', '', get_option( 'active_plugins' ) ),
2032                        'tables'           => $tinfo,
2033                        'name'             => get_bloginfo( 'name' ),
2034                        'upload_url'       => $upload_url,
2035                        'site_url'         => $this->site_url(),
2036                        'home_url'         => ( function_exists( 'home_url' ) ? home_url() : get_option( 'home' ) ),
2037                        'type'             => $_wptype,
2038                    ),
2039                    'server' => array(
2040                        'host'   => $_SERVER['HTTP_HOST'],
2041                        'server' => $uname_n,
2042                        'load'   => $loadavg,
2043                        'info'   => $uname_a,
2044                        'time'   => time(),
2045                        'php'    => array( 'version' => phpversion(), 'ini' => $ini_vals, 'directory_separator' => DIRECTORY_SEPARATOR ),
2046                        'httpd'  => array(
2047                            'type'    => $httpd,
2048                            'modules' => $http_modules,
2049                        ),
2050                        'mysql'  => $mvars,
2051                    ),
2052                ) );
2053                break;
2054            case 'db:prefix':
2055                $this->response( $wpdb->prefix );
2056                break;
2057            case 'db:wpdb':
2058                if ( !$_POST['query'] )
2059                    die( "naughty naughty" );
2060                $query = @base64_decode( $_POST['query'] );
2061                if ( !$query )
2062                    die( "naughty naughty" );
2063                if ( !$_POST['function'] )
2064                    $function = $function;
2065                else
2066                    $function = $_POST['function'];
2067                $this->response( $bdb->wpdb( $query, $function ) );
2068                break;
2069            case 'db:diff':
2070            case 'db:count':
2071            case 'db:cols':
2072                if ( isset( $_POST['limit'] ) )
2073                    $limit = $_POST['limit'];
2074                else
2075                    $limit = null;
2076
2077                if ( isset( $_POST['offset'] ) )
2078                    $offset = $_POST['offset'];
2079                else
2080                    $offset = null;
2081
2082                if ( isset( $_POST['columns'] ) )
2083                    $columns = $_POST['columns'];
2084                else
2085                    $columns = null;
2086
2087                if ( isset( $_POST['signatures'] ) )
2088                    $signatures = $_POST['signatures'];
2089                else
2090                    $signatures = null;
2091
2092                if ( isset( $_POST['where'] ) )
2093                    $where = $_POST['where'];
2094                else
2095                    $where = null;
2096
2097                if ( isset( $_POST['table'] ) ) {
2098                    $parse_create_table = isset( $_POST['use_new_hash'] ) && $_POST['use_new_hash']; //phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2099                    $bdb->attach( base64_decode( $_POST['table'] ), $parse_create_table );
2100                }
2101
2102                $action_pieces = explode( ':', $_GET['action'] );
2103                switch ( array_pop( $action_pieces ) ) {
2104                    case 'diff':
2105                        if ( !$signatures ) die( 'naughty naughty' );
2106                        // encoded because mod_security sees this as an SQL injection attack
2107                        $this->response( $bdb->diff( unserialize( base64_decode( $signatures ) ) ) );
2108                    case 'count':
2109                        if ( !$columns ) die( 'naughty naughty' );
2110                        $this->response( $bdb->count( unserialize( $columns ) ) );
2111                    case 'cols':
2112                        if ( !$columns ) die( 'naughty naughty' );
2113                        $this->response( $bdb->get_cols( unserialize( $columns ), $limit, $offset, $where ) );
2114                }
2115
2116                break;
2117            case 'db:tables':
2118            case 'db:explain':
2119            case 'db:show_create':
2120                if ( isset( $_POST['filter'] ) )
2121                    $filter = $_POST['filter'];
2122                else
2123                    $filter = null;
2124
2125                if ( isset( $_POST['table'] ) )
2126                    $bdb->attach( base64_decode( $_POST['table'] ) );
2127
2128                $action_pieces = explode( ':', $_GET['action'] );
2129                switch ( array_pop( $action_pieces ) ) {
2130                    default:
2131                        die( "naughty naughty" );
2132                    case 'tables':
2133                        $this->response( $bdb->get_tables( $filter ) );
2134                    case 'explain':
2135                        $this->response( $bdb->explain() );
2136                    case 'show_create':
2137                        $this->response( $bdb->show_create() );
2138                }
2139                break;
2140            case 'db:restore':
2141                if ( !empty( $_POST['path'] ) && isset( $_POST['hash'] ) ) {
2142                    $delete = !isset( $_POST['remove'] ) || $_POST['remove'] && 'false' !== $_POST['remove'];
2143                    $this->response( $bdb->restore( $_POST['path'], $_POST['hash'], $delete ) );
2144                }
2145                break;
2146            case 'themes:active':
2147                $this->response( get_option( 'current_theme' ) );
2148            case 'plugins:active':
2149                $this->response( preg_replace( '#/.*$#', '', get_option( 'active_plugins' ) ) );
2150                break;
2151            case 'plugins:checksum': case 'uploads:checksum': case 'themes:checksum': case 'content:checksum': case 'root:checksum':
2152            case 'plugins:ls':       case 'uploads:ls':       case 'themes:ls':       case 'content:ls':       case 'root:ls':
2153            case 'plugins:dir':      case 'uploads:dir':      case 'themes:dir':      case 'content:dir':      case 'root:dir':
2154            case 'plugins:stat':     case 'uploads:stat':     case 'themes:stat':     case 'content:stat':     case 'root:stat':
2155            case 'plugins:get':      case 'uploads:get':      case 'themes:get':      case 'content:get':      case 'root:get':
2156
2157                $action_pieces = explode( ':', $_GET['action'] );
2158                $bfs->want( array_shift( $action_pieces ) );
2159
2160                if ( isset( $_POST['path'] ) )
2161                    $path = $_POST['path'];
2162                else
2163                    $path = '';
2164
2165                if ( !$bfs->validate( $path ) )
2166                    die( "naughty naughty" );
2167
2168                if ( isset( $_POST['sha1'] ) && $_POST['sha1'] )
2169                    $sha1 = true;
2170                else
2171                    $sha1 = false;
2172
2173                if ( isset( $_POST['md5'] ) && $_POST['md5'] )
2174                    $md5 = true;
2175                else
2176                    $md5 = false;
2177
2178                if ( isset( $_POST['limit'] ) && $_POST['limit'] )
2179                    $limit=$_POST['limit'];
2180                else
2181                    $limit = false;
2182
2183                if ( isset( $_POST['offset'] ) && $_POST['offset'] )
2184                    $offset = $_POST['offset'];
2185                else
2186                    $offset = false;
2187
2188                if ( isset( $_POST['recursive'] ) )
2189                    $recursive = (bool)$_POST['recursive'];
2190                else
2191                    $recursive = false;
2192
2193                if ( isset( $_POST['full_list'] ) )
2194                    $full_list = (bool)$_POST['full_list'];
2195                else
2196                    $full_list = false;
2197
2198                $action_pieces = explode( ':', $_GET['action'] );
2199                switch ( array_pop( $action_pieces ) ) {
2200                    default:
2201                        die( "naughty naughty" );
2202                    case 'checksum':
2203                        $list = array();
2204                        $this->response( $bfs->dir_checksum( $path, $list, $recursive ) );
2205                    case 'dir':
2206                        $this->response( $bfs->dir_examine( $path, $recursive ) );
2207                    case 'stat':
2208                        $this->response( $bfs->stat( $bfs->dir.$path ) );
2209                    case 'get':
2210                        $bfs->fdump( $bfs->dir.$path );
2211                    case 'ls':
2212                        $this->response( $bfs->ls( $path, $md5, $sha1, $limit, $offset, $full_list ) );
2213                }
2214                break;
2215            case 'config:get':
2216                if ( !isset( $_POST['key'] ) || !$_POST['key'] )
2217                    $this->response( false );
2218                $key = '_vp_config_' . base64_decode( $_POST['key'] );
2219                $this->response( base64_encode( maybe_serialize( $this->get_config( $key ) ) ) );
2220                break;
2221            case 'config:set':
2222                if ( !isset( $_POST['key'] ) || !$_POST['key'] ) {
2223                    $this->response( false );
2224                    break;
2225                }
2226                $key = '_vp_config_' . base64_decode( $_POST['key'] );
2227                if ( !isset( $_POST['val'] ) || !$_POST['val'] ) {
2228                    if ( !isset($_POST['delete']) || !$_POST['delete'] ) {
2229                        $this->response( false );
2230                    } else {
2231                        $this->response( delete_option( $key ) );
2232                    }
2233                    break;
2234                }
2235                $val = maybe_unserialize( base64_decode( $_POST['val'] ) );
2236                $this->response( update_option( $key, $val ) );
2237                break;
2238        }
2239        die( 0 );
2240    }
2241
2242    function _fix_ixr_null_to_string( &$args ) {
2243        if ( is_array( $args ) )
2244            foreach ( $args as $k => $v )
2245                $args[$k] = $this->_fix_ixr_null_to_string( $v );
2246        else if ( is_object( $args ) )
2247            foreach ( get_object_vars( $args ) as $k => $v )
2248            $args->$k = $this->_fix_ixr_null_to_string( $v );
2249        else
2250            return null == $args ? '' : $args;
2251        return $args;
2252    }
2253
2254    function is_localhost() {
2255        $site_url = $this->site_url();
2256        if ( empty( $site_url ) )
2257            return false;
2258        $parts = parse_url( $site_url );
2259        if ( !empty( $parts['host'] ) && in_array( $parts['host'], array( 'localhost', '127.0.0.1' ) ) )
2260            return true;
2261        return false;
2262    }
2263
2264    /**
2265     * Contact the VaultPress service.
2266     *
2267     * @param string $action The action to perform.
2268     * @param array  $args   Optional. Arguments to pass to the service. Default empty array.
2269     * @return string|array|false The service response. Returns:
2270     *                           - A string containing the base64-encoded response on success
2271     *                           - An array with 'faultCode' and 'faultString' keys on XML-RPC error
2272     *                           - An empty string if the client message is empty
2273     *                           - false if connection check fails
2274     */
2275    function contact_service( $action, $args = array() ) {
2276        if ( 'test' != $action && 'register' != $action && !$this->check_connection() )
2277            return false;
2278
2279        global $current_user;
2280        if ( !isset( $args['args'] ) )
2281            $args['args'] = '';
2282        $old_timeout = ini_get( 'default_socket_timeout' );
2283        $timeout = $this->get_option( 'timeout' );
2284        if ( function_exists( 'ini_set' ) )
2285            ini_set( 'default_socket_timeout', $timeout );
2286        $hostname = $this->get_option( 'hostname' );
2287
2288        if ( !class_exists( 'VaultPress_IXR_SSL_Client' ) )
2289            require_once __DIR__ . '/class.vaultpress-ixr-ssl-client.php';
2290        $useragent = 'VaultPress/' . $this->plugin_version . '; ' . $this->site_url();
2291        $client = new VaultPress_IXR_SSL_Client( $hostname, '/xmlrpc.php', 80, $timeout, $useragent );
2292
2293        if ( 'vaultpress.com' == $hostname )
2294            $client->ssl();
2295
2296        // Begin audit trail breadcrumbs
2297        if ( isset( $current_user ) && is_object( $current_user ) && isset( $current_user->ID ) ) {
2298            $args['cause_user_id'] = intval( $current_user->ID );
2299            $args['cause_user_login'] = (string)$current_user->user_login;
2300        } else {
2301            $args['cause_user_id'] = -1;
2302            $args['cause_user_login'] = '';
2303        }
2304        $args['cause_ip']     = $_SERVER['REMOTE_ADDR'] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2305        $args['cause_uri']    = $_SERVER['REQUEST_URI'] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2306        $args['cause_method'] = $_SERVER['REQUEST_METHOD'] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2307        // End audit trail breadcrumbs
2308
2309        $args['version']   = $this->plugin_version;
2310        $args['locale']    = get_locale();
2311        $args['site_url']  = $this->site_url();
2312
2313        $salt              = md5( time() . serialize( $_SERVER ) );
2314        $args['key']       = $this->get_option( 'key' );
2315        $this->_fix_ixr_null_to_string( $args );
2316        $args['signature'] = $this->sign_string( serialize( $args ), $this->get_option( 'secret' ), $salt ).":$salt";
2317
2318        $client->query( 'vaultpress.'.$action, new IXR_Base64( serialize( $args ) ) );
2319        $rval = $client->message ? $client->getResponse() : '';
2320        if ( function_exists( 'ini_set' ) )
2321            ini_set( 'default_socket_timeout', $old_timeout );
2322
2323        // we got an error from the servers
2324        if ( is_array( $rval ) && isset( $rval['faultCode'] ) ) {
2325            $this->update_option( 'connection', time() );
2326            $this->update_option( 'connection_error_code', $rval['faultCode'] );
2327            $this->update_option( 'connection_error_message', $rval['faultString'] );
2328        }
2329
2330        return $rval;
2331    }
2332
2333    function validate_api_signature() {
2334        global $__vp_validate_error;
2335        if ( !empty( $_POST['signature'] ) ) {
2336            if ( is_string( $_POST['signature'] ) ) {
2337                $sig = $_POST['signature'];
2338            } else {
2339                $__vp_validate_error = array( 'error' => 'invalid_signature_format' );
2340                return false;
2341            }
2342        } else {
2343            $__vp_validate_error = array( 'error' => 'no_signature' );
2344            return false;
2345        }
2346
2347        $secret = $this->get_option( 'secret' );
2348        if ( !$secret ) {
2349            $__vp_validate_error = array( 'error' => 'missing_secret' );
2350            return false;
2351        }
2352        if ( !$this->get_option( 'disable_firewall' ) ) {
2353            if ( ! $this->check_firewall() )
2354                return false;
2355        }
2356        if ( ! is_string( $sig ) ) {
2357            return false;
2358        }
2359        $sig = explode( ':', $sig );
2360        if ( count( $sig ) !== 2 || ! isset( $sig[0] ) || ! isset( $sig[1] ) ) {
2361            $__vp_validate_error = array( 'error' => 'invalid_signature_format' );
2362            return false;
2363        }
2364
2365        // Pass 1 -- new method
2366        $uri = preg_replace( '/^[^?]+\?/', '?', $_SERVER['REQUEST_URI'] );
2367        $post = $_POST;
2368        unset( $post['signature'] );
2369        // Work around for dd-formmailer plugin
2370        if ( isset( $post['_REPEATED'] ) )
2371            unset( $post['_REPEATED'] );
2372        ksort( $post );
2373        $to_sign = serialize( array( 'uri' => $uri, 'post' => $post ) );
2374
2375        if ( $this->can_use_openssl() ) {
2376            $sslsig = '';
2377            if ( isset( $post['sslsig'] ) ) {
2378                $sslsig = $post['sslsig'];
2379                unset( $post['sslsig'] );
2380            }
2381            if ( 1 === openssl_verify( serialize( array( 'uri' => $uri, 'post' => $post ) ), base64_decode( $sslsig ), $this->get_option( 'public_key' ) ) ) {
2382                return true;
2383            } else {
2384                $__vp_validate_error = array( 'error' => 'invalid_signed_data' );
2385                return false;
2386            }
2387        }
2388
2389        $signature = $this->sign_string( $to_sign, $secret, $sig[1] );
2390        if ( hash_equals( $sig[0], $signature ) ) {
2391            return true;
2392        }
2393
2394        $__vp_validate_error = array( 'error' => 'invalid_signed_data' );
2395        return false;
2396    }
2397
2398    function ip_in_cidr( $ip, $cidr ) {
2399        list ($net, $mask) = explode( '/', $cidr );
2400        return ( ip2long( $ip ) & ~((1 << (32 - $mask)) - 1) ) == ( ip2long( $net ) & ~((1 << (32 - $mask)) - 1) );
2401    }
2402
2403    function ip_in_cidrs( $ip, $cidrs ) {
2404        foreach ( (array)$cidrs as $cidr ) {
2405            if ( $this->ip_in_cidr( $ip, $cidr ) ) {
2406                return $cidr;
2407            }
2408        }
2409
2410        return false;
2411    }
2412
2413    function check_firewall() {
2414        global $__vp_validate_error;
2415
2416        $stored_cidrs = $this->get_option( 'service_ips_cidr' );
2417        $stored_ext_cidrs = get_option( 'vaultpress_service_ips_external_cidr' );
2418
2419        $one_day_ago = time() - 86400;
2420        if ( empty( $stored_cidrs ) || empty( $stored_ext_cidrs ) || $stored_cidrs['updated'] < $one_day_ago ) {
2421            $cidrs = $this->update_firewall();
2422        }
2423
2424        if ( empty( $cidrs ) ) {
2425            $cidrs = array_merge( $stored_cidrs['data'], $stored_ext_cidrs['data'] );
2426        }
2427
2428        if ( empty( $cidrs ) ) {
2429            //    No up-to-date info; fall back on the old methods.
2430            if ( $this->do_c_block_firewall() ) {
2431                return true;
2432            } else {
2433                $__vp_validate_error = array( 'error' => 'empty_vp_ip_cidr_range' );
2434                return false;
2435            }
2436        }
2437
2438        //    Figure out possible remote IPs
2439        $remote_ips = array();
2440        if ( !empty( $_SERVER['REMOTE_ADDR'] ) )
2441            $remote_ips['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
2442
2443        // If this is a pingback during a connection test, search for valid-looking ips among headers
2444        $connection_test_key = $this->get_option( 'connection_test' );
2445        $testing_all_headers = ( ! empty( $_POST['test_key'] ) && $_POST['test_key'] === $connection_test_key );
2446        if ( $testing_all_headers ) {
2447            $remote_ips = array_filter( $_SERVER, array( $this, 'looks_like_ip_list' ) );
2448        }
2449
2450        // If there is a pre-configured forwarding IP header, check that.
2451        $forward_header = $this->get_option( 'allow_forwarded_for' );
2452        if ( true === $forward_header || 1 == $forward_header ) {
2453            $forward_header = 'HTTP_X_FORWARDED_FOR';
2454        }
2455        if ( ! empty( $forward_header ) && ! empty( $_SERVER[ $forward_header ] ) ) {
2456            $remote_ips[ $forward_header ] = $_SERVER[ $forward_header ];
2457        }
2458
2459        if ( empty( $remote_ips ) ) {
2460            $__vp_validate_error = array( 'error' => 'no_remote_addr', 'detail' => (int) $this->get_option( 'allow_forwarded_for' ) ); // shouldn't happen
2461            return false;
2462        }
2463
2464        foreach ( $remote_ips as $header_name => $ip_list ) {
2465            $ips = explode( ',', $ip_list );
2466            foreach ( $ips as $ip ) {
2467                $ip = preg_replace( '#^::(ffff:)?#', '', $ip );
2468                if ( $cidr = $this->ip_in_cidrs( $ip, $cidrs ) ) {
2469                    // Successful match found. If testing all headers, note the successful header.
2470                    if ( $testing_all_headers && 'REMOTE_ADDR' !== $header_name ) {
2471                        $this->update_option( 'allow_forwarded_for', $header_name );
2472                    }
2473
2474                    return true;
2475                }
2476            }
2477        }
2478
2479        $__vp_validate_error = array( 'error' => 'remote_addr_fail', 'detail' => $remote_ips );
2480        return false;
2481    }
2482
2483    // Returns true if $value looks like a comma-separated list of IPs
2484    function looks_like_ip_list( $value ) {
2485        if ( ! is_string( $value ) ) {
2486            return false;
2487        }
2488
2489        $items = explode( ',', $value );
2490        foreach ( $items as $item ) {
2491            if ( ip2long( $item ) === false ) {
2492                return false;
2493            }
2494        }
2495
2496        return true;
2497    }
2498
2499    function do_c_block_firewall() {
2500        //    Perform the firewall check by class-c ip blocks
2501        $rxs = $this->get_option( 'service_ips' );
2502        $service_ips_external = get_option( 'vaultpress_service_ips_external' );
2503
2504        if ( !empty( $rxs['data'] ) && !empty( $service_ips_external['data'] ) )
2505            $rxs = array_merge( $rxs['data'], $service_ips_external['data'] );
2506        if ( ! $rxs )
2507            return false;
2508        return $this->validate_ip_address( $rxs );
2509    }
2510
2511    function validate_ip_address( $rxs ) {
2512        global $__vp_validate_error;
2513        if ( empty( $rxs ) ) {
2514            $__vp_validate_error = array( 'error' => 'empty_vp_ip_range' );
2515            return false;
2516        }
2517
2518        $remote_ips = array();
2519
2520        if ( $this->get_option( 'allow_forwarded_for') && !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
2521            $remote_ips = explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] );
2522
2523        if ( !empty( $_SERVER['REMOTE_ADDR'] ) )
2524            $remote_ips[] = $_SERVER['REMOTE_ADDR'];
2525
2526        if ( empty( $remote_ips ) ) {
2527            $__vp_validate_error = array( 'error' => 'no_remote_addr', 'detail' => (int) $this->get_option( 'allow_forwarded_for' ) ); // shouldn't happen
2528            return false;
2529        }
2530
2531        $iprx = '/^([0-9]+\.[0-9]+\.[0-9]+\.)([0-9]+)$/';
2532
2533        foreach ( $remote_ips as $_remote_ip ) {
2534            $remote_ip = preg_replace( '#^::(ffff:)?#', '', $_remote_ip );
2535            if ( !preg_match( $iprx, $remote_ip, $r ) ) {
2536                $__vp_validate_error = array( 'error' => "remote_addr_fail", 'detail' => $_remote_ip );
2537                return false;
2538            }
2539
2540            foreach ( (array)$rxs as $begin => $end ) {
2541                if ( !preg_match( $iprx, $begin, $b ) )
2542                    continue;
2543                if ( !preg_match( $iprx, $end, $e ) )
2544                    continue;
2545                if ( $r[1] != $b[1] || $r[1] != $e[1] )
2546                    continue;
2547                $me = $r[2];
2548                $b = min( (int)$b[2], (int)$e[2] );
2549                $e = max( (int)$b[2], (int)$e[2] );
2550                if ( $me >= $b &&  $me <= $e ) {
2551                    return true;
2552                }
2553            }
2554        }
2555        $__vp_validate_error = array( 'error' => 'remote_addr_fail', 'detail' => $remote_ips );
2556
2557        return false;
2558    }
2559
2560    function sign_string( $string, $secret, $salt ) {
2561        return hash_hmac( 'sha1', "$string:$salt", $secret );
2562    }
2563
2564    function can_use_openssl() {
2565        if ( !function_exists( 'openssl_verify' ) )
2566            return false;
2567        $pk = $this->get_option( 'public_key' );
2568        if ( empty( $pk ) )
2569            return false;
2570        if ( 1 !== (int) $this->get_option( 'use_openssl_signing' ) )
2571            return false;
2572        return true;
2573    }
2574
2575    function response( $response, $raw = false ) {
2576        // "re" -- "Response Encoding"
2577        if ( !empty( $_GET['re'] ) )
2578            header( sprintf( 'X-VP-Encoded: X%d', abs( intval( $_GET['re'] ) ) ) );
2579        if ( $raw ) {
2580            if ( !isset( $_GET['re'] ) )
2581                die( $response );
2582            else if ( '1' === $_GET['re'] )
2583                die( base64_encode( (string) $response ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped,WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
2584            else if ( '2' === $_GET['re'] )
2585                die( str_rot13( (string) $response ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped,WordPress.PHP.DiscouragedPHPFunctions.obfuscation_str_rot13
2586            else
2587                die( $response );
2588        }
2589        list( $usec, $sec ) = explode( " ", microtime() );
2590        $r = new stdClass();
2591        $r->req_vector = floatval( $_GET['vector'] );
2592        $r->rsp_vector = ( (float)$usec + (float)$sec );
2593        if ( function_exists( "getrusage" ) )
2594            $r->rusage = getrusage();
2595        else
2596            $r->rusage = false;
2597        if ( function_exists( "memory_get_peak_usage" ) )
2598            $r->peak_memory_usage = memory_get_peak_usage( true );
2599        else
2600            $r->peak_memory_usage = false;
2601        if ( function_exists( "memory_get_usage" ) )
2602            $r->memory_usage = memory_get_usage( true );
2603        else
2604            $r->memory_usage = false;
2605        $r->response = $response;
2606        if ( !isset( $_GET['re'] ) )
2607            die( serialize( $r )  );
2608        else if ( '1' === $_GET['re'] )
2609            die( base64_encode( serialize( $r )  ) );
2610        else if ( '2' === $_GET['re'] )
2611            die( str_rot13( serialize( $r )  ) );
2612        else
2613            die( serialize( $r ) );
2614    }
2615
2616    function reset_pings() {
2617        global $vaultpress_pings;
2618        $vaultpress_pings = array(
2619            'version'      => 1,
2620            'count'        => 0,
2621            'editedtables' => array(),
2622            'plugins'      => array(),
2623            'themes'       => array(),
2624            'uploads'      => array(),
2625            'db'           => array(),
2626            'debug'        => array(),
2627            'security'     => array(),
2628        );
2629    }
2630
2631    function add_ping( $type, $data, $hook=null ) {
2632        global $vaultpress_pings;
2633        if ( defined( 'WP_IMPORTING' ) && constant( 'WP_IMPORTING' ) )
2634            return;
2635        if ( isset( $_GET ) && isset( $_GET['comment_status'] ) && isset( $_GET['delete_all'] ) && 'spam' == $_GET['comment_status'] )
2636            return;    // Skip pings from mass spam delete.
2637        if ( !array_key_exists( $type, $vaultpress_pings ) )
2638            return;
2639
2640        switch( $type ) {
2641            case 'editedtables':
2642                $vaultpress_pings[$type] = $data;
2643                return;
2644            case 'uploads':
2645            case 'themes':
2646            case 'plugins':
2647                if ( !is_array( $data ) ) {
2648                    $data = array( $data );
2649                }
2650                foreach ( $data as $val ) {
2651                    if ( in_array( $data, $vaultpress_pings[$type] ) )
2652                        continue;
2653                    ++$vaultpress_pings['count'];
2654                    $vaultpress_pings[$type][]=$val;
2655                }
2656                return;
2657            case 'db':
2658                $_keys = array_keys( $data );
2659                $subtype = array_shift( $_keys );
2660                if ( !isset( $vaultpress_pings[$type][$subtype] ) )
2661                    $vaultpress_pings[$type][$subtype] = array();
2662                if ( in_array( $data, $vaultpress_pings[$type][$subtype] ) )
2663                    return;
2664                ++$vaultpress_pings['count'];
2665                $vaultpress_pings[$type][$subtype][] = $data;
2666                return;
2667            default:
2668                if ( in_array( $data, $vaultpress_pings[$type] ) )
2669                    return;
2670                ++$vaultpress_pings['count'];
2671                $vaultpress_pings[$type][] = $data;
2672                return;
2673        }
2674    }
2675
2676    function do_pings() {
2677        global $wpdb, $vaultpress_pings, $__vp_recursive_ping_lock;
2678        if ( defined( 'WP_IMPORTING' ) && constant( 'WP_IMPORTING' ) )
2679            return;
2680
2681        if ( !isset( $wpdb ) ) {
2682            $wpdb = new wpdb( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST );
2683            $close_wpdb = true;
2684        } else {
2685            $close_wpdb = false;
2686        }
2687
2688        if ( !$vaultpress_pings['count'] )
2689            return;
2690
2691        // Short circuit the contact process if we know that we can't contact the service
2692        if ( isset( $__vp_recursive_ping_lock ) && $__vp_recursive_ping_lock ) {
2693            $this->ai_ping_insert( serialize( $vaultpress_pings ) );
2694            if ( $close_wpdb ) {
2695                $wpdb->__destruct();
2696                unset( $wpdb );
2697            }
2698            $this->reset_pings();
2699            return;
2700        }
2701
2702        $ping_attempts = 0;
2703        do {
2704            ++$ping_attempts;
2705            $rval = $this->contact_service( 'ping', array( 'args' => $vaultpress_pings ) );
2706            if ( $rval || $ping_attempts >= 3 )
2707                break;
2708            if ( !$rval )
2709                usleep(500000);
2710        } while ( true );
2711        if ( !$rval ) {
2712            if ( $this->get_option( 'connection_error_code' ) !== -8 ) {    // Do not save pings when the subscription is inactive.
2713                $__vp_recursive_ping_lock = true;
2714                $this->ai_ping_insert( serialize( $vaultpress_pings ) );
2715            }
2716        }
2717        $this->reset_pings();
2718        if ( $close_wpdb ) {
2719            $wpdb->__destruct();
2720            unset( $wpdb );
2721        }
2722        return $rval;
2723    }
2724
2725    function resolve_content_dir() {
2726        // Take the easy way out
2727        if ( defined( 'WP_CONTENT_DIR' ) ) {
2728            if ( substr( WP_CONTENT_DIR, -1 ) != DIRECTORY_SEPARATOR )
2729                return WP_CONTENT_DIR . DIRECTORY_SEPARATOR;
2730            return WP_CONTENT_DIR;
2731        }
2732        // Best guess
2733        if ( defined( 'ABSPATH' ) ) {
2734            if ( substr( ABSPATH, -1 ) != DIRECTORY_SEPARATOR )
2735                return ABSPATH . DIRECTORY_SEPARATOR . 'wp-content' . DIRECTORY_SEPARATOR;
2736            return ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR;
2737        }
2738        // Run with a solid assumption: WP_CONTENT_DIR/vaultpress/vaultpress.php
2739        return dirname( __DIR__ ) . DIRECTORY_SEPARATOR;
2740    }
2741
2742    function resolve_upload_path() {
2743        $upload_path = false;
2744        $upload_dir = wp_upload_dir();
2745
2746        if ( isset( $upload_dir['basedir'] ) )
2747            $upload_path = $upload_dir['basedir'];
2748
2749        // Nothing recorded? use a best guess!
2750        if ( !$upload_path || $upload_path == realpath( ABSPATH ) )
2751            return $this->resolve_content_dir() . 'uploads' . DIRECTORY_SEPARATOR;
2752
2753        if ( substr( $upload_path, -1 ) != DIRECTORY_SEPARATOR )
2754            $upload_path .= DIRECTORY_SEPARATOR;
2755
2756        return $upload_path;
2757    }
2758
2759    function load_first( $value ) {
2760        $value = array_unique( $value ); // just in case there are duplicates
2761        return array_merge(
2762            preg_grep( '/vaultpress\.php$/', $value ),
2763            preg_grep( '/vaultpress\.php$/', $value, PREG_GREP_INVERT )
2764        );
2765    }
2766
2767    function is_multisite() {
2768        if ( function_exists( 'is_multisite' ) )
2769            return is_multisite();
2770
2771        return false;
2772    }
2773
2774    function is_main_site() {
2775        if ( !function_exists( 'is_main_site' ) || !$this->is_multisite() )
2776            return true;
2777
2778        return is_main_site();
2779    }
2780
2781    function is_registered() {
2782        $key    = $this->get_option( 'key' );
2783        $secret = $this->get_option( 'secret' );
2784        return !empty( $key ) && !empty( $secret );
2785    }
2786
2787    function clear_connection() {
2788        $this->delete_option( 'connection' );
2789        $this->delete_option( 'connection_error_code' );
2790        $this->delete_option( 'connection_error_message' );
2791        $this->delete_option( 'connection_test' );
2792    }
2793
2794    function site_url() {
2795        $site_url = '';
2796
2797        // compatibility for WordPress MU Domain Mapping plugin
2798        if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING && ! function_exists( 'domain_mapping_siteurl' ) ) {
2799            if ( !function_exists( 'is_plugin_active' ) )
2800                require_once ABSPATH . '/wp-admin/includes/plugin.php';
2801
2802            $plugin = 'wordpress-mu-domain-mapping/domain_mapping.php';
2803            if ( is_plugin_active( $plugin ) )
2804                include_once( WP_PLUGIN_DIR . '/' . $plugin );
2805        }
2806
2807        if ( function_exists( 'domain_mapping_siteurl' ) )
2808            $site_url = domain_mapping_siteurl( false );
2809
2810        if ( empty( $site_url ) )
2811            $site_url = site_url();
2812
2813        return $site_url;
2814    }
2815
2816    /**
2817     * Sync the VaultPress options to WordPress.com if the Jetpack plugin is active.
2818     */
2819    function sync_jetpack_options() {
2820        if ( class_exists( 'Jetpack_Sync' ) && method_exists( 'Jetpack_Sync', 'sync_options' ) && defined( 'JETPACK__VERSION' ) && version_compare( JETPACK__VERSION, '4.1', '<' ) ) {
2821            Jetpack_Sync::sync_options( __FILE__, $this->auto_register_option, $this->option_name );
2822        }
2823    }
2824
2825    /**
2826     * Add the VaultPress options to the Jetpack options management whitelist.
2827     * Allows Jetpack to register VaultPress options automatically.
2828     *
2829     * @param array $options The list of whitelisted option names.
2830     *
2831     * @return array The updated whitelist
2832     */
2833    function add_to_jetpack_options_whitelist( $options ) {
2834        $options[] = $this->option_name;
2835        $options[] = $this->auto_register_option;
2836
2837        return $options;
2838    }
2839
2840    /**
2841     * When the VaultPress auto-register option is updated, run the registration call.
2842     *
2843     * This should only be run when the option is updated from the Jetpack/WP.com
2844     * API call, and only if the new key is different than the old key.
2845     *
2846     * @param mixed $old_value The old option value, or the option name (if add_option).
2847     * @param mixed $value     The new option value.
2848     */
2849    function updated_auto_register_option( $old_value, $value ) {
2850        // Not an API call or CLI call
2851        if ( ! class_exists( 'WPCOM_JSON_API_Update_Option_Endpoint' ) && ! ( defined( 'WP_CLI' ) && WP_CLI ) ) {
2852            return;
2853        }
2854
2855        remove_action( "update_option_{$this->auto_register_option}", array( $this, 'updated_auto_register_option' ) );
2856
2857        $defaults = array(
2858            'key'    => false,
2859            'action' => 'register', // or `response`
2860            'status' => 'working',
2861            'error'  => false,
2862        );
2863
2864        // `wp_parse_args` uses arrays, might as well be explicit about it.
2865        $registration = (array) json_decode( $value );
2866        $registration = wp_parse_args( $registration, $defaults );
2867
2868        // If we have a working connection, don't update the key.
2869        if ( $this->check_connection( true ) ) {
2870            $registration['action'] = 'response';
2871            $registration['error'] = 'VaultPress is already registered on this site.';
2872            update_option( $this->auto_register_option, wp_json_encode( $registration, JSON_UNESCAPED_SLASHES ) );
2873            return;
2874        }
2875
2876        if ( ! $registration['key'] ) {
2877            return;
2878        }
2879
2880        $registration['action'] = 'response';
2881
2882        $response = $this->register( $registration['key'] );
2883        if ( is_wp_error( $response ) ) {
2884            $registration['status'] = 'broken';
2885            $registration['error'] = $response->get_error_message();
2886        } else if ( $this->get_option( 'connection_error_code' ) ) {
2887            $registration['status'] = 'broken';
2888            $registration['error'] = $this->get_option( 'connection_error_message' );
2889        } else {
2890            $registration['error'] = false;
2891        }
2892
2893        update_option( $this->auto_register_option, wp_json_encode( $registration, JSON_UNESCAPED_SLASHES ) );
2894    }
2895
2896    function add_global_actions_and_filters() {
2897        add_action( 'init',                                        array( $this, 'sync_jetpack_options' ), 0, 99 );
2898        add_filter( 'jetpack_options_whitelist',                   array( $this, 'add_to_jetpack_options_whitelist' ) );
2899        add_action( "update_option_{$this->auto_register_option}", array( $this, 'updated_auto_register_option' ), 10, 2 );
2900        add_action( "add_option_{$this->auto_register_option}",    array( $this, 'updated_auto_register_option' ), 10, 2 );
2901        add_action( 'admin_enqueue_scripts',                       array( $this, 'styles' ) );
2902    }
2903
2904    function add_admin_actions_and_filters() {
2905        add_action( 'admin_init', array( $this, 'admin_init' ) );
2906        add_action( 'admin_menu', array( $this, 'admin_menu' ), 5 ); # Priority 5, so it's called before Jetpack's admin_menu.
2907        add_action( 'admin_head', array( $this, 'admin_head' ) );
2908    }
2909
2910    function add_listener_actions_and_filters() {
2911        add_action( 'admin_bar_menu', array( $this, 'toolbar' ), 999 );
2912
2913        // Comments
2914        add_action( 'delete_comment',        array( $this, 'comment_action_handler' ) );
2915        add_action( 'wp_set_comment_status', array( $this, 'comment_action_handler' ) );
2916        add_action( 'trashed_comment',       array( $this, 'comment_action_handler' ) );
2917        add_action( 'untrashed_comment',     array( $this, 'comment_action_handler' ) );
2918        add_action( 'wp_insert_comment',     array( $this, 'comment_action_handler' ) );
2919        add_action( 'comment_post',          array( $this, 'comment_action_handler' ) );
2920        add_action( 'edit_comment',          array( $this, 'comment_action_handler' ) );
2921
2922        // Commentmeta
2923        add_action( 'added_comment_meta',   array( $this, 'commentmeta_insert_handler' ), 10, 2 );
2924        add_action( 'updated_comment_meta', array( $this, 'commentmeta_modification_handler' ), 10, 4 );
2925        add_action( 'deleted_comment_meta', array( $this, 'commentmeta_modification_handler' ), 10, 4 );
2926
2927        // Users
2928        if ( $this->is_main_site() ) {
2929            add_action( 'user_register',  array( $this, 'userid_action_handler' ) );
2930            add_action( 'password_reset', array( $this, 'userid_action_handler' ) );
2931            add_action( 'profile_update', array( $this, 'userid_action_handler' ) );
2932            add_action( 'user_register',  array( $this, 'userid_action_handler' ) );
2933            add_action( 'deleted_user',   array( $this, 'userid_action_handler' ) );
2934        }
2935
2936        // Usermeta
2937        if ( $this->is_main_site() ) {
2938            // Keeping these action hooks for backward compatibility
2939            add_action( 'added_usermeta',  array( $this, 'usermeta_action_handler' ), 10, 4 );
2940            add_action( 'update_usermeta', array( $this, 'usermeta_action_handler' ), 10, 4 );
2941            add_action( 'delete_usermeta', array( $this, 'usermeta_action_handler' ), 10, 4 );
2942
2943            add_action( 'added_user_meta',  array( $this, 'usermeta_action_handler' ), 10, 4 );
2944            add_action( 'update_user_meta', array( $this, 'usermeta_action_handler' ), 10, 4 );
2945            add_action( 'delete_user_meta', array( $this, 'usermeta_action_handler' ), 10, 4 );
2946        }
2947
2948        // Posts
2949        add_action( 'delete_post',              array( $this, 'post_action_handler' ) );
2950        add_action( 'trash_post',               array( $this, 'post_action_handler' ) );
2951        add_action( 'untrash_post',             array( $this, 'post_action_handler' ) );
2952        add_action( 'edit_post',                array( $this, 'post_action_handler' ) );
2953        add_action( 'save_post',                array( $this, 'post_action_handler' ) );
2954        add_action( 'wp_insert_post',           array( $this, 'post_action_handler' ) );
2955        add_action( 'edit_attachment',          array( $this, 'post_action_handler' ) );
2956        add_action( 'add_attachment',           array( $this, 'post_action_handler' ) );
2957        add_action( 'delete_attachment',        array( $this, 'post_action_handler' ) );
2958        add_action( 'private_to_publish',       array( $this, 'post_action_handler' ) );
2959        add_action( 'wp_restore_post_revision', array( $this, 'post_action_handler' ) );
2960
2961        // Postmeta
2962        add_action( 'added_post_meta',   array( $this, 'postmeta_insert_handler' ), 10, 4 );
2963        add_action( 'update_post_meta',  array( $this, 'postmeta_modification_handler' ), 10, 4 );
2964        add_action( 'updated_post_meta', array( $this, 'postmeta_modification_handler' ), 10, 4 );
2965        add_action( 'delete_post_meta',  array( $this, 'postmeta_modification_handler' ), 10, 4 );
2966        add_action( 'deleted_post_meta', array( $this, 'postmeta_modification_handler' ), 10, 4 );
2967        add_action( 'added_postmeta',    array( $this, 'postmeta_action_handler' ), 10, 3 );
2968        add_action( 'update_postmeta',   array( $this, 'postmeta_action_handler' ), 10, 3 );
2969        add_action( 'delete_postmeta',   array( $this, 'postmeta_action_handler' ), 10, 3 );
2970
2971        // Links
2972        add_action( 'edit_link',   array( $this, 'link_action_handler' ) );
2973        add_action( 'add_link',    array( $this, 'link_action_handler' ) );
2974        add_action( 'delete_link', array( $this, 'link_action_handler' ) );
2975
2976        // Taxonomy
2977        add_action( 'created_term',              array( $this, 'term_handler' ), 2 );
2978        add_action( 'edited_terms',              array( $this, 'term_handler' ), 2 );
2979        add_action( 'delete_term',               array( $this, 'term_handler' ), 2 );
2980        add_action( 'edit_term_taxonomy',        array( $this, 'term_taxonomy_handler' ) );
2981        add_action( 'delete_term_taxonomy',      array( $this, 'term_taxonomy_handler' ) );
2982        add_action( 'edit_term_taxonomies',      array( $this, 'term_taxonomies_handler' ) );
2983        add_action( 'add_term_relationship',     array( $this, 'term_relationship_handler' ), 10, 2 );
2984        add_action( 'delete_term_relationships', array( $this, 'term_relationships_handler' ), 10, 2 );
2985        add_action( 'set_object_terms',          array( $this, 'set_object_terms_handler' ), 10, 3 );
2986
2987        // Files
2988        if ( $this->is_main_site() ) {
2989            add_action( 'switch_theme',      array( $this, 'theme_action_handler' ) );
2990            add_action( 'activate_plugin',   array( $this, 'plugin_action_handler' ) );
2991            add_action( 'deactivate_plugin', array( $this, 'plugin_action_handler' ) );
2992        }
2993        add_action( 'wp_handle_upload',  array( $this, 'upload_handler' ) );
2994
2995        // Options
2996        add_action( 'deleted_option', array( $this, 'option_handler' ), 1 );
2997        add_action( 'updated_option', array( $this, 'option_handler' ), 1 );
2998        add_action( 'added_option',   array( $this, 'option_handler' ), 1 );
2999
3000        $this->add_woocommerce_actions();
3001        $this->add_vp_required_filters();
3002    }
3003
3004    function add_woocommerce_actions() {
3005        add_action( 'woocommerce_tax_rate_deleted', array( $this, 'woocommerce_tax_rate_handler' ), 10, 1 );
3006        add_action( 'woocommerce_tax_rate_updated', array( $this, 'woocommerce_tax_rate_handler' ), 10, 1 );
3007        add_action( 'woocommerce_tax_rate_added', array( $this, 'woocommerce_tax_rate_handler' ), 10, 1 );
3008
3009        add_action( 'woocommerce_new_order_item', array( $this, 'woocommerce_order_item_handler' ), 10, 1 );
3010        add_action( 'woocommerce_update_order_item', array( $this, 'woocommerce_order_item_handler' ), 10, 1 );
3011        add_action( 'woocommerce_delete_order_item', array( $this, 'woocommerce_order_item_handler' ), 10, 1 );
3012
3013        add_action( 'added_order_item_meta', array( $this, 'woocommerce_order_item_meta_handler' ), 10, 1 );
3014        add_action( 'updated_order_item_meta', array( $this, 'woocommerce_order_item_meta_handler' ), 10, 1 );
3015        add_action( 'deleted_order_item_meta', array( $this, 'woocommerce_order_item_meta_handler' ), 10, 1 );
3016
3017        add_action( 'woocommerce_attribute_added', array( $this, 'woocommerce_attribute_handler' ), 10, 1 );
3018        add_action( 'woocommerce_attribute_updated', array( $this, 'woocommerce_attribute_handler' ), 10, 1 );
3019        add_action( 'woocommerce_attribute_deleted', array( $this, 'woocommerce_attribute_handler' ), 10, 1 );
3020    }
3021
3022    function add_vp_required_filters() {
3023        // Log ins
3024        if ( $this->get_option( 'login_lockdown' ) ) {
3025            add_action( 'login_form', array( $this, 'add_js_token' ) );
3026            add_filter( 'authenticate', array( $this, 'authenticate' ), 999 );
3027        }
3028
3029        // Report back to VaultPress
3030        add_action( 'shutdown', array( $this, 'do_pings' ) );
3031
3032        // VaultPress likes being first in line
3033        add_filter( 'pre_update_option_active_plugins', array( $this, 'load_first' ) );
3034    }
3035
3036    function get_jetpack_email() {
3037        if ( ! class_exists( 'Jetpack' ) ) {
3038            return false;
3039        }
3040
3041        // For version of Jetpack prior to 7.7.
3042        if ( defined( 'JETPACK__VERSION' ) && version_compare( JETPACK__VERSION, '7.7', '<' ) && ! class_exists( 'Jetpack_IXR_Client' ) ) {
3043            Jetpack::load_xml_rpc_client();
3044        }
3045
3046        $xml = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
3047        $xml->query( 'wpcom.getUserEmail' );
3048        if ( ! $xml->isError() ) {
3049            return $xml->getResponse();
3050        }
3051
3052        return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() );
3053    }
3054
3055    function get_key_via_jetpack( $already_purchased = false ) {
3056        if ( ! class_exists( 'Jetpack' ) ) {
3057            return false;
3058        }
3059
3060        // For version of Jetpack prior to 7.7.
3061        if ( defined( 'JETPACK__VERSION' ) && version_compare( JETPACK__VERSION, '7.7', '<' ) && ! class_exists( 'Jetpack_IXR_Client' ) ) {
3062            Jetpack::load_xml_rpc_client();
3063        }
3064
3065        $xml = new Jetpack_IXR_Client( array( 'user_id' => Jetpack_Options::get_option( 'master_user' ) ) );
3066        $xml->query( 'vaultpress.registerSite', $already_purchased );
3067        if ( ! $xml->isError() ) {
3068            return $xml->getResponse();
3069        }
3070
3071        return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() );
3072    }
3073
3074    function register_via_jetpack( $already_purchased = false ) {
3075        $registration_key = $this->get_key_via_jetpack( $already_purchased );
3076        if ( is_wp_error( $registration_key ) ) {
3077            return $registration_key;
3078        }
3079
3080        return self::register( $registration_key );
3081    }
3082}
3083
3084$vaultpress = VaultPress::init();
3085
3086if ( isset( $_GET['vaultpress'] ) && $_GET['vaultpress'] ) {
3087    if ( !function_exists( 'wp_magic_quotes' ) ) {
3088        // Escape with wpdb.
3089        $_GET    = add_magic_quotes( $_GET    );
3090        $_POST   = add_magic_quotes( $_POST   );
3091        $_COOKIE = add_magic_quotes( $_COOKIE );
3092        $_SERVER = add_magic_quotes( $_SERVER );
3093
3094        // Force REQUEST to be GET + POST.  If SERVER, COOKIE, or ENV are needed, use those superglobals directly.
3095        $_REQUEST = array_merge( $_GET, $_POST );
3096    } else {
3097        wp_magic_quotes();
3098    }
3099
3100    if ( !function_exists( 'wp_get_current_user' ) )
3101        include ABSPATH . '/wp-includes/pluggable.php';
3102
3103    // TODO: this prevents some error notices but do we need it? is there a better way to check capabilities/logged in user/etc?
3104    if ( function_exists( 'wp_cookie_constants' ) && !defined( 'AUTH_COOKIE' ) )
3105        wp_cookie_constants();
3106
3107    $vaultpress->parse_request( null );
3108
3109    die( 0 );
3110}
3111
3112// only load hotfixes if it's not a VP request
3113require_once __DIR__ . '/class.vaultpress-hotfixes.php';
3114$hotfixes = new VaultPress_Hotfixes();
3115
3116// Add a helper method to WP CLI for auto-registerion via Jetpack
3117if ( defined( 'WP_CLI' ) && WP_CLI ) {
3118    require_once __DIR__ . '/class.vaultpress-cli.php';
3119}
3120
3121require_once __DIR__ . '/cron-tasks.php';