: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
const TABLE_2FA_SECRETS = 'wfls_2fa_secrets';
const TABLE_SETTINGS = 'wfls_settings';
const TABLE_ROLE_COUNTS = 'wfls_role_counts';
const TABLE_ROLE_COUNTS_TEMPORARY = 'wfls_role_counts_temporary';
const SCHEMA_VERSION = 2;
* Returns the singleton Controller_DB.
public static function shared() {
$_shared = new Controller_DB();
* Returns the table prefix for the main site on multisites and the site itself on single site installations.
public static function network_prefix() {
return $wpdb->base_prefix;
* Returns the table with the site (single site installations) or network (multisite) prefix added.
public static function network_table($table) {
return self::network_prefix() . $table;
public function __get($key) {
return self::network_table(self::TABLE_2FA_SECRETS);
return self::network_table(self::TABLE_SETTINGS);
return self::network_table(self::TABLE_ROLE_COUNTS);
case 'role_counts_temporary':
return self::network_table(self::TABLE_ROLE_COUNTS_TEMPORARY);
throw new \OutOfBoundsException('Unknown key: ' . $key);
public function install() {
$wpdb->query($wpdb->prepare("UPDATE `{$table}` SET `vtime` = LEAST(`vtime`, %d)", Controller_Time::time()));
public function uninstall() {
$tables = array(self::TABLE_2FA_SECRETS, self::TABLE_SETTINGS, self::TABLE_ROLE_COUNTS);
foreach ($tables as $table) {
$wpdb->query('DROP TABLE IF EXISTS `' . self::network_table($table) . '`');
private function create_table($name, $definition, $temporary = false) {
if (is_array($definition)) {
foreach ($definition as $attempt) {
if ($this->create_table($name, $attempt, $temporary))
return $wpdb->query('CREATE ' . ($temporary ? 'TEMPORARY ' : '') . 'TABLE IF NOT EXISTS `' . self::network_table($name) . '` ' . $definition);
private function create_temporary_table($name, $definition) {
if (Controller_Settings::shared()->get_bool(Controller_Settings::OPTION_DISABLE_TEMPORARY_TABLES))
if ($this->create_table($name, $definition, true))
Controller_Settings::shared()->set(Controller_Settings::OPTION_DISABLE_TEMPORARY_TABLES, true);
private function get_role_counts_table_definition($engine = null) {
$engineClause = $engine === null ? '' : "ENGINE={$engine}";
serialized_roles VARBINARY(255) NOT NULL,
two_factor_inactive TINYINT(1) NOT NULL,
user_count BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (serialized_roles, two_factor_inactive)
private function get_role_counts_table_definition_options() {
$this->get_role_counts_table_definition('MEMORY'),
$this->get_role_counts_table_definition('MyISAM'),
$this->get_role_counts_table_definition()
protected function _create_schema() {
self::TABLE_2FA_SECRETS => '(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned NOT NULL,
`secret` tinyblob NOT NULL,
`recovery` blob NOT NULL,
`ctime` int(10) unsigned NOT NULL,
`vtime` int(10) unsigned NOT NULL,
`mode` enum(\'authenticator\') NOT NULL DEFAULT \'authenticator\',
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;',
self::TABLE_SETTINGS => '(
`name` varchar(191) NOT NULL DEFAULT \'\',
`autoload` enum(\'no\',\'yes\') NOT NULL DEFAULT \'yes\',
) ENGINE=InnoDB DEFAULT CHARSET=utf8;',
self::TABLE_ROLE_COUNTS => $this->get_role_counts_table_definition_options()
foreach ($tables as $table => $def) {
$this->create_table($table, $def);
Controller_Settings::shared()->set(Controller_Settings::OPTION_SCHEMA_VERSION, self::SCHEMA_VERSION);
public function require_schema_version($version) {
$current = Controller_Settings::shared()->get_int(Controller_Settings::OPTION_SCHEMA_VERSION);
if ($current < $version) {
public function query($query) {
if ($wpdb->query($query) === false)
throw new RuntimeException("Failed to execute query: {$query}");
public function get_wpdb() {
public function create_temporary_role_counts_table() {
return $this->create_temporary_table(self::TABLE_ROLE_COUNTS_TEMPORARY, $this->get_role_counts_table_definition_options());