Phase 6: AIOS security plugin with conservative login lockdown config (10 attempts)
This commit is contained in:
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace AIOWPS\Firewall;
|
||||
|
||||
trait File_Prefix_Trait {
|
||||
|
||||
/**
|
||||
* Get the file's prefix content. N.B. Some code assumes that this doesn't change, so review all consumers of this method before changing its output.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_file_content_prefix() {
|
||||
$prefix = "<?php __halt_compiler();\n";
|
||||
$prefix .= "/**\n";
|
||||
$prefix .= " * This file was created by All In One Security (AIOS) plugin.\n";
|
||||
$prefix .= self::get_prefix_description();
|
||||
$prefix .= " */\n";
|
||||
return $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of the file
|
||||
* You can override this method for each file that needs a file prefix in order to give it its own description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_prefix_description() {
|
||||
return " * The file is required for storing and retrieving your firewall's settings.\n";
|
||||
}
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
namespace AIOWPS\Firewall;
|
||||
|
||||
class Allow_List {
|
||||
|
||||
/**
|
||||
* Include a file prefix when the file is created
|
||||
*/
|
||||
use File_Prefix_Trait;
|
||||
|
||||
/**
|
||||
* Holds the path to the allow list
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $path;
|
||||
|
||||
/**
|
||||
* Overwrite the prefix description from File_Prefix_Trait
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_prefix_description() {
|
||||
return " * The file is required for storing and retrieving your firewall's allow list.\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user's IP address is in the allow list
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_ip_allowed() {
|
||||
|
||||
$ips = self::get_ips();
|
||||
|
||||
if (empty($ips)) return false;
|
||||
|
||||
$ips = explode("\n", $ips);
|
||||
|
||||
return \AIOS_Helper::is_user_ip_address_within_list($ips);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of IP addresses in the allow list
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_ips() {
|
||||
|
||||
clearstatcache();
|
||||
if (!file_exists(self::$path)) return '';
|
||||
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Cannot use WP API. Firewall is loaded independent of WP.
|
||||
$contents = file_get_contents(self::$path, false, null, strlen(self::get_file_content_prefix()));
|
||||
|
||||
return (false !== $contents ? trim($contents) : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path of the allow list
|
||||
*
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
public static function set_path($path) {
|
||||
self::$path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add IPs to the allow list
|
||||
* This overwrites the whole allow list with the given IPs
|
||||
*
|
||||
* @param mixed $ips - A string of IPs; one per line or an array of individual IPs
|
||||
* @return bool
|
||||
*/
|
||||
public static function add_ips($ips) {
|
||||
|
||||
if (is_array($ips)) $ips = implode("\n", $ips);
|
||||
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Cannot use WP API. Firewall is loaded independent of WP.
|
||||
return (false !== file_put_contents(self::$path, self::get_file_content_prefix().$ips));
|
||||
}
|
||||
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
// phpcs:disable WordPress.WP.AlternativeFunctions -- WP isn't loaded here. WP API is unavailable
|
||||
namespace AIOWPS\Firewall;
|
||||
|
||||
/**
|
||||
* Gives us access to our firewall's config
|
||||
*/
|
||||
class Config {
|
||||
|
||||
use File_Prefix_Trait;
|
||||
|
||||
/**
|
||||
* The path to our config file
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Constructs object
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function __construct($path) {
|
||||
$this->path = $path;
|
||||
$this->init_file();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the file if it doesn't exist
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function init_file() {
|
||||
clearstatcache();
|
||||
if (!file_exists($this->path)) {
|
||||
|
||||
$dir = dirname($this->path);
|
||||
if (!file_exists($dir)) Utility::wp_mkdir_p($dir);
|
||||
|
||||
file_put_contents($this->path, self::get_file_content_prefix() . json_encode(array()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the config file with the new prefix whenever the prefix changes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function update_prefix() {
|
||||
|
||||
$valid_prefix = self::get_file_content_prefix();
|
||||
$current_prefix = file_get_contents($this->path, false, null, 0, strlen($valid_prefix));
|
||||
|
||||
if ($current_prefix === $valid_prefix) return; // prefix is valid
|
||||
|
||||
$contents = file_get_contents($this->path);
|
||||
|
||||
$matches = array();
|
||||
if (preg_match('/\{.*\}/', $contents, $matches)) {
|
||||
//update settings
|
||||
file_put_contents($this->path, $valid_prefix . $matches[0]);
|
||||
} else {
|
||||
//reset settings
|
||||
file_put_contents($this->path, $valid_prefix . json_encode(array()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value from the config array
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function get_value($key) {
|
||||
|
||||
$contents = $this->get_contents();
|
||||
|
||||
if (null === $contents) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isset($contents[$key])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $contents[$key];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value in our config array
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return boolean
|
||||
*/
|
||||
public function set_value($key, $value) {
|
||||
|
||||
$contents = $this->get_contents();
|
||||
|
||||
if (null === $contents) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$contents[$key] = $value;
|
||||
|
||||
return (false !== file_put_contents($this->path, self::get_file_content_prefix() . json_encode($contents), LOCK_EX));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the config array from file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_contents() {
|
||||
|
||||
clearstatcache();
|
||||
if (!file_exists($this->path)) $this->init_file();
|
||||
|
||||
// __COMPILER_HALT_OFFSET__ doesn't define in a few PHP versions. It's a PHP bug.
|
||||
// https://bugs.php.net/bug.php?id=70164
|
||||
$contents = file_get_contents($this->path, false, null, strlen(self::get_file_content_prefix()));
|
||||
|
||||
if (false === $contents) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (empty($contents)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return json_decode($contents, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets entire firewall config from array.
|
||||
*
|
||||
* @param Array $contents
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function set_contents($contents) {
|
||||
|
||||
if (null === $contents) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (false !== file_put_contents($this->path, self::get_file_content_prefix() . json_encode($contents), LOCK_EX));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.WP.AlternativeFunctions -- WP isn't loaded here. WP API is unavailable
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
namespace AIOWPS\Firewall;
|
||||
|
||||
/**
|
||||
* A class for accessing constants (including from wp-config) from the firewall
|
||||
* Only supports parsing 'defines' that have scalar types: int, float, boolean, string and null
|
||||
*/
|
||||
class Constants implements \ArrayAccess, \IteratorAggregate {
|
||||
|
||||
/**
|
||||
* The list of constants parsed
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $constants;
|
||||
|
||||
/**
|
||||
* The token part of the token identifier
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.token-get-all#refsect1-function.token-get-all-returnvalues
|
||||
*/
|
||||
const TOKEN = 0;
|
||||
|
||||
/**
|
||||
* The string content of the token identifier
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.token-get-all#refsect1-function.token-get-all-returnvalues
|
||||
*/
|
||||
const CONTENT = 1;
|
||||
|
||||
/**
|
||||
* The line number of the token identifier
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.token-get-all#refsect1-function.token-get-all-returnvalues
|
||||
*/
|
||||
const LINE = 2;
|
||||
|
||||
/**
|
||||
* Offset for define's name [ define(NAME, VALUE); ]
|
||||
*/
|
||||
const DEFINE_NAME_OFFSET = 2;
|
||||
|
||||
/**
|
||||
* Offset for define's value [ define(NAME, VALUE); ]
|
||||
*/
|
||||
const DEFINE_VALUE_OFFSET = 4;
|
||||
|
||||
/**
|
||||
* Constructs our object
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->constants = array();
|
||||
$this->populate_constants();
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates our internal constant array with the defines from wp-config
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function populate_constants() {
|
||||
|
||||
$wpconfig = Utility::get_wpconfig_path();
|
||||
|
||||
clearstatcache();
|
||||
if (!file_exists($wpconfig)) return;
|
||||
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- WP isn't loaded. WP_Filesystem cannot be used.
|
||||
$source = file_get_contents($wpconfig);
|
||||
|
||||
if (false === $source) return;
|
||||
|
||||
$tokens = token_get_all($source);
|
||||
|
||||
//Filter out any unwanted tokens
|
||||
$tokens = array_values(array_filter($tokens, function($token) {
|
||||
|
||||
//All tokens that are not arrays are allowed
|
||||
if (!is_array($token)) return true;
|
||||
|
||||
$unwanted_tokens = array(
|
||||
'T_COMMENT',
|
||||
'T_WHITESPACE',
|
||||
'T_DOC_COMMENT',
|
||||
);
|
||||
|
||||
return (!in_array(token_name($token[self::TOKEN]), $unwanted_tokens));
|
||||
}));
|
||||
|
||||
$token_count = count($tokens);
|
||||
for ($i = 0; $i < $token_count; $i++) {
|
||||
|
||||
$current = $tokens[$i];
|
||||
|
||||
if (!is_array($current)) continue;
|
||||
|
||||
if ('T_STRING' === token_name($current[self::TOKEN]) && 'define' === strtolower($current[self::CONTENT])) {
|
||||
|
||||
// Name of the define without the surrounding quotes
|
||||
$name = substr($tokens[$i + self::DEFINE_NAME_OFFSET][self::CONTENT], 1, -1);
|
||||
|
||||
// Grabs the value of the define
|
||||
$value = $tokens[$i + self::DEFINE_VALUE_OFFSET];
|
||||
|
||||
if (!is_array($value)) continue;
|
||||
|
||||
// We need to interpret the data type of the define's value
|
||||
switch (token_name($value[self::TOKEN])) {
|
||||
case 'T_CONSTANT_ENCAPSED_STRING':
|
||||
$this->constants[$name] = substr($value[self::CONTENT], 1, -1);
|
||||
break;
|
||||
case 'T_LNUMBER':
|
||||
$this->constants[$name] = intval($value[self::CONTENT]);
|
||||
break;
|
||||
case 'T_DNUMBER':
|
||||
$this->constants[$name] = floatval($value[self::CONTENT]);
|
||||
break;
|
||||
case 'T_STRING':
|
||||
$this->constants[$name] = filter_var($value[self::CONTENT], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
break;
|
||||
default:
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the constants as properties
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name) {
|
||||
return $this[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over the constants
|
||||
*
|
||||
* @return iterable
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator() {
|
||||
foreach ($this->constants as $name => $value) yield $name => $value;
|
||||
foreach (get_defined_constants() as $name => $value) yield $name => $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives us array access to the constants
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset) {
|
||||
if (defined($offset)) {
|
||||
return constant($offset);
|
||||
} elseif (isset($this->constants[$offset])) {
|
||||
return $this->constants[$offset];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the constant exists
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @return boolean
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($offset) {
|
||||
return defined($offset) || isset($this->constants[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if constant exists
|
||||
*
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
*/
|
||||
public function __isset($name) {
|
||||
return $this->offsetExists($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constant. This is disabled as we want it read-only
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
* @throws \Exception - Throws an exception if called to ensure it's read-only.
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($offset, $value) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Needed for ArrayAccess interface but not used by us as we require read-only
|
||||
throw new \Exception('Constants are read-only.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets the constant. This is disabled as we want it read-only
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @return void
|
||||
* @throws \Exception - Throws an exception if called to ensure it's read-only.
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($offset) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Needed for ArrayAccess interface but not used by us as we require read-only
|
||||
throw new \Exception('Constants are read-only.');
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
namespace AIOWPS\Firewall;
|
||||
|
||||
/**
|
||||
* Class to help debug the firewall
|
||||
*/
|
||||
class Debug {
|
||||
|
||||
/**
|
||||
* Constructs our object
|
||||
*/
|
||||
public function __construct() {
|
||||
//Capture the events that relate to the firewall's rules
|
||||
Event::capture('rule_triggered', array($this, 'rule_debug'));
|
||||
Event::capture('rule_not_triggered', array($this, 'rule_debug'));
|
||||
Event::capture('rule_active', array($this, 'rule_debug'));
|
||||
Event::capture('rule_not_active', array($this, 'rule_debug'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures the firewall's events for debugging rules
|
||||
*
|
||||
* @global Constants $aiowps_firewall_constants
|
||||
* @global Message_Store $aiowps_firewall_message_store
|
||||
*
|
||||
* @param string $event
|
||||
* @param Rule $rule
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rule_debug($event, Rule $rule) {
|
||||
global $aiowps_firewall_constants, $aiowps_firewall_message_store;
|
||||
if (!$aiowps_firewall_constants->AIOS_FIREWALL_DEBUG && 'rule_triggered' !== $event) return;
|
||||
|
||||
$details = array(
|
||||
'name' => $rule->name,
|
||||
'family' => $rule->family,
|
||||
'ip' => \AIOS_Helper::get_user_ip_address(),
|
||||
'time' => time(),
|
||||
);
|
||||
|
||||
// Get any user information
|
||||
foreach ($_COOKIE as $key => $value) {
|
||||
if (preg_match('/^wordpress_logged_in_/', $key)) {
|
||||
$details['potential_user'] = stripslashes($value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$details['request'] = $_SERVER;
|
||||
unset($details['request']['HTTP_COOKIE']);
|
||||
|
||||
// Uncomment when the firewall log issues have been resolved
|
||||
//$aiowps_firewall_message_store->set($event, $details);
|
||||
|
||||
// Remove when the firewall log issues have been resolved
|
||||
$aiowps_firewall_message_store->clear_message_store();
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
namespace AIOWPS\Firewall;
|
||||
|
||||
class Event {
|
||||
|
||||
/**
|
||||
* Stores our events
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $events = array();
|
||||
|
||||
/**
|
||||
* Captures an event
|
||||
*
|
||||
* @param string $name - Name of the event
|
||||
* @param callable $callback - Callback to execute when the event is raised
|
||||
* @return void
|
||||
*/
|
||||
public static function capture($name, callable $callback) {
|
||||
$name = strtolower($name);
|
||||
|
||||
if (!isset(self::$events[$name])) {
|
||||
self::$events[$name] = array();
|
||||
}
|
||||
|
||||
self::$events[$name][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Raises the event
|
||||
*
|
||||
* All the callbacks in a given name are executed
|
||||
*
|
||||
* @param string $name - Name of the event to raise
|
||||
* @param array ...$args - Variable list of arguments to pass to the callback
|
||||
* @return void
|
||||
*/
|
||||
public static function raise($name, ...$args) {
|
||||
$name = strtolower($name);
|
||||
|
||||
if (empty(self::$events[$name])) return;
|
||||
|
||||
array_unshift($args, $name);
|
||||
|
||||
foreach (self::$events[$name] as $event) {
|
||||
call_user_func_array($event, $args);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
namespace AIOWPS\Firewall;
|
||||
|
||||
/**
|
||||
* Use this when throwing an exception if you want to also exit the request
|
||||
*/
|
||||
class Exit_Exception extends \Exception {
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
namespace AIOWPS\Firewall;
|
||||
|
||||
class Message_Store {
|
||||
|
||||
/**
|
||||
* Makes this class a singleton
|
||||
*/
|
||||
use Singleton_Trait;
|
||||
|
||||
/**
|
||||
* Internal store of the messages
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $messages;
|
||||
|
||||
/**
|
||||
* Holds the name of the message store's table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $table_name;
|
||||
|
||||
/**
|
||||
* A key should only be loaded from the database once per request; this keeps track of them
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $keys_loaded;
|
||||
|
||||
/**
|
||||
* Constructs our object
|
||||
*/
|
||||
private function __construct() {
|
||||
Event::capture('action_before_exit', array($this, 'dump'));
|
||||
$this->messages = array();
|
||||
$this->keys_loaded = array();
|
||||
$this->table_name = 'aiowps_message_store';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets internal message store
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
|
||||
if (!is_string($key)) return;
|
||||
|
||||
if (!isset($this->messages[$key])) {
|
||||
$this->messages[$key] = array();
|
||||
}
|
||||
|
||||
$this->messages[$key][] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the messages associated with a key
|
||||
*
|
||||
* @param string $key
|
||||
* @return array
|
||||
*/
|
||||
public function get($key) {
|
||||
|
||||
$is_key_loaded = in_array($key, $this->keys_loaded);
|
||||
$can_check_database = isset($GLOBALS['wpdb']) && !$is_key_loaded && class_exists('Updraft_Semaphore_3_0');
|
||||
|
||||
//Load requested messages from the database
|
||||
if ($can_check_database) {
|
||||
|
||||
$lock = new \Updraft_Semaphore_3_0('aios_message_store_lock_'.$key, 60);
|
||||
$to_delete = array();
|
||||
|
||||
if ($lock->lock()) {
|
||||
|
||||
try {
|
||||
global $wpdb;
|
||||
|
||||
$table = $this->get_table();
|
||||
|
||||
// If we can't get the table to check the DB, still check our internal store for the key
|
||||
if (empty($table)) return isset($this->messages[$key]) ? $this->messages[$key] : array();
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
|
||||
$rows = $wpdb->get_results($wpdb->prepare("SELECT id, message_value FROM `{$table}` WHERE message_key = %s", $key));
|
||||
|
||||
if (!empty($rows)) {
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$values = json_decode($row->message_value, true);
|
||||
|
||||
foreach ($values as $value) $this->set($key, $value);
|
||||
|
||||
$to_delete[] = $row->id;
|
||||
}
|
||||
|
||||
$this->keys_loaded[] = $key;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- PCP warning. Necessary for AIOS error reporting system.
|
||||
error_log("AIOS: Error getting database entries for key '{$key}': {$e->getMessage()}");
|
||||
|
||||
} catch (\Error $e) { // phpcs:ignore PHPCompatibility.Classes.NewClasses.errorFound -- this won't run on PHP 5.6 so we still want to catch it on other versions
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- PCP warning. Necessary for AIOS error reporting system.
|
||||
error_log("AIOS: Error getting database entries for key '{$key}': {$e->getMessage()}");
|
||||
} finally {
|
||||
|
||||
//Delete IDs of loaded messages
|
||||
if (!empty($to_delete)) {
|
||||
$ids = implode(',', $to_delete);
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
|
||||
$wpdb->query("DELETE FROM `{$table}` WHERE id IN ({$ids})");
|
||||
}
|
||||
|
||||
$lock->release();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return isset($this->messages[$key]) ? $this->messages[$key] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the message store to the database
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function dump() {
|
||||
//No point saving if there are no messages
|
||||
if (empty($this->messages)) return;
|
||||
|
||||
if (!Utility::attempt_to_access_wpdb()) throw new Exit_Exception('Unable to save the message store to the database: wpdb is inaccessible.');
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$table = $this->get_table();
|
||||
|
||||
if (empty($table)) throw new Exit_Exception('Unable to save messages store to the database: unable to get the correct table.');
|
||||
|
||||
$statement = "INSERT INTO `{$table}` (message_key, message_value, created) VALUES ";
|
||||
$values = array();
|
||||
|
||||
foreach ($this->messages as $key => $value) {
|
||||
$statement .= '(%s, %s, %s),';
|
||||
$values[] = $table;
|
||||
$values[] = $key;
|
||||
$values[] = json_encode($value); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- This method runs outside the WordPress environment and therefore cannot use WordPress functions.
|
||||
$values[] = time();
|
||||
}
|
||||
|
||||
$statement = rtrim($statement, ',');
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Prepared above.
|
||||
$wpdb->query($wpdb->prepare($statement, $values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the table name if it exists
|
||||
*
|
||||
* @return string - Table name on success; blank string otherwise
|
||||
*/
|
||||
private function get_table() {
|
||||
global $wpdb;
|
||||
|
||||
if (!$wpdb) return '';
|
||||
|
||||
$table = $wpdb->get_blog_prefix(0).$this->table_name;
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
|
||||
if ($table != $wpdb->get_var("SHOW TABLES LIKE '{$table}'")) return '';
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all the messages from the message store table if it contains data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_message_store() {
|
||||
global $wpdb;
|
||||
|
||||
$table = $this->get_table();
|
||||
|
||||
// Check if the table exists and is accessible
|
||||
if (empty($table)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Check if the table has any rows
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery -- PCP warning. Direct query necessary. No caching necessary.
|
||||
$row_exists = $wpdb->get_var(
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- PCP error. Ignore.
|
||||
$wpdb->prepare("SELECT EXISTS (SELECT 1 FROM `{$table}` LIMIT 1)")
|
||||
);
|
||||
|
||||
// If there are no rows, $row_exists will be 0
|
||||
if (!$row_exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the table (delete all records)
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- PCP error. Ignore.
|
||||
$wpdb->query($wpdb->prepare("DELETE FROM `{$table}`"));
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace AIOWPS\Firewall;
|
||||
|
||||
/**
|
||||
* A trait with a basic singleton implementation
|
||||
*/
|
||||
trait Singleton_Trait {
|
||||
|
||||
/**
|
||||
* Internally stores the class's instance
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Returns an instance of the class
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function instance() {
|
||||
|
||||
if (is_null(self::$instance)) self::$instance = new self();
|
||||
|
||||
return self::$instance;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't want our singleton object to be cloned
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function __clone() {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user