file = __FILE__; $self->url = plugin_dir_url( $self->file ); $self->path = plugin_dir_path( $self->file ); $self->basename = plugin_basename( $self->file ); $self->includes(); $self->hooks(); //$self->registerJavaScripts(); //$self->registerCSS(); } return self::$instance; } /** * Include the dependencies. * * @since 1.0 */ private function includes() { require_once( 'includes/class.wp-rest-post.php'); require_once( 'includes/class.wp-rest-add-featured-image.php'); } /** * Register the plugins actions/filters. * * @since 1.0 */ private function hooks() { add_shortcode( 'display-posts-remote', array( __CLASS__, 'shortcode' ) ); } /** * Get the plugin's bas URL. * * @since 1.0 */ public function getURL() { return $this->url; } /** * Register the plugin's JavaScript. * * @since 1.0 */ private function registerJavaScripts() { } /** * Enqueue the plugin's JavaScript. * * @since 1.0 */ public static function enqueueJS() { } /** * Register the plugin's CSS. * * @since 1.0 */ private function registerCSS() { } /** * Enqueue the plugin's JavaScript. * * @since 1.0 */ public static function enqueueCSS() { } /** * Cache the REST response. * * @since 1.0 * * @param string $url * @param array $response * @param float|int $timeout */ protected function setCache( $url, $response, $timeout = DAY_IN_SECONDS ) { set_transient( $this->cacheKey( $url ), $response, $timeout ); } /** * Get cached REST response. * * @since 1.0 * * @param string $url * * @return array|false */ protected function getCache( $url ) { if ( is_array( $response = get_transient( $this->cacheKey( $url ) ) ) ) { return $response; } return FALSE; } /** * Clear cache. * * @since 1.0 * * @param string $url */ public function clearCache( $url ){ delete_transient( $this->cacheKey( $url ) ); } /** * Create cache key based on URL. * * @since 1.0 * * @param string $url * * @return string */ protected function cacheKey( $url ) { return md5( preg_replace( '(^https?://)', '', $url ) ); } /** * Query a remote site's posts. * * @since 1.0 * * @param array $untrusted * * @return array|WP_Error */ public function getPosts( $untrusted ) { $defaults = array( 'url' => '', 'category_id' => 0, 'per_page' => 10, 'order' => 'DESC', 'orderby' => 'date', 'cache_timeout' => DAY_IN_SECONDS, ); $atts = shortcode_atts( $defaults, $untrusted ); $atts['url'] = esc_url( filter_var( $atts['url'], FILTER_SANITIZE_URL ) ); if ( 0 >= strlen( $atts['url'] ) ) { return new WP_Error( 'invalid_url', __( 'Remote site URL must be provided.', 'display-posts-shortcode-remote' ), $atts['url'] ); } $url = trailingslashit( $atts['url'] ) . 'wp-json/wp/v2/posts'; $url = add_query_arg( '_embed' , '', $url ); if ( ! empty( $atts['category_id'] ) ) { if ( is_array( $atts['category_id'] ) ) { $atts['category_id'] = implode( ',', $atts['category_id'] ); } $url = add_query_arg( 'categories', $atts['category_id'], $url ); } $url = add_query_arg( array( 'per_page' => $atts['per_page'], 'order' => $atts['order'], 'orderby' => $atts['orderby'], ), $url ); if ( 0 >= $atts['cache_timeout'] ) { $this->clearCache( $url ); } if ( FALSE === $response = $this->getCache( $url ) ) { $response = wp_safe_remote_get( $url ); if ( ! is_wp_error( $response ) && 0 < $atts['cache_timeout'] ) { /* * NOTE: cache will be saved during Gutenberg autosaves via the REST API. */ $this->setCache( $url, $response, $atts['cache_timeout'] ); } } if ( is_wp_error( $response ) ) { return $response; } $posts = json_decode( wp_remote_retrieve_body( $response ) ); if ( JSON_ERROR_NONE !== json_last_error() ) { return new WP_Error( 'invalid_response', json_last_error_msg(), $posts ); } return $posts; } /** * The shortcode default options/values. * * @since 1.0 * * @return array */ public function getDefaults() { return array( 'category_id' => 0, 'content_class' => 'content', 'date_format' => '(n/j/Y)', 'include_content' => FALSE, 'include_date' => FALSE, 'include_date_modified' => FALSE, 'include_link' => TRUE, 'include_title' => TRUE, 'image_size' => 'thumbnail', 'no_posts_message' => __( 'No posts to display.', 'display-posts-shortcode-remote' ), 'order' => 'desc', 'orderby' => 'date', 'posts_per_page' => 10, 'title' => '', 'url' => '', 'wrapper' => 'ul', 'cache_timeout' => DAY_IN_SECONDS, ); } /** * Parse and sanitize the user supplied shortcode values. * * @since 1.0 * * @param array $untrusted The user defined shortcode attributes. * * @return array */ public function parseShortcodeAtts( $untrusted ) { $defaults = Display_Posts_Remote()->getDefaults(); $atts = shortcode_atts( $defaults, $untrusted, 'display-posts-remote' ); $restSupportOrderby = array( 'author', 'date', 'id', 'include', 'modified', 'parent', 'relevance', 'slug', 'include_slugs', 'title', ); $atts['category_id'] = wp_parse_id_list( $atts['category_id'] ); $atts['content_class'] = array_map( 'sanitize_html_class', ( explode( ' ', $atts['content_class'] ) ) ); $atts['date_format'] = sanitize_text_field( $atts['date_format'] ); $atts['include_content'] = self::toBoolean( $atts['include_content'] ); $atts['include_date'] = self::toBoolean( $atts['include_date'] ); $atts['include_date_modified'] = self::toBoolean( $atts['include_date_modified'] ); $atts['include_link'] = self::toBoolean( $atts['include_link'] ); $atts['include_title'] = self::toBoolean( $atts['include_title'] ); $atts['image_size'] = sanitize_key( $atts['image_size'] ); $atts['no_posts_message'] = sanitize_text_field( $atts['no_posts_message'] ); $atts['order'] = in_array( strtolower( $atts['order'] ), array( 'asc', 'desc' ) ) ? strtolower( sanitize_key( $atts['order'] ) ) : 'desc'; $atts['orderby'] = in_array( strtolower( $atts['orderby'] ), $restSupportOrderby ) ? strtolower( sanitize_key( $atts['orderby'] ) ) : 'date'; $atts['posts_per_page'] = filter_var( $atts['posts_per_page'], FILTER_VALIDATE_INT, array( 'options' => array( 'min_range' => 1, 'max_range' => 100, 'default' => 10, ), ) ); $atts['title'] = sanitize_text_field( $atts['title'] ); $atts['url'] = filter_var( $atts['url'], FILTER_SANITIZE_URL ); $atts['wrapper'] = sanitize_text_field( $atts['wrapper'] ); $atts['cache_timeout'] = absint( $atts['cache_timeout'] ); // Map shortcode option to REST API Parameter. $atts['per_page'] = $atts['posts_per_page']; return $atts; } /** * Callback for the `display-posts-remote` shortcode. * * @since 1.0 * * @param array $untrusted * @param string $content * @param string $tag * * @return string */ public static function shortcode( $untrusted, $content, $tag = 'display-posts-remote' ) { $self = Display_Posts_Remote(); $html = ''; $atts = $self->parseShortcodeAtts( $untrusted ); $result = $self->getPosts( $atts ); if ( is_wp_error( $result ) ) { return '
' . $result->get_error_message() . '
'; } if ( empty( $result ) || ! is_array( $result ) ) { /** * Filter content to display if no posts match the current query. * * @since 1.1 * * @param string $no_posts_message Content to display, returned via {@see wpautop()}. */ return apply_filters( 'display_posts_shortcode_no_results', wpautop( $atts['no_posts_message'] ) ); } // Set up html elements used to wrap the posts. // Default is ul/li, but can also be ol/li and div/div. if ( ! in_array( $atts['wrapper'], array( 'ul', 'ol', 'div' ) ) ) { $atts['wrapper'] = 'ul'; } $itemElement = 'div' === $atts['wrapper'] ? 'div' : 'li'; foreach ( $result as $data ) { $image = $date = $postContent = ''; $post = new Display_Posts_Remote_Post( $data ); if ( $atts['include_title'] && $atts['include_link'] ) { $title = '' . esc_attr( strip_tags( $post->get_the_title() ) ) . ''; } elseif ( $atts['include_title'] ) { $title = '' . esc_attr( strip_tags( $post->get_the_title() ) ) . ''; } else { $title = ''; } $imageAttributes = $self->getImageAttributes( $atts ); if ( $atts['image_size'] && $post->has_post_thumbnail() && $atts['include_link'] ) { $image = '' . $post->get_the_post_thumbnail( $atts['image_size'], $imageAttributes ) . ' '; } elseif ( $atts['image_size'] && $post->has_post_thumbnail() ) { $image = '' . $post->get_the_post_thumbnail( $atts['image_size'], $imageAttributes ) . ' '; } elseif ( $post->has_featured_media() && $atts['include_link'] ) { $image = '' . $post->get_the_post_thumbnail( 'full', $imageAttributes ) . ' '; } elseif ( $post->has_featured_media() ) { $image = '' . $post->get_the_post_thumbnail( 'full', $imageAttributes ) . ' '; } if ( $atts['include_date'] ) { $date = ' ' . $post->get_the_date( $atts['date_format'] ) . ''; } elseif ( $atts['include_date_modified'] ) { $date = ' ' . $post->get_the_modified_date( $atts['date_format'] ) . ''; } if ( $atts['include_content'] ) { $postContent = '
' . $post->get_the_content() . '
'; } /** * Filter the HTML markup for output via the shortcode. * * Use the same filter name and pass the same values so existing filters work on DPS Remote. * * @since 1.0 * * @param string $html The shortcode's HTML output. * @param array $original_atts Original attributes passed to the shortcode. * @param string $image HTML markup for the post's featured image element. * @param string $title HTML markup for the post's title element. * @param string $date HTML markup for the post's date element. * @param string $excerpt HTML markup for the post's excerpt element. * @param string $inner_wrapper Type of container to use for the post's inner wrapper element. * @param string $content The post's content. * @param string $class Space-separated list of post classes to supply to the $inner_wrapper element. * @param string $author HTML markup for the post's author. * @param string $category_display_text */ $html .= apply_filters( 'display_posts_shortcode_output', "<{$itemElement} class=\"listing-item\">{$image}{$title}{$date}{$postContent}" . PHP_EOL, array(), // $original_atts $image, $title, $date, '', // $excerpt $itemElement, // $inner_wrapper $postContent, // $content array( 'listing-item' ), // $class '', // $author '' // $category_display_text ); } if ( 0 < strlen( $atts['title'] ) ) { /** * Filter the shortcode output title tag element. * * @since 1.1 * * @param string $tag Type of element to use for the output title tag. Default 'h2'. * @param array $original_atts Original attributes passed to the shortcode. */ $titleTag = apply_filters( 'display_posts_shortcode_title_tag', 'h2', $atts ); $heading = '<' . $titleTag . ' class="display-posts-title">' . $atts['title'] . '' . "\n"; $html = $heading . $html; } $open = "<{$atts['wrapper']} class=\"display-posts-listing\">" . PHP_EOL; $close = "" . PHP_EOL; return $open . $html . $close; } /** * Return the image attributes that should be applied ti the image tags. * * This is primarily to support DPS Pinch Zoomer add on. * * @since 1.0 * * @param array $atts * * @return array */ public function getImageAttributes( $atts = array() ) { $attributes = array(); /* * Support DPS Pinch Zoomer. */ if ( function_exists( 'Display_Posts_Pinch_Zoomer' ) ) { $options = Display_Posts_Pinch_Zoomer()->shortcodeAtts( $atts ); $attributes = Display_Posts_Pinch_Zoomer()->postImageAttributes( $options ); } return $attributes; } /** * Converts the following strings: yes/no; true/false and 0/1 to boolean values. * If the supplied string does not match one of those values the method will return NULL. * * @since 1.0 * * @param string|int|bool $value * * @return bool */ public static function toBoolean( &$value ) { // Already a bool, return it. if ( is_bool( $value ) ) return $value; $value = filter_var( strtolower( $value ), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); if ( is_null( $value ) ) { $value = FALSE; } return $value; } } /** * @since 1.0 * * @return Display_Posts_Remote */ function Display_Posts_Remote() { return Display_Posts_Remote::instance(); } Display_Posts_Remote(); }