File Manager
<?php
namespace Imagely\NGG\DataMapper;
use Imagely\NGG\Util\Serializable;
class WPPostDriver extends DriverBase {
public $cache;
public $primary_key_column = 'ID';
public $query_args = [];
public $use_cache = true;
public static $post_table_columns = [];
public $ID;
public $post_parent;
/**
* @throws \Exception
*/
public function __construct( $object_name = '' ) {
if ( strlen( $object_name ) > 20 ) {
throw new \Exception( 'The custom post name can be no longer than 20 characters long' );
}
parent::__construct( $object_name );
}
public function lookup_columns() {
if ( empty( self::$post_table_columns ) ) {
$columns = parent::lookup_columns();
foreach ( $columns as $column ) {
self::$post_table_columns[] = $column;
}
} else {
foreach ( self::$post_table_columns as $column ) {
$this->_table_columns[] = $column;
}
}
}
/**
* Gets the name of the table
*
* @global string $table_prefix
* @return string
*/
public function get_table_name() {
global $table_prefix;
return $table_prefix . 'posts';
}
/**
* Returns a list of querable table columns for posts
*
* @return array
*/
public function _get_querable_table_columns() {
return [ 'name', 'author', 'date', 'title', 'modified', 'menu_order', 'parent', 'ID', 'rand', 'comment_count' ];
}
/**
* Specifies an order clause
*
* @param string $order_by
* @param string $direction
* @return WPPostDriver
*/
public function order_by( $order_by, $direction = 'ASC' ) {
// Make an exception for the rand() method.
$order_by = preg_replace( '/rand\(\s*\)/', 'rand', $order_by );
if ( in_array( $order_by, $this->_get_querable_table_columns() ) ) {
$this->query_args['orderby'] = $order_by;
} else {
// ordering by a meta value.
$this->query_args['orderby'] = 'meta_value';
$this->query_args['meta_key'] = $order_by;
}
$this->query_args['order'] = $direction;
return $this;
}
/**
* Specifies a limit and optional offset
*
* @param int $max
* @param int|bool $offset (optional)
* @return object
*/
public function limit( $max, $offset = false ) {
if ( $max ) {
$this->query_args['paged'] = true;
if ( $offset ) {
$this->query_args['offset'] = $offset;
} else {
unset( $this->query_args['offset'] );
}
$this->query_args['posts_per_page'] = $max;
}
return $this;
}
/**
* Specifies a list of columns to group by
*
* @param array|string $columns
* @return object
*/
public function group_by( $columns = [] ) {
if ( ! isset( $this->query_args['group_by_columns'] ) ) {
$this->query_args['group_by_columns'] = $columns;
} else {
$this->query_args['group_by_columns'] = array_merge(
$this->query_args['group_by_columns'],
$columns
);
}
return $this;
}
/**
* Adds a WP_Query where clause
*
* @param array $where_clauses
* @param string $join
*/
public function add_where_clause( $where_clauses, $join ) {
foreach ( $where_clauses as $clause ) {
// Determine where what the where clause is comparing.
switch ( $clause['column'] ) {
case 'author':
case 'author_id':
$this->query_args['author'] = $clause['value'];
break;
case 'author_name':
$this->query_args['author_name'] = $clause['value'];
break;
case 'cat':
case 'cat_id':
case 'category_id':
switch ( $clause['compare'] ) {
case '=':
case 'BETWEEN';
case 'IN';
if ( ! isset( $this->query_args['category__in'] ) ) {
$this->query_args['category__in'] = [];
}
$this->query_args['category__in'][] = $clause['value'];
break;
case '!=':
case 'NOT BETWEEN';
case 'NOT IN';
if ( ! isset( $this->query_args['category__not_in'] ) ) {
$this->query_args['category__not_in'] = [];
}
$this->query_args['category__not_in'][] = $clause['value'];
break;
}
break;
case 'category_name':
$this->query_args['category_name'] = $clause['value'];
break;
case 'post_id':
case $this->get_primary_key_column():
switch ( $clause['compare'] ) {
case '=':
case 'IN';
case 'BETWEEN';
if ( ! isset( $this->query_args['post__in'] ) ) {
$this->query_args['post__in'] = [];
}
$this->query_args['post__in'][] = $clause['value'];
break;
default:
if ( ! isset( $this->query_args['post__not_in'] ) ) {
$this->query_args['post__not_in'] = [];
}
$this->query_args['post__not_in'][] = $clause['value'];
break;
}
break;
case 'pagename':
case 'postname':
case 'page_name':
case 'post_name':
if ( $clause['compare'] == 'LIKE' ) {
$this->query_args['page_name__like'] = $clause['value'];
} elseif ( $clause['compare'] == '=' ) {
$this->query_args['pagename'] = $clause['value'];
} elseif ( $clause['compare'] == 'IN' ) {
$this->query_args['page_name__in'] = $clause['value'];
}
break;
case 'post_title':
// Post title uses custom WHERE clause.
if ( $clause['compare'] == 'LIKE' ) {
$this->query_args['post_title__like'] = $clause['value'];
} else {
$this->query_args['post_title'] = $clause['value'];
}
break;
default:
// Must be metadata.
$clause['key'] = $clause['column'];
unset( $clause['column'] );
// Convert values to array, when required.
if ( in_array( $clause['compare'], [ 'IN', 'BETWEEN' ] ) ) {
$clause['value'] = explode( ',', $clause['value'] );
foreach ( $clause['value'] as &$val ) {
if ( ! is_numeric( $val ) ) {
// In the _parse_where_clause() method, we.
// quote the strings and add slashes.
$val = stripslashes( $val );
$val = substr( $val, 1, strlen( $val ) - 2 );
}
}
}
if ( ! isset( $this->query_args['meta_query'] ) ) {
$this->query_args['meta_query'] = [];
}
$this->query_args['meta_query'][] = $clause;
break;
}
}
// If any where clauses have been added, specify how the conditions.
// will be conbined/joined.
if ( isset( $this->query_args['meta_query'] ) ) {
$this->query_args['meta_query']['relation'] = $join;
}
}
/**
* Converts a post to an entity.
*
* @param \stdClass $post
* @param boolean $model
* @return \stdClass
*/
public function convert_post_to_entity( $post, $model = false ) {
$entity = new \stdClass();
// Unserialize the post_content_filtered field.
if ( is_string( $post->post_content_filtered ) ) {
if ( ( $post_content = Serializable::unserialize( $post->post_content_filtered ) ) ) {
foreach ( $post_content as $key => $value ) {
$post->$key = $value;
}
}
}
// Unserialize the post content field.
if ( is_string( $post->post_content ) ) {
if ( ( $post_content = Serializable::unserialize( $post->post_content ) ) ) {
foreach ( $post_content as $key => $value ) {
$post->$key = $value;
}
}
}
// Copy post fields to entity.
unset( $post->post_content );
unset( $post->post_content_filtered );
foreach ( $post as $key => $value ) {
$entity->$key = $value;
}
$this->_convert_to_entity( $entity );
return $model ? $this->convert_to_model( $entity ) : $entity;
}
/**
* Converts an entity to a post
*
* @param object $entity
* @return object
*/
public function _convert_entity_to_post( $entity ) {
// Was a model passed instead of an entity?
$post = $entity;
// Create the post content.
$post_content = clone $post;
foreach ( $this->_table_columns as $column ) {
unset( $post_content->$column );
}
unset( $post->id_field );
unset( $post->post_content_filtered );
unset( $post->post_content );
$post->post_content = Serializable::serialize( $post_content );
$post->post_content_filtered = $post->post_content;
$post->post_type = $this->get_object_name();
// Sometimes an entity can contain a data stored in an array or object
// Those will be removed from the post, and serialized in the
// post_content field.
foreach ( $post as $key => $value ) {
if ( in_array( strtolower( gettype( $value ) ), [ 'object', 'array' ] ) ) {
unset( $post->$key );
}
}
// A post required a title.
if ( empty( $post->post_title ) ) {
$post->post_title = $this->get_post_title( $post );
}
// A post also requires an excerpt.
if ( empty( $post->post_excerpt ) ) {
$post->post_excerpt = $this->get_post_excerpt( $post );
}
return $post;
}
/**
* Returns the WordPress database class
*
* @global \wpdb $wpdb
* @return \wpdb
*/
public function _wpdb() {
global $wpdb;
return $wpdb;
}
/**
* Flush and update all postmeta for a particular post
*
* @param int $post_id
*/
public function _flush_and_update_postmeta( $post_id, $entity, $omit = [] ) {
// We need to insert post meta data for each property
// Unfortunately, that means flushing all existing postmeta
// and then inserting new values. Depending on the number of
// properties, this could be slow. So, we directly access the database.
/* @var \wpdb $wpdb */
global $wpdb;
if ( ! is_array( $omit ) ) {
$omit = [ $omit ];
}
// By default, we omit creating meta values for columns in the posts table.
$omit = array_merge( $omit, $this->_table_columns );
// Delete the existing meta values.
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE post_id = %s", $post_id ) );
// Create query for new meta values.
$sql_parts = [];
foreach ( $entity as $key => $value ) {
if ( in_array( $key, $omit ) ) {
continue;
}
if ( is_array( $value ) or is_object( $value ) ) {
$value = Serializable::serialize( $value );
}
$sql_parts[] = $wpdb->prepare( '(%s, %s, %s)', $post_id, $key, $value );
}
// The following $sql_parts is already sent through $wpdb->prepare() -- look directly above this line
//
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( "INSERT INTO {$wpdb->postmeta} (post_id, meta_key, meta_value) VALUES " . implode( ',', $sql_parts ) );
}
/**
* Determines whether the current statement is SELECT
*
* @return boolean
*/
public function is_select_statement() {
return isset( $this->query_args['is_select'] ) && $this->query_args['is_select'];
}
/**
* Runs the query
*
* @param string|bool $sql (optional) Run the specified query
* @param object|bool $model (optional)
* @param bool $convert_to_entities (optional) Default = true
* @return array
*/
public function run_query( $sql = false, $model = true, $convert_to_entities = true ) {
$retval = [];
$results = [];
// All of our custom fields are stored as post meta, but is also stored as a serialized value in the post_content
// field. Because of this, we don't need to look up and cache the post meta values.
$this->query_args['no_found_posts'] = false;
$this->query_args['update_post_meta_cache'] = false;
// Don't cache any manual SQL query.
if ( $sql ) {
$this->query_args['cache_results'] = false;
$this->query_args['custom_sql'] = $sql;
}
// If this is a select query, then try fetching the results from cache.
$cache_key = md5( json_encode( $this->query_args ) );
if ( $this->is_select_statement() && $this->use_cache ) {
$results = $this->get_from_cache( $cache_key );
}
// Execute the query.
if ( ! $results ) {
$query = new \WP_Query( [ 'datamapper' => true ] );
if ( isset( $this->debug ) ) {
$this->query_args['debug'] = true;
}
$query->query_vars = $this->query_args;
add_action( 'pre_get_posts', [ &$this, 'set_query_args' ], PHP_INT_MAX - 1, 1 );
$results = $query->get_posts();
// Cache the result.
if ( $this->is_select_statement() ) {
$this->cache( $cache_key, $results );
}
remove_action( 'pre_get_posts', [ &$this, 'set_query_args' ], PHP_INT_MAX - 1 );
}
// Convert the result.
if ( $convert_to_entities ) {
foreach ( $results as $row ) {
$retval[] = $this->convert_post_to_entity( $row, $model );
}
} else {
$retval = $results;
}
// Ensure that we return an empty array when there are no results.
if ( ! $retval ) {
$retval = [];
}
return $retval;
}
/**
* Ensure that the query args are set. We need to do this in case a third-party
* plugin overrides our query.
*
* @param $query
*/
public function set_query_args( $query ) {
if ( $query->get( 'datamapper' ) ) {
$query->query_vars = $this->query_args;
}
$filter = isset( $query->query_vars['suppress_filters'] ) ? $query->query_vars['suppress_filters'] : false;
$query->query_vars['suppress_filters'] = apply_filters( 'wpml_suppress_filters', $filter );
}
/**
* Returns the number of total records/entities that exist
*
* @return int
*/
public function count() {
$this->select( $this->get_primary_key_column() );
$retval = $this->run_query( false, false, false );
return count( $retval );
}
/**
* Used to select which fields should be returned. NOT currently used by
* this implementation of the datamapper driver
*
* @param string $fields
* @return WPPostDriver
*/
public function select( $fields = '*' ) {
$this->query_args = [
'post_type' => $this->get_object_name(),
'paged' => false,
'fields' => $fields,
'post_status' => 'any',
'datamapper' => true,
'posts_per_page' => -1,
'is_select' => true,
'is_delete' => false,
];
return $this;
}
/**
* Destroys/deletes an entity from the database
*
* @param \stdClass|Model|int $entity
* @param bool $skip_trash (optional) Default = true
* @return bool
*/
public function destroy( $entity, $skip_trash = true ) {
$retval = false;
$key = $this->get_primary_key_column();
// Find the id of the entity.
if ( is_object( $entity ) && isset( $entity->$key ) ) {
$id = (int) $entity->$key;
} else {
$id = (int) $entity;
}
// If we have an ID, then delete the post.
if ( is_integer( $id ) ) {
// TODO: We assume that we can skip the trash. Is that correct?
// FYI, Deletes postmeta as wells.
if ( is_object( wp_delete_post( $id, true ) ) ) {
$retval = true;
}
}
return $retval;
}
/**
* Saves an entity to the database
*
* @param object $entity
* @return int Post ID
*/
public function save_entity( $entity ) {
$post = $this->_convert_entity_to_post( $entity );
$primary_key = $this->get_primary_key_column();
// TODO: unsilence this. WordPress 3.9-beta2 is generating an error that should be corrected before its final release.
if ( ( $post_id = @wp_insert_post( $post ) ) ) {
$new_entity = $this->find( $post_id, true );
if ( $new_entity ) {
foreach ( get_object_vars( $new_entity ) as $key => $value ) {
$entity->$key = $value;
}
}
// Save properties as post meta.
$this->_flush_and_update_postmeta( $post_id, $entity );
$entity->$primary_key = $post_id;
// Clean cache.
$this->cache = [];
}
$entity->id_field = $primary_key;
return $post_id;
}
/**
* Starts a new DELETE statement.
*/
public function delete() {
$this->select();
$this->query_args['is_select'] = false;
$this->query_args['is_delete'] = true;
return $this;
}
/**
* Returns the title of the post. Used when post_title is not set
*
* @param \stdClass $entity
* @return string
*/
public function get_post_title( $entity ) {
return "Untitled {$this->get_object_name()}";
}
/**
* Returns the excerpt of the post. Used when post_excerpt is not set
*
* @param \stdClass $entity
* @return string
*/
public function get_post_excerpt( $entity ) {
return '';
}
}
File Manager Version 1.0, Coded By Lucas
Email: hehe@yahoo.com