LLMS_Post_Model
LLMS_Post_Model abstract class.
Description Description
Defines base methods and properties for programmatically interfacing with LifterLMS Custom Post Types.
Source Source
File: includes/abstracts/abstract.llms.post.model.php
abstract class LLMS_Post_Model implements JsonSerializable { /** * Name of the post type as stored in the database * This will be prefixed (where applicable) * ie: "llms_order" for the "llms_order" post type * * @var string * @since 3.0.0 */ protected $db_post_type; /** * WP Post ID * * @var int * @since 3.0.0 */ protected $id; /** * Define this in extending classes * * Allows models to use unprefixed post type names for filters and more * ie: "order" for the "llms_order" post type. * * @var string * @since 3.0.0 */ protected $model_post_type; /** * A prefix to add to all meta properties * * Child classes can redefine this. * * @var string * @since 3.0.0 */ protected $meta_prefix = '_llms_'; /** * Instance of WP_Post * * @var WP_Post * @since 3.0.0 */ protected $post; /** * Array of meta properties and their property type * * @var array * @since 3.3.0 */ protected $properties = array(); /** * Array of default property values * * In the form of key => default value. * * @var array * @since 3.24.0 */ protected $property_defaults = array(); /** * Constructor * * Setup ID and related post property. * * @since 3.0.0 * @since 3.13.0 Unknown. * * @param string|int|LLMS_Post_Model|WP_Post $model 'new', WP post id, instance of an extending class, instance of WP_Post. * @param array $args Args to create the post, only applies when $model is 'new'. * @return void */ public function __construct( $model, $args = array() ) { if ( 'new' === $model ) { $model = $this->create( $args ); if ( ! is_wp_error( $model ) ) { $created = true; } } else { $created = false; } if ( empty( $model ) || is_wp_error( $model ) ) { return; } if ( is_numeric( $model ) ) { $this->id = absint( $model ); $this->post = get_post( $this->id ); } elseif ( is_subclass_of( $model, 'LLMS_Post_Model' ) ) { $this->id = absint( $model->id ); $this->post = $model->post; } elseif ( $model instanceof WP_Post && isset( $model->ID ) ) { $this->id = absint( $model->ID ); $this->post = $model; } if ( $created ) { $this->after_create(); } } /** * Magic Getter * * @since 3.0.0 * * @param string $key Key to retrieve. * @return mixed */ public function __get( $key ) { return $this->___get( $key ); } /** * Magic Isset * * @since 3.0.0 * * @param string $key Check if a key exists in the database. * @return boolean */ public function __isset( $key ) { return metadata_exists( 'post', $this->id, $this->meta_prefix . $key ); } /** * Magic Setter * * @since 3.0.0 * * @param string $key Key of the property. * @param mixed $val Value to set the property with. * @return void */ public function __set( $key, $val ) { $this->$key = $val; } /** * Allow extending classes to add custom meta properties to the object * * @since 3.16.0 * * @param array $props Key val array of prop key => prop type (see $this->properties). */ protected function add_properties( $props = array() ) { $this->properties = array_merge( $this->properties, $props ); } /** * Modify allowed post tags for wp_kses for this post * * @since 3.19.2 * * @return void */ protected function allowed_post_tags_set() { global $allowedposttags; $allowedposttags['iframe'] = array( 'allowfullscreen' => true, 'frameborder' => true, 'height' => true, 'src' => true, 'width' => true, ); } /** * Remove modified allowed post tags for wp_kses for this post * * @since 3.19.2 * * @return void */ protected function allowed_post_tags_unset() { global $allowedposttags; unset( $allowedposttags['iframe'] ); } /** * Wrapper for $this-get() which allows translation of the database value before outputting on screen * * Extending classes should define this and translate any possible strings * with a switch statement or something. * This will return the untranslated string if a translation isn't defined. * * @since 3.0.0 * * @param string $key Key to retrieve. * @return string */ public function translate( $key ) { $val = $this->get( $key ); // ******* example ******* // switch( $key ) { // case 'example_key': // if ( 'example-val' === $val ) { // return translate( 'Example Key', 'lifterlms' ); // } // break; // default: // return $val; // } // ******* example ******* return $val; } /** * Wrapper for the $this->translate() that echos the result rather than returning it * * @since 3.0.0 * * @param string $key Key to translate. * @return void */ public function _e( $key ) { // phpcs:ignore -- This is to mimic localization functions. echo $this->translate( $key ); } /** * Called immediately after creating / inserting a new post into the database * * This stub can be overwritten by child classes. * * @since 3.0.0 * * @return void */ protected function after_create() {} /** * Create a new post of the Instantiated Model * * This can be called by instantiating an instance with "new" * as the value passed to the constructor. * * @since 3.0.0 * @since 3.30.3 Use `wp_slash()` for the post title. * * @param string $title Title to create the post with. * @return int WP Post ID of the new Post on success or 0 on error. */ private function create( $title = '' ) { return wp_insert_post( wp_slash( /** * Filters the creation arguments used to create a new post. * * The return array is passed through {@see wp_slash} and ultimately * passed directly to {@see wp_insert_post}. * * The dynamic portion of this hook, `{$this->model_post_type}`, refers to the post * model's `$model_post_type` property. * * @since 3.0.0 * * @param array $creation_args An array of arguments passed. */ apply_filters( "llms_new_{$this->model_post_type}", $this->get_creation_args( $title ) ) ), true ); } /** * Clones the Post if the post is cloneable * * @since 3.3.0 * @since 4.7.0 Use `LLMS_Generator::get_generated_content()` in favor of deprecated `LLMS_Generator::get_generated_posts()`. * * @return WP_Error|int|null WP_Error, WP Post ID of the clone (new) post, or null if post is not cloneable. */ public function clone_post() { // If post type doesn't support cloning, don't proceed. if ( ! $this->is_cloneable() ) { return null; } $this->allowed_post_tags_set(); $generator = new LLMS_Generator( $this->toArray() ); $generator->set_generator( 'LifterLMS/Single' . ucwords( $this->model_post_type ) . 'Cloner' ); if ( ! $generator->is_error() ) { $generator->generate(); } $this->allowed_post_tags_unset(); $generated = $generator->get_generated_content(); if ( isset( $generated[ $this->db_post_type ] ) ) { return $generated[ $this->db_post_type ][0]; } return new WP_Error( 'generator-error', __( 'An unknown error occurred during post cloning. Please try again.', 'lifterlms' ) ); } /** * Trigger an export download of the given post type * * @since 3.3.0 * @since 3.19.2 Unknown. * @since 4.8.0 Made sure extra data are added to the posts model array representation during export. * * @return void */ public function export() { // If post type doesn't support exporting don't proceed. if ( ! $this->is_exportable() ) { return; } $title = str_replace( ' ', '-', $this->get( 'title' ) ); $title = preg_replace( '/[^a-zA-Z0-9-]/', '', $title ); /** * Filters the export file name * * @since Unknown * * @param string $title The exported file name. Doesn't include the extension. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ $filename = apply_filters( 'llms_post_model_export_filename', $title . '_' . current_time( 'Ymd' ), $this ); header( 'Content-type: application/json' ); header( 'Content-Disposition: attachment; filename="' . $filename . '.json"' ); header( 'Pragma: no-cache' ); header( 'Expires: 0' ); $this->allowed_post_tags_set(); add_filter( 'llms_post_model_to_array_add_extras', '__return_true', 99 ); $arr = $this->toArray(); remove_filter( 'llms_post_model_to_array_add_extras', '__return_true', 99 ); $arr['_generator'] = 'LifterLMS/Single' . ucwords( $this->model_post_type ) . 'Exporter'; $arr['_source'] = get_site_url(); $arr['_version'] = llms()->version; ksort( $arr ); echo json_encode( $arr ); $this->allowed_post_tags_unset(); die(); } /** * Private getter. * * @since 3.34.0 * @since 4.10.0 Add `post_name` as a property to skip scrubbing and add a filter on the list of properties to skip scrubbing. * @since 5.1.2 Pass second parameter to the `get_the_excerpt` filter hook (the WP_Post object), introduced in WordPress 4.5.0. * * @param string $key The property key. * @param boolean $raw Optional. Whether or not we need to get the raw value. Default false. * @return mixed */ private function ___get( $key, $raw = false ) { // Force numeric id and prevent filtering on the id. if ( 'id' === $key ) { return absint( $this->$key ); } elseif ( in_array( $key, array_keys( $this->get_post_properties() ) ) ) { $post_key = 'post_' . $key; // Ensure post is set globally for filters below. global $post; $temp = $post; $post = $this->post; switch ( $key ) { case 'content': $val = $raw ? $this->post->$post_key : llms_content( $this->post->$post_key ); break; case 'excerpt': /* This is a WordPress filter. */ $val = $raw ? $this->post->$post_key : apply_filters( 'get_the_excerpt', $this->post->$post_key, $this->post ); break; case 'ping_status': case 'comment_status': case 'menu_order': $val = $this->post->$key; break; case 'title': /* This is a WordPress filter. */ $val = $raw ? $this->post->$post_key : apply_filters( 'the_title', $this->post->$post_key, $this->get( 'id' ) ); break; default: $val = $this->post->$post_key; } // Return the original global. $post = $temp; } elseif ( ! in_array( $key, $this->get_unsettable_properties() ) ) { if ( metadata_exists( 'post', $this->id, $this->meta_prefix . $key ) ) { $val = get_post_meta( $this->id, $this->meta_prefix . $key, true ); } else { $val = $this->get_default_value( $key ); } } else { return $this->$key; } // If we found a value, apply default llms get filter and return the value. if ( isset( $val ) ) { /** * Filters the list of properties which should be excluded from scrubbing during a property read. * * The dynamic portion of this hook, `{$this->model_post_type}`, refers to the post's model type, * for example "course" for an `LLMS_Course`, "membership" for an `LLMS_Membership`, etc... * * @since 4.10.0 * * @param string[] $props An array of property keys to be excluded from scrubbing. * @param LLMS_Post_Model $this Instance of the post object. */ $exclude = apply_filters( "llms_get_{$this->model_post_type}_no_scrub_props", array( 'content', 'name' ), $this ); if ( ! $raw && ! in_array( $key, $exclude, true ) ) { $val = $this->scrub( $key, $val ); } /** * Filters the property value * * The first dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * The second dynamic part of this hook, `$key`, refers to the property name. * * @since Unknown * * @param mixed $val The property value. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ return apply_filters( "llms_get_{$this->model_post_type}_{$key}", $val, $this ); } // Shouldn't ever get here. return false; } /** * Getter * * @since 3.0.0 * * @param string $key The property key. * @param boolean $raw Optional. Whether or not we need to get the raw value. Default is `false`. * @return mixed */ public function get( $key, $raw = false ) { if ( $raw ) { return $this->___get( $key, $raw ); } return $this->$key; } /** * Getter for array values * * Ensures that even empty values return an array. * * @since 3.0.0 Unknown. * * @param string $key Property key. * @return array */ public function get_array( $key ) { $val = $this->get( $key ); if ( ! is_array( $val ) ) { $val = array( $val ); } return $val; } /** * Getter for date strings with optional date format conversion * * If no format is supplied, the default format available via $this->get_date_format() will be used. * * @since 3.0.0 * * @param string $key Property key. * @param string $format Any valid date format that can be passed to date(). * @return string */ public function get_date( $key, $format = null ) { $format = ( ! $format ) ? $this->get_date_format() : $format; $raw = $this->get( $key ); // Only convert the date if we actually have something stored, otherwise we'll return the current date, which we probably aren't expecting. $date = $raw ? date_i18n( $format, strtotime( $raw ) ) : ''; /** * Filters the date(s) * * The first dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * The second dynamic part of this hook, `$key`, refers to the date property name. * * @since 3.0.0 * * @param string $date The formatted date. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ return apply_filters( "llms_get_{$this->model_post_type}_{$key}_date", $date, $this ); } /** * Retrieve the default date format for the post model * * This *can* be overridden by child classes if the post type requires a different default date format. * * If no format is supplied by the child class, the default WP date & time formats available * via General Settings will be combined and used. * * @since 3.0.0 * * @return string */ protected function get_date_format() { $format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' ); /** * Filters the date format * * The dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * * @since 3.0.0 * * @param string $format The date format. */ return apply_filters( "llms_get_{$this->model_post_type}_date_format", $format ); } /** * Get the default value of a property * * If defaults don't exist returns an empty string in accordance with the return of get_post_meta() when no metadata exists. * * @since 3.24.0 * * @param string $key Property key/name. * @return mixed */ public function get_default_value( $key ) { $defaults = $this->get_property_defaults(); return isset( $defaults[ $key ] ) ? $defaults[ $key ] : ''; } /** * Retrieve URL for an image associated with the post * * Currently, only retrieves the featured image if the post type supports it. * In the future, this will allow retrieval of custom post images as well. * * @since 3.3.0 * @since 3.8.0 Unknown. * * @param string|array $size Registered image size or a numeric array with width/height. * @param string $key Currently unused but here for forward compatibility if * additional custom images are added * @return string Empty string if no image or not supported. */ public function get_image( $size = 'full', $key = '' ) { if ( 'thumbnail' === $key && post_type_supports( $this->db_post_type, 'thumbnail' ) ) { $url = get_the_post_thumbnail_url( $this->get( 'id' ), $size ); } else { $id = $this->get( $key ); if ( is_numeric( $id ) ) { $src = wp_get_attachment_image_src( $id, $size ); if ( $src ) { $url = $src[0]; } } } return ! empty( $url ) ? $url : ''; } /** * Retrieve the Post's post type data object * * @since 3.0.0 * * @return WP_Post_Type|null */ public function get_post_type_data() { return get_post_type_object( $this->get( 'type' ) ); } /** * Retrieve a label from the post type data object's labels object * * @since 3.0.0 * @since 3.8.0 Unknown. * * @param string $label Key for the label. * @return string */ public function get_post_type_label( $label = 'singular_name' ) { $obj = $this->get_post_type_data(); if ( property_exists( $obj, 'labels' ) && property_exists( $obj->labels, $label ) ) { return $obj->labels->$label; } return ''; } /** * Getter for price strings with optional formatting options * * @since 3.0.0 * @since 3.7.0 Unknown. * @since 4.8.0 Use strict type comparison where possible. * * @param string $key Property key. * @param array $price_args Optional. Array of arguments that can be passed to llms_price(). Default is empty array. * @param string $format Optional. Format conversion method [html|raw|float]. Default is 'html'. * @return mixed */ public function get_price( $key, $price_args = array(), $format = 'html' ) { $price = $this->get( $key ); // Handle empty or unset values gracefully. if ( '' === $price ) { $price = 0; } if ( 'html' === $format || 'raw' === $format ) { $price = llms_price( $price, $price_args ); if ( 'raw' === $format ) { $price = strip_tags( $price ); } } elseif ( 'float' === $format ) { $price = floatval( number_format( $price, get_lifterlms_decimals(), '.', '' ) ); } else { /** * Allows applying custom formatting to price(s). * * This is only fired when the `get_price()`'s `$format` passed param is not one of html|raw|float. * * @since Unknown * * The first dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * The second dynamic part of this hook, `$key`, refers to the price property name. * The third dynamic part of this hook, `$format`, refers to the custom format conversion method. */ $price = apply_filters( "llms_get_{$this->model_post_type}_{$key}_{$format}", $price, $key, $price_args, $format, $this ); } /** * Filters the price(s) * * The first dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * The second dynamic part of this hook, `$key`, refers to the price property name. * * @since Unknown * * @param string $price The maybe formatted price. * @param string $key The price property name. * @param array $price_args Array of arguments that can be passed to llms_price(). * @param string $format Format conversion method. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ return apply_filters( "llms_get_{$this->model_post_type}_{$key}_price", $price, $key, $price_args, $format, $this ); } /** * Retrieve the default values for properties * * @since 3.24.0 * * @return array */ public function get_property_defaults() { /** * Filters the defaults properties. * * The dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * * @since 3.24.0 * * @param array $property_defaults Array of default property values. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ return apply_filters( "llms_get_{$this->model_post_type}_property_defaults", $this->property_defaults, $this ); } /** * An array of default arguments to pass to $this->create() when creating a new post * * This *should* be overridden by child classes. * * @since 3.0.0 * @since 3.18.0 Unknown. * * @param array $args Args of data to be passed to wp_insert_post. * @return array */ protected function get_creation_args( $args = null ) { // Allow nothing to be passed in. if ( empty( $args ) ) { $args = array(); } // Backwards compat to original 3.0.0 format when just a title was passed in. if ( is_string( $args ) ) { $args = array( 'post_title' => $args, ); } $args = wp_parse_args( $args, array( 'comment_status' => 'closed', 'ping_status' => 'closed', 'post_author' => get_current_user_id(), 'post_content' => '', 'post_excerpt' => '', 'post_status' => 'draft', 'post_title' => '', 'post_type' => $this->get( 'db_post_type' ), ) ); /** * Filters the llms post creation args * * The dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * * @since 3.24.0 * * @param array $args Array of default creation args to be passed to `wp_insert_post()`. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ return apply_filters( "llms_{$this->model_post_type}_get_creation_args", $args, $this ); } /** * Get media embeds * * @since 3.17.0 * @since 3.17.5 Unknown. * * @param string $type Optional. Embed type [video|audio]. Default is 'video'. * @param string $prop Optional. Postmeta property name. Default is empty string. * If not supplied it will default to {$type}_embed. * @return string */ protected function get_embed( $type = 'video', $prop = '' ) { $ret = ''; $prop = $prop ? $prop : $type . '_embed'; $url = $this->get( $prop ); if ( $url ) { $ret = wp_oembed_get( $url ); if ( ! $ret ) { $ret = do_shortcode( sprintf( '[%1$s src="%2$s"]', $type, $url ) ); } } /** * Filters the embed html * * The first dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * The second dynamic portion of this hook, `$type`, refers to the embed type [video|audio]. * * @since Unknown * * @param array $embed The embed html. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. * @param string $type Embed type [video|audio]. * @param string $prop Postmeta property name. */ return apply_filters( "llms_{$this->model_post_type}_{$type}", $ret, $this, $type, $prop ); } /** * Get a property's data type for scrubbing * * Used by $this->scrub() to determine how to scrub the property. * * @since 3.3.0 * * @param string $key Property key. * @return string */ protected function get_property_type( $key ) { $props = $this->get_properties(); // Check against the properties array. if ( in_array( $key, array_keys( $props ) ) ) { $type = $props[ $key ]; } else { $type = 'text'; } return $type; } /** * Retrieve an array of post properties * * These properties need to be get/set with alternate methods. * * @since 3.0.0 * @since 3.31.0 Treat excerpts as HTML instead of plain text. * @since 3.34.0 Add date and modified dates GMT version, comment and ping status, post password and menu_order. * * @return array */ protected function get_post_properties() { /** * Filters the properties of the model that are properties of WP_Post. * * @since Unknown * * @param array $post_properties Associative array of the type $post_property_name => type. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ return apply_filters( 'llms_post_model_get_post_properties', array( 'author' => 'absint', 'content' => 'html', 'date' => 'text', 'date_gmt' => 'text', 'excerpt' => 'html', 'password' => 'text', 'parent' => 'absint', 'menu_order' => 'absint', 'modified' => 'text', 'modified_gmt' => 'text', 'name' => 'text', 'status' => 'text', 'title' => 'text', 'type' => 'text', 'comment_status' => 'text', 'ping_status' => 'text', ), $this ); } /** * Retrieve an array of properties defined by the model * * @since 3.3.0 * @since 3.16.0 Unknown. * * @return array */ public function get_properties() { $props = array_merge( $this->get_post_properties(), $this->properties ); /** * Filters the llms post properties * * The dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * * @since Unknown * * @param array $properties Array of properties defined by the model * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ return apply_filters( "llms_get_{$this->model_post_type}_properties", $props, $this ); } /** * Get the properties that will be used to generate the array representation of the model. * * @since 5.4.1 * * @return string[] Array of property keys to be used by {@see toArray}. */ protected function get_to_array_properties() { $all_props = array_keys( $this->get_properties() ); /** * Filters the properties which will excluded form the array representation of the model * * The dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * * @since Unknown * * @param string[] $excluded Array of property names. * @param string[] $all_props The full property list without the applied exclusions. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ $excluded = apply_filters( "llms_get_{$this->model_post_type}_excluded_to_array_properties", $this->get_to_array_excluded_properties(), $all_props, $this ); $props = array_diff( $all_props, $excluded ); /** * Filters the properties which will populate the array representation of the model. * * The dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * * @since Unknown * * @param string[] $props Array of property names. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ return apply_filters( "llms_get_{$this->model_post_type}_to_array_properties", $props, $this ); } /** * Get the properties that will be explicitly excluded from the array representation of the model. * * This stub can be overloaded by an extending class and the property list is filterable via the * {@see llms_get_{$this->model_post_type}_excluded_to_array_properties} filter. * * @since 5.4.1 * * @return string[] */ protected function get_to_array_excluded_properties() { return array(); } /** * Retrieve the registered Label of the post's current status * * @since 3.0.0 * * @return string */ public function get_status_name() { $obj = get_post_status_object( $this->get( 'status' ) ); /** * Filters the registered label of the post's current status. * * The dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * * @since 3.0.0 * * @param string $label The registered label of the post's current status. */ return apply_filters( "llms_get_{$this->model_post_type}_status_name", $obj->label ); } /** * Get an array of terms for a given taxonomy for the post * * @since 3.8.0 * * @param string $tax Taxonomy name. * @param boolean $single Return only one term as an int, useful for taxes which * Can only have one term (eg: visibilities and difficulties and such) * @return mixed When single a single term object or null. * When not single an array of term objects. */ public function get_terms( $tax, $single = false ) { $terms = get_the_terms( $this->get( 'id' ), $tax ); if ( $single ) { return $terms ? $terms[0] : null; } return $terms ? $terms : array(); } /** * Array of properties which *cannot* be set * * If a child class adds any properties which should not be settable * the class should override this property and add their custom * properties to the array. * * @since 3.0.0 * * @return array */ protected function get_unsettable_properties() { /** * Filters the properties of the model that *cannot* be set * * @since Unknown * * @param array $unsettable_properties Array of property names. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ return apply_filters( 'llms_post_model_get_unsettable_properties', array( 'db_post_type', 'id', 'meta_prefix', 'model_post_type', 'post', ), $this ); } /** * Determine if the associated post is exportable * * @since 3.3.0 * * @return boolean */ public function is_cloneable() { return post_type_supports( $this->db_post_type, 'llms-clone-post' ); } /** * Determine if the associated post is exportable * * @since 3.3.0 * * @return boolean */ public function is_exportable() { return post_type_supports( $this->db_post_type, 'llms-export-post' ); } /** * Format the object for json serialization * * Encodes the results of $this->toArray(). * * @todo The `mixed` return type declared by the parent method, which should be defined here as well, * is not available until PHP 8.0. Once support is dropped for 7.4 we can add the return type declaration * and remove the `#[ReturnTypeWillChange]` attribute. This *must* happen before the release of PHP 9.0. * * @since 3.3.0 * * @return array */ #[ReturnTypeWillChange] public function jsonSerialize() { /** * Filters the properties of the model that *cannot* be set * * @since 3.3.0 * * @param array $model Array representation of the LLMS_Post_Model object. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ return apply_filters( 'llms_post_model_json_serialize', $this->toArray(), $this ); } /** * Scrub field according to it's type * * This is automatically called by set() method before anything is actually set. * * @since 3.0.0 * @since 3.16.0 Unknown. * * @param string $key Property key. * @param mixed $val Property value. * @return mixed */ protected function scrub( $key, $val ) { /** * Filters the property type being scrubbed. * * The dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * * @since Unknown * * @param string $type The property type. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ $type = apply_filters( "llms_get_{$this->model_post_type}_property_type", $this->get_property_type( $key ), $this ); /** * Filters the scrubbed property. * * The first dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * The second dynamic part of this hook, `$key`, refers to the property name. * * @since Unknown * * @param mixed $scrubbed The scrubbed property value. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. * @param string $key The property name. * @param mixed $val The original property value. */ return apply_filters( "llms_scrub_{$this->model_post_type}_field_{$key}", $this->scrub_field( $val, $type ), $this, $key, $val ); } /** * Scrub fields according to datatype * * @since 3.0.0 * @since 3.19.2 Unknown. * @since 5.9.0 Use `wp_strip_all_tags()` in favor of `strip_tags()`. * Only strip tags from string values. * Coerce `null` html input to an empty string. * * @param mixed $val Property value to scrub. * @param string $type Data type. * @return mixed */ protected function scrub_field( $val, $type ) { if ( is_string( $val ) && 'html' !== $type ) { $val = wp_strip_all_tags( $val ); } switch ( $type ) { case 'absint': $val = absint( $val ); break; case 'array': if ( '' === $val ) { $val = array(); } $val = (array) $val; break; case 'bool': case 'boolean': $val = boolval( $val ); break; case 'float': $val = floatval( $val ); break; case 'html': $this->allowed_post_tags_set(); $val = wp_kses_post( $val ?? '' ); $this->allowed_post_tags_unset(); break; case 'int': $val = intval( $val ); break; case 'yesno': $val = 'yes' === $val ? 'yes' : 'no'; break; case 'text': case 'string': default: $val = sanitize_text_field( $val ); } return $val; } /** * Setter. * * @since 3.0.0 * @since 3.30.3 Use `wp_slash()` when setting properties. * @since 3.34.0 Turned to be only a wrapper for the set_bulk() method. * @since 6.5.0 Introduced `$allow_same_meta_value` param. * * @param string|array $key_or_array Key of the property or an associative array of key/val pairs. * @param mixed $val Value to set the property with. * This parameter will be ignored when the first parameter is an associative array of key/val pairs. * @param boolean $allow_same_meta_value Whether or not updating a meta with the same value as stored in the db is allowed. * @return boolean true on success, false on error or if the submitted value is the same as what's in the database and `$allow_same_meta_value` is `false`. */ public function set( $key_or_array, $val = '', $allow_same_meta_value = false ) { $model_array = $key_or_array; if ( ! is_array( $key_or_array ) ) { $model_array = array( $key_or_array => $val, ); } return $this->set_bulk( $model_array, false, $allow_same_meta_value ); } /** * Bulk setter. * * @since 3.34.0 * @since 3.36.1 Use WP_Error::$errors in place of WP_Error::has_errors() to support WordPress version prior to 5.1. * @since 5.3.1 Fix quote slashing when the user is not an admin. * @since 6.5.0 Introduced `$allow_same_meta_value` param. * Code reorganization. * * @param array $model_array Associative array of key/val pairs. * @param array $wp_error Whether or not return a WP_Error. * @param boolean $allow_same_meta_value Whether or not updating a meta with the same value as stored in the db is allowed. * @return boolean|WP_Error True on success. If the param $wp_error is set to false this will be false on error or if there was nothing to update. * Otherwise, this will be a WP_Error object collecting all the errors encountered along the way. */ public function set_bulk( $model_array, $wp_error = false, $allow_same_meta_value = false ) { if ( empty( $model_array ) ) { return $wp_error ? new WP_Error( 'empty_data', __( 'Empty data', 'lifterlms' ) ) : false; } $llms_post = $this->parse_properties_to_set( $model_array ); if ( empty( $llms_post ) ) { return $wp_error ? new WP_Error( 'invalid_data', __( 'Invalid data', 'lifterlms' ) ) : false; } $update_post_properties = $this->update_post_properties( $llms_post['post'] ); $update_meta_properties = $this->update_meta_properties( $llms_post['meta'], $allow_same_meta_value ); $error = is_wp_error( $update_post_properties ) ? $update_post_properties : new WP_Error(); if ( is_wp_error( $update_meta_properties ) ) { foreach ( $update_meta_properties->get_error_messages( 'invalid_meta' ) as $message ) { $error->add( 'invalid_meta', $message ); } } if ( ! empty( $error->has_errors() ) ) { return $wp_error ? $error : false; } return true; } /** * Parse the LifterLMS post properties to set. * * Logic moved from `set_bulk()` method. * * @since 6.5.0 * * @param array $model_array Associative array of key/val pairs. * @return array|bool Returns `false` if nothing to set or an array that contains all the post properties and all the metas to set. */ private function parse_properties_to_set( $model_array ) { $llms_post = array( 'post' => array(), 'meta' => array(), ); $post_properties = array_keys( $this->get_post_properties() ); $unsettable_properties = $this->get_unsettable_properties(); foreach ( $model_array as $key => $val ) { // Sanitize the post properties keys by removing the 'post_' prefix. if ( 'post_' === substr( $key, 0, 5 ) ) { $_key = substr( $key, 5 ); if ( in_array( $_key, $post_properties, true ) ) { $key = $_key; } } $val = $this->scrub( $key, $val ); /** * WordPress Post properties to be updated using the wp_insert_post() function. * * The 'edit_date' must be passed to the wp_update_post() function in order * to allow 'drafty' posts' creation date to be modified. */ if ( in_array( $key, $post_properties, true ) || 'edit_date' === $key ) { $type = 'post'; $llms_post_key = "post_{$key}"; switch ( $key ) { case 'content': /** This is a WordPress core filter. {@see kses_init_filters()}*/ $val = stripslashes( apply_filters( 'content_save_pre', addslashes( $val ) ) ); break; case 'excerpt': /** This is a WordPress core filter. {@see kses_init_filters()}*/ $val = stripslashes( apply_filters( 'excerpt_save_pre', addslashes( $val ) ) ); break; case 'edit_date': case 'ping_status': case 'comment_status': case 'menu_order': $llms_post_key = $key; break; case 'title': /** This is a WordPress core filter. {@see kses_init_filters()}*/ $val = stripslashes( apply_filters( 'title_save_pre', addslashes( $val ) ) ); break; } } elseif ( ! in_array( $key, $unsettable_properties, true ) ) { $type = 'meta'; $llms_post_key = $key; } else { continue; } /** * Filters the property value prior to be set. * * The first dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * The second dynamic part of this hook, `$key`, refers to the property name. * * @since Unknown * * @param mixed $val The property value. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ $llms_post[ $type ][ $llms_post_key ] = apply_filters( "llms_set_{$this->model_post_type}_{$key}", $val, $this ); } return empty( $llms_post['post'] ) && empty( $llms_post['meta'] ) ? false : $llms_post; } /** * Update post properties. * * Logic moved from `set_bulk()` method. * * @since 6.5.0 * * @param array $post_properties Array of post properties to set. * @return void|WP_Error */ private function update_post_properties( $post_properties ) { if ( empty( $post_properties ) ) { return; } $args = array_merge( $post_properties, array( 'ID' => $this->get( 'id' ), ) ); $update_post = wp_update_post( wp_slash( $args ), true ); if ( is_wp_error( $update_post ) ) { return $update_post; } // Update this post. $this->post = get_post( $this->get( 'id' ) ); } /** * Update post meta properties. * * Logic moved from `set_bulk()` method. * * @param array $post_meta_properties Array of post meta properties to set. * @param boolean $allow_same_meta_value Whether or not updating a meta with the same value as stored in the db is allowed. * By default `update_post_meta` doesn't allow that. * @return void|WP_Error */ private function update_meta_properties( $post_meta_properties, $allow_same_meta_value ) { if ( empty( $post_meta_properties ) ) { return; } $error = new WP_Error(); foreach ( $post_meta_properties as $key => $val ) { if ( $allow_same_meta_value ) { /** * Do pretty much(*) the same check for a duplicate value as in `update_metadata()` * to avoid `update_post_meta()` returning false. * {@see WP_REST_Meta_Fields::update_meta_value()}. * * If the new value to be set equals the old one don't update it. * * (*) This is not exactly the same check you can find in `update_metadata()` as that * account for multiple meta values for the same key, while we don't. */ $old_value = get_post_meta( $this->id, $this->meta_prefix . $key, true ); if ( $this->is_meta_value_same_as_stored_value( $key, $old_value, $val ) ) { continue; } } $u = update_post_meta( $this->id, $this->meta_prefix . $key, $val ); if ( ! ( is_numeric( $u ) || true === $u ) ) { $error->add( 'invalid_meta', sprintf( __( 'Cannot insert/update the %s meta', 'lifterlms' ), $key ) ); } } if ( $error->has_errors() ) { return $error; } } /** * Checks if the user provided value is equivalent to a stored value for the given meta key. * * {@see WP_REST_Meta_Fields::is_meta_value_same_as_stored_value()}. * * @param string $key The un-prefixed meta key being checked. * @param mixed $stored_value The currently stored value retrieved from get_metadata(). * @param mixed $new_value The new value. * @return bool */ private function is_meta_value_same_as_stored_value( $key, $stored_value, $new_value ) { $sanitized = sanitize_meta( $this->meta_prefix . $key, $new_value, 'post', $this->db_post_type ); // The return value of get_metadata will always be a string for scalar types. $scalar_types = array( 'string', 'text', 'absint', 'yesno', 'html', 'float', 'int', 'bool', 'boolean', ); if ( in_array( $this->get_property_type( $key ), $scalar_types, true ) ) { $sanitized = (string) $sanitized; } return $sanitized === $stored_value; } /** * Update terms for the post for a given taxonomy * * @since 3.8.0 * * @param array $terms Array of terms (name or ids). * @param string $tax The name of the tax. * @param boolean $append Optional. If true, will append the terms, false will replace existing terms. Default is `false`. * @return bool */ public function set_terms( $terms, $tax, $append = false ) { $set = wp_set_object_terms( $this->get( 'id' ), $terms, $tax, $append ); // wp_set_object_terms has 3 options when unsuccessful and only 1 for success // an array of terms when successful, let's keep it simple... return is_array( $set ); } /** * Coverts the object to an associative array * * Any property returned by $this->get_properties() will be retrieved * via $this->get() and added to the array. * * Extending classes can add additional properties to the array * by overriding $this->toArrayAfter(). * * This function is also utilized to serialize the object to JSON. * * @since 3.3.0 * @since 3.17.0 Unknown. * @since 4.7.0 Add exporting of extra data (images and blocks). * @since 4.8.0 Exclude extra data by default. Added `llms_post_model_to_array_add_extras` filter. * @since 5.4.1 Load properties to be used to generate the array from the new `get_to_array_properties()` method. * * @return array */ public function toArray() { $arr = array( 'id' => $this->get( 'id' ), ); foreach ( $this->get_to_array_properties() as $prop ) { if ( in_array( $prop, array( 'content', 'excerpt', 'title' ), true ) ) { $post_prop = "post_{$prop}"; $arr[ $prop ] = $this->post->$post_prop; } else { $arr[ $prop ] = $this->get( $prop ); } } // Add the featured image if the post type supports it. if ( post_type_supports( $this->db_post_type, 'thumbnail' ) ) { $arr['featured_image'] = $this->get_image( 'full', 'thumbnail' ); } // Expand instructors if instructors are supported. if ( ! empty( $arr['instructors'] ) && method_exists( $this, 'instructors' ) ) { foreach ( $arr['instructors'] as &$data ) { $instructor = llms_get_instructor( $data['id'] ); if ( $instructor ) { $data = array_merge( $data, $instructor->toArray() ); } } } elseif ( ! empty( $arr['author'] ) ) { $instructor = llms_get_instructor( $arr['author'] ); if ( $instructor ) { $arr['author'] = $instructor->toArray(); } } /** * Filter whether or not "extra" content should be included in the post array * * `__return_true` (with priority 99) is used to force the filter on during exports. * * @since 4.8.0 * * @param boolean $include Whether or not to include extra data. Default is `false`, except on during exports. * @param LLMS_Post_Model $model Post model instance. */ $add_array_extra = apply_filters( 'llms_post_model_to_array_add_extras', false, $this ); /** * Filter whether or not "extra" content should be included in the post array * * The dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * * @since 4.7.0 * * @param boolean $include Whether or not to include extra data. * @param LLMS_Post_Model $model Post model instance. */ $add_array_extra = apply_filters( "llms_{$this->model_post_type}_to_array_add_extras", $add_array_extra, $this ); if ( $add_array_extra ) { $arr = $this->to_array_extra( $arr ); } // Add custom fields. $arr = $this->toArrayCustom( $arr ); // Allow extending classes to add properties easily without overriding the class. $arr = $this->toArrayAfter( $arr ); $cpt_data = $this->get_post_type_data(); if ( $cpt_data->public ) { $arr['permalink'] = get_permalink( $this->get( 'id' ) ); } ksort( $arr ); // Because i'm anal... /** * Filter the final post array created when converting the object to an array * * The dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * * @since 4.7.0 * * @param array $arr Associative array of the model. * @param LLMS_Post_Model $model Post model instance. */ return apply_filters( "llms_{$this->model_post_type}_to_array", $arr, $this ); } /** * Called before data is sorted and returned by $this->toArray() * * Extending classes should override this data if custom data should * be added when object is converted to an array or json. * * @since 3.3.0 * * @param array $arr Array of data to be serialized. * @return array */ protected function toArrayAfter( $arr ) { return $arr; } /** * Add "extra" data to the post array during export/serialization * * This method adds two arrays of data, "blocks" and "images". * * The "blocks" array is an array of reusable blocks used in the post's content. During * an import these blocks will be imported into the site. * * The "images" array is an array of image element source URLs found in the post's content. During * an import these images will be imported into the new site via media sideloading. * * @since 4.7.0 * * @param array $arr Post array from `toArray()`. * @return array[] */ protected function to_array_extra( $arr ) { $arr['_extras'] = array( 'blocks' => empty( $arr['content'] ) ? array() : $this->to_array_extra_blocks( $arr['content'] ), 'images' => empty( $arr['content'] ) ? array() : $this->to_array_extra_images( $arr['content'] ), ); return $arr; } /** * Add reusable blocks found in the post's content to the post's array * * @since 4.7.0 * * @param string $content Raw `post_content` string. * @return array[] { * Array of reusable block information arrays. The array key is the WP_Post ID of the reusable block. * * @type string $title Reusable block title. * @type string $content Reusable block content. * } */ protected function to_array_extra_blocks( $content ) { $blocks = array(); foreach ( parse_blocks( $content ) as $block ) { if ( 'core/block' !== $block['blockName'] ) { continue; } $post = get_post( $block['attrs']['ref'] ); if ( ! $post ) { continue; } $blocks[ $post->ID ] = array( 'title' => $post->post_title, 'content' => $post->post_content, ); } return $blocks; } /** * Add images found in the post's content to the post's array * * @since 4.7.0 * * @param string $content Raw `post_content` string. * @return string[] Array of image source URLs. */ protected function to_array_extra_images( $content ) { $images = array(); $dom = llms_get_dom_document( $content ); if ( is_wp_error( $dom ) ) { return $images; } $site_url = get_site_url(); foreach ( $dom->getElementsByTagName( 'img' ) as $img ) { $src = $img->getAttribute( 'src' ); // Only include images stored in this site's media library. if ( 0 !== strpos( $src, $site_url ) ) { continue; } $images[] = $src; } return array_values( array_unique( $images ) ); } /** * Called by toArray to add custom fields via get_post_meta() * * Removes all custom props registered to the $this->properties automatically. * Also removes some fields used by the WordPress core that don't hold necessary data. * Extending classes may override this class to exclude, extend, or modify the custom fields for a post type. * * @since 3.16.11 * @since 3.30.0 Use `maybe_unserialize()` to ensure array data is accessible as an array. * @since 3.30.2 Add filter to allow 3rd parties to prevent a field from being added to the custom field array. * * @param array $arr Existing post array. * @return array */ protected function toArrayCustom( $arr ) { // Build an array of keys that are registered or can be excluded as a custom field. $props = array_keys( $this->get_properties() ); foreach ( $props as &$prop ) { $prop = $this->meta_prefix . $prop; } $props[] = '_edit_lock'; $props[] = '_edit_last'; // Get all meta data. $custom = array(); foreach ( get_post_meta( $this->get( 'id' ) ) as $key => $vals ) { // Skip registered fields or fields 3rd parties want to skip. /** * Filters whether the custom field should be excluded in the array representation of the post model * * The dynamic portion of this hook, `$this->model_post_type`, refers to the model's post type. For example "course", * "lesson", "membership", etc... * * @since 3.30.2 * * @param boolean $exclude Whether the custom field should be excluded. Default is `false`. * @param string $key The custom field name. * @param LLMS_Post_Model $llms_post The LLMS_Post_Model instance. */ if ( in_array( $key, $props, true ) || apply_filters( "llms_{$this->model_post_type}_skip_custom_field", false, $key, $this ) ) { continue; } // Add it. $custom[ $key ] = array_map( 'maybe_unserialize', $vals ); } // Add the compiled custom array. $arr['custom'] = $custom; return $arr; } }
Expand full source code Collapse full source code View on GitHub
Properties Properties
The following post and post meta properties are accessible for this class. See LLMS_Post_Model::get() and LLMS_Post_Model::set() for more information.
- $author
-
(string) ID of post author.
- $content
-
(string) The post's content.
- $date
-
(string) The post's local publication time.
- $excerpt
-
(string) The post's excerpt.
- $menu_order
-
(int) A field used for ordering posts.
- $modified
-
(string) The post's local modified time.
- $name
-
(string) The post's slug.
- $parent
-
(int) WP_Post ID of the post's parent post.
- $status
-
(string) The post's status.
- $title
-
(string) The post's title.
- $type
-
(string) The post's type, like post or page.
Methods Methods
- ___get — Private getter.
- __construct — Constructor
- __get — Magic Getter
- __isset — Magic Isset
- __set — Magic Setter
- _e — Wrapper for the $this->translate() that echos the result rather than returning it
- add_properties — Allow extending classes to add custom meta properties to the object
- after_create — Called immediately after creating / inserting a new post into the database
- allowed_post_tags_set — Modify allowed post tags for wp_kses for this post
- allowed_post_tags_unset — Remove modified allowed post tags for wp_kses for this post
- clone_post — Clones the Post if the post is cloneable
- create — Create a new post of the Instantiated Model
- export — Trigger an export download of the given post type
- get — Getter
- get_array — Getter for array values
- get_creation_args — An array of default arguments to pass to $this->create() when creating a new post
- get_date — Getter for date strings with optional date format conversion
- get_date_format — Retrieve the default date format for the post model
- get_default_value — Get the default value of a property
- get_embed — Get media embeds
- get_image — Retrieve URL for an image associated with the post
- get_post_properties — Retrieve an array of post properties
- get_post_type_data — Retrieve the Post's post type data object
- get_post_type_label — Retrieve a label from the post type data object's labels object
- get_price — Getter for price strings with optional formatting options
- get_properties — Retrieve an array of properties defined by the model
- get_property_defaults — Retrieve the default values for properties
- get_property_type — Get a property's data type for scrubbing
- get_status_name — Retrieve the registered Label of the post's current status
- get_terms — Get an array of terms for a given taxonomy for the post
- get_to_array_excluded_properties — Get the properties that will be explicitly excluded from the array representation of the model.
- get_to_array_properties — Get the properties that will be used to generate the array representation of the model.
- get_unsettable_properties — Array of properties which *cannot* be set
- is_cloneable — Determine if the associated post is exportable
- is_exportable — Determine if the associated post is exportable
- is_meta_value_same_as_stored_value — Checks if the user provided value is equivalent to a stored value for the given meta key.
- jsonSerialize
- parse_properties_to_set — Parse the LifterLMS post properties to set.
- scrub — Scrub field according to it's type
- scrub_field — Scrub fields according to datatype
- set — Setter.
- set_bulk — Bulk setter.
- set_terms — Update terms for the post for a given taxonomy
- to_array_extra — Add "extra" data to the post array during export/serialization
- to_array_extra_blocks — Add reusable blocks found in the post's content to the post's array
- to_array_extra_images — Add images found in the post's content to the post's array
- toArray — Coverts the object to an associative array
- toArrayAfter — Called before data is sorted and returned by $this->toArray()
- toArrayCustom — Called by toArray to add custom fields via get_post_meta()
- translate — Wrapper for $this-get() which allows translation of the database value before outputting on screen
- update_meta_properties — Update post meta properties.
- update_post_properties — Update post properties.
Changelog Changelog
Version | Description |
---|---|
3.36.1 | In set_bulk() method, use WP_Error::$errors in place of WP_Error::has_errors() to support WordPress version prior to 5.1. |
3.34.0 | Refresh the whole $post property with the just updated instance of WP_Post after updating it. |
3.31.0 | Treat post_excerpt fields as HTML instead of plain text. |
3.30.3 | Use wp_slash() when creating new posts. |
3.30.2 | Add filter to allow 3rd parties to prevent a field from being added to the custom field array. |
3.30.0 | Improve handling of custom field data to toArrayCustom() . |
3.0.0 | Introduced. |