You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
393 lines
12 KiB
393 lines
12 KiB
9 months ago
|
<?php
|
||
|
/**
|
||
|
* Post Model
|
||
|
*
|
||
|
* @package TablePress
|
||
|
* @subpackage Models
|
||
|
* @author Tobias Bäthge
|
||
|
* @since 1.0.0
|
||
|
*/
|
||
|
|
||
|
// Prohibit direct script loading.
|
||
|
defined( 'ABSPATH' ) || die( 'No direct script access allowed!' );
|
||
|
|
||
|
/**
|
||
|
* Post Model class
|
||
|
*
|
||
|
* @package TablePress
|
||
|
* @subpackage Models
|
||
|
* @author Tobias Bäthge
|
||
|
* @since 1.0.0
|
||
|
*/
|
||
|
class TablePress_Post_Model extends TablePress_Model {
|
||
|
|
||
|
/**
|
||
|
* Name of the "Custom Post Type" for the tables.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $post_type = 'tablepress_table';
|
||
|
|
||
|
/**
|
||
|
* Init the model by registering the Custom Post Type.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*/
|
||
|
public function __construct() {
|
||
|
parent::__construct();
|
||
|
$this->_register_post_type(); // We are on WP "init" hook already.
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register the Custom Post Type which the tables use.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*/
|
||
|
protected function _register_post_type(): void {
|
||
|
/**
|
||
|
* Filters the "Custom Post Type" that TablePress uses for storing tables in the database.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param string $post_type The "Custom Post Type" that TablePress uses.
|
||
|
*/
|
||
|
$this->post_type = apply_filters( 'tablepress_post_type', $this->post_type );
|
||
|
$post_type_args = array(
|
||
|
'labels' => array(
|
||
|
'name' => 'TablePress Tables',
|
||
|
),
|
||
|
'public' => false,
|
||
|
'show_ui' => false,
|
||
|
'query_var' => false,
|
||
|
'rewrite' => false,
|
||
|
'capability_type' => 'tablepress_table', // This ensures, that WP's regular CPT UI respects our capabilities.
|
||
|
'map_meta_cap' => false, // Integrated WP mapping does not fit our needs, therefore use our own in a filter.
|
||
|
'supports' => array( 'title', 'editor', 'excerpt', 'revisions' ),
|
||
|
'can_export' => true,
|
||
|
);
|
||
|
/**
|
||
|
* Filters the arguments for the registration of the "Custom Post Type" that TablePress uses.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param array<string, mixed> $post_type_args Arguments for the registration of the TablePress "Custom Post Type".
|
||
|
*/
|
||
|
$post_type_args = apply_filters( 'tablepress_post_type_args', $post_type_args );
|
||
|
register_post_type( $this->post_type, $post_type_args );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Insert a post with the correct Custom Post Type and default values in the the wp_posts table in the database.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param array<string, mixed> $post Post to insert.
|
||
|
* @return int|WP_Error Post ID of the inserted post on success, WP_Error on error.
|
||
|
*/
|
||
|
public function insert( array $post ) /* : int|WP_Error */ {
|
||
|
$default_post = array(
|
||
|
'ID' => false, // false on new insert, but existing post ID on update.
|
||
|
'comment_status' => 'closed',
|
||
|
'ping_status' => 'closed',
|
||
|
'post_category' => false,
|
||
|
'post_content' => '',
|
||
|
'post_excerpt' => '',
|
||
|
'post_parent' => 0,
|
||
|
'post_password' => '',
|
||
|
'post_status' => 'publish',
|
||
|
'post_title' => '',
|
||
|
'post_type' => $this->post_type,
|
||
|
'tags_input' => '',
|
||
|
'to_ping' => '',
|
||
|
);
|
||
|
$post = array_merge( $default_post, $post );
|
||
|
// WP expects everything to be slashed.
|
||
|
$post = wp_slash( $post );
|
||
|
|
||
|
// Remove balanceTags() from sanitize_post(), as it can destroy the JSON when messing with HTML.
|
||
|
remove_filter( 'content_save_pre', 'balanceTags', 50 );
|
||
|
remove_filter( 'excerpt_save_pre', 'balanceTags', 50 );
|
||
|
|
||
|
/*
|
||
|
* Remove possible KSES filtering, as it can destroy the JSON when messing with HTML.
|
||
|
* KSES filtering is done to table cells individually, when saving.
|
||
|
*/
|
||
|
$has_kses = ( false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' ) );
|
||
|
if ( $has_kses ) {
|
||
|
kses_remove_filters();
|
||
|
}
|
||
|
|
||
|
// Remove filter that adds `rel="noopener" to <a> HTML tags, but destroys JSON code. See https://core.trac.wordpress.org/ticket/46316.
|
||
|
$has_targeted_link_rel_filters = ( false !== has_filter( 'content_save_pre', 'wp_targeted_link_rel' ) );
|
||
|
if ( $has_targeted_link_rel_filters ) {
|
||
|
wp_remove_targeted_link_rel_filters();
|
||
|
}
|
||
|
|
||
|
$post_id = wp_insert_post( $post, true );
|
||
|
|
||
|
// Restore removed content filters.
|
||
|
add_filter( 'content_save_pre', 'balanceTags', 50 );
|
||
|
add_filter( 'excerpt_save_pre', 'balanceTags', 50 );
|
||
|
if ( $has_kses ) {
|
||
|
kses_init_filters();
|
||
|
}
|
||
|
if ( $has_targeted_link_rel_filters ) {
|
||
|
wp_init_targeted_link_rel_filters();
|
||
|
}
|
||
|
|
||
|
// In rare cases, `wp_insert_post()` returns 0 as the post ID, when an error happens, so it's converted to a WP_Error here.
|
||
|
if ( 0 === $post_id ) { // @phpstan-ignore-line (False-positive in the PHPStan WordPress stubs.)
|
||
|
return new WP_Error( 'post_insert', '' );
|
||
|
}
|
||
|
|
||
|
return $post_id;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update an existing post with the correct Custom Post Type and default values in the the wp_posts table in the database.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param array<string, mixed> $post Post.
|
||
|
* @return int|WP_Error Post ID of the updated post on success, WP_Error on error.
|
||
|
*/
|
||
|
public function update( array $post ) /* : int|WP_Error */ {
|
||
|
$default_post = array(
|
||
|
'ID' => false, // false on new insert, but existing post ID on update.
|
||
|
'comment_status' => 'closed',
|
||
|
'ping_status' => 'closed',
|
||
|
'post_category' => false,
|
||
|
'post_content' => '',
|
||
|
'post_excerpt' => '',
|
||
|
'post_parent' => 0,
|
||
|
'post_password' => '',
|
||
|
'post_status' => 'publish',
|
||
|
'post_title' => '',
|
||
|
'post_type' => $this->post_type,
|
||
|
'tags_input' => '',
|
||
|
'to_ping' => '',
|
||
|
);
|
||
|
$post = array_merge( $default_post, $post );
|
||
|
// WP expects everything to be slashed.
|
||
|
$post = wp_slash( $post );
|
||
|
|
||
|
// Remove balanceTags() from sanitize_post(), as it can destroy the JSON when messing with HTML.
|
||
|
remove_filter( 'content_save_pre', 'balanceTags', 50 );
|
||
|
remove_filter( 'excerpt_save_pre', 'balanceTags', 50 );
|
||
|
|
||
|
/*
|
||
|
* Remove possible KSES filtering, as it can destroy the JSON when messing with HTML.
|
||
|
* KSES filtering is done to table cells individually, when saving.
|
||
|
*/
|
||
|
$has_kses = ( false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' ) );
|
||
|
if ( $has_kses ) {
|
||
|
kses_remove_filters();
|
||
|
}
|
||
|
|
||
|
// Remove filter that adds `rel="noopener" to <a> HTML tags, but destroys JSON code. See https://core.trac.wordpress.org/ticket/46316.
|
||
|
$has_targeted_link_rel_filters = ( false !== has_filter( 'content_save_pre', 'wp_targeted_link_rel' ) );
|
||
|
if ( $has_targeted_link_rel_filters ) {
|
||
|
wp_remove_targeted_link_rel_filters();
|
||
|
}
|
||
|
|
||
|
$post_id = wp_update_post( $post, true );
|
||
|
|
||
|
// Restore removed content filters.
|
||
|
add_filter( 'content_save_pre', 'balanceTags', 50 );
|
||
|
add_filter( 'excerpt_save_pre', 'balanceTags', 50 );
|
||
|
if ( $has_kses ) {
|
||
|
kses_init_filters();
|
||
|
}
|
||
|
if ( $has_targeted_link_rel_filters ) {
|
||
|
wp_init_targeted_link_rel_filters();
|
||
|
}
|
||
|
|
||
|
return $post_id;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a post from the wp_posts table in the database.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param int $post_id Post ID.
|
||
|
* @return WP_Post|false Post on success, false on error.
|
||
|
*/
|
||
|
public function get( int $post_id ) /* : WP_Post|false */ {
|
||
|
$post = get_post( $post_id );
|
||
|
if ( is_null( $post ) ) {
|
||
|
return false;
|
||
|
}
|
||
|
return $post;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delete a post (and all revisions) from the wp_posts table in the database.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param int $post_id Post ID.
|
||
|
* @return WP_Post|false|null Post data on success, false or null on failure.
|
||
|
*/
|
||
|
public function delete( int $post_id ) /* : WP_Post|false|null */ {
|
||
|
return wp_delete_post( $post_id, true ); // true means force delete, although for CPTs this is automatic in this function.
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Move a post to the trash (if trash is globally enabled), instead of directly deleting the post.
|
||
|
* (yet unused)
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param int $post_id Post ID.
|
||
|
* @return WP_Post|false|null Post data on success, false or null on failure.
|
||
|
*/
|
||
|
public function trash( int $post_id ) /* : WP_Post|false|null */ {
|
||
|
return wp_trash_post( $post_id );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Restore a post from the trash.
|
||
|
* (yet unused)
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param int $post_id Post ID.
|
||
|
* @return WP_Post|false|null Post on success, false or null on error.
|
||
|
*/
|
||
|
public function untrash( int $post_id ) /* : WP_Post|false */ {
|
||
|
return wp_untrash_post( $post_id );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Load all posts with one query, to prime the cache.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @global wpdb $wpdb WordPress database abstraction object.
|
||
|
* @see get_post()
|
||
|
*
|
||
|
* @param int[] $all_post_ids List of Post IDs.
|
||
|
* @param bool $update_meta_cache Optional. Whether to update the Post Meta Cache (for table options and visibility).
|
||
|
*/
|
||
|
public function load_posts( array $all_post_ids, bool $update_meta_cache = true ): void {
|
||
|
global $wpdb;
|
||
|
|
||
|
// Split post loading, to save memory.
|
||
|
$offset = 0;
|
||
|
$length = 100; // 100 posts at a time
|
||
|
$number_of_posts = count( $all_post_ids );
|
||
|
while ( $offset < $number_of_posts ) {
|
||
|
$post_ids = array_slice( $all_post_ids, $offset, $length );
|
||
|
// Don't load posts that are in the cache already.
|
||
|
$post_ids = _get_non_cached_ids( $post_ids, 'posts' );
|
||
|
if ( ! empty( $post_ids ) ) {
|
||
|
$post_ids_list = implode( ',', $post_ids );
|
||
|
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||
|
$posts = $wpdb->get_results( "SELECT {$wpdb->posts}.* FROM {$wpdb->posts} WHERE ID IN ({$post_ids_list})" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||
|
update_post_cache( $posts );
|
||
|
if ( $update_meta_cache ) {
|
||
|
// Get all post meta data for all table posts, @see get_post_meta().
|
||
|
update_meta_cache( 'post', $post_ids );
|
||
|
}
|
||
|
}
|
||
|
$offset += $length; // next array_slice() $offset.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Count the number of posts with the model's CPT in the wp_posts table in the database.
|
||
|
* (currently for debug only)
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @return int Number of posts.
|
||
|
*/
|
||
|
public function count_posts(): int {
|
||
|
return array_sum( (array) wp_count_posts( $this->post_type ) ); // Original return value is object with the counts for each post_status.
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a post meta field to a post.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param int $post_id ID of the post for which the field shall be added.
|
||
|
* @param string $field Name of the post meta field.
|
||
|
* @param string $value Value of the post meta field (not slashed).
|
||
|
* @return bool True on success, false on error.
|
||
|
*/
|
||
|
public function add_meta_field( int $post_id, string $field, string $value ): bool {
|
||
|
// WP expects a slashed value.
|
||
|
$value = wp_slash( $value );
|
||
|
$success = add_post_meta( $post_id, $field, $value, true ); // true means unique.
|
||
|
// Make sure that $success is a boolean, as add_post_meta() returns an ID or false.
|
||
|
$success = ( false === $success ) ? false : true;
|
||
|
return $success;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update the value of a post meta field of a post.
|
||
|
*
|
||
|
* If the field does not yet exist, it is added.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param int $post_id ID of the post for which the field shall be updated.
|
||
|
* @param string $field Name of the post meta field.
|
||
|
* @param string $value Value of the post meta field (not slashed).
|
||
|
* @return bool True on success, false on error.
|
||
|
*/
|
||
|
public function update_meta_field( int $post_id, string $field, string $value ): bool {
|
||
|
$prev_value = (string) get_post_meta( $post_id, $field, true );
|
||
|
// No need to update, if values are equal (also, update_post_meta() would return false for this).
|
||
|
if ( $prev_value === $value ) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// WP expects a slashed value.
|
||
|
$value = wp_slash( $value );
|
||
|
return (bool) update_post_meta( $post_id, $field, $value, $prev_value );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the value of a post meta field of a post.
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param int $post_id ID of the post for which the field shall be retrieved.
|
||
|
* @param string $field Name of the post meta field.
|
||
|
* @return string Value of the meta field.
|
||
|
*/
|
||
|
public function get_meta_field( int $post_id, string $field ): string {
|
||
|
return get_post_meta( $post_id, $field, true ); // true means single value.
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delete a post meta field of a post.
|
||
|
* (yet unused)
|
||
|
*
|
||
|
* @since 1.0.0
|
||
|
*
|
||
|
* @param int $post_id ID of the post of which the field shall be deleted.
|
||
|
* @param string $field Name of the post meta field.
|
||
|
* @return bool True on success, false on error.
|
||
|
*/
|
||
|
public function delete_meta_field( int $post_id, string $field ): bool {
|
||
|
return delete_post_meta( $post_id, $field, true ); // true means single value.
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the Custom Post Type that TablePress uses.
|
||
|
*
|
||
|
* @since 1.5.0
|
||
|
*
|
||
|
* @return string The used Custom Post Type.
|
||
|
*/
|
||
|
public function get_post_type(): string {
|
||
|
return $this->post_type;
|
||
|
}
|
||
|
|
||
|
} // class TablePress_Post_Model
|