Current File : /home/jeconsul/public_html/wp-content/plugins/presto-player/inc/Models/Model.php |
<?php
namespace PrestoPlayer\Models;
use PrestoPlayer\Support\Utility;
use PrestoPlayer\Support\HasOneRelationship;
/**
* Model for interfacing with custom database tables
*/
abstract class Model implements ModelInterface {
/**
* Needs a table name
*
* @var string
*/
protected $table = '';
/**
* Store model attributes
*
* @var object
*/
protected $attributes;
/**
* Model schema
*
* @return array
*/
public function schema() {
return array();
}
/**
* Guarded variables
*
* @var array
*/
protected $guarded = array();
/**
* Attributes we can query by
*
* @var array
*/
protected $queryable = array();
/**
* Optionally get something from the db
*
* @param integer $id
*/
public function __construct( $id = 0 ) {
$this->attributes = new \stdClass();
if ( ! empty( $id ) ) {
return $this->set( $this->get( $id )->toObject() );
return $this;
}
return $this;
}
/**
* Get attributes properties
*
* @param string $property
* @return mixed
*/
public function __get( $property ) {
if ( property_exists( $this->attributes, $property ) ) {
return $this->attributes->$property;
}
}
/**
* Get attributes properties
*
* @param string $property
* @return mixed
*/
public function __set( $property, $value ) {
$this->attributes->$property = $value;
}
public function getTableName() {
return $this->table;
}
/**
* Convert to Object
*
* @return object
*/
public function toObject() {
$output = new \stdClass();
foreach ( $this->attributes as $key => $attribute ) {
if ( is_a( $attribute, self::class ) ) {
$output->$key = $attribute->toObject();
} else {
$output->$key = $attribute;
}
}
return $output;
}
/**
* Convert to array
*
* @return array
*/
public function toArray() {
return (array) $this->toObject();
}
/**
* Formats row data based on schema
*
* @param object $columns
* @return object
*/
public function formatRow( $columns ) {
$columns = (array) $columns;
$schema = $this->schema();
$columns = $this->maybeUnSerializeArgs( $columns );
foreach ( $columns as $key => $column ) {
if ( ! empty( $schema[ $key ]['type'] ) ) {
settype( $columns[ $key ], $schema[ $key ]['type'] );
}
}
return (object) $columns;
}
/**
* Fetch all models
*
* @return array Array of preset objects
*/
public function all() {
global $wpdb;
// maybe get only published if we have soft deletes
$where = ! empty( $this->schema()['deleted_at'] ) ? "WHERE (deleted_at IS NULL OR deleted_at = '0000-00-00 00:00:00') " : '';
$results = $wpdb->get_results(
"SELECT * FROM {$wpdb->prefix}{$this->table} $where"
);
return $this->parseResults( $results );
}
/**
* Fetch models from db
*
* @param array $args
* @return Object Array of models with pagination data
*/
public function fetch( $args = array() ) {
global $wpdb;
// remove empties for querying
$args = array_filter(
wp_parse_args(
$args,
array(
'status' => 'published',
'per_page' => 10,
'order_by' => array(),
'page' => 1,
)
)
);
// get query args
$query = array_filter(
$args,
function ( $key ) {
return in_array( $key, array( 'per_page', 'page', 'status', 'date_query', 'fields', 'order_by' ) );
},
ARRAY_FILTER_USE_KEY
);
$where = 'WHERE 1=1 ';
$schema = $this->schema();
foreach ( $args as $attribute => $value ) {
// must be queryable and in schema
if ( ! in_array( $attribute, $this->queryable ) || empty( $schema[ $attribute ] ) ) {
unset( $args[ $attribute ] );
continue;
}
// attribute schema
$attr_schema = $schema[ $attribute ];
// force type
settype( $value, $attr_schema['type'] );
// sanitize input
if ( ! empty( $attr_schema['sanitize_callback'] ) ) {
$value = $attr_schema['sanitize_callback']( $value );
}
// maybe add quotes
if ( in_array( $attr_schema['type'], array( 'integer', 'number', 'boolean' ) ) ) {
$where .= $wpdb->prepare( 'AND %1s=%2s ', $attribute, $value );
} else {
$where .= $wpdb->prepare( "AND %1s='%2s' ", $attribute, $value );
}
}
// soft deletes
if ( ! empty( $this->schema()['deleted_at'] ) ) {
$status = ! empty( $args['status'] ) ? $args['status'] : '';
switch ( $status ) {
case 'trashed':
$where .= "AND (deleted_at IS NOT NULL OR deleted_at != '0000-00-00 00:00:00') ";
break;
default: // default to published
$where .= "AND (deleted_at IS NULL OR deleted_at = '0000-00-00 00:00:00') ";
break;
}
}
// before and after
if ( ! empty( $query['date_query'] ) ) {
// use created at by default
$query['date_query'] = wp_parse_args(
$query['date_query'],
array(
'field' => 'created_at',
)
);
// check for field
$field = ! empty( $this->schema()[ $query['date_query']['field'] ] ) ? sanitize_text_field( $query['date_query']['field'] ) : null;
if ( ! $field ) {
return new \WP_Error( 'invalid_field', 'Cannot do a date query by ' . sanitize_text_field( $query['date_query']['field'] ) );
}
// if after
if ( ! empty( $query['date_query']['after'] ) ) {
$where .= $wpdb->prepare(
"AND %1s >= '%2s' ",
sanitize_text_field( $field ), // i.e. created_at
date( 'Y-m-d H:i:s', strtotime( $query['date_query']['after'] ) ) // convert to date
);
}
// before
if ( ! empty( $query['date_query']['before'] ) ) {
$where .= $wpdb->prepare(
"AND %1s <= '%2s' ",
sanitize_text_field( $field ), // i.e. created_at
date( 'Y-m-d H:i:s', strtotime( $query['date_query']['before'] ) ) // convert to date
);
}
}
$limit = (int) $query['per_page'];
$offset = (int) ( $query['per_page'] * ( $query['page'] - 1 ) );
$pagination = $wpdb->prepare( 'LIMIT %1s OFFSET %2s ', $limit, $offset );
$select = '*';
if ( ! empty( $query['fields'] ) && 'ids' === $query['fields'] ) {
$select = 'id';
}
$order_by = '';
if ( ! empty( $query['order_by'] ) ) {
$order_by .= 'ORDER BY';
$number = count( $query['order_by'] );
$i = 1;
foreach ( $query['order_by'] as $attribute => $direction ) {
$order_by .= $wpdb->prepare( ' %1s %2s', $attribute, $direction );
$order_by .= $i === $number ? '' : ',';
++$i;
}
$order_by .= ' ';
}
$total = $wpdb->get_var( "SELECT count(id) as count FROM {$wpdb->prefix}{$this->table} $where$order_by" );
$results = $wpdb->get_results( "SELECT $select FROM {$wpdb->prefix}{$this->table} $where$order_by$pagination" );
return (object) array(
'total' => (int) $total,
'per_page' => (int) $query['per_page'],
'page' => (int) $query['page'],
'data' => 'id' === $select ? $this->parseIds( $results ) : $this->parseResults( $results ),
);
}
/**
* Find a specific model based on query
*/
public function findWhere( $args = array() ) {
$args = wp_parse_args( $args, array( 'per_page' => 1 ) );
$items = $this->fetch( $args );
return ! empty( $items->data[0] ) ? $items->data[0] : false;
}
/**
* Turns raw sql query results into models
*
* @param array $results
* @return array Array of Models
*/
protected function parseResults( $results ) {
if ( is_wp_error( $results ) ) {
return $results;
}
if ( empty( $results ) ) {
return array();
}
$output = array();
// return new models for each row
foreach ( $results as $result ) {
$class = get_class( $this );
$output[] = ( new $class() )->set( $result );
}
return $output;
}
public function parseIds( $results ) {
if ( is_wp_error( $results ) ) {
return $results;
}
if ( empty( $results ) ) {
return array();
}
$ids = array();
foreach ( $results as $result ) {
$ids[] = (int) $result->id;
}
return $ids;
}
/**
* Gets fresh data from the db
*
* @return Model
*/
public function fresh() {
if ( $this->id ) {
return $this->get( $this->id );
}
return $this;
}
/**
* Get default values set from scheam
*
* @return array
*/
protected function getDefaults() {
$schema = $this->schema();
$defaults = array();
foreach ( $schema as $attribute => $scheme ) {
if ( empty( $scheme['default'] ) ) {
continue;
}
$defaults[ $attribute ] = $scheme['default'];
}
return $defaults;
}
/**
* Unset guarded variables
*
* @param array $args
* @return void
*/
protected function unsetGuarded( $args = array() ) {
// unset guarded
foreach ( $this->guarded as $arg ) {
if ( $args[ $arg ] ) {
unset( $args[ $arg ] );
}
}
// we should never set an ID
unset( $args['id'] );
return $args;
}
/**
* Create a preset
*
* @param array $args
* @return integer
*/
public function create( $args ) {
global $wpdb;
// unset guarded args
$args = $this->unsetGuarded( $args );
// parse args with default args
$args = wp_parse_args( $args, $this->getDefaults() );
// creation time
if ( ! empty( $this->schema()['created_at'] ) ) {
$args['created_at'] = ! empty( $args['created_at'] ) ? $args['created_at'] : current_time( 'mysql' );
}
// maybe serialize args
$args = $this->maybeSerializeArgs( $args );
// insert
$wpdb->insert( $wpdb->prefix . $this->table, $args );
// set ID in attributes
$this->attributes->id = $wpdb->insert_id;
// created action
do_action( "{$this->table}_created", $this );
// return id
return $this->attributes->id;
}
protected function maybeSerializeArgs( $args ) {
foreach ( $args as $key => $arg ) {
if ( ! empty( $this->schema()[ $key ] ) ) {
if ( 'array' === $this->schema()[ $key ]['type'] ) {
$args[ $key ] = maybe_serialize( $args[ $key ] );
}
}
}
return $args;
}
protected function maybeUnSerializeArgs( $args ) {
foreach ( $args as $key => $arg ) {
if ( ! empty( $this->schema()[ $key ] ) ) {
if ( 'array' === $this->schema()[ $key ]['type'] ) {
$args[ $key ] = maybe_unserialize( $args[ $key ] );
}
}
}
return $args;
}
/**
* Attempt to locate a database record using the given
* column / value pairs. If the model can NOT be found
* in the database, a record will be inserted with
* the attributes resulting from merging the first array
* argument with the optional second array argument.
*
* @param array $search Model to search for
* @param array $create Attributes to create
* @return Model|\WP_Error
*/
public function firstOrCreate( $search, $create = array() ) {
if ( $this->id ) {
return new \WP_Error( 'already_created', 'This model has already been created.' );
}
$models = $this->fetch( $search );
if ( is_wp_error( $models ) ) {
return $models;
}
// already created
if ( ! empty( $models->data[0] ) ) {
$this->set( $models->data[0]->toObject() );
return $this;
}
// merge and create
$merged = array_merge( $search, $create );
$this->create( $merged );
// return fresh instance
return $this->fresh();
}
/**
* Create and get a model
*
* @param array $args
* @return Model|\WP_Error
*/
public function createAndGet( $args ) {
$id = $this->create( $args );
if ( is_wp_error( $id ) || ! $id ) {
return $id;
}
return $this->fresh();
}
/**
* Attempt to locate a database record using the given
* column / value pairs and update. If the model can NOT be found
* in the database, a record will be inserted with
* the attributes resulting from merging the first array
* argument with the optional second array argument.
*
* @param array $search Model to search for
* @param array $create Attributes to create
* @return Model|\WP_Error
*/
public function getOrCreate( $search, $update = array() ) {
// look for model
$models = $this->fetch( $search );
if ( is_wp_error( $models ) ) {
return $models;
}
// already created, update it
if ( ! empty( $models->data[0] ) && ! empty( $update ) ) {
$this->set( $models->data[0]->toObject() );
return $this;
}
// merge and create
$merged = array_merge( $search, $update );
// unset query stuff
if ( ! empty( $merged['date_query'] ) ) {
unset( $merged['date_query'] );
}
$this->create( $merged );
// return fresh instance
return $this->fresh();
}
/**
* Attempt to locate a database record using the given
* column / value pairs and update. If the model can NOT be found
* in the database, a record will be inserted with
* the attributes resulting from merging the first array
* argument with the optional second array argument.
*
* @param array $search Model to search for
* @param array $create Attributes to create
* @return Model|\WP_Error
*/
public function updateOrCreate( $search, $update = array() ) {
// look for model
$models = $this->fetch( $search );
if ( is_wp_error( $models ) ) {
return $models;
}
// already created, update it
if ( ! empty( $models->data[0] ) && ! empty( $update ) ) {
$this->set( $models->data[0]->toObject() );
$this->update( $update );
return $this;
}
// merge and create
$merged = array_merge( $search, $update );
// unset query stuff
if ( ! empty( $merged['date_query'] ) ) {
unset( $merged['date_query'] );
}
$this->create( $merged );
// return fresh instance
return $this->fresh();
}
/**
* Gets a single model
*
* @param int $id
*
* @return Model Model object
*/
public function get( $id ) {
global $wpdb;
// maybe cache results
$results = $wpdb->get_row(
$wpdb->prepare( "SELECT * FROM {$wpdb->prefix}{$this->table} WHERE id=%d", $id )
);
if ( ! empty( $this->with ) ) {
foreach ( $this->with as $with ) {
$method_name = Utility::snakeToCamel( $with );
if ( method_exists( $this, $method_name ) ) {
$relationship_class = $this->$method_name()->getRelationshipClass();
$parent_field = $this->$method_name()->getParentField();
if ( $results->$parent_field ) {
$results->$parent_field = ( new $relationship_class() )->get( $results->$parent_field );
}
}
}
}
return $this->set( $results );
}
/**
* Set attributes
*
* @param array $args
* @return Model
*/
public function set( $args ) {
$this->attributes = apply_filters( "presto_player/{$this->table}/data", $this->formatRow( $args ) );
return $this;
}
/**
* Update a model
*
* @param array $args
* @return Model
*/
public function update( $args = array() ) {
global $wpdb;
// id is required
if ( empty( $this->attributes->id ) ) {
return new \WP_Error( 'missing_parameter', __( 'You must first create or save this model to update it.', 'presto-player' ) );
}
// unset guarded args
$args = $this->unsetGuarded( $args );
// parse args with default args
$args = wp_parse_args( $args, $this->getDefaults() );
// update time
if ( ! empty( $this->schema()['updated_at'] ) ) {
$args['updated_at'] = ! empty( $args['updated_at'] ) ? $args['updated_at'] : current_time( 'mysql' );
}
// maybe serialize
$args = $this->maybeSerializeArgs( $args );
// make update
$updated = $wpdb->update( $wpdb->prefix . $this->table, $args, array( 'id' => (int) $this->id ) );
// check for failure
if ( false === $updated ) {
return new \WP_Error( 'update_failure', __( 'There was an issue updating the model.', 'presto-player' ) );
}
// set attributes in model
$this->set( $this->get( $this->id )->toObject() );
// created action
do_action( "{$this->table}_updated", $this );
return $this;
}
/**
* Trash model
*
* @return Model
*/
public function trash() {
return $this->update( array( 'deleted_at' => current_time( 'mysql' ) ) );
}
/**
* Untrash model
*
* @return Model
*/
public function untrash() {
return $this->update( array( 'deleted_at' => null ) );
}
/**
* Permanently delete model
*
* @return boolean Whether the model was deleted
*/
public function delete( $where = array() ) {
if ( empty( $where ) ) {
$where = array( 'id' => (int) $this->id );
}
global $wpdb;
return (bool) $wpdb->delete( $wpdb->prefix . $this->table, $where );
}
/**
* Bulk delete by a list of ids
*
* @param array $ids
* @return void
*/
public function bulkDelete( $ids = array() ) {
global $wpdb;
// convert to comman separated
$ids = implode( ',', array_map( 'absint', $ids ) );
// delete in bulk
return (bool) $wpdb->query(
$wpdb->prepare( "DELETE FROM {$wpdb->prefix}{$this->table} WHERE id IN(%1s)", $ids )
);
}
/**
* Has One Relationship
*/
public function hasOne( $classname, $parent_field ) {
return new HasOneRelationship( $classname, $this, $parent_field );
}
}