File "posts.php"
Full Path: /home/naijiwfb/sabisentinel.com/wp-content/plugins/smartmag-core/lib/posts.php
File size: 19.07 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Helpers and functionality relevant to posts (single and multiple).
*/
class Bunyad_Posts
{
public $read_more;
public $more_text = null;
public $more_html = null;
private $state = [];
public function __construct()
{
// Add offsets support with correct pagination.
add_filter('found_posts', [$this, 'fix_offset_pagination'], 10, 2);
add_action('pre_get_posts', [$this, 'pre_get_posts']);
}
/**
* Custom excerpt function - utilize existing wordpress functions to add real support for <!--more-->
* to excerpts which is missing in the_excerpt().
*
* Maintain plugin functionality by utilizing wordpress core functions and filters.
*
* @param string|null $text
* @param integer $length
* @param array $options {
* Options to modify excerpt behavior.
*
* @type bool $add_more Add read more text if needed based on excerpt length.
* @type string $more_text More link anchor text.
* @type bool $force_more Always add more link.
* @type bool $use_teaser Whether or not to use <!--more--> teaser as excerpt.
* }
* @return string
*/
public function excerpt($text = null, $length = 55, $options = [])
{
global $more;
// Add defaults.
$options = array_merge([
'add_more' => null,
'force_more' => null,
'use_teaser' => false,
'filters' => true,
'more_text' => $this->more_text,
'more_html' => $this->more_html,
], $options);
$this->_store_state();
// Add support for <!--more--> cut-off on custom home-pages and the like
$old_more = $more;
$more = false;
// Override options
extract($options);
// Maybe overridden in parameters.
$this->more_text = $more_text;
$this->more_html = $more_html;
if ($force_more) {
$this->read_more = true;
}
if (!$text) {
// have a manual excerpt?
if (has_excerpt()) {
return apply_filters('the_excerpt', get_the_excerpt()) . ($force_more ? $this->excerpt_read_more() : '');
}
// don't add "more" link
$text = get_the_content('');
}
$text = strip_shortcodes(apply_filters('bunyad_excerpt_pre_strip_shortcodes', $text));
$text = str_replace(']]>', ']]>', $text);
// Matches wp_trim_excerpt()
if (has_blocks()) {
$text = excerpt_remove_blocks($text);
if (function_exists('excerpt_remove_footnotes')) {
$text = excerpt_remove_footnotes($text);
}
}
// Has <!--more--> teaser to use as excerpt?
$post = get_post();
if ($use_teaser && preg_match('/<!--more(.*?)?-->/', $post->post_content)) {
$excerpt = $text;
}
else {
// Get plaintext excerpt trimmed to right length
$excerpt = wp_trim_words($text, $length, apply_filters('bunyad_excerpt_hellip', '…') . ($add_more !== false ? $this->excerpt_read_more() : ''));
}
/**
* Force "More" link?
*
* wp_trim_words() will only add the read more link if it's needed - if the length actually EXCEEDS. In some cases,
* for styling, read more has to be always present.
*/
if ($force_more) {
$read_more = $this->excerpt_read_more();
if (substr($excerpt, -strlen($read_more)) !== $read_more) {
$excerpt .= $read_more;
}
}
// Fix extra spaces
$excerpt = trim(str_replace(' ', ' ', $excerpt));
// apply filters after to prevent added html functionality from being stripped
// REMOVED: the_content filters often clutter the HTML - use the_excerpt filter instead
// $excerpt = apply_filters('the_content', $excerpt);
// VC excerpts can go in infinte loop with do_shortcode(). Disable filters if nested.
if (doing_filter('the_excerpt')) {
$filters = false;
}
if ($filters) {
$excerpt = apply_filters('the_excerpt', apply_filters('get_the_excerpt', $excerpt));
}
else {
// Still apply the defaults to remain consistent
$excerpt = wpautop(
wptexturize($excerpt)
);
}
// Revert original states.
$more = $old_more;
$this->_restore_state();
return $excerpt;
}
/**
* Wrapper for the_content()
*
* @see the_content()
*/
public function the_content($more_link_text = null, $strip_teaser = false, $options = [])
{
$options = array_merge([
'ignore_more' => false,
], $options);
// Add <!--more--> support on static pages when not using excerpts.
// Conflicts with pagination 1st page active, so we restore it.
if (is_page() && !$options['ignore_more']) {
global $more;
$old_more = $more;
$more = 0;
}
// Get the content
$content = get_the_content($more_link_text, $strip_teaser);
// Delete first gallery shortcode if featured area is enabled.
if (get_post_format() == 'gallery' && !$this->meta('featured_disable')) {
$content = $this->_strip_shortcode_gallery($content);
}
// Apply bunyad_main_content filters first - for page builder.
$content = apply_filters('the_content', $content, 'bunyad_main_content');
$content = str_replace(']]>', ']]>', $content);
echo $content; // phpcs:ignore WordPress.Security.EscapeOutput -- Pre-filtered/escaped from get_the_content()
// Restore if needed.
if (isset($old_more)) {
$more = $old_more;
}
}
/**
* Deletes first gallery shortcode and returns content.
*/
public function _strip_shortcode_gallery($content)
{
$gallery = $this->get_first_gallery_block($content);
if ($gallery) {
// WordPress 5.9+ - innerHTML is incomplete.
if (!empty($gallery['useRegex'])) {
$regex = '\<\!--\s*wp:gallery(.+?)';
$regex .= array_reduce(
$gallery['attrs']['ids'],
function($acc, $item) {
return $acc . '\<\!--\s*wp:image(.+?)"id"\s*:\s*' . $item . '(.+?)';
},
''
);
$regex .= '\<\!--\s*\/wp:gallery\s*--\>';
$content = preg_replace('#' . $regex . '#is', '', $content);
}
else {
// Strip it once.
$pos = strpos($content, $gallery['innerHTML']);
if ($pos !== false) {
$content = substr_replace($content, '', $pos, strlen($gallery['innerHTML']));
}
}
}
return $content;
}
/**
* Get the first gallery block - provided block editor content.
*
* @param string $content
* @return boolean|array
*/
public function get_first_gallery_block($content = null)
{
if (!$content) {
$content = get_the_content();
}
// First try a Gutenberg / block editor gallery.
if (function_exists('has_block') && has_block('gallery', $content)) {
$post_blocks = parse_blocks($content);
$found = false;
while ($block = array_shift($post_blocks)) {
if ('core/gallery' === $block['blockName']) {
$found = $block;
break;
}
// Look into nested blocks as well.
elseif (!empty($block['innerBlocks'])) {
while ($inner = array_pop($block['innerBlocks'])) {
array_push($post_blocks, $inner);
}
}
}
// WordPress 5.9+ gallery has images in innerBlocks now.
if ($found && $found['innerBlocks']) {
$ids = [];
foreach ($found['innerBlocks'] as $image) {
$ids[] = $image['attrs']['id'] ?? '';
}
$found['useRegex'] = true;
$found['attrs']['ids'] = array_filter($ids);
}
if ($found) {
return $found;
}
}
// Legacy / Classic Editor's gallery shortcode.
preg_match_all('/'. get_shortcode_regex() .'/s', $content, $matches, PREG_SET_ORDER);
if (!empty($matches)) {
foreach ($matches as $shortcode) {
if ('gallery' === $shortcode[2]) {
// We need innerHTML and attrs['ids].
$pseudo_block = [
'innerHTML' => $shortcode[0]
];
$atts = shortcode_parse_atts($shortcode[3]);
if (!empty($atts['ids'])) {
$pseudo_block['attrs'] = [
'ids' => explode(',', $atts['ids'])
];
}
return $pseudo_block;
}
}
}
return false;
}
/**
* Get the image ids used in the first gallery block/shortcode.
*
* @param string|null $content
* @return boolean|array
*/
public function get_first_gallery_ids($content = null)
{
$gallery = $this->get_first_gallery_block();
if ($gallery && !empty($gallery['attrs']['ids'])) {
return $gallery['attrs']['ids'];
}
return false;
}
/**
* Get custom post meta using the bunyad prefix.
*
* @param string|null $key
* @param integer|null $post_id
* @param boolean $defaults whether or not to use default options mapped to certain keys - only when $key is set
* @uses Bunyad::options() used when defaults are tested for a specific key
*/
public function meta($key = null, $post_id = null, $defaults = true)
{
$prefix = Bunyad::options()->get_config('meta_prefix') . '_';
if (!$post_id) {
$post = get_post();
if (is_object($post)) {
$post_id = $post->ID;
}
// Fallback to queried object id.
if (!$post_id) {
$post_id = get_queried_object_id();
}
}
if (is_string($key)) {
$meta = get_post_meta($post_id, $prefix . $key, true);
/**
* Use values from specified key mapping of Bunyad::options() if meta value is empty
*/
if ($defaults) {
// bool_inverse will inverse the value in Bunyad::options()
$default_map = [
'featured_disable' => ['key' => 'show_featured', 'bool_inverse' => true],
'layout_template' => ['key' => 'post_layout_template'],
'layout_spacious' => ['key' => 'post_layout_spacious'],
];
// Legacy Fix: In CheerUp <6.1.0 & CB <1.6, featured disabled was saved with
// "0" value instead of empty: ''
if ($key === 'featured_disable' && !$meta) {
$meta = '';
}
// Have a key association with theme settings?
// get_post_meta() returns '' when it can't find a record.
if ($meta === '' && array_key_exists($key, $default_map)) {
$expression = Bunyad::options()->get($default_map[$key]['key']);
$meta = (!empty($default_map[$key]['bool_inverse']) ? !$expression : $expression);
}
}
return apply_filters('bunyad_meta_' . $key, $meta);
}
return $this->get_all_meta($post_id, true);
}
/**
* Get custom post meta that's using the bunyad prefix.
*
* @param string|null $post_id
* @param boolean $remove_prefix Return keys with bunyad prefix removed.
* @return array
*/
public function get_all_meta($post_id = null, $remove_prefix = true)
{
$prefix = Bunyad::options()->get_config('meta_prefix') . '_';
$meta = get_post_custom($post_id);
$new_meta = [];
foreach ($meta as $key => $value) {
// Preserve meta with our prefix.
if (strpos($key, $prefix) === 0) {
if ($remove_prefix) {
$key = substr($key, strlen($prefix));
}
$new_meta[$key] = $this->_fix_meta_value($value);
}
}
return $new_meta;
}
// Helper to fix meta value
private function _fix_meta_value($value)
{
if (count($value) === 1) {
return $value[0];
}
return $value;
}
/**
* Get meta for page first if available
*/
public function page_meta($key = null, $post_id = null)
{
global $page;
if (!$post_id) {
$post_id = $page->ID;
}
return $this->meta($key, $post_id);
}
/**
* Get bunyad term meta removing our prefix when returning all.
*
* @param string|null $key
* @param int|null $term_id
* @return mixed Array or single value.
*/
public function term_meta($key = null, $term_id = '')
{
$prefix = Bunyad::options()->get_config('meta_prefix') . '_';
if (!$term_id) {
$term_id = get_query_var('cat');
}
// Single value.
if ($key) {
return apply_filters(
'bunyad_term_meta_' . $key,
get_term_meta($term_id, $prefix . $key, true),
$term_id
);
}
$meta = (array) get_term_meta($term_id);
$new_meta = [];
foreach ($meta as $key => $value) {
// Preserve meta with our prefix.
if (strpos($key, $prefix) === 0) {
$key = substr($key, strlen($prefix));
$new_meta[$key] = $this->_fix_meta_value($value);
}
}
return apply_filters('bunyad_term_meta', $new_meta, $term_id);
}
/**
* Get related posts
*
* @param integer $count number of posts to return
* @param integer|null $post_id
* @return WP_Query
*/
public function get_related($count = 5, $post_id = null, $options = [])
{
if (!$post_id) {
global $post;
$post_id = $post->ID;
}
$options = array_replace([
'yarpp' => Bunyad::options()->related_posts_yarpp,
'posts_by' => Bunyad::options()->related_posts_by,
'custom' => false,
], $options);
$args = [
'posts_per_page' => $count,
'post__not_in' => [$post_id],
'ignore_sticky_posts' => true
];
if ($options['yarpp'] && function_exists('yarpp_get_related')) {
$yarpp = yarpp_get_related(['limit' => $count], $post_id);
return new WP_Query(array_merge($args, [
'post__in' => wp_list_pluck($yarpp, 'ID')
]));
}
// Get related posts using tags or categories?
switch ($options['posts_by']) {
// Match by tags.
case 'tags':
$args['tag__in'] = wp_get_post_tags($post_id, ['fields' => 'ids']);
break;
// Match posts either by tags or categories.
case 'cat_tags':
$args['tax_query'] = [
// OR relationship - one of the below
'relation' => 'OR',
[
'taxonomy' => 'category',
'field' => 'term_id',
'terms' => (array) wp_get_post_categories($post_id),
],
[
'taxonomy' => 'post_tag',
'field' => 'term_id',
'terms' => (array) wp_get_post_tags($post_id, ['fields' => 'ids']),
]
];
break;
// Match by category.
default:
$args['category__in'] = wp_get_post_categories($post_id);
break;
}
$related = new WP_Query(apply_filters('bunyad_get_related_query', $args, $options));
return $related;
}
/**
* Custom pagination
*
* @param array $options extend options for paginate_links()
* @see paginate_links()
*/
public function paginate($options = [], $query = null)
{
// Paged is global only for always_prev_next use-case
global $wp_rewrite, $wp_query, $paged;
if (!$query) {
$query = $wp_query;
}
$total_pages = $query->max_num_pages;
// use page on static front-page - paged isn't set there
// non-static home-page, and other archives use paged
$paged = ($query->get('paged') ? $query->get('paged') : $query->get('page'));
$args = [
//'base' => add_query_arg('paged', '%#%'),
//'format' => '',
'current' => max(1, $paged),
'total' => $total_pages,
// accessibility + fontawesome for pagination links
'next_text' => '<span class="visuallyhidden">' . esc_html_x('Next', 'pagination', 'bunyad') . '</span><i class="tsi tsi-angle-right"></i>',
'prev_text' => '<i class="tsi tsi-angle-left"></i><span class="visuallyhidden">' . esc_html_x('Previous', 'pagination', 'bunyad') . '</span>'
];
$args = array_replace($args, $options);
if ($args['total'] <= 1) {
return '';
}
/**
* Always show previous / next?
*/
$prev_link = $next_link = '';
if (!empty($options['always_prev_next'])) {
// Disable it for paginate_links()
$args['prev_next'] = false;
// Previous link
$prev_link = get_previous_posts_link($args['prev_text']);
if (!$prev_link) {
$prev_link = '<span class="disabled">' . $args['prev_text'] . '</span>';
}
// Next link
$next_link = get_next_posts_link($args['next_text']);
if (!$next_link) {
$next_link = '<span class="disabled">' . $args['next_text'] . '</span>';
}
// Wrap them
$prev_link = '<span class="page-numbers label-prev">' . $prev_link . '</span>';
$next_link = '<span class="page-numbers label-next">' . $next_link . '</span>';
}
$pagination = paginate_links($args);
// Add wrapper?
if (!empty($options['wrapper_before'])) {
$pagination = $options['wrapper_before'] . $pagination . $options['wrapper_after'];
}
return $prev_link . $pagination . $next_link;
}
/**
* Fix query LIMIT when using offsets.
*
* @param object $query
*/
public function fix_query_offset(&$query)
{
if (empty($query->query_vars['offset']) || empty($query->query_vars['orig_offset'])) {
return;
}
// When paged, manually determine page query offset.
if ($query->is_paged) {
// (offset + current page (minus one) x posts per page)
$page_offset = $query->query_vars['offset'] + (($query->query_vars['paged'] - 1) * $query->query_vars['posts_per_page']);
$query->set('offset', $page_offset);
}
else {
// First page? Just use the offset.
$query->set('offset', $query->query_vars['offset']);
}
}
/**
* Preserve original offset query var as it will be changed.
*
* @param array $query Query vars.
*/
public function add_query_offset($query = [])
{
if (isset($query['offset'])) {
$query['orig_offset'] = $query['offset'];
}
return $query;
}
/**
* A wrapper for common pre_get_posts filters.
*
* @param object $query
*/
public function pre_get_posts(&$query)
{
$this->fix_query_offset($query);
}
/**
* Fix found_posts when an offset is set.
*
* WordPress found_posts doesn't account for offset.
* @param integer $found_posts
* @param object $query
*/
public function fix_offset_pagination($found_posts, $query)
{
if (empty($query->query_vars['offset']) OR empty($query->query_vars['orig_offset'])) {
return $found_posts;
}
$offset = $query->query_vars['orig_offset'];
// Reduce WordPress's found_posts count by the offset.
return $found_posts - $offset;
}
/**
* Callback: Record post ids to prevent duplicates later. Use on loop_end hook.
*
* @param \WP_Query $query
* @return void
*/
public function _record_displayed(&$query)
{
// The query must enable logging.
if (empty($query->query_vars['record_displayed'])) {
return;
}
// Add to list.
$displayed = (array) Bunyad::registry()->page_displayed_posts;
foreach ($query->posts as $post) {
array_push($displayed, $post->ID);
}
Bunyad::registry()->set('page_displayed_posts', $displayed);
}
/**
* Callback: Enable displayed posts prevention the provided query args.
*
* @param array $args Arguments for WP_Query.
* @return array
*/
public function _exclude_displayed($args)
{
$displayed = (array) Bunyad::registry()->page_displayed_posts;
$args['post__not_in'] = array_merge(
isset($args['post__not_in']) ? (array) $args['post__not_in'] : [],
$displayed
);
$args['record_displayed'] = true;
return $args;
}
/**
* Add the read more text to excerpts
*/
public function excerpt_read_more()
{
global $post;
if (is_feed()) {
return ' [...]';
}
// Add more link if enabled.
if ($this->read_more) {
$text = $this->more_text;
if (!$text) {
$text = esc_html__('Read More', 'bunyad');
}
if ($this->more_html === null) {
$this->more_html = '<div class="read-more"><a href="%1$s" title="%2$s">%3$s</a></div>';
}
return sprintf(
apply_filters('bunyad_read_more_html', $this->more_html),
get_permalink($post->ID),
esc_attr($text),
$text
);
}
return '';
}
/**
* Make a copy of original state to be restored later.
*/
protected function _store_state()
{
$this->state = [
'read_more' => $this->read_more,
'more_text' => $this->more_text,
'more_html' => $this->more_html
];
}
/**
* Restore state after it was temporarily changed.
*/
protected function _restore_state()
{
foreach ($this->state as $key => $value) {
$this->{$key} = $value;
}
}
}