<?php
/*
	Copyright (C) 2015-25 CERBER TECH INC., https://wpcerber.com

    Licenced under the GNU GPL.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/

/*

*========================================================================*
|                                                                        |
|	       ATTENTION!  Do not change or edit this file!                  |
|                                                                        |
*========================================================================*

*/

if ( ! defined( 'WPINC' ) ) {
	exit;
}

require_once( cerber_plugin_dir() . '/cerber-maintenance.php' );
require_once( __DIR__ . '/cerber-nexus-list.php' );

const CRB_ADD_MANAGED_LNK = '#TB_inline?width=450&height=350&inlineId=crb-popup-add-site';
const CRB_NX_MANAGED = 'managed-edit-form'; // Special form for editing managed site data

/**
 * @since 8.9.5.7
 */
final class CRB_Nexus {
	private static $net_data = array();
	private static $last_http;
	private static $last_curl;

	/**
	 * Sends request to a remote client website
	 *
	 * @param array $request
	 * @param int $client_id
	 *
	 * @return bool|mixed
	 */
	static function send( $request, $client_id = null ) {

		nexus_diag_log( '/\/ Initiating ' . $request['type'] . ' request to the website' . ( ( $client_id ) ? ' #' . $client_id : ' from context' ) );

		$website = ( $client_id ) ? nexus_get_client_data( $client_id ) : nexus_get_context();

		if ( ! $website ) {
			return false;
		}

		$network = CRB_Nexus::net_send_request( $request, $website );

		nexus_update_client_data( $website->id, array( 'last_http' => self::$last_http ) );

		if ( ! crb_is_wp_error( $network ) ) {

			nexus_process_extra( $network, $website );

			if ( ! empty( $network['payload']['error'] ) ) { // A critical error on the remote website
				$m = 'An error occurred on ' . $website->site_name . ': ' . crb_escape_html( $network['payload']['error'][1] );
				cerber_admin_notice( $m );
				nexus_diag_log( $m );

				return '';
			}

			$response = $network['payload'];

			if ( cerber_is_wp_ajax() ) {
				return $response;
			}

			if ( isset( $response['redirect'] ) ) {
				if ( $redirect_to = crb_array_get( $response, 'redirect_url' ) ) {
					nexus_diag_log( '> > > Redirecting to ' . $redirect_to );
					CRB_Globals::$redirect_url = $redirect_to;

                    // TODO should we use crb_safe_redirect()???
					header( 'Location: ' . $redirect_to, true, 302 );
					exit;
				}
				else {
					cerber_safe_redirect( crb_array_get( $response, 'remove_args' ) );
				}
			}

			return $response;
		}
		else {

			$codes = array(
				301 => 'Unexpected HTTP redirection on the managed website. Check if WP Cerber is installed and active on the website.',
				302 => 'Unexpected HTTP redirection on the managed website. Check if WP Cerber is installed and active on the website.',
				403 => 'Access to the managed website is denied.',
				500 => 'The remote website (web server) is unable to proceed due to a fatal software (PHP) error. Check the server error log on the managed website.'
			);

			$causes = array(
				'The remote website is configured to be managed from an IP address that does not match the IP address of this main website.',
				'A security plugin on the remote website is interfering with the WP Cerber plugin',
				'A directive in the .htaccess file on the remote website is blocking incoming requests',
				'A firewall or a proxy service (like Cloudflare) is blocking (filtering out) incoming requests to the remote website',
				'The IP address of this main website is locked out or in the Black Access List on the remote website',

				'The managed mode on the remote website has been re-enabled making the security token saved on this main website invalid',
				'The managed mode has been disabled on the remote website',
				'The IP address of this main website does not match the one set in the access settings on the remote website',
				'The WP Cerber plugin has been deactivated on the managed website',

				'The remote server is redirecting incoming requests to another website',
				'The domain name of the managed website has been changed',

				'The remote server is down or not responding',
				'The SSL certificate of the managed website is expired or invalid',
				'There is no network connectivity between this main website server and the server on which the managed website is running',
			);

			$kb = array(
				// 200
				'nexus_json_decode_error' => array( 5, 6, 2, 7, 1 ),
				'checksum_error'          => array( 5 ),

				// Not 200
				0                         => array( 9, 10, 11, 12, 13 ),
				301                       => array( 9, 10, 12 ),
				302                       => array( 9, 10, 12 ),
				403                       => array( 0, 1, 2, 3, 4 ),
			);

			if ( $network->get_error_code() == 'nexus_http_error' ) {
				if ( ! $error = crb_array_get( $codes, self::$last_http ) ) {
					$desc = self::$last_http . ' ' . get_status_header_desc( self::$last_http );
					$error = '<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/' . self::$last_http . '" target="_blank">' . $desc . '</a>';
				}
				else {
					$error = self::$last_http . ' ' . $error;
				}
				$error = 'HTTP ERROR ' . $error;
			}
			else {
				$error = crb_escape_html( $network->get_error_message() );
			}

			nexus_diag_log( 'NETWORK ERROR: ' . $error );

			$parse = parse_url( $website->site_url );
			$domain = $parse['host'];
			$ip = gethostbyname( $domain );
			if ( ! cerber_is_ip( $ip ) ) {
				$ip = 'Unknown. Unable to resolve the IP address. Possibly the domain ' . $domain . ' is not delegated.';
				$hostname = 'Unknown';
			}
			else {
				$hostname = gethostbyaddr( $ip );
			}

			?>
            <div style="padding: 4em;">

                <h3><?php _e( 'Invalid response from the remote website', 'wp-cerber' ); ?></h3>
                <p style="color: red; font-weight: 600;"><?php echo $error; ?></p>
                Remote website: <?php echo $website->site_name; ?><br/>
                Remote website URL: <?php echo $website->site_url; ?><br/>
                <p style="font-weight: 600;">This may be caused by a number of reasons</p>
                <ul style="list-style: disc;">
					<?php

					if ( ! $kb_filter = crb_array_get( $kb, $network->get_error_code() ) ) {
						if ( ! $kb_filter = crb_array_get( $kb, self::$last_http ) ) {
							$kb_filter = null;
						}
					}

					$show = array();
					if ( $kb_filter ) {
						foreach ( $kb_filter as $key ) {
							$show[] = $causes[ $key ];
						}
						//$show = array_intersect_key( $causes, array_flip( $kb_filter ) );
					}

					if ( ! $show ) {
						$show = $causes;
					}

					echo '<li>' . implode( '</li><li>', $show ) . '</li>';

					?>
                </ul>

				<?php

                if ( $website = nexus_get_context() ) {
					echo '<p><a href="' . nexus_get_back_link() . '">Switch back to the main website</a></p>';
					echo '<p><a href="' . rtrim( $website->site_url, '/' ) . '/wp-admin/admin.php?page=cerber-nexus" target="_blank">Check the WP Cerber access settings on ' . $website->site_name . '</a></p>';
				}

				?>

                <h3>Diagnostic information</h3>
                <p class="crb-monospace" style="font-size: 90%">
                    HTTP code: <?php echo self::$last_http; ?><br/>
                    Response size: <?php echo self::$last_curl['size_download']; ?><br/>
                    IP address: <?php echo $ip; ?><br/>
                    Hostname: <?php echo $hostname; ?><br/>
					<?php

					foreach ( self::$last_curl as $key => $val ) {
						if ( is_scalar( $val ) ) {
							echo $key . ': ' . crb_escape_html( $val );
						}
						else {
							echo $key . ': ';
							print_r( $val );
						}
						echo '<br/>';
					}
					?>
                </p>

				<?php

				if ( $http_content = $network->get_error_data() ) {
					echo '<h3>Response from the remote website</h3>';
					echo '<div class="crb-monospace" style="width: 100%; max-height: 50vh; overflow: auto; background-color: #fff; border: 2px solid #aaa; padding: 1em;">' . crb_escape_html( $http_content ) . '</div>';
				}
				?>

            </div>
			<?php
		}

		return false;
	}

	/**
	 * @param $payload array
	 * @param $remote object
	 *
	 * @return bool|array|string|WP_Error
	 */
	static function net_send_request( $payload, $remote ) {

		CRB_Nexus::$net_data = array();

		if ( ! is_super_admin()
		     && ! ( defined( 'CRB_DOING_BG_TASK' ) && CRB_DOING_BG_TASK ) ) {
			return new WP_Error( 'no_context', 'Not permitted in this context' );
		}

		if ( empty( $remote->site_pass )
		     || empty( $remote->site_url )
		     || empty( $remote->x_field )
		     || empty( $remote->x_num )
		     || ( ! $field_names = nexus_get_fields( $remote ) ) ) {
			return new WP_Error( 'invalid_client_data', 'Mandatory fields for connecting to the managed website #' . $remote->id . ' are invalid or missing' );
		}

		array_walk_recursive( $payload, function ( &$e ) {
			$e = str_replace( array( "\r\n", "\n", "\r" ), '<br/>', $e ); // preserve new lines before json_encode
		} );

		$data = array();
		$data['seal'] = nexus_seal();
		$data['params'] = $_GET;
		$data['base'] = ( ! is_multisite() ) ? admin_url() : network_admin_url();
		$data['assets'] = CRB_Globals::assets_url();
		$data['is_post'] = cerber_is_http_post();
		$data['payload'] = $payload;
		$data[ rand() ] = rand(); // random checksum for identical requests

		if ( crb_get_settings( 'master_locale' ) ) {
			$data['master_locale'] = crb_get_admin_locale();
		}

		if ( ! cerber_is_wp_ajax()
		     && ! cerber_is_wp_cron() ) {
			$data['page'] = crb_admin_get_page();
			$data['tab'] = crb_admin_get_tab();
			$data['at_site'] = ( ! crb_get_settings( 'master_at_site' ) ) ? '' : ' @ ' . $remote->site_name;
			$data['screen'] = array( 'per_page' => crb_admin_get_per_page() );
		}

		$x_num = array_shift( $field_names );

		$fields = array();

		$fields[ $field_names[0] ] = json_encode( $data, JSON_UNESCAPED_UNICODE );
		if ( JSON_ERROR_NONE != json_last_error() ) {
			return new WP_Error( 'json_error', 'Unable to encode request: ' . json_last_error_msg() );
		}

		$auth = hash( 'sha512', $remote->site_pass . sha1( $fields[ $field_names[0] ] ) );

		foreach ( $field_names as $i => $name ) {
			if ( isset( $fields[ $name ] ) ) {
				continue;
			}
			if ( $x_num == $i ) {
				$fields[ $name ] = $auth;
			}
			else {
				$fields[ $name ] = str_shuffle( $auth );
			}
		}

		$curl = @curl_init();
		if ( ! $curl ) {
			return new WP_Error( 'no_curl', 'Unable to init cURL library. Enable PHP cURL extension in your hosting control panel.' );
		}

		nexus_diag_log( 'Sending HTTP request to ' . $remote->site_url );

		$curl_opt = array(
			CURLOPT_URL               => $remote->site_url,
			CURLOPT_FOLLOWLOCATION    => 0,
			CURLOPT_POST              => true,
			CURLOPT_POSTFIELDS        => http_build_query( $fields ),
			CURLOPT_RETURNTRANSFER    => true,
			CURLOPT_CONNECTTIMEOUT    => 10, // including domain resolving
			CURLOPT_TIMEOUT           => 15, // including CURLOPT_CONNECTTIMEOUT
			CURLOPT_DNS_CACHE_TIMEOUT => 1 * 3600,
			CURLOPT_SSL_VERIFYHOST    => 2,
			CURLOPT_SSL_VERIFYPEER    => true,
			//CURLOPT_CERTINFO          => 1,
			//CURLOPT_VERBOSE          => 1,
			CURLOPT_CAINFO            => ABSPATH . WPINC . '/certificates/ca-bundle.crt',
			CURLOPT_ENCODING          => '', // allows built-in compressions
		);

		if ( defined( 'CERBER_HUB_UA' ) ) {
			$curl_opt[ CURLOPT_USERAGENT ] = (string) CERBER_HUB_UA;
		}

		crb_configure_curl( $curl, $curl_opt, 'cerber_hub_proxy' );

		$response = @curl_exec( $curl );
		$curl_info = curl_getinfo( $curl );
		$code = intval( curl_getinfo( $curl, CURLINFO_HTTP_CODE ) );
        self::$last_http = $code;
		self::$last_curl = $curl_info;

		curl_close( $curl );

		if ( $code == 200 ) {
			nexus_diag_log( 'HTTP 200 OK' );
			nexus_diag_log( 'Remote data size ' . $curl_info['size_download'] . ', receiving took ' . $curl_info['total_time'] . ' seconds' );
		}

		nexus_diag_log( 'Domain name lookup took ' . $curl_info['namelookup_time'] . ' seconds' );

		if ( $code != 200 ) {
			nexus_diag_log( '=== NETWORK SUBSYSTEM ===' );
			nexus_diag_log( $curl_info );

			/*if ( $code != 403 ) {
				cerber_update_set( 'bad_response_' . $remote->id, array( time(), $code ) );
			}*/

			return new WP_Error( 'nexus_http_error', $code, substr( $response, 0, 3000 ) );
		}

		$ret = json_decode( $response, true );

		if ( JSON_ERROR_NONE != json_last_error() ) {
			return new WP_Error( 'nexus_json_decode_error', 'Unable to decode the response from the remote website: ' . json_last_error_msg(), substr( $response, 0, 3000 ) );
		}

		if ( ! isset( $ret['payload'] )
		     || ! isset( $ret['echo'] ) ) {
			return new WP_Error( 'nexus_invalid_client_response', 'Invalid client response.' );
		}

		if ( is_array( $ret['payload'] ) ) {
			if ( isset( $ret['scheme'] ) ) { // @since 8.0.5
				$sha = sha1( json_encode( $ret['payload'], JSON_UNESCAPED_UNICODE ) );
			}
			else {
				$sha = sha1( serialize( $ret['payload'] ) ); // 8.0
			}
		}
		else {
			$sha = sha1( $ret['payload'] );
		}

		if ( ! hash_equals( $ret['echo'], hash( 'sha512', $remote->site_echo . $sha ) ) ) {
			return new WP_Error( 'checksum_error', 'Checksum mismatch: the response we got from the remote website has been altered or secret tokens mismatch.' );
		}

		nexus_diag_log( 'Website "' . $remote->site_name . '" has generated response for ' . $ret['p_time'] );

		nexus_diag_log( '=== NETWORK HAS FINISHED OK ===' );

		CRB_Nexus::$net_data = $ret;

		return $ret;
	}

	/**
	 * @return array
	 */
	static function get_remote_data() {
		return self::$net_data;
	}
}

function nexus_show_website_list() {

	cerber_cache_enable();

    echo '<form id="crb-nexus-sites" method="get" action="">';
	wp_nonce_field( 'control', 'cerber_nonce' );
	echo '<input type="hidden" name="page" value="' . crb_admin_get_page() . '">';
	echo '<input type="hidden" name="cerber_admin_do" value="nexus_site_table">';

    $websites = new CRB_Nexus_Sites();
	$websites->prepare_items();
	$websites->search_box( 'Search', 'search_id' );
	$websites->display();

    echo '</form>';
}

function nexus_master_screen() {
	// Standard WP options
	crb_admin_screen_options();

	// Add our own fields
	add_filter( 'screen_settings', function ( $form, $screen ) {
		$set = get_site_option( '_cerber_slist_screen', array() );
		$checked = ( crb_array_get( $set, 'url_name' ) ) ? 'checked="checked"' : '';
		$form .= '<legend>Layout</legend>';
		$form .= '<label for="crbuina">' . __( 'Show homepage in the Website column', 'wp-cerber' ) . '</label><input id="crbuina" type="checkbox" name="crb_url_in_name" value="1" ' . $checked . '/>';
		$checked = ( crb_array_get( $set, 'srv_ip' ) ) ? 'checked="checked"' : '';
		$form .= '<br/><label for="crbsrip">' . __( 'Hide server IP address', 'wp-cerber' ) . '</label><input id="crbsrip" type="checkbox" name="crb_srv_ip" value="1" ' . $checked . '/>';
		$form .= '<input type="hidden" name="nexus_slave_list_screen" value="1" />';

		return $form;
	}, 10, 2 );
}

add_filter( 'set-screen-option', function ( $status ) {
	// Save our own fields
	if ( isset( $_POST['nexus_slave_list_screen'] ) ) {
		$set             = get_site_option( '_cerber_slist_screen', array() );
		$set['url_name'] = crb_array_get( $_POST, 'crb_url_in_name', 0, '\d' );
		$set['srv_ip']   = crb_array_get( $_POST, 'crb_srv_ip', 0, '\d' );
		update_site_option( '_cerber_slist_screen', $set );
	}

	return $status;
} );

function nexus_show_site_edit_form( $site_id ) {
	$site = nexus_get_client_data( $site_id );
	if ( ! $site ) {
		echo 'Website not found. <a href="' . cerber_admin_link( 'nexus_sites' ) . '">Return to the list</a>.';

		return;
	}

	$p = __( 'Select an existing group or enter a new one to add it', 'wp-cerber' );

	// We utilize WP settings API routines just to render the edit form

	$edit_fields = nexus_client_form_fields(); // TODO implement this

	$edit_fields = array(
		'main' => array(
			'name'   => __( 'Website Properties', 'wp-cerber' ),
			//'info'   => __( 'User related settings', 'wp-cerber' ),
			'fields' => array(
				'site_id'    => array(
					'value'    => $site->id,
					'db_field' => 'id',
					'type'     => 'hidden',
					'title'    => '',
				),
				'site_url'   => array(
					'title'    => __( 'Website URL', 'wp-cerber' ),
					'value'    => $site->site_url,
					'disabled' => true,
				),
				'site_name'  => array(
					'title'     => __( 'Display as', 'wp-cerber' ),
					'label'     => 'Original website name: ' . $site->site_name_remote,
					'value'     => $site->site_name,
					'required'  => true,
					'maxlength' => 200
				),
				'site_group' => array(
					'input_class' => 'crb-wide crb-select2-tags',
					'title'       => __( 'Group', 'wp-cerber' ),
					'set'         => nexus_get_groups( true ),
					'value'       => $site->group_id,
					'db_field'    => 'group_id',
					'type'        => 'select',
					'label'       => $p,
					'placeholder' => $p
				),
				/*'site_server' => array(
					'title'     => __( 'Server Name', 'wp-cerber' ),
					'value'     => $site->site_server,
					'maxlength' => 1000
				),*/
				'site_notes' => array(
					'title'     => __( 'Notes', 'wp-cerber' ),
					'value'     => $site->site_notes,
					'type'      => 'textarea',
					'maxlength' => 1000
				),
			)
		),
		'owner' => array(
			'name'   => __( 'Website Owner', 'wp-cerber' ),
			//'info'   => __( 'User related settings', 'wp-cerber' ),
			'fields' => array(
				'first_name'    => array(
					'title'     => __( 'First Name' ),
					'maxlength' => 100
				),
				'last_name'     => array(
					'title'     => __( 'Last Name' ),
					'maxlength' => 100
				),
				'owner_email'   => array(
					'title'     => __( 'Email' ),
					'maxlength' => 100
				),
				'owner_phone'   => array(
					'title'     => __( 'Phone', 'wp-cerber' ),
					'maxlength' => 100,
				),
				'owner_biz'   => array(
					'title'     => __( 'Company', 'wp-cerber' ),
					'maxlength' => 200,
				),
				'owner_address' => array(
					'title'     => __( 'Address', 'wp-cerber' ),
					'maxlength' => 200
				),
			)
		),
	);

	foreach ( $edit_fields['owner']['fields'] as $key => &$f ) {
		$f['value'] = crb_array_get( $site->details, $key );
	}

	// TODO: replace WP settings API with a new form-processing engine
	cerber_wp_settings_setup( CRB_NX_MANAGED, $edit_fields );
	cerber_show_settings_form( CRB_NX_MANAGED );
}

function nexus_client_form_fields() {
	return array( 'first_name', 'last_name', 'owner_email', 'owner_phone', 'owner_biz', 'owner_address' );
}

function nexus_get_groups( $sort = false ) {
	if ( ! $groups = cerber_get_set( 'nexus_groups' ) ) {
		$groups = array( 'Default' );
	}

	if ( $sort ) {
		asort( $groups );
	}

	return $groups;
}

add_action( 'admin_init', function () {

	CRB_Globals::admin_init();

	if ( is_admin() && nexus_is_main() ) { // @since 8.6.3.3
		nexus_set_context();
		if ( nexus_get_context() ) {
			nexus_send_admin_request();
		}
	}

	if ( nexus_is_main() && function_exists( 'nexus_schedule_refresh' ) ) {
		nexus_schedule_refresh();
	}

	if ( ! is_super_admin() ) {
		return;
	}

	if ( cerber_is_wp_ajax() ) {
		nexus_ajax_router();
	}

	if ( nexus_get_context() ) {
        // @since 9.5.3.3
		//add_action( 'admin_notices', 'cerber_show_admin_notice', 999 );
		//add_action( 'network_admin_notices', 'cerber_show_admin_notice', 999 );
	}

	// Some tricks to obtain form data via WP settings API
	register_setting( 'cerberus-' . CRB_NX_MANAGED, 'cerber-' . CRB_NX_MANAGED );
	add_filter( 'pre_update_option_cerber-' . CRB_NX_MANAGED, function ( $fields, $old_value, $option ) {

		cerber_cache_enable();

		$site_id = absint( $fields['site_id'] );

		$group_id = 0;
		if ( ! empty( $fields['site_group'] ) ) {
			$groups = nexus_get_groups();
			if ( is_numeric( $fields['site_group'] ) ) {
				$group_id = (int) $fields['site_group'];
			}
			if ( ! $group_id || ! isset( $groups[ $group_id ] ) ) {
				// Add new group
				$new = strip_tags( $fields['site_group'] );
				if ( $new ) {
					$groups   = nexus_get_groups();
					$groups[] = $new;
					end( $groups );
					$group_id = key( $groups );
					cerber_update_set( 'nexus_groups', $groups );
				}
			}
		}

		$new_details = array_intersect_key( $fields, array_flip( nexus_client_form_fields() ) );
		$new_details = array_map( 'strip_tags', $new_details );

		$fields = array_replace( $fields, $new_details );

		$fields['group_id'] = $group_id;
		nexus_update_client_data( $site_id, $fields );
		nexus_delete_unused( 'nexus_groups', 'group_id' );

		return '';
	}, 10, 3 );

}, 0 );

function nexus_add_client( $token ) {
	if ( ! is_super_admin() || ! nexus_is_main() ) {
		return;
	}
	$token = trim( $token );
	if ( ( ! $t = nexus_the_token( $token ) )
	     || empty( $t[0] )
	     || empty( $t[1] )
	     || empty( $t[2] )
	     || empty( $t[3] )
	     || empty( $t[4] )
	     || empty( $t[5] )
	     || empty( $t[6] )
	) {
		cerber_admin_notice( __( 'Secret Access Token is invalid', 'wp-cerber' ) );

		return;
	}

	// Subdir install? Add slash to avoid 301 redirection
	if ( strpos( substr( $t[5], strpos( $t[5], '.' ) ), '/' ) ) {
		$url = rtrim( $t[5], '/' ) . '/';
	}
	else {
		$url = $t[5];
    }

	$data                     = array();
	$data['site_pass']        = $t[0];
	$data['site_echo']        = $t[1];
	$data['x_field']          = $t[2];
	$data['x_num']            = $t[3];
	// These are shown in the dashboard, make them safe
	$data['site_url']         = substr( crb_escape_url( $url ), 0, 250 );
	$data['site_name']        = mb_substr( crb_escape_html( htmlspecialchars_decode( $t[6] ) ), 0, 250 );
	$data['site_name_remote'] = $data['site_name'];

	if ( cerber_db_get_var( 'SELECT id FROM ' . cerber_get_db_prefix() . CERBER_MS_TABLE . ' WHERE site_url = ' . $data['site_url'] ) ) {
		cerber_admin_notice( __( 'The website you are trying to add is already in the list', 'wp-cerber' ) );
	}

	if ( ! cerber_db_insert( cerber_get_db_prefix() . CERBER_MS_TABLE, $data ) ) {
		cerber_admin_notice( 'Unable to add website' );
	}
	else {
		$site_id = cerber_db_get_var( ' SELECT LAST_INSERT_ID()' );
		$edit = cerber_admin_link( 'nexus_sites', array( 'site_id' => $site_id ) );

        cerber_admin_message( __( 'The website has been added successfully', 'wp-cerber' )
                              . '&nbsp; [ <a href="' . $edit . '">' . __( 'Click to edit', 'wp-cerber' ) . '</a> | '
		                      . ' <a href="' . cerber_admin_link_add( [ 'cerber_admin_do' => 'nexus_switch', 'nexus_site_id' => $site_id ] ) . '">' . __( 'Switch to the Dashboard', 'wp-cerber' ) . '</a> ]' );

        if ( 'https://' !== substr( $url, 0, 8 ) ) {
			cerber_admin_notice( __( 'Keep in mind: You have added the website that does not support SSL encryption. This may lead to data leakage.', 'wp-cerber' ) );
		}

		nexus_add_bg_refresh( $site_id );
	}

}

/**
 * @param $id int Remote site ID
 * @param $data array Sanitized remote site data
 *
 * @return bool
 */
function nexus_update_client_data( $id, $data ) {

	$id = absint( $id );
	if ( ! $id ) {
		return false;
	}

	$old = nexus_get_client_data( $id );

	// Details
	$old_details = ( is_array( $old->details ) ) ? $old->details : array();
	$details_fields = nexus_client_form_fields();
	if ( $new_details = array_intersect_key( $data, array_flip( $details_fields ) ) ) {
		$data['details'] = serialize( array_replace( $old_details, $new_details ) );
	}

	// Name is always stored in escaped form!
	if ( isset( $data['site_name'] ) ) {
		$data['site_name'] = crb_escape_html( $data['site_name'] );
	}

	// 1. Numbers
	$int_columns = array( 'group_id', 'site_status', 'updates', 'refreshed', 'last_scan', 'last_http', 'site_key' );
	$update      = array_map( function ( $e ) {
		return absint( $e );
	}, array_intersect_key( $data, array_flip( $int_columns ) ) );

	// 2. Escaping strings
	$str_columns = array( 'site_name', 'details', 'site_notes', 'plugin_v', 'wp_v', 'server_id', 'server_country' );
	$update      = array_merge( $update,
		array_map( function ( $e ) {
			return cerber_db_real_escape( $e );
		}, array_intersect_key( $data, array_flip( $str_columns ) ) ) );

	// SQL clause
	$fields = array();
	foreach ( $update as $field => $value ) {
		$fields[] = $field . '="' . $value . '"';
	}
	$sql_fields = implode( ',', $fields );

	if ( cerber_db_query( 'UPDATE ' . cerber_get_db_prefix() . CERBER_MS_TABLE . ' SET ' . $sql_fields . ' WHERE id = ' . $id ) ) {
		return true;
	}

	return false;
}

/**
 * Returns remote website data from the local storage
 *
 * @param int $site_id
 *
 * @return bool|object
 */
function nexus_get_client_data( $site_id ) {
	$site_id = absint( $site_id );
	$site = cerber_db_get_row( 'SELECT * FROM ' . cerber_get_db_prefix() . CERBER_MS_TABLE . ' WHERE id = ' . $site_id, MYSQL_FETCH_OBJECT );

    if ( ! $site ) {
		return false;
	}

	if ( ! empty( $site->details ) ) {
		$site->details = crb_unserialize( $site->details );
	}

	return $site;
}

function nexus_get_clients( $args ) {
	$order = '';
	if ( isset( $args['orderby'] ) ) {
		$order = ' ORDER BY ' . $args['orderby'] . ' ';
	}
	if ( isset( $args['order'] ) ) {
		$order .= $args['order'];
	}

	return cerber_db_get_results( 'SELECT * FROM ' . cerber_get_db_prefix() . CERBER_MS_TABLE . $order, MYSQL_FETCH_OBJECT );
}

/**
 * Deletes client(s) and all remote website data stored locally
 *
 * @param $ids integer|integer[]
 *
 * @return bool
 */
function nexus_delete_client( $ids ) {
	if ( ! is_super_admin() ) {
		return false;
	}

	if ( ! is_array( $ids ) ) {
		$ids = array( $ids );
	}

	$ids = array_map( function ( $e ) {
		return absint( $e );
	}, $ids );

	$ret = cerber_db_query( 'DELETE FROM ' . cerber_get_db_prefix() . CERBER_MS_TABLE . ' WHERE id IN (' . implode( ',', $ids ) . ')' );

	if ( $ret ) {
		$num = cerber_db_get_var( 'SELECT ROW_COUNT()' );
		/* translators: Placeholder %d will be replaced by the number of deleted websites. */
		cerber_admin_message( sprintf( _n( 'Website has been deleted', '%d websites have been deleted', $num, 'wp-cerber' ), $num ) );

		foreach ( $ids as $id ) {
			nexus_delete_list( $id );
		}

		nexus_delete_unused( 'nexus_servers', 'server_id' );
		nexus_delete_unused( 'nexus_countries', 'server_country' );
		nexus_delete_unused( 'nexus_groups', 'group_id' );

		return true;
	}
	else {
		cerber_admin_notice( 'Unable to delete website' );
		return false;
	}
}

function nexus_get_back_link() {
	return cerber_admin_link_add( array( 'cerber_admin_do' => 'nexus_switch', 'nexus_site_id' => 0 ) );
}

// ======================================================================================

function nexus_show_remote_page() {

    /* This code for new settings mechanism

	$client = nexus_get_context();
	if ( cerber_is_http_post()
		 && ( $m = cerber_get_post( 'cerber_nexus_seal' ) )
		 && sha1( $client->id . '|' . get_current_user_id() ) === $m ) {
		$response = CRB_Nexus::send( array(
			'type' => 'submit',
			'data' => array(
				'post' => $_POST,
			)
		) );
	}
	else {
		$response = CRB_Nexus::send( array(
			'type' => 'get_page',
			'data' => array(
			)
		) );
	}*/

    // An old, two steps version
    // TODO: remove the second step after upgrading Cerber's settings mechanism to a new version

	if ( cerber_is_http_post()
	     && ( nexus_seal( crb_array_get( $_POST, 'cerber_nexus_seal', 'none' ) ) ) ) {
		$response = CRB_Nexus::send( array(
			'type' => 'submit',
			'data' => array(
				'post' => $_POST,
			)
		) );
	}

	// A separate request to render the page cause settings cache is updated now
	$response = CRB_Nexus::send( array(
		'type' => 'get_page'
	) );

	if ( is_array( $response ) ) {
		echo $response['html'];
	}
	else {
		echo $response;
	}
}

function nexus_send_admin_request() {
	if ( empty( $_GET['cerber_admin_do'] )
	     || ( empty( $_GET['cerber_nonce'] )
	          && empty( $_POST['cerber_nonce'] ) ) ) {
		return;
	}
	$response = CRB_Nexus::send( array(
		'type' => 'manage'
	) );
}

function nexus_ajax_router() {
    //return;

    if ( empty( $_REQUEST['action'] )
	     || empty( $_REQUEST['ajax_nonce'] ) ) {
		return;
	}

	if ( ! nexus_is_main()
	     || ! nexus_get_context()
	     || ! crb_admin_allowed_ajax( $_REQUEST['action'] )
	     || ! is_user_logged_in()
    ) {
		return;
	}

	check_ajax_referer( 'crb-ajax-admin', 'ajax_nonce' );

	$response = CRB_Nexus::send( array(
		'type'  => 'ajax',
		//'cache' => false,
		'data'  => array(
			'post' => $_POST,
		)
	) );

	if ( crb_is_wp_error( $response ) ) {
		nexus_diag_log( 'NETWORK ERROR: ' . $response->get_error_message() );
	}
	else {
		echo $response;
	}

	exit;
}

/**
 * @param array $request
 * @param int $client_id
 *
 * @return bool|mixed
 */
function nexus_send( $request, $client_id = null ) {
	return CRB_Nexus::send( $request, $client_id );
}

/**
 * @param array $data
 * @param object $client
 *
 * @return void
 */
function nexus_process_extra( $data, $client ) {

	$update = array();

	$v = $data['extra']['versions'];

	if ( $client->plugin_v != $v[0] ) {
		$update['plugin_v'] = $v[0];
	}
	if ( $client->wp_v != $v[1] ) {
		$update['wp_v'] = $v[1];
	}

	if ( isset( $v[6] ) && $client->site_key != $v[6] ) {
		$update['site_key'] = $v[6];
	}

	if ( $nums = crb_array_get( $data['payload'], 'numbers' ) ) {

		$update['refreshed'] = time();

		$u = $nums['updates']['plugins'] + $nums['updates']['wp'];
		if ( $client->updates != $u ) {
			$update['updates'] = $u;
		}

		if ( ! empty( $nums['scan'] ) ) {
			$update['last_scan'] = $nums['scan']['finished'];

			if ( $nb = crb_array_get( $nums['scan'], 'numbers' ) ) {
				// TODO move to separate meta table?
				cerber_update_set( '_nexus_tmp_' . $client->id, $nb );
			}
		}

		// Plugins

        if ( $plugins = crb_array_get( $nums, 'plugins' ) ) {
			$installed = array();

			$active = array_flip( crb_array_get( $nums, 'active', array() ) );

	        foreach ( $plugins as $plugin => $data ) {

	            $key = 'nexus_p_' . sha1( $plugin . $data['Version'] );

		        $installed[] = array(
			        $key,
			        $data['Version'],
			        (string) ( isset( $active[ $plugin ] ) ) ? '1' : '0'
		        );

		        // Plugin data for common usage

		        if ( ! cerber_get_set( $key, null, false ) ) {
			        $data['plugin_slug'] = $plugin;
			        $data['plugin_key']  = sha1( $plugin );
			        cerber_update_set( $key, $data );
		        }
	        }

			nexus_update_list( $client->id, 'plugins', $installed );
		}

		// Updates

		if ( $pl_updates = crb_array_get( $nums, 'pl_updates' ) ) {
			nexus_update_updates( $pl_updates );
		}

	}

	if ( $update ) {
		nexus_update_client_data( $client->id, $update );
		if ( ! $nums ) {
			nexus_add_bg_refresh( $client->id );
		}
	}

}

function nexus_update_updates( $pl_updates ) {
	foreach ( $pl_updates as $plugin => $data ) {

		$key = 'nexus_upd_' . sha1( $plugin );
		$go  = true;

		if ( ( $pup = cerber_get_set( $key ) )
		     && ( $data['new_version'] == $pup['new_version'] ) ) {
			$go = false;
		}
		if ( $go ) {
			cerber_update_set( $key, $data );
		}
	}
}

function nexus_get_update( $plugin, $version = null ) {

	if ( ! $update = cerber_get_set( 'nexus_upd_' . sha1( $plugin ) ) ) {
		return false;
	}

	if ( $version
	     && version_compare( $version, $update['new_version'], '>=' ) ) {
		return false;
	}

	return $update;
}

function nexus_set_context() {
	if ( 'nexus_switch' != cerber_get_get( 'cerber_admin_do' )
	     || ! wp_verify_nonce( cerber_get_get( 'cerber_nonce' ), 'control' )
	     || ! is_super_admin() ) {
		return;
	}

	$id = absint( cerber_get_get( 'nexus_site_id' ) );

	if ( $client = nexus_get_client_data( $id ) ) {
		if ( crb_get_settings( 'master_swshow' ) ) {
			cerber_admin_message( sprintf( __( 'You have switched to %s', 'wp-cerber' ), $client->site_name ) . '. ' . 'To switch back to the main website, click the X icon on the toolbar.' );
		}

		$expire = time() + apply_filters( 'auth_cookie_expiration', 14 * DAY_IN_SECONDS, get_current_user_id(), true );

		if ( $back = cerber_get_get( 'back' ) ) {
			update_user_meta( get_current_user_id(), 'nexus_back_to_url', $back );
		}
	}
	else {
		cerber_admin_message( __( 'You have switched back to the main website', 'wp-cerber' ) );
		$expire = time();
		$id = 0;
	}

	cerber_set_cookie( 'cerber_nexus_id', $id, $expire, '/' );
	$remove = array(
		'cerber_admin_do',
		'cerber_nonce',
		'nexus_site_id',
        'back'
	);

	if ( $id ) {
		if ( crb_admin_get_page() == 'cerber-nexus' ) {
			$url = cerber_admin_link();
		}
		else {
			$url = remove_query_arg( $remove );
		}
	}
	else {
		if ( crb_get_settings( 'master_tolist' ) ) {
			if ( ! $url = get_user_meta( get_current_user_id(), 'nexus_back_to_url', true ) ) {
				$url = cerber_admin_link( 'nexus_sites' );
			}
			else {
				update_user_meta( get_current_user_id(), 'nexus_back_to_url', '' );
			}
		}
		else {
			$url = remove_query_arg( $remove );
		}
	}

	if ( ! $url ) {
		$url = cerber_admin_link();
	}

	crb_safe_redirect( $url );
	exit();
}

/**
 * A light version of nonce (pre-nonce)
 *
 * @param string $check_it
 *
 * @return bool|string
 */
function nexus_seal( $check_it = null ) {
	$client = nexus_get_context();

    if ( ! $client
         || ! $uid = get_current_user_id() ) {
		return false;
	}

	$seal = sha1( $client->id . '|' . $uid . '|' . PHP_VERSION . '|' . PHP_SAPI );

	if ( $check_it === null ) {
		return $seal;
	}

	return ( $seal === $check_it );
}

function nexus_do_bulk() {
	if ( ! $ids = cerber_get_get( 'ids', '\d+' ) ) {
		cerber_admin_notice( 'No items selected' );
		return;
	}
	switch ( cerber_get_bulk_action() ) {
		case 'nexus_delete_slave':
			nexus_delete_client( $ids );
			break;
		case 'nexus_upgrade_plugins':
			nexus_bg_upgrade( $ids, array() );
			//nexus_do_upgrade( $ids[0], array( CERBER_PLUGIN_ID ) );
			break;
		case 'nexus_upgrade_cerber':
			nexus_bg_upgrade( $ids, array( CERBER_PLUGIN_ID ) );
			//nexus_do_upgrade( $ids[0], array( CERBER_PLUGIN_ID ) );
			break;
	}
}

function nexus_bg_upgrade( $ids, $plugins ) {
	$error = new WP_Error();

	foreach ( $ids as $id ) {
		$ret = cerber_bg_task_add( 'nexus_do_upgrade', array(
			'args'       => array( $id, $plugins, false ),
			'exec_until' => 'stop', // may not be boolean
		) );

		if ( crb_is_wp_error( $ret ) ) {
			$error->merge_from( $ret );
		}
	}

	if ( $error->has_errors() ) {
		crb_admin_error_notice( $error );

        return;
	}

    cerber_admin_message( 'A background upgrade tasks have been launched.' );
}

function nexus_do_upgrade( $client_id, $plugins, $display_errors = false ) {
	$response = CRB_Nexus::send( array(
		'type'    => 'sw_upgrade',
		'sw_type' => 'plugins',
		'list'    => $plugins
	), $client_id );

	if ( ! empty( $response['results'] ) ) {
		nexus_diag_log( cerber_flat_results( $response['results'], $display_errors ) );
	}

	if ( ! empty( $response['wait'] ) ) {
		nexus_diag_log( 'Waiting for request from ' . $response['wait'] . ' is completed' );
		sleep( 10 );
	}

	if ( empty( $response ) || ! empty( $response['completed'] ) ) {
		nexus_add_bg_refresh( $client_id );

		return 'stop';
	}

	sleep( 3 );

	return 0;
}

function nexus_schedule_refresh() {
	// ORDER BY id DESC ?
	$t = time() - ( is_super_admin() ? 1800 : 3600 );
	if ( $sites = cerber_db_get_col( 'SELECT id FROM ' . cerber_get_db_prefix() . CERBER_MS_TABLE . ' WHERE refreshed < ' . $t . ' AND last_http <= 200 LIMIT 50' ) ) {

		foreach ( $sites as $id ) {

			// Protective interval
			$key  = 'nexus_schedule';
			$last = cerber_get_set( $key, $id, false );
			if ( $last && ( $last > ( time() - 600 ) ) ) {
				continue;
			}
			
			cerber_update_set( $key, time(), $id, false, time() + 3600 );

			/*
			$key = 'nexus_refresh';
			$log = array();
			if ( $log = cerber_get_set( $key, $id ) ) {
				if ( ! is_array( $log ) ) {
					$log = array();
				}
				elseif ( ! empty( $log ) ) {
					$delay = 300 * count( $log );
					if ( reset( $log ) > ( time() - $delay ) ) {
						continue;
					}
				}
			}

			array_shift( $log );
			$log[] = time();
			cerber_update_set( $key, $log, $id, false, time() + 60 );
			*/

			//cerber_bg_task_add( array( 'func' => 'nexus_send', 'args' => array( array( 'type' => 'hello' ), $id ) ) );
			nexus_add_bg_refresh( $id );
		}
	}
}

function nexus_add_bg_refresh( $client_id ) {
	cerber_bg_task_add( 'nexus_send', array( 'args' => array( array( 'type' => 'hello' ), $client_id ), 'load_admin' => 1 ) );
	cerber_bg_task_add( 'nexus_refresh_client_servers', array( 'args' => array( $client_id ) ) );
}

add_action( 'wp_before_admin_bar_render', function () {
	global $wp_admin_bar;

	if ( ! cerber_is_admin_page()
	     || ! nexus_is_main() ) {
		return;
	}

	if ( ! is_admin_bar_showing()
	     || ! $wp_admin_bar instanceof WP_Admin_Bar ) {
		return;
	}

	?>
    <div id="crb-popup-add-site" style="display:none;">
        <form id="crb-add-new-site" method="post" action="<?php echo cerber_admin_link( 'nexus_sites' ); ?>"
              style="padding-top:15px; height: 95%;">
			<?php wp_nonce_field( 'control', 'cerber_nonce' ); ?>
            <input type="hidden" name="page" value="cerber-nexus">
            <textarea name="new_slave_token" style="width: 100%; height: 80%; font-family: monospace;"
                      placeholder="Copy and paste Secret Access Token here"></textarea>
	        <?php
            //echo cerber_select( 'new_slave_group', nexus_get_groups(), 0, 'crb-wide crb-select2-tags' )
            ?>
            <input type="hidden" name="cerber_admin_do" value="add_slave">
            <p style="text-align: center;"><input type="submit" class="button button-primary" value="Add Website"></p>
        </form>
    </div>
	<?php

	//$wp_admin_bar->remove_node( 'new-content' );
	//$wp_admin_bar->remove_node( 'my-account' );
	$exclude = array();
	$exclude = array( 'query-monitor' );
	//$exclude = array( 'top-secondary' );
	foreach ( $wp_admin_bar->get_nodes() as $node => $data ) {
		if ( in_array( $data->id, $exclude ) ) {
			continue;
		}
		if ( empty( $data->parent ) ) {
			$wp_admin_bar->remove_node( $data->id );
		}
	}

	$current_id = null;
	if ( $current = nexus_get_context() ) {
		$current_id = $current->id;
		$title = __( 'You are here:', 'wp-cerber' ) . ' ' . $current->site_name;
	}
	else {
		$title = __( 'My Websites', 'wp-cerber' );
    }

	$wp_admin_bar->add_node( array(
		'id'    => 'crb_site_switch',
		'title' => '<span class="ab-icon"></span>' . $title,
		'href'  => cerber_admin_link( 'nexus_sites' ),
		'meta'  => array( 'class' => 'cerber-site-select' )
	) );

	$this_page = cerber_admin_link( crb_admin_get_tab(), array( 'page' => crb_admin_get_page() ), true );

	if ( $current ) {
		$wp_admin_bar->add_node( array(
			'id'    => 'crb_managed_site_menu',
			'title' => '<span class="ab-icon"></span>' . __( 'Visit Site' ),
			'href'  => $current->site_url,
		) );
		$wp_admin_bar->add_node( array(
			'id'     => 'crb_slave_admin',
			'parent' => 'crb_managed_site_menu',
			'title'  => 'Dashboard',
			'href'   => trim( $current->site_url, '/' ) . '/wp-admin/',
		) );


		$wp_admin_bar->add_node( array(
			'id'    => 'crb_to_master',
			//'parent' => 'top-secondary',
			'title' => '<span class="ab-icon"></span>',
			'meta'  => array( 'class' => 'ab-top-secondary', 'title' => 'Switch to the main website' ),
			'href'  => nexus_get_back_link(),
		) );
	}

	if ( $clients = nexus_get_clients( array( 'orderby' => 'id', 'order' => 'DESC' ) ) ) {
		foreach ( $clients as $client ) {
			if ( $current_id === $client->id ) {
				continue;
			}
			$wp_admin_bar->add_node( array(
				'parent' => 'crb_site_switch',
				'id'     => 'site' . $client->id,
				'title'  => $client->site_name,
				'href'   => $this_page . '&amp;cerber_admin_do=nexus_switch&nexus_site_id=' . $client->id,
			) );
		}
	}
	else {
		$wp_admin_bar->add_node( array(
			'parent' => 'crb_site_switch',
			'id'     => 'none',
			'title'  => 'Click here to add a remote website',
			'href'   => cerber_admin_link( 'nexus_sites' ),
		) );
	}

}, 9999 );

add_filter( 'admin_body_class', function ( $var ) {
	if ( cerber_is_admin_page() && nexus_get_context() ) {
		$var .= ' crb-remote';
	}

	return $var;
} );

add_action( 'admin_head', function () {

	if ( ! cerber_is_admin_page()
         || ! nexus_is_main() ) {
		return;
	}
	?>

    <script id="wp-cerber-js-<?php echo crb_sanitize_id( __FUNCTION__ . '_' . __LINE__ ); ?>">
        jQuery(function ($) {

            $("#wp-admin-bar-crb_managed_site_menu a").attr('target', '_blank');

            $("#crb-nexus-sites .bulkactions .button").on('click', function (e) {
                if ('nexus_delete_slave' === $(this).prev('select').find(':selected').val()) {
                    if (!confirm('<?php echo crb_escape_string_for_js_in_html( __( 'Are you sure you want to delete selected websites?', 'wp-cerber' ) ); ?>')) {
                        e.preventDefault();
                    }
                }
            });
        });
    </script>

	<?php
});

add_action( 'wp_ajax_cerber_master_ajax', function () {

	check_ajax_referer( 'crb-ajax-admin', 'ajax_nonce' );

    if ( ! is_super_admin() ) {
		wp_die( 'Oops! Access denied.' );
	}

	$response = array();

	switch ( crb_array_get( $_REQUEST, 'crb_ajax_do' ) ) {
		case 'nexus_view_updates':
			$client_id = absint( $_REQUEST['managed_site_id'] );

			if ( $list = nexus_get_client_plugins( $client_id ) ) {
				$tbody = '';

				foreach ( $list as $item ) {
					$data = $item['data'];

					$data = array_map( function ( $e ) {
						return strip_tags( $e );
					}, $data );

					$c   = '';
					$upd = '';

					if ( $update = crb_array_get( $item, 'update' ) ) {
						$c = 'crb-slave-update';
						array_walk_recursive( $update, function ( &$e ) {
							$e = strip_tags( $e );
						} );

						//	dashicons-info
						$upd = '<span style="color: green;"><i class="dashicons dashicons-info"></i> ' . __( 'A newer version is available', 'wp-cerber' ) . ' (' . $update['new_version'] . ')</span>';
                        // $upd = '<span style="color: red;"><i class="dashicons dashicons-warning"></i> There is a newer version available' . ' (' . $update['new_version'] . ')</span>';
						//$upd = '<span style="color: red;"><i style="font-size: 100%;" class="crb-icon crb-icon-bxs-bell"></i> There is a newer version available' . ' (' . $update['new_version'] . ')</span>';
					}

					$tbody .= '<tr class="' . $c . '" data-plugin-slug="' . $data['plugin_slug'] . '"><td><p style="font-size: 1.09em;"><b>' . $data['Name'] . ' ' . $data['Version'] . '</b> ' . $upd . '</p><p>'.$data['Description'].'</p></td></tr>';

				}

				/*$heading = array(
					__( 'Plugin', 'wp-cerber' ),
				);
				$titles = '<tr><th>' . implode( '</th><th>', $heading ) . '</th></tr>';
				$html = '<table id="" class="widefat crb-table cerber-margin"><thead>' . $titles . '</thead><tfoot>' . $titles . '</tfoot><tbody>' . $tbody . '</tbody></table>';
				*/

                $html = '<table id="" class="widefat crb-table cerber-margin"><tbody>' . $tbody . '</tbody></table>';

				//$html .= '<pre style=" white-space: pre-wrap;">';
				//$html .= print_r( $list,1 );
				//$html .= '</pre>';

				$response = array( 'html' => $html, 'header' => __( 'Active plugins and updates on', 'wp-cerber' ) );
			}
			else {
				// TODO remove in production
				//cerber_bg_task_add( array( 'func' => 'nexus_send', 'args' => array( array( 'type' => 'hello' ), $client_id ) ) );

				$client = nexus_get_client_data( $client_id );
				$response = array( 'html'   => 'No information available. Refreshed: ' . cerber_auto_date( $client->refreshed ),
				                   'header' => __( 'Active plugins and updates on', 'wp-cerber' )
				);
            }
			break;
	}

	echo json_encode( $response );
	exit;
} );

/**
 * Returns the list of plugins installed on the remote website
 *
 * @param int $client_id
 * @param bool $sort
 * @param bool $active_only
 * @param bool $inc_updates
 *
 * @return array
 */
function nexus_get_client_plugins( $client_id, $sort = true, $active_only = true, $inc_updates = true ) {

    $client_id = absint( $client_id );
	$ac = ( $active_only ) ? ' AND status = "1"' : '';
	$plugins = cerber_db_get_results( 'SELECT * FROM ' . cerber_get_db_prefix() . CERBER_MS_LIST_TABLE . ' lst JOIN ' . cerber_get_db_prefix() . CERBER_SETS_TABLE . ' sts ON (lst.list_item = sts.the_key) WHERE lst.list_key = "plugins" AND lst.site_id = ' . $client_id . ' ' . $ac );

	if ( ! $plugins ) {
		return array();
	}

	$ret = array();

	foreach ( $plugins as $plugin ) {
        $p           = array();
        $data        = crb_unserialize( $plugin['the_value'] );
        $p['extra']  = $plugin['extra'];
        $p['status'] = $plugin['status'];
        $p['Name']   = $data['Name'];
        $p['data']   = $data;
        if ( $inc_updates ) {
            //$p['update'] = cerber_get_set( 'nexus_upd_' . $data['plugin_key'] );
            $p['update'] = nexus_get_update( $data['plugin_slug'], $data['Version'] );
        }
        $ret[] = $p;
    }

	if ( $sort ) {
        uasort( $ret, function ( $a, $b ) {
            return strnatcasecmp( $a['Name'], $b['Name'] );
        } );
    }

	return $ret;
}

/**
 * Refreshes stored locally client web server meta data
 *
 * @param int $client_id
 *
 * @return void
 */
function nexus_refresh_client_servers( $client_id ) {

	if ( ! $client = nexus_get_client_data( $client_id ) ) {
		return;
	}

	$server_host = parse_url( $client->site_url, PHP_URL_HOST );
	$srv_ip      = @gethostbyname( $server_host );

	if ( ! cerber_is_ip( $srv_ip ) ) {
		return;
	}

	$srv_country = lab_get_country( $srv_ip, false );

	if ( $srv_ip != $client->server_id || $srv_country != $client->server_country ) {
		nexus_update_client_data( $client_id, array( 'server_id' => $srv_ip, 'server_country' => $srv_country ) );
	}

	// Updating servers

	if ( ! $servers = cerber_get_set( 'nexus_servers' ) ) {
		$servers = array();
	}

	$srv = crb_array_get( $servers, $srv_ip, array() );

	if ( ! $srv || ( $srv[0] < ( time() - 300 ) ) ) {
		$srv[0]             = time();
		$srv[1]             = @gethostbyaddr( $srv_ip );
		$srv[2]             = $srv_country;
		$servers[ $srv_ip ] = $srv;
		cerber_update_set( 'nexus_servers', $servers );
	}

	// Updating list of server countries

	if ( ! $list = cerber_get_set( 'nexus_countries' ) ) {
		$list = array();
	}

	if ( $srv_country && ! isset( $list[ $srv_country ] ) ) {
		$list[ $srv_country ] = crb_get_country_name( $srv_country );
		cerber_update_set( 'nexus_countries', $list );
	}

}

/**
 * Cleanup dependable lists
 *
 * @param $key string List key
 * @param $field string DB field
 */
function nexus_delete_unused( $key, $field ) {
	if ( ! $list = cerber_get_set( $key ) ) {
		return;
	}

	$field = preg_replace( '/[^\w]/', '', $field );

	$used = cerber_db_get_col( 'SELECT DISTINCT ' . $field . ' FROM ' . cerber_get_db_prefix() . CERBER_MS_TABLE );

	if ( $used ) {
		$filtered = array_intersect_key( $list, array_flip( array_intersect( array_keys( $list ), $used ) ) );
		if ( count( $list ) != count( $filtered ) ) {
			cerber_update_set( $key, $filtered );
		}
	}
	else {
		cerber_update_set( $key, array() );
	}
}

function nexus_get_srv_info( $server_ip ) {
	if ( ! $servers = cerber_get_set( 'nexus_servers' ) ) {
		return false;
	}

	return crb_array_get( $servers, $server_ip, array() );
}

function nexus_update_list( $site_id, $key, $items = array() ) {

	list( $site_id, $key ) = nexus_sanitize( $site_id, $key );

	if ( empty( $site_id ) || empty( $key )  ) {
		return false;
	}

	if ( empty( $items ) ) {
		return nexus_delete_list( $site_id, $key );
	}

	// Reduce DB overhead if no changes in the list
    // Note: works only if values in $items are all strings
    // Doesn't work of the DB returns $old with not the same order as in $items

	if ( $old = nexus_get_list( $site_id, $key, array( 'list_item', 'extra', 'status' ), 0 ) ) {
		if ( count( $old ) === count( $items ) ) {
			if ( sha1( serialize( $old ) ) === sha1( serialize( $items ) ) ) {
				return true;
			}
		}
	}

	// Insert a new list

	$values = array();
	foreach ( $items as $item ) {
		if ( is_array( $item ) ) {
			$list_item = cerber_db_real_escape( $item[0] );
			$extra     = cerber_db_real_escape( crb_array_get( $item, 1, '' ) );
			$status    = substr( crb_array_get( $item, 2, '' ), 0, 1 );
		}
		else {
			$list_item = cerber_db_real_escape( $item );
			$extra     = '';
			$status    = '0';
		}
		$values[] = '(' . $site_id . ',"' . $key . '","' . $list_item . '","' . $extra . '", ' . $status . ')';
	}

	nexus_delete_list( $site_id, $key );

	$query = 'INSERT INTO ' . cerber_get_db_prefix() . CERBER_MS_LIST_TABLE . ' (site_id, list_key, list_item, extra, status) VALUES ' . implode( ',', $values );

	$ret = cerber_db_query( $query );

	if ( $e = cerber_db_get_errors() ) {
		nexus_diag_log( $e );
	}

	return $ret;
}

function nexus_get_list( $site_id, $key = '', $fields = array(), $type = MYSQLI_ASSOC ) {

	list( $site_id, $key, $table_fields ) = nexus_sanitize( $site_id, $key, $fields );

	if ( empty( $site_id ) || empty( $key ) ) {
		return false;
	}

	$where = nexus_make_where( $site_id, $key );

	$sql_fields = ( $table_fields ) ? implode( ',', $fields ) : '*';

	return cerber_db_get_results( 'SELECT ' . $sql_fields . ' FROM ' . cerber_get_db_prefix() . CERBER_MS_LIST_TABLE . ' WHERE ' . $where, $type );
}

function nexus_delete_list( $site_id, $key = '' ) {

	list( $site_id, $key ) = nexus_sanitize( $site_id, $key );

	if ( empty( $site_id ) ) {
		return false;
	}

	$where = nexus_make_where( $site_id, $key );

	return cerber_db_query( 'DELETE FROM ' . cerber_get_db_prefix() . CERBER_MS_LIST_TABLE . ' WHERE ' . $where );
}

function nexus_make_where( $site_id, $key = '' ) {
	$where = ' site_id = ' . $site_id;
	if ( $key ) {
		$where .= ' AND list_key = "' . $key . '" ';
	}

	return $where;
}

function nexus_sanitize( $site_id, $key = '', $fields = array() ) {
	$ret = array( 0, '', array() );

	if ( ! $ret[0] = absint( $site_id ) ) {
		return $ret;
	}

	if ( $key ) {
		if ( ! preg_match( '/^[\w\-]+$/', $key ) ) {
			return $ret;
		}
		$ret[1] = $key;
	}

	if ( $fields ) {
		$ret[2] = array_filter( $fields, function ( $val ) {
			if ( preg_match( '/^[\w]+$/', $val ) ) {
				return true;
			}

			return false;
		} );
	}

	return $ret;
}

function nexus_create_db( $role ) {

	list ( $charset, $collate ) = cerber_db_detect_collate();

	$sql = array();
	if ( ! cerber_is_table( cerber_get_db_prefix() . CERBER_MS_TABLE ) ) {
		$sql[] = '
			CREATE TABLE IF NOT EXISTS ' . cerber_get_db_prefix() . CERBER_MS_TABLE . ' (
			id int(20) UNSIGNED NOT NULL AUTO_INCREMENT,
			site_name varchar(250) NOT NULL,
			site_name_remote varchar(250) NOT NULL,
			site_url varchar(250) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
			group_id int(10) UNSIGNED NOT NULL DEFAULT 0,
			plugin_v varchar(250) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT "",
			wp_v varchar(250) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT "",
			last_scan int(10) UNSIGNED NOT NULL DEFAULT 0,
			updates int(10) UNSIGNED NOT NULL DEFAULT 0,
			last_http int(10) UNSIGNED NOT NULL DEFAULT 0,
			refreshed int(10) UNSIGNED NOT NULL DEFAULT 0,
			x_field varchar(250) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
			x_num int(10) UNSIGNED NOT NULL,
			site_echo varchar(250) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
			site_pass varchar(250) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
			details text NOT NULL,
			site_notes text NOT NULL,
			PRIMARY KEY (id),
			UNIQUE KEY site_url (site_url),
			KEY group_id (group_id),
			KEY refreshed (refreshed)			
			) DEFAULT CHARSET='.$charset.' COLLATE='.$collate.' COMMENT="Keep it secret";
        ';
	}

	if ( ! cerber_is_table( cerber_get_db_prefix() . CERBER_MS_LIST_TABLE ) ) {
		$sql[] = '
			CREATE TABLE IF NOT EXISTS ' . cerber_get_db_prefix() . CERBER_MS_LIST_TABLE . ' (
            site_id bigint(20) UNSIGNED NOT NULL,
            list_key varchar(250) CHARACTER SET ascii NOT NULL,
            list_item varchar(250) NOT NULL,
            extra varchar(250) NOT NULL DEFAULT "",
            status char(1) CHARACTER SET ascii NOT NULL DEFAULT "",
            updated int(10) UNSIGNED NOT NULL DEFAULT 0,
    		KEY site_id (site_id) USING HASH,
			KEY the_key (list_key) USING BTREE
			) DEFAULT CHARSET='.$charset.' COLLATE='.$collate.' COMMENT="";
        ';
	}

	foreach ( $sql as $query ) {
		cerber_db_query( $query );
	}

	if ( $e = cerber_db_get_errors( true ) ) {
		cerber_admin_notice( $e );

		return false;
	}

	return nexus_upgrade_db();
}

function nexus_upgrade_db( $force = false ) {

	$sql = array();

	if ( $force || ! cerber_is_column( cerber_get_db_prefix() . CERBER_MS_TABLE, 'server_id' ) ) {
		$sql[] = 'ALTER TABLE ' . cerber_get_db_prefix() . CERBER_MS_TABLE . '
		 ADD server_id VARCHAR(64) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT "" AFTER group_id, 
		 ADD INDEX server (server_id),
         ADD server_country CHAR(3) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT "" AFTER server_id, 
         ADD INDEX country (server_country);
		 ';
	}

	if ( $force || ! cerber_is_column( cerber_get_db_prefix() . CERBER_MS_TABLE, 'site_key' ) ) {
		$sql[] = 'ALTER TABLE ' . cerber_get_db_prefix() . CERBER_MS_TABLE . '
		 ADD site_key INT(11) UNSIGNED NOT NULL DEFAULT "0" AFTER plugin_v;
		 ';
	}

	foreach ( $sql as $query ) {
		cerber_db_query( $query );
	}

	if ( $e = cerber_db_get_errors( true ) ) {
		cerber_admin_notice( $e );

		return false;
	}

	return true;
}