diff --git a/README.md b/README.md index aee011b..e37a222 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Helps keeping WordPress websites secure. ## Requirements -* [PHP](https://secure.php.net/) 7.0 or newer +* [PHP](https://secure.php.net/) 7.1 or newer * [WordPress](https://wordpress.org/) 4.9 or newer ## Limitations @@ -12,6 +12,12 @@ Helps keeping WordPress websites secure. * BC Security has not been tested on WordPress multisite installation. * BC Security is primarily being developed for Apache webserver and Unix-like environments. +## Setup + +Several features of BC Security depends on the knowledge of remote IP address, so it is important that you let the plugin know how your server is connected to the Internet. You can either set connection type via _Setup_ page or with via `BC_SECURITY_CONNECTION_TYPE` constant. + +**Note:** If you already have an installation with BC Security set up and would like to set up another installation in the same way, you can export plugin settings (including connection type) from the former installation and import them to the latter. + ## Features ### Checklist @@ -122,6 +128,7 @@ Some of the modules listed above come with settings panel. Further customization 1. Part of [psr/log](https://packagist.org/packages/psr/log) package codebase is shipped with the plugin. 1. [WordPress core integrity check](#wordpress-core-integrity-check) is heavily inspired by [Checksum Verifier](https://github.com/pluginkollektiv/checksum-verifier) plugin by Sergej Müller. 1. Some features (like "[Removed plugins check](#removed-plugins-check)") are inspired by [Wordfence Security](https://wordpress.org/plugins/wordfence/) from [Defiant](https://www.defiant.com/). +1. Big thanks to [Vincent Driessen](https://nvie.com/about/) for his "[A successful Git branching model](https://nvie.com/posts/a-successful-git-branching-model/)" article that I find particularly useful every time I do some work on BC Security. ## Alternatives (and why I do not use them) diff --git a/bc-security.php b/bc-security.php index dd0628f..1733f5a 100644 --- a/bc-security.php +++ b/bc-security.php @@ -3,23 +3,23 @@ * Plugin Name: BC Security * Plugin URI: https://github.com/chesio/bc-security * Description: Helps keeping WordPress websites secure. - * Version: 0.9.0 + * Version: 0.10.0 * Author: Česlav Przywara * Author URI: https://www.chesio.com - * Requires PHP: 7.0 + * Requires PHP: 7.1 * Requires WP: 4.9 * Tested up to: 4.9 * Text Domain: bc-security * GitHub Plugin URI: https://github.com/chesio/bc-security */ -if (version_compare(PHP_VERSION, '7.0', '<')) { +if (version_compare(PHP_VERSION, '7.1', '<')) { // Warn user that his/her PHP version is too low for this plugin to function. add_action('admin_notices', function () { echo '

'; echo esc_html( sprintf( - __('BC Security plugin requires PHP 7.0 to function properly, but you have version %s installed. The plugin has been auto-deactivated.', 'bc-security'), + __('BC Security plugin requires PHP 7.1 to function properly, but you have version %s installed. The plugin has been auto-deactivated.', 'bc-security'), PHP_VERSION ) ); diff --git a/classes/BlueChip/Security/Core/Settings.php b/classes/BlueChip/Security/Core/Settings.php index 322f369..44199f2 100644 --- a/classes/BlueChip/Security/Core/Settings.php +++ b/classes/BlueChip/Security/Core/Settings.php @@ -148,6 +148,42 @@ public function getOptionName(): string } + /** + * Get option data. + * + * @return array + */ + public function get(): array + { + return $this->data; + } + + + /** + * Set $data as option data. + * + * @param array $data + * @return bool + */ + public function set(array $data): bool + { + $this->data = $this->sanitize($data); + return $this->persist(); + } + + + /** + * Reset option data. + * + * @return bool + */ + public function reset(): bool + { + $this->data = static::DEFAULTS; + return $this->persist(); + } + + /** * Sanitize $settings array: only keep known keys, provide default values for missing keys. * @@ -221,7 +257,7 @@ protected static function parseList($list): array /** * Persist the value of data into database. * - * @return bool + * @return bool True, if settings have been updated (= changed), false otherwise. */ protected function persist(): bool { diff --git a/classes/BlueChip/Security/Helpers/Hooks.php b/classes/BlueChip/Security/Helpers/Hooks.php index d8bf187..0c651ff 100644 --- a/classes/BlueChip/Security/Helpers/Hooks.php +++ b/classes/BlueChip/Security/Helpers/Hooks.php @@ -16,4 +16,12 @@ interface Hooks * @see \BlueChip\Security\Helpers\Is::admin() */ const IS_ADMIN = 'bc-security/filter:is-admin'; + + + /** + * Filter: allows to change plugin's changelog URL. + * + * @see \BlueChip\Security\Helpers\Plugin::getChangelogUrl() + */ + const PLUGIN_CHANGELOG_URL = 'bc-security/filter:plugin-changelog-url'; } diff --git a/classes/BlueChip/Security/Helpers/Plugin.php b/classes/BlueChip/Security/Helpers/Plugin.php index 5b79504..30e32f7 100644 --- a/classes/BlueChip/Security/Helpers/Plugin.php +++ b/classes/BlueChip/Security/Helpers/Plugin.php @@ -14,12 +14,35 @@ abstract class Plugin */ const CHECKSUMS_API_URL_BASE = 'https://downloads.wordpress.org/plugin-checksums/'; - /** - * @var string + * @var string URL of Plugins Directory. */ const PLUGINS_DIRECTORY_URL = 'https://wordpress.org/plugins/'; + /** + * @var string Path (although not technically) to changelog page relative to URL of plugin homepage at Plugins Directory. + */ + const PLUGINS_DIRECTORY_CHANGELOG_PATH = '#developers'; + + + /** + * @param string $plugin_basename + * @return string URL of the plugin changelog page or empty string, if it cannot be determined. + */ + public static function getChangelogUrl(string $plugin_basename): string + { + // By default, changelog URL is unknown. + $url = ''; + + if (self::hasReadmeTxt($plugin_basename)) { + // Assume that any plugin with readme.txt comes from Plugins Directory. + $url = self::getDirectoryUrl($plugin_basename) . self::PLUGINS_DIRECTORY_CHANGELOG_PATH; + } + + // Allow the changelog URL to be filtered. + return apply_filters(Hooks::PLUGIN_CHANGELOG_URL, $url, $plugin_basename); + } + /** * @param string $plugin_basename @@ -103,6 +126,18 @@ public static function getPluginsInstalledFromWordPressOrg(): array } + /** + * @internal Only use in admin (back-end) context. + * @param string $plugin_basename + * @return array + */ + public static function getPluginData(string $plugin_basename): array + { + // Note: get_plugin_data() function is only defined in admin. + return get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_basename); + } + + /** * Get absolute path to plugin directory for given $plugin_basename (ie. "bc-security/bc-security.php"). * diff --git a/classes/BlueChip/Security/Helpers/Transients.php b/classes/BlueChip/Security/Helpers/Transients.php index 0fd28a3..7090cc2 100644 --- a/classes/BlueChip/Security/Helpers/Transients.php +++ b/classes/BlueChip/Security/Helpers/Transients.php @@ -15,6 +15,7 @@ abstract class Transients */ const NAME_PREFIX = 'bc-security_'; + /** * Delete transient. * @@ -26,6 +27,32 @@ public static function deleteFromSite(string ...$key): bool return delete_site_transient(self::name($key)); } + + /** + * Remove all stored transients from database. Entire object cache is flushed as well, so use with caution. + * + * @link https://css-tricks.com/the-deal-with-wordpress-transients/ + * + * @param \wpdb $wpdb WordPress database access abstraction object + */ + public static function flush(\wpdb $wpdb) + { + $table_name = is_multisite() ? $wpdb->sitemeta : $wpdb->options; + + // First, delete all transients from database... + $wpdb->query( + sprintf( + "DELETE FROM {$table_name} WHERE (option_name LIKE '%s' OR option_name LIKE '%s')", + '_site_transient_' . self::NAME_PREFIX . '%', + '_site_transient_timeout_' . self::NAME_PREFIX . '%' + ) + ); + + // ...then flush object cache, because transients may be stored there as well. + wp_cache_flush(); + } + + /** * Get transient. * @@ -37,6 +64,7 @@ public static function getForSite(string ...$key) return get_site_transient(self::name($key)); } + /** * Set transient. * @@ -52,6 +80,7 @@ public static function setForSite($value, ...$args): bool return set_site_transient(self::name($args), $value, $expiration); } + /** * Create transient name from $key. * diff --git a/classes/BlueChip/Security/Modules/Checklist/AdminPage.php b/classes/BlueChip/Security/Modules/Checklist/AdminPage.php index 29072c6..62b6d58 100644 --- a/classes/BlueChip/Security/Modules/Checklist/AdminPage.php +++ b/classes/BlueChip/Security/Modules/Checklist/AdminPage.php @@ -80,6 +80,15 @@ public function loadPage() } + /** + * @return int Number of meaningful checks that are monitored and failed the last time they have been executed. + */ + public function getCount(): int + { + return count($this->checklist_manager->getChecks(['meaningful' => true, 'monitored' => true, 'status' => false])); + } + + /** * Output admin page. */ @@ -102,9 +111,9 @@ public function printContents() echo '

'; - $this->printBasicChecksSection($this->checklist_manager->getChecks(true, BasicCheck::class)); + $this->printBasicChecksSection($this->checklist_manager->getBasicChecks()); - $this->printAdvancedChecksSection($this->checklist_manager->getChecks(true, AdvancedCheck::class)); + $this->printAdvancedChecksSection($this->checklist_manager->getAdvancedChecks()); $this->printChecklistMonitoringSection(); diff --git a/classes/BlueChip/Security/Modules/Checklist/Check.php b/classes/BlueChip/Security/Modules/Checklist/Check.php index 8d4c3ab..096bf43 100644 --- a/classes/BlueChip/Security/Modules/Checklist/Check.php +++ b/classes/BlueChip/Security/Modules/Checklist/Check.php @@ -102,11 +102,11 @@ public function getResult(): CheckResult /** - * By default, every check makes sense. + * By default, every check is meaningful. * * @return bool */ - public function makesSense(): bool + public function isMeaningful(): bool { return true; } diff --git a/classes/BlueChip/Security/Modules/Checklist/Checks/DisplayOfPhpErrorsIsOff.php b/classes/BlueChip/Security/Modules/Checklist/Checks/DisplayOfPhpErrorsIsOff.php index 7384c65..f25a184 100644 --- a/classes/BlueChip/Security/Modules/Checklist/Checks/DisplayOfPhpErrorsIsOff.php +++ b/classes/BlueChip/Security/Modules/Checklist/Checks/DisplayOfPhpErrorsIsOff.php @@ -27,7 +27,7 @@ public function __construct() * * @return bool */ - public function makesSense(): bool + public function isMeaningful(): bool { return defined('WP_ENV') && (WP_ENV === 'production'); } diff --git a/classes/BlueChip/Security/Modules/Checklist/Checks/ErrorLogNotPubliclyAccessible.php b/classes/BlueChip/Security/Modules/Checklist/Checks/ErrorLogNotPubliclyAccessible.php index 725628a..8756de2 100644 --- a/classes/BlueChip/Security/Modules/Checklist/Checks/ErrorLogNotPubliclyAccessible.php +++ b/classes/BlueChip/Security/Modules/Checklist/Checks/ErrorLogNotPubliclyAccessible.php @@ -23,7 +23,7 @@ public function __construct() * * @return bool */ - public function makesSense(): bool + public function isMeaningful(): bool { return WP_DEBUG && WP_DEBUG_LOG; } diff --git a/classes/BlueChip/Security/Modules/Checklist/Manager.php b/classes/BlueChip/Security/Modules/Checklist/Manager.php index 295062c..a59d9a6 100644 --- a/classes/BlueChip/Security/Modules/Checklist/Manager.php +++ b/classes/BlueChip/Security/Modules/Checklist/Manager.php @@ -51,7 +51,7 @@ public function init() $this->settings->addUpdateHook([$this, 'updateCronJobs']); // Hook into cron job execution. add_action(Modules\Cron\Jobs::CHECKLIST_CHECK, [$this, 'runBasicChecks'], 10, 0); - foreach ($this->getChecks(true, AdvancedCheck::class) as $advanced_check) { + foreach ($this->getAdvancedChecks() as $advanced_check) { add_action($advanced_check->getCronJobHook(), [$advanced_check, 'runInCron'], 10, 0); } // Register AJAX handler. @@ -103,23 +103,40 @@ public function constructChecks(\wpdb $wpdb): array /** * Return list of all implemented checks, optionally filtered. * - * @param bool $meaningful_only If true, only checks that make sense in current context are returned. - * @param string $class [optional] Return only checks of given class. + * @param array $filters [optional] Extra conditions to filter the list by: class (string), meaningful (boolean), + * monitored (boolean), status (null|boolean). * @return \BlueChip\Security\Modules\Checklist\Check[] */ - public function getChecks(bool $meaningful_only = false, string $class = ''): array + public function getChecks(array $filters = []): array { $checks = $this->checks; - if (!empty($class)) { + if (isset($filters['class'])) { + $class = $filters['class']; $checks = array_filter($checks, function (Check $check) use ($class): bool { return $check instanceof $class; }); } - if ($meaningful_only) { - $checks = array_filter($checks, function (Check $check): bool { - return $check->makesSense(); + if (isset($filters['meaningful'])) { + $is_meaningful = $filters['meaningful']; + $checks = array_filter($checks, function (Check $check) use ($is_meaningful): bool { + return $is_meaningful ? $check->isMeaningful() : !$check->isMeaningful(); + }); + } + + if (isset($filters['monitored'])) { + $monitored = $filters['monitored']; + $settings = $this->settings; + $checks = array_filter($checks, function (string $check_id) use ($monitored, $settings): bool { + return $monitored ? $settings[$check_id] : !$settings[$check_id]; + }, ARRAY_FILTER_USE_KEY); + } + + if (isset($filters['status'])) { + $status = $filters['status']; + $checks = array_filter($checks, function (Check $check) use ($status): bool { + return $check->getResult()->getStatus() === $status; }); } @@ -128,13 +145,41 @@ public function getChecks(bool $meaningful_only = false, string $class = ''): ar /** - * Run all basic checks that make sense in current context and are set to be monitored in non-interactive mode. + * @param bool $only_meaningful + * @return \BlueChip\Security\Modules\Checklist\Check[] + */ + public function getAdvancedChecks(bool $only_meaningful = true): array + { + $filters = ['class' => AdvancedCheck::class]; + if ($only_meaningful) { + $filters['meaningful'] = true; + } + return $this->getChecks($filters); + } + + + /** + * @param bool $only_meaningful + * @return \BlueChip\Security\Modules\Checklist\Check[] + */ + public function getBasicChecks(bool $only_meaningful = true): array + { + $filters = ['class' => BasicCheck::class]; + if ($only_meaningful) { + $filters['meaningful'] = true; + } + return $this->getChecks($filters); + } + + + /** + * Run all basic checks that are meaningful and are set to be monitored in non-interactive mode. * * @internal Method is intended to be run from within cron request. */ public function runBasicChecks() { - $checks = $this->getChecks(true, BasicCheck::class); + $checks = $this->getBasicChecks(); $issues = []; foreach ($checks as $check_id => $check) { @@ -197,7 +242,7 @@ public function runCheck() */ public function updateCronJobs() { - foreach ($this->getChecks(false, AdvancedCheck::class) as $check_id => $advanced_check) { + foreach ($this->getAdvancedChecks(false) as $check_id => $advanced_check) { if ($this->settings[$check_id]) { $this->cron_manager->activateJob($advanced_check->getCronJobHook()); } else { diff --git a/classes/BlueChip/Security/Modules/Notifications/Watchman.php b/classes/BlueChip/Security/Modules/Notifications/Watchman.php index 0a96554..05aaf88 100644 --- a/classes/BlueChip/Security/Modules/Notifications/Watchman.php +++ b/classes/BlueChip/Security/Modules/Notifications/Watchman.php @@ -6,6 +6,7 @@ namespace BlueChip\Security\Modules\Notifications; use BlueChip\Security\Helpers\Is; +use BlueChip\Security\Helpers\Plugin; use BlueChip\Security\Helpers\Transients; use BlueChip\Security\Modules; use BlueChip\Security\Modules\Log\Logger; @@ -223,14 +224,22 @@ public function watchPluginUpdatesAvailable($update_transient) $message = []; foreach ($plugin_updates as $plugin_file => $plugin_update_data) { - // Note: get_plugin_data() function is only defined in admin, - // but it seems that it is always available in this context... - $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_file); - $message[] = sprintf( + $plugin_data = Plugin::getPluginData($plugin_file); + $plugin_message = sprintf( __('Plugin "%1$s" has an update to version %2$s available.', 'bc-security'), $plugin_data['Name'], $plugin_update_data->new_version ); + + if (!empty($plugin_changelog_url = Plugin::getChangelogUrl($plugin_file))) { + // Append link to changelog, if available. + $plugin_message .= ' ' . sprintf( + __('Changelog: %1$s', 'bc-security'), + $plugin_changelog_url + ); + } + + $message[] = $plugin_message; } // Now it is time to make sure the method is not invoked anymore. diff --git a/classes/BlueChip/Security/Modules/Tools/AdminPage.php b/classes/BlueChip/Security/Modules/Tools/AdminPage.php new file mode 100644 index 0000000..9027e53 --- /dev/null +++ b/classes/BlueChip/Security/Modules/Tools/AdminPage.php @@ -0,0 +1,245 @@ +page_title = _x('Tools', 'Dashboard page title', 'bc-security'); + $this->menu_title = _x('Tools', 'Dashboard menu item name', 'bc-security'); + + $this->settings = $settings; + } + + + public function loadPage() + { + $this->processActions(); + } + + + public function printContents() + { + echo '
'; + echo '

' . esc_html($this->page_title) . '

'; + + $this->printExportForm(); + echo '
'; + $this->printImportForm(); + echo '
'; + $this->printResetForm(); + + echo '
'; + } + + + private function printExportForm() + { + echo '

' . esc_html__('Export settings', 'bc-security') . '

'; + echo '

' . esc_html__('Create JSON file with plugin settings that can be used as backup or to clone the settings to another installation.', 'bc-security') . '

'; + echo ''; + // Form nonce + wp_nonce_field(self::EXPORT_ACTION, self::NONCE_NAME); + // Submit button + submit_button(__('Export settings', 'bc-security'), 'primary', self::EXPORT_ACTION, true); + echo '
'; + } + + + private function printImportForm() + { + echo '

' . esc_html__('Import settings', 'bc-security') . '

'; + echo '

' . esc_html__('Import only JSON files created with the same version of the plugin!', 'bc-security') . '

'; + echo '
'; + // Form nonce + wp_nonce_field(self::IMPORT_ACTION, self::NONCE_NAME); + // File input + echo '
'; + echo ''; + // Submit button + submit_button(__('Import settings', 'bc-security'), 'primary', self::IMPORT_ACTION, true); + echo '
'; + } + + + private function printResetForm() + { + echo '

' . esc_html__('Reset settings', 'bc-security') . '

'; + echo '

'; + echo sprintf( + /* translators: %s: link to plugin setup page */ + esc_html__('Set all plugin settings (including %s) back to their default values.', 'bc-security'), + sprintf( + '%s', + Setup\AdminPage::getPageUrl(), + esc_html__('connection type', 'bc-security') + ) + ); + echo '

'; + echo '
'; + // Form nonce + wp_nonce_field(self::RESET_ACTION, self::NONCE_NAME); + // Submit button + submit_button(__('Reset settings', 'bc-security'), 'primary', self::RESET_ACTION, true); + echo '
'; + } + + + /** + * Dispatch any action that is indicated by POST data (form submission). + */ + private function processActions() + { + $nonce = filter_input(INPUT_POST, self::NONCE_NAME, FILTER_SANITIZE_STRING); + if (empty($nonce)) { + // No nonce, no action. + return; + } + + if (isset($_POST[self::EXPORT_ACTION]) && wp_verify_nonce($nonce, self::EXPORT_ACTION)) { + // Export settings to a file. + $this->processExportAction(); + } + + if (isset($_POST[self::IMPORT_ACTION]) && wp_verify_nonce($nonce, self::IMPORT_ACTION)) { + // Import settings from provided file. + $this->processImportAction(); + } + + if (isset($_POST[self::RESET_ACTION]) && wp_verify_nonce($nonce, self::RESET_ACTION)) { + // Reset all settings to default values. + $this->processResetAction(); + } + } + + + private function processExportAction() + { + $export = []; + + foreach ($this->settings as $settings) { + $export[$settings->getOptionName()] = $settings->get(); + } + + // Send headers. + $file_name = 'bc-security-export-' . date('Y-m-d') . '.json'; + header("Content-Disposition: attachment; filename={$file_name}"); + header("Content-Type: application/json; charset=utf-8"); + + // Send content. + echo json_encode($export, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + exit; + } + + + private function processImportAction() + { + $import_file = $_FILES['import-file']; + + // Validate file upload. + if (empty($import_file['size'])) { + AdminNotices::add(__('No file selected.', 'bc-security'), AdminNotices::ERROR); + return; + } + if ($import_file['error']) { + AdminNotices::add(__('File failed to upload. Please try again.', 'bc-security'), AdminNotices::ERROR); + return; + } + if (pathinfo($import_file['name'], PATHINFO_EXTENSION) !== 'json') { + AdminNotices::add(__('Incorrect file type!', 'bc-security'), AdminNotices::ERROR); + return; + } + + // Read the file. + if (empty($json = file_get_contents($import_file['tmp_name']))) { + AdminNotices::add(__('File could not be read!', 'bc-security'), AdminNotices::ERROR); + return; + } + + // Parse JSON. + if (empty($import = json_decode($json, true))) { // true -> convert objects into associative arrays + AdminNotices::add(__('File is either empty or corrupted!', 'bc-security'), AdminNotices::ERROR); + return; + } + + $status = true; + + foreach ($this->settings as $settings) { + $option_name = $settings->getOptionName(); + if (!isset($import[$option_name])) { + $status = false; + continue; + } + + $data = $import[$option_name]; + if (!is_array($data)) { + $status = false; + continue; + } + + $settings->set($data); + } + + if ($status) { + AdminNotices::add( + __('Plugin settings have been imported successfully.', 'bc-security'), + AdminNotices::SUCCESS + ); + } else { + AdminNotices::add( + __('Some or all plugin settings could not be updated. Make sure you are importing file that has been created by the same version of the plugin.', 'bc-security'), + AdminNotices::WARNING + ); + } + } + + + private function processResetAction() + { + foreach ($this->settings as $settings) { + $settings->reset(); + } + + AdminNotices::add( + __('Plugin settings have been reset to their defaults.', 'bc-security'), + AdminNotices::SUCCESS + ); + } +} diff --git a/classes/BlueChip/Security/Plugin.php b/classes/BlueChip/Security/Plugin.php index 72e3cbc..58a31fc 100644 --- a/classes/BlueChip/Security/Plugin.php +++ b/classes/BlueChip/Security/Plugin.php @@ -43,17 +43,13 @@ public function __construct(string $plugin_filename, \wpdb $wpdb) $this->wpdb = $wpdb; // Read plugin settings. - $this->settings = $this->constructSettings(); + $this->settings = $settings = self::constructSettings(); // Get setup info. - $setup = new Setup\Core($this->settings['setup']); - - // IP addresses are at core interest within this plugin :) - $remote_address = $setup->getRemoteAddress(); - $server_address = $setup->getServerAddress(); + $setup = new Setup\Core($settings['setup']); // Construct modules. - $this->modules = $this->constructModules($wpdb, $remote_address, $server_address, $this->settings); + $this->modules = self::constructModules($wpdb, $setup->getRemoteAddress(), $setup->getServerAddress(), $settings); } @@ -62,7 +58,7 @@ public function __construct(string $plugin_filename, \wpdb $wpdb) * * @return array */ - private function constructSettings(): array + private static function constructSettings(): array { return [ 'cron-jobs' => new Modules\Cron\Settings('bc-security-cron-jobs'), @@ -85,7 +81,7 @@ private function constructSettings(): array * @param array $settings * @return array */ - private function constructModules(\wpdb $wpdb, string $remote_address, string $server_address, array $settings): array + private static function constructModules(\wpdb $wpdb, string $remote_address, string $server_address, array $settings): array { $hostname_resolver = new Modules\Services\ReverseDnsLookup\Resolver(); $cron_job_manager = new Modules\Cron\Manager($settings['cron-jobs']); @@ -180,6 +176,7 @@ public function init() $this->settings['log'], $this->modules['logger'] )) + ->addPage(new Modules\Tools\AdminPage($this->settings)) ; } } @@ -240,13 +237,7 @@ public function uninstall() } // Remove site transients set by plugin. - $this->wpdb->query( - sprintf( - "DELETE FROM {$this->wpdb->options} WHERE (option_name LIKE '%s' OR option_name LIKE '%s')", - '_site_transient_' . Helpers\Transients::NAME_PREFIX . '%', - '_site_transient_timeout_' . Helpers\Transients::NAME_PREFIX . '%' - ) - ); + Helpers\Transients::flush($this->wpdb); // Uninstall every module that requires it. foreach ($this->modules as $module) { diff --git a/classes/BlueChip/Security/Setup/AdminPage.php b/classes/BlueChip/Security/Setup/AdminPage.php index 83605ac..5bd6105 100644 --- a/classes/BlueChip/Security/Setup/AdminPage.php +++ b/classes/BlueChip/Security/Setup/AdminPage.php @@ -5,6 +5,7 @@ namespace BlueChip\Security\Setup; +use BlueChip\Security\Helpers\AdminNotices; use BlueChip\Security\Helpers\FormHelper; class AdminPage extends \BlueChip\Security\Core\Admin\AbstractPage @@ -34,6 +35,19 @@ public function __construct(Settings $settings) public function loadPage() { $this->displaySettingsErrors(); + + if (!empty($connection_type = Core::getConnectionType())) { + // Connection type is set via constant. + AdminNotices::add( + sprintf( + __('You have set BC_SECURITY_CONNECTION_TYPE to %s, therefore the setting below is ignored.', 'bc-security'), + $connection_type + ), + AdminNotices::WARNING, + false, // ~ not dismissible + false // ~ do not escape HTML + ); + } } diff --git a/classes/BlueChip/Security/Setup/Core.php b/classes/BlueChip/Security/Setup/Core.php index 5f2f646..5dd9c8c 100644 --- a/classes/BlueChip/Security/Setup/Core.php +++ b/classes/BlueChip/Security/Setup/Core.php @@ -23,13 +23,22 @@ public function __construct(Settings $settings) /** - * Get remote IP address according to configured connection type. + * @return string Connection type as set by `BC_SECURITY_CONNECTION_TYPE` constant or empty string if constant is not set. + */ + public static function getConnectionType(): string + { + return defined('BC_SECURITY_CONNECTION_TYPE') ? BC_SECURITY_CONNECTION_TYPE : ''; + } + + + /** + * Get remote IP address according to connection type configured either by constant or backend setting. * * @return string */ public function getRemoteAddress(): string { - return IpAddress::get($this->connection_type); + return IpAddress::get(self::getConnectionType() ?: $this->connection_type); } diff --git a/composer.json b/composer.json index 1d1835b..dab2219 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "issues": "https://github.com/chesio/bc-security/issues" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1.0", "composer/installers": "~1.0" }, "require-dev": {