= time() - 604800) $data = json_decode(file_get_contents($cacheFile)); else { // Otherwise, use the QWeb API to get its information $data = qweb_aca_ip_lookup($accessKey, $ip); // And cache the result if(isset($data->answer) && $data->answer == 'success') file_put_contents($cacheFile, json_encode($data)); } if(isset($data)) { if(isset($data->answer)) { if($data->answer == 'success') { // If we've received a successful response, we must be within the quota allowance so remove any previously set 'reached' flag if(get_transient('qweb_aca_quota_reached') !== false) delete_transient('qweb_aca_quota_reached'); // Check that the returned country code is in our allowlist and, if allow_known_proxies is false, check that the IP isn't a known proxy if(($data->is_proxy == 'yes' && !get_option('qweb_aca_allow_known_proxies')) || !in_array($data->country, $allowedCountries)) { // Access isn't allowed header('HTTP/1.0 403 Forbidden'); exit; } } else { // We don't want to bombard inboxes when an access quota is reached, so: $quotaReached = stripos($data->answer, 'this limit has already been reached') !== false; if(!$quotaReached || ($quotaReached && get_transient('qweb_aca_quota_reached') === false)) { if($quotaReached) { set_transient('qweb_aca_quota_reached', true); $data->answer .= ' Blocking is now paused until you fall back within your quota.'; } wp_mail(get_bloginfo('admin_email'), get_bloginfo('name').' - '.__('IP to country lookup failed!', 'admin-country-allowlist'), sprintf( __('The following response was received from the lookup API when attempting to verify the country of IP %1$s: %2$s', 'admin-country-allowlist'), esc_html($ip), esc_html($data->answer) )); } } } else { wp_mail(get_bloginfo('admin_email'), get_bloginfo('name').' - '.__('IP to country lookup failed!', 'admin-country-allowlist'), sprintf( __('The following error was generated when attempting to verify the country of IP %1$s: %2$s', 'admin-country-allowlist'), esc_html($ip), esc_html($data) )); } } } } return true; } // Function to create necessary folders/options and do basic sanity checks on activation function qweb_aca_activation() { // Create the cache folder $cacheDirectory = qweb_aca_cache_folder(); if(!is_dir($cacheDirectory)) mkdir($cacheDirectory, 0755); // Create the options add_option('qweb_aca_access_key', ''); add_option('qweb_aca_allowed_countries', array()); add_option('qweb_aca_allow_known_proxies', false); add_option('qweb_aca_block_xmlrpc_access', false); // Schedule the cache cleaning cron if(!wp_next_scheduled('qweb_aca_clear_old_cache_event')) wp_schedule_event(time(), 'daily', 'qweb_aca_clear_old_cache_event'); return true; } function qweb_aca_deactivation() { // We keep the cache and options in place until uninstallation, but we don't want to keep the cron running if the plugin is deactivated wp_unschedule_event(wp_next_scheduled('qweb_aca_clear_old_cache_event'), 'qweb_aca_clear_old_cache_event'); } // Function to clean up on uninstallation function qweb_aca_uninstallation() { // Unschedule the cache cleaning cron. We do this first, to make sure it doesn't run again after deleting the folder wp_unschedule_event(wp_next_scheduled('qweb_aca_clear_old_cache_event'), 'qweb_aca_clear_old_cache_event'); // Delete the cache folder $cacheDirectory = qweb_aca_cache_folder(); if(is_dir($cacheDirectory)) { // Folders must be empty before deletion foreach (array_diff(scandir($cacheDirectory), array('.','..')) as $file) { unlink($cacheDirectory.DIRECTORY_SEPARATOR.$file); } rmdir($cacheDirectory); } // Delete the options delete_option('qweb_aca_access_key'); delete_option('qweb_aca_allowed_countries'); delete_option('qweb_aca_allow_known_proxies'); delete_option('qweb_aca_block_xmlrpc_access'); // Remove htaccess entries if(extract_from_markers(get_home_path().'.htaccess', 'QWeb Admin Country Allowlist XMLRPC Blocking')) insert_with_markers(get_home_path().'.htaccess', 'QWeb Admin Country Allowlist XMLRPC Blocking', ''); return true; } // Function to return an array of recognised country codes. function qweb_aca_country_codes() { return array('AD','AE','AF','AG','AI','AL','AM','AO','AR','AT','AU','AW','AX','AZ','BA','BB','BD','BE','BF','BG','BH','BI','BJ','BL','BM','BN','BO','BQ','BR','BS','BT','BV','BW','BY','BZ','CA','CC','CD','CF','CG','CH','CI','CK','CL','CM','CN','CO','CR','CU','CV','CW','CX','CY','CZ','DE','DJ','DK','DM','DO','DZ','EC','EE','EG','ER','ES','ET','FI','FJ','FK','FM','FO','FR','GA','GB','GD','GE','GF','GG','GH','GI','GL','GM','GN','GP','GQ','GR','GS','GT','GU','GW','GY','HK','HM','HN','HR','HT','HU','ID','IE','IL','IM','IN','IO','IQ','IR','IS','IT','JE','JM','JO','JP','KE','KG','KH','KI','KM','KN','KP','KR','KW','KY','KZ','LA','LB','LC','LI','LK','LR','LS','LT','LU','LV','LY','MA','MC','MD','ME','MF','MG','MH','MK','ML','MM','MN','MO','MP','MQ','MR','MS','MT','MU','MV','MW','MX','MY','MZ','NA','NC','NE','NF','NG','NI','NL','NO','NP','NR','NU','NZ','OM','PA','PE','PF','PG','PH','PK','PL','PM','PN','PR','PS','PT','PW','PY','QA','RE','RO','RS','RU','RW','SA','SB','SC','SD','SE','SG','SH','SI','SK','SL','SM','SN','SO','SR','SS','ST','SV','SX','SY','SZ','TC','TD','TF','TG','TH','TJ','TK','TL','TM','TN','TO','TR','TT','TV','TW','TZ','UA','UG','UM','US','UY','UZ','VA','VC','VE','VG','VI','VN','VU','WF','WS','XK','YE','YT','ZA','ZM','ZW'); } // Function to generate the settings page function qweb_aca_settings_page() { // Check user capabilities if(!current_user_can('manage_options')) { return; } ?>
QWeb API Console and enter it here. Access keys are FREE!', 'admin-country-allowlist'); ?>
QWeb API Console. Simply log in, locate your access key from the list, and tap the Usage report icon.', 'admin-country-allowlist'); ?>
leave a review to help other website owners know what you think of this plugin.', 'admin-country-allowlist'); ?>
'.__('Your access key provided by the QWeb API Console.', 'admin-country-allowlist').'
'; }, __FILE__, 'qweb_aca_options_general'); add_settings_field( 'qweb_aca_allowed_countries', 'Allowed countries', function() { // TODO need a fancy mechanic to make populating this field easier, while keeping it a text input for copy + paste population echo ''.__('Comma separated list of ISO 3166-1 alpha-2 country codes you allow admin panel access to.', 'admin-country-allowlist').'
'; }, __FILE__, 'qweb_aca_options_general'); add_settings_field( 'qweb_aca_allow_known_proxies', 'Allow known proxies?', function() { $currentValue = get_option('qweb_aca_allow_known_proxies'); echo ''.__('If an IP is from an allowed country, but we know it to be a proxy server, should we still allow access?', 'admin-country-allowlist').'
'; }, __FILE__, 'qweb_aca_options_general'); add_settings_field( 'qweb_aca_block_xmlrpc_access', 'Block XMLRPC access?', function() { $currentValue = get_option('qweb_aca_block_xmlrpc_access'); echo ''.__('If you\'re certain that nothing needs access to the Wordpress XMLRPC mechanism, then blocking access completely will save from having to perform API lookups for these requests, which can dramatically reduce the number of API requests that this website needs to invoke and help keep you within your API usage quota.', 'admin-country-allowlist').'
'.__('If you don\'t know what XMLRPC is, then you can almost certainly block access completely.', 'admin-country-allowlist').'
'; }, __FILE__, 'qweb_aca_options_general'); } // Function to add a settings link to the plugin in the plugins list function qweb_aca_list_settings_link($links) { // Filters the links in the plugin item of the plugins list, to include a settings link too array_push($links, ''.__('Settings', 'admin-country-allowlist').''); return $links; } // Function to clear old cache files function qweb_aca_clear_old_cache() { $cacheDirectory = qweb_aca_cache_folder(); // Only continue if this is a valid directory if(is_dir($cacheDirectory)) { // Delete cache older than 1 week $maxAge = time() - 604800; foreach (array_diff(scandir($cacheDirectory), array('.','..')) as $file) { if(filemtime($cacheDirectory.DIRECTORY_SEPARATOR.$file) < $maxAge) unlink($cacheDirectory.DIRECTORY_SEPARATOR.$file); } } } // Function to add a new item to the settings menu function qweb_aca_create_menu() { add_options_page('Admin Country Allowlist', 'Admin Country Allowlist', 'administrator', __FILE__, 'qweb_aca_settings_page'); } // Add settings link to the plugins list page add_filter('plugin_action_links_'.plugin_basename(__FILE__), 'qweb_aca_list_settings_link'); // Add settings link to the menu add_action('admin_menu', 'qweb_aca_create_menu'); // Register the settings add_action('admin_init', 'qweb_aca_register_settings'); // Plugin activation, deactivation, and uninstallation hooks register_activation_hook(__FILE__, 'qweb_aca_activation'); register_deactivation_hook(__FILE__, 'qweb_aca_deactivation'); register_uninstall_hook(__FILE__, 'qweb_aca_uninstallation'); // Cron event to delete old cache every week. This is scheduled during activation add_action('qweb_aca_clear_old_cache_event', 'qweb_aca_clear_old_cache'); // admin-ajax.php requests cause is_admin() to return true, but front end plugins also use it so we shouldn't block if(strpos($_SERVER['REQUEST_URI'], 'admin-ajax.php') === false) { if(is_admin()) { // Basic sanity checks and error outputs if logged in to the admin panel // Check cache directory exists $qwebAcaCacheDirectory = qweb_aca_cache_folder(); if(!is_dir($qwebAcaCacheDirectory) && !mkdir($qwebAcaCacheDirectory, 0755)) { function qweb_aca_cache_folder_missing() { printf( '%2$s %3$s
%2$s %3$s
%2$s