: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Abstract class for database
* @copyright Copyright (c) 2023, Code Atlantic LLC
if ( ! defined( 'ABSPATH' ) ) {
* Abstract database class.
abstract class PUM_Abstract_Database {
* The name of our database table.
* The version of our database table.
* The name of the primary column.
public $primary_key = 'ID';
public function __construct() {
$current_db_version = $this->get_installed_version();
if ( ! $current_db_version || $current_db_version < $this->version ) {
if ( $wpdb->get_var( "SHOW TABLES LIKE '{$this->table_name()}'" ) === $this->table_name() ) {
$this->update_db_version();
$wpdb->{$this->table_name} = $this->table_name();
* Gets db version from new or old source.
public function get_installed_version() {
// Get list of all current db table versions.
$db_versions = get_option( 'pum_db_versions', [] );
// #1 If it exists in new pum_db_vers[] option, move on.
if ( isset( $db_versions[ $this->table_name ] ) ) {
return (float) $db_versions[ $this->table_name ];
// #2 Else look for old key, if exists, migrate and delete.
$db_version_old_key = get_option( $this->table_name . '_db_version' );
if ( $db_version_old_key ) {
if ( $db_version_old_key > 0 ) {
$db_versions[ $this->table_name ] = (float) $db_version_old_key;
update_option( 'pum_db_versions', $db_versions );
delete_option( $this->table_name . '_db_version' );
return (float) $db_version_old_key;
public function update_db_version() {
// Get list of all current db table versions.
$db_versions = get_option( 'pum_db_versions', [] );
$db_versions[ $this->table_name ] = (float) $this->version;
update_option( 'pum_db_versions', $db_versions );
abstract public function create_table();
public static function instance() {
$class = get_called_class();
if ( ! isset( self::$instance[ $class ] ) ) {
self::$instance[ $class ] = new $class();
return self::$instance[ $class ];
* Retrieve a row by the primary key
public function get( $row_id ) {
return $this->prepare_result( $wpdb->get_row( "SELECT * FROM {$this->table_name()} WHERE $this->primary_key = $row_id LIMIT 1;" ) );
* @param object|array $result
public function prepare_result( $result ) {
if ( ! $result || ( ! is_array( $result ) && ! is_object( $result ) ) ) {
if ( is_object( $result ) ) {
$vars = get_object_vars( $result );
foreach ( $vars as $key => $value ) {
if ( is_string( $value ) ) {
$result->$key = maybe_unserialize( $value );
} elseif ( is_array( $result ) ) {
foreach ( $result as $key => $value ) {
if ( is_string( $value ) ) {
$result[ $key ] = maybe_unserialize( $value );
* @param array|object[] $results
public function prepare_results( $results ) {
foreach ( $results as $key => $result ) {
$results[ $key ] = $this->prepare_result( $result );
public function table_name() {
return $wpdb->prefix . $this->table_name;
* Retrieve a row by a specific column / value
public function get_by( $column, $row_id ) {
return $this->prepare_result( $wpdb->get_row( "SELECT * FROM {$this->table_name()} WHERE $column = '$row_id' LIMIT 1;" ) );
* Retrieve a specific column's value by the primary key
public function get_column( $column, $row_id ) {
return $wpdb->get_var( "SELECT $column FROM {$this->table_name()} WHERE $this->primary_key = $row_id LIMIT 1;" );
* Retrieve a specific column's value by the the specified column / value
public function get_column_by( $column, $column_where, $column_value ) {
return $wpdb->get_var( "SELECT $column FROM {$this->table_name()} WHERE $column_where = '$column_value' LIMIT 1;" );
public function insert( $data ) {
$data = wp_parse_args( $data, $this->get_column_defaults() );
do_action( 'pum_pre_insert_' . $this->table_name, $data );
// Initialise column format array
$column_formats = $this->get_columns();
// Force fields to lower case
$data = array_change_key_case( $data );
$data = array_intersect_key( $data, $column_formats );
// Reorder $column_formats to match the order of columns given in $data
$data_keys = array_keys( $data );
$column_formats = array_merge( array_flip( $data_keys ), $column_formats );
foreach ( $data as $key => $value ) {
if ( is_array( $value ) ) {
$data[ $key ] = maybe_serialize( $value );
$wpdb->insert( $this->table_name(), $data, $column_formats );
do_action( 'pum_post_insert_' . $this->table_name, $wpdb->insert_id, $data );
public function get_column_defaults() {
public function get_columns() {
public function update( $row_id, $data = [], $where = '' ) {
// Row ID must be positive integer
$row_id = absint( $row_id );
if ( empty( $row_id ) ) {
$where = $this->primary_key;
// Initialise column format array
$column_formats = $this->get_columns();
// Force fields to lower case
$data = array_change_key_case( $data );
$data = array_intersect_key( $data, $column_formats );
foreach ( $data as $key => $value ) {
if ( is_array( $value ) ) {
$data[ $key ] = maybe_serialize( $value );
// Reorder $column_formats to match the order of columns given in $data
$data_keys = array_keys( $data );
$column_formats = array_merge( array_flip( $data_keys ), $column_formats );
if ( false === $wpdb->update( $this->table_name(), $data, [ $where => $row_id ], $column_formats ) ) {
* Delete a row identified by the primary key
public function delete( $row_id = 0 ) {
// Row ID must be positive integer
$row_id = absint( $row_id );
if ( empty( $row_id ) ) {
if ( false === $wpdb->query( $wpdb->prepare( "DELETE FROM {$this->table_name()} WHERE $this->primary_key = %d", $row_id ) ) ) {
* Delete a row identified by the primary key
public function delete_by( $column, $row_id ) {
if ( empty( $row_id ) ) {
if ( false === $wpdb->query( $wpdb->prepare( "DELETE FROM {$this->table_name()} WHERE $column = '%s'", $row_id ) ) ) {
public function prepare_query( $query, $args = [] ) {
if ( $args['orderby'] ) {
$query .= " ORDER BY {$args['orderby']} {$args['order']}";
$query .= " LIMIT {$args['limit']}";
$query .= " OFFSET {$args['offset']}";
* @param string $return_type
* @return array|mixed|object[]
public function query( $args = [], $return_type = OBJECT ) {
$columns = $this->get_columns();
$fields = $args['fields'];
$fields = array_keys( $columns );
$fields = explode( ',', $args['fields'] );
$fields = array_map( 'trim', $fields );
$fields = array_map( 'sanitize_text_field', $fields );
$select_fields = implode( '`, `', $fields );
$query = "SELECT `$select_fields` FROM {$this->table_name()}";
// Set up $values array for wpdb::prepare
// Define an empty WHERE clause to start from.
if ( $args['s'] && ! empty( $args['s'] ) ) {
$search = wp_unslash( trim( $args['s'] ) );
foreach ( $columns as $key => $type ) {
if ( in_array( $key, $fields ) ) {
if ( '%s' === $type || ( '%d' === $type && is_numeric( $search ) ) ) {
$values[] = '%' . $wpdb->esc_like( $search ) . '%';
$search_where[] = "`$key` LIKE '%s'";
if ( ! empty( $search_where ) ) {
$where .= ' AND (' . join( ' OR ', $search_where ) . ')';
if ( ! empty( $args['orderby'] ) ) {
$query .= ' ORDER BY %s';
$values[] = wp_unslash( trim( $args['orderby'] ) );
switch ( $args['order'] ) {
if ( ! empty( $args['limit'] ) ) {
$values[] = absint( $args['limit'] );