/
var
/
www
/
barefootlaw.org
/
wp-content
/
plugins
/
autodescription
/
inc
/
classes
/
Upload File
HOME
<?php /** * @package The_SEO_Framework\Classes\Facade\Generate_Description * @subpackage The_SEO_Framework\Getters\Description */ namespace The_SEO_Framework; \defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die; /** * The SEO Framework plugin * Copyright (C) 2015 - 2023 Sybre Waaijer, CyberWire B.V. (https://cyberwire.nl/) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** * Class The_SEO_Framework\Generate_Description * * Generates Description SEO data based on content. * * @since 2.8.0 */ class Generate_Description extends Generate { /** * Returns the meta description from custom fields. Falls back to autogenerated description. * * @since 3.0.6 * @since 3.1.0 The first argument now accepts an array, with "id" and "taxonomy" fields. * @since 4.2.0 Now supports the `$args['pta']` index. * @uses $this->get_description_from_custom_field() * @uses $this->get_generated_description() * * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * Leave null to autodetermine query. * @param bool $escape Whether to escape the description. * @return string The real description output. */ public function get_description( $args = null, $escape = true ) { $desc = $this->get_description_from_custom_field( $args, false ) ?: $this->get_generated_description( $args, false ); return $escape ? $this->escape_description( $desc ) : $desc; } /** * Returns the Open Graph meta description. Falls back to meta description. * * @since 3.0.4 * @since 3.1.0 1. Now tries to get the homepage social descriptions. * 2. The first argument now accepts an array, with "id" and "taxonomy" fields. * @since 4.2.0 Now supports the `$args['pta']` index. * @uses $this->get_open_graph_description_from_custom_field() * @uses $this->get_generated_open_graph_description() * * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * Leave null to autodetermine query. * @param bool $escape Whether to escape the description. * @return string The real Open Graph description output. */ public function get_open_graph_description( $args = null, $escape = true ) { $desc = $this->get_open_graph_description_from_custom_field( $args, false ) ?: $this->get_generated_open_graph_description( $args, false ); return $escape ? $this->escape_description( $desc ) : $desc; } /** * Returns the Open Graph meta description from custom field. * Falls back to meta description. * * @since 3.1.0 * @see $this->get_open_graph_description() * * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * Leave null to autodetermine query. * @param bool $escape Whether to escape the title. * @return string TwOpen Graphitter description. */ protected function get_open_graph_description_from_custom_field( $args, $escape ) { if ( null === $args ) { $desc = $this->get_custom_open_graph_description_from_query(); } else { $this->fix_generation_args( $args ); $desc = $this->get_custom_open_graph_description_from_args( $args ); } return $escape ? $this->escape_description( $desc ) : $desc; } /** * Returns the Open Graph meta description from custom field, based on query. * Falls back to meta description. * * @since 3.1.0 * @since 3.2.2 Now tests for the static frontpage metadata prior getting fallback data. * @since 4.0.0 Added term meta item checks. * @since 4.2.0 1. No longer returns an escaped custom field description. * 2. Now returns custom descriptions for post type archives. * @see $this->get_open_graph_description() * @see $this->get_open_graph_description_from_custom_field() * * @return string Open Graph description. */ protected function get_custom_open_graph_description_from_query() { if ( $this->is_real_front_page() ) { if ( $this->is_static_frontpage() ) { $desc = $this->get_option( 'homepage_og_description' ) ?: $this->get_post_meta_item( '_open_graph_description' ) ?: $this->get_description_from_custom_field( null, false ); } else { $desc = $this->get_option( 'homepage_og_description' ) ?: $this->get_description_from_custom_field( null, false ); } } elseif ( $this->is_singular() ) { $desc = $this->get_post_meta_item( '_open_graph_description' ) ?: $this->get_description_from_custom_field( null, false ); } elseif ( $this->is_term_meta_capable() ) { $desc = $this->get_term_meta_item( 'og_description' ) ?: $this->get_description_from_custom_field( null, false ); } elseif ( \is_post_type_archive() ) { $desc = $this->get_post_type_archive_meta_item( 'og_description' ) ?: $this->get_description_from_custom_field( null, false ); } return $desc ?? '' ?: ''; } /** * Returns the Open Graph meta description from custom field, based on arguments. * Falls back to meta description. * * @since 3.1.0 * @since 3.2.2 1. Now tests for the static frontpage metadata prior getting fallback data. * 2. Now obtains custom field data for terms. * @since 4.0.0 Added term meta item checks. * @since 4.2.0 1. No longer returns an escaped custom field description. * 2. Now supports the `$args['pta']` index. * @see $this->get_open_graph_description() * @see $this->get_open_graph_description_from_custom_field() * * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * @return string Open Graph description. */ protected function get_custom_open_graph_description_from_args( $args ) { if ( $args['taxonomy'] ) { $desc = $this->get_term_meta_item( 'og_description', $args['id'] ) ?: $this->get_description_from_custom_field( $args, false ); } elseif ( $args['pta'] ) { $desc = $this->get_post_type_archive_meta_item( 'og_description', $args['pta'] ) ?: $this->get_description_from_custom_field( $args, false ); } else { if ( $this->is_static_frontpage( $args['id'] ) ) { $desc = $this->get_option( 'homepage_og_description' ) ?: $this->get_post_meta_item( '_open_graph_description', $args['id'] ) ?: $this->get_description_from_custom_field( $args, false ); } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) { $desc = $this->get_option( 'homepage_og_description' ) ?: $this->get_description_from_custom_field( $args, false ); } else { $desc = $this->get_post_meta_item( '_open_graph_description', $args['id'] ) ?: $this->get_description_from_custom_field( $args, false ); } } return $desc ?: ''; } /** * Returns the Twitter meta description. * Falls back to Open Graph description. * * @since 3.0.4 * @since 3.1.0 1. Now tries to get the homepage social descriptions. * 2. The first argument now accepts an array, with "id" and "taxonomy" fields. * @since 4.2.0 Now supports the `$args['pta']` index. * @uses $this->get_twitter_description_from_custom_field() * @uses $this->get_generated_twitter_description() * * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * Leave null to autodetermine query. * @param bool $escape Whether to escape the description. * @return string The real Twitter description output. */ public function get_twitter_description( $args = null, $escape = true ) { $desc = $this->get_twitter_description_from_custom_field( $args, false ) ?: $this->get_generated_twitter_description( $args, false ); return $escape ? $this->escape_description( $desc ) : $desc; } /** * Returns the Twitter meta description from custom field. * Falls back to Open Graph description. * * @since 3.1.0 * @see $this->get_twitter_description() * * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * Leave null to autodetermine query. * @param bool $escape Whether to escape the title. * @return string Twitter description. */ protected function get_twitter_description_from_custom_field( $args, $escape ) { if ( null === $args ) { $desc = $this->get_custom_twitter_description_from_query(); } else { $this->fix_generation_args( $args ); $desc = $this->get_custom_twitter_description_from_args( $args ); } return $escape ? $this->escape_description( $desc ) : $desc; } /** * Returns the Twitter meta description from custom field, based on query. * Falls back to Open Graph description. * * @since 3.1.0 * @since 3.2.2 1. Now tests for the static frontpage metadata prior getting fallback data. * 2. Now obtains custom field data for terms. * @since 4.0.0 Added term meta item checks. * @since 4.2.0 1. No longer returns an escaped custom field description. * 2. Now returns custom descriptions for post type archives. * @see $this->get_twitter_description() * @see $this->get_twitter_description_from_custom_field() * * @return string Twitter description. */ protected function get_custom_twitter_description_from_query() { if ( $this->is_real_front_page() ) { if ( $this->is_static_frontpage() ) { $desc = $this->get_option( 'homepage_twitter_description' ) ?: $this->get_post_meta_item( '_twitter_description' ) ?: $this->get_option( 'homepage_og_description' ) ?: $this->get_post_meta_item( '_open_graph_description' ) ?: $this->get_description_from_custom_field( null, false ); } else { $desc = $this->get_option( 'homepage_twitter_description' ) ?: $this->get_option( 'homepage_og_description' ) ?: $this->get_description_from_custom_field( null, false ); } } elseif ( $this->is_singular() ) { $desc = $this->get_post_meta_item( '_twitter_description' ) ?: $this->get_post_meta_item( '_open_graph_description' ) ?: $this->get_description_from_custom_field( null, false ); } elseif ( $this->is_term_meta_capable() ) { $desc = $this->get_term_meta_item( 'tw_description' ) ?: $this->get_term_meta_item( 'og_description' ) ?: $this->get_description_from_custom_field( null, false ); } elseif ( \is_post_type_archive() ) { $desc = $this->get_post_type_archive_meta_item( 'tw_description' ) ?: $this->get_post_type_archive_meta_item( 'og_description' ) ?: $this->get_description_from_custom_field( null, false ); } return $desc ?? '' ?: ''; } /** * Returns the Twitter meta description from custom field, based on arguments. * Falls back to Open Graph description. * * @since 3.1.0 * @since 3.2.2 1. Now tests for the static frontpage metadata prior getting fallback data. * 2. Now obtains custom field data for terms. * @since 4.0.0 Added term meta item checks. * @since 4.2.0 1. No longer returns an escaped custom field description. * 2. Now supports the `$args['pta']` index. * @see $this->get_twitter_description() * @see $this->get_twitter_description_from_custom_field() * * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * @return string Twitter description. */ protected function get_custom_twitter_description_from_args( $args ) { if ( $args['taxonomy'] ) { $desc = $this->get_term_meta_item( 'tw_description', $args['id'] ) ?: $this->get_term_meta_item( 'og_description', $args['id'] ) ?: $this->get_description_from_custom_field( $args, false ); } elseif ( $args['pta'] ) { $desc = $this->get_post_type_archive_meta_item( 'tw_description', $args['pta'] ) ?: $this->get_post_type_archive_meta_item( 'og_description', $args['pta'] ) ?: $this->get_description_from_custom_field( $args, false ); } else { if ( $this->is_static_frontpage( $args['id'] ) ) { $desc = $this->get_option( 'homepage_twitter_description' ) ?: $this->get_post_meta_item( '_twitter_description', $args['id'] ) ?: $this->get_option( 'homepage_og_description' ) ?: $this->get_post_meta_item( '_open_graph_description', $args['id'] ) ?: $this->get_description_from_custom_field( $args, false ); } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) { $desc = $this->get_option( 'homepage_twitter_description' ) ?: $this->get_option( 'homepage_og_description' ) ?: $this->get_description_from_custom_field( $args, false ); } else { $desc = $this->get_post_meta_item( '_twitter_description', $args['id'] ) ?: $this->get_post_meta_item( '_open_graph_description', $args['id'] ) ?: $this->get_description_from_custom_field( $args, false ); } } return $desc ?: ''; } /** * Returns the custom user-inputted description. * * @since 3.0.6 * @since 3.1.0 The first argument now accepts an array, with "id" and "taxonomy" fields. * @since 4.2.0 Now supports the `$args['pta']` index. * * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * Leave null to autodetermine query. * @param bool $escape Whether to escape the description. * @return string The custom field description. */ public function get_description_from_custom_field( $args = null, $escape = true ) { if ( null === $args ) { $desc = $this->get_custom_description_from_query(); } else { $this->fix_generation_args( $args ); $desc = $this->get_custom_description_from_args( $args ); } /** * @since 2.9.0 * @since 3.0.6 1. Duplicated from $this->generate_description() (deprecated) * 2. Removed all arguments but the 'id' argument. * @since 4.2.0 1. No longer gets supplied custom query arguments when in the loop. * 2. Now supports the `$args['pta']` index. * @param string $desc The custom-field description. * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'. * Is null when query is autodetermined. */ $desc = (string) \apply_filters_ref_array( 'the_seo_framework_custom_field_description', [ $desc, $args, ] ); return $escape ? $this->escape_description( $desc ) : $desc; } /** * Gets a custom description, based on expected or current query, without escaping. * * @since 3.1.0 * @since 3.2.2 Now tests for the static frontpage metadata prior getting fallback data. * @since 4.2.0 Now returns custom descriptions for post type archives. * @internal * @see $this->get_description_from_custom_field() * * @return string The custom description. */ protected function get_custom_description_from_query() { if ( $this->is_real_front_page() ) { if ( $this->is_static_frontpage() ) { $desc = $this->get_option( 'homepage_description' ) ?: $this->get_post_meta_item( '_genesis_description' ); } else { $desc = $this->get_option( 'homepage_description' ); } } elseif ( $this->is_singular() ) { $desc = $this->get_post_meta_item( '_genesis_description' ); } elseif ( $this->is_term_meta_capable() ) { $desc = $this->get_term_meta_item( 'description' ); } elseif ( \is_post_type_archive() ) { /** * @since 4.0.6 * @since 4.2.0 Deprecated. * @deprecated Use options instead. * @param string $desc The post type archive description. */ $desc = (string) \apply_filters_deprecated( 'the_seo_framework_pta_description', [ $this->get_post_type_archive_meta_item( 'description' ) ?: '' ], '4.2.0 of The SEO Framework' ); } return $desc ?? '' ?: ''; } /** * Gets a custom description, based on input arguments query, without escaping. * * @since 3.1.0 * @since 3.2.2 Now tests for the static frontpage metadata prior getting fallback data. * @since 4.2.0 Now supports the `$args['pta']` index. * @internal * @see $this->get_description_from_custom_field() * * @param array $args Array of 'id' and 'taxonomy' values. * @return string The custom description. */ protected function get_custom_description_from_args( $args ) { if ( $args['taxonomy'] ) { $desc = $this->get_term_meta_item( 'description', $args['id'] ); } elseif ( $args['pta'] ) { $desc = $this->get_post_type_archive_meta_item( 'description', $args['pta'] ); } else { if ( $this->is_static_frontpage( $args['id'] ) ) { $desc = $this->get_option( 'homepage_description' ) ?: $this->get_post_meta_item( '_genesis_description', $args['id'] ); } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) { $desc = $this->get_option( 'homepage_description' ); } else { $desc = $this->get_post_meta_item( '_genesis_description', $args['id'] ); } } return $desc ?: ''; } /** * Returns the autogenerated meta description. * * @since 3.0.6 * @since 3.1.0 1. The first argument now accepts an array, with "id" and "taxonomy" fields. * 2. No longer caches. * 3. Now listens to option. * 4. Added type argument. * @since 3.1.2 1. Now omits additions when the description will be deemed too short. * 2. Now no longer converts additions into excerpt when no excerpt is found. * @since 3.2.2 Now converts HTML characters prior trimming. * @since 4.2.0 Now supports the `$args['pta']` index. * @uses $this->generate_description() * @TODO Should we enforce a minimum description length, where this result is ignored? e.g., use the input * guidelines' 'lower' value as a minimum, so that TSF won't ever generate "bad" descriptions? * This isn't truly helpful, since then search engines can truly fetch whatever with zero guidance. * * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * Leave null to autodetermine query. * @param bool $escape Whether to escape the description. * @param string $type Type of description. Accepts 'search', 'opengraph', 'twitter'. * @return string The generated description output. */ public function get_generated_description( $args = null, $escape = true, $type = 'search' ) { if ( ! $this->is_auto_description_enabled( $args ) ) return ''; if ( ! \in_array( $type, [ 'opengraph', 'twitter', 'search' ], true ) ) $type = 'search'; if ( null === $args ) { $excerpt = $this->get_description_excerpt_from_query(); } else { $this->fix_generation_args( $args ); $excerpt = $this->get_description_excerpt_from_args( $args ); } /** * @since 2.9.0 * @since 3.1.0 No longer passes 3rd and 4th parameter. * @since 4.0.0 1. Deprecated second parameter. * 2. Added third parameter: $args. * @since 4.2.0 Now supports the `$args['pta']` index. * @param string $excerpt The excerpt to use. * @param int $page_id Deprecated. * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'. * Is null when query is autodetermined. */ $excerpt = (string) \apply_filters_ref_array( 'the_seo_framework_fetched_description_excerpt', [ $excerpt, 0, $args, ] ); // This page has a generated description that's far too short: https://theseoframework.com/em-changelog/1-0-0-amplified-seo/. // A direct directory-'site:' query will accept the description outputted--anything else will ignore it... // We should not work around that, because it won't direct in the slightest what to display. $excerpt = $this->trim_excerpt( $excerpt, 0, $this->get_input_guidelines()['description'][ $type ]['chars']['goodUpper'] ); /** * @since 2.9.0 * @since 3.1.0 No longer passes 3rd and 4th parameter. * @since 4.2.0 Now supports the `$args['pta']` index. * @param string $desc The generated description. * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'. * Is null when query is autodetermined. */ $desc = (string) \apply_filters_ref_array( 'the_seo_framework_generated_description', [ $excerpt, $args, ] ); return $escape ? $this->escape_description( $desc ) : $desc; } /** * Returns the autogenerated Twitter meta description. Falls back to meta description. * * @since 3.0.4 * @since 4.2.0 Now supports the `$args['pta']` index. * * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * Leave null to autodetermine query. * @param bool $escape Whether to escape the description. * @return string The generated Twitter description output. */ public function get_generated_twitter_description( $args = null, $escape = true ) { return $this->get_generated_description( $args, $escape, 'twitter' ); } /** * Returns the autogenerated Open Graph meta description. Falls back to meta description. * * @since 3.0.4 * @since 4.2.0 Now supports the `$args['pta']` index. * @uses $this->generate_description() * * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * Leave null to autodetermine query. * @param bool $escape Whether to escape the description. * @return string The generated Open Graph description output. */ public function get_generated_open_graph_description( $args = null, $escape = true ) { return $this->get_generated_description( $args, $escape, 'opengraph' ); } /** * Returns a description excerpt for the current query. * * @since 3.1.0 * @since 4.2.0 Flipped order of query tests. * * @return string */ protected function get_description_excerpt_from_query() { // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- I know. if ( null !== $memo = memo() ) return $memo; if ( $this->is_real_front_page() ) { $excerpt = $this->get_front_page_description_excerpt(); } elseif ( $this->is_home_as_page() ) { $excerpt = $this->get_blog_page_description_excerpt(); } elseif ( $this->is_singular() ) { $excerpt = $this->get_singular_description_excerpt(); } elseif ( $this->is_archive() ) { $excerpt = $this->get_archival_description_excerpt(); } return memo( $excerpt ?? '' ?: '' ); } /** * Returns a description excerpt for the current query. * * @since 3.1.0 * @since 3.2.2 Fixed front-page as blog logic. * @since 4.2.0 Now supports the `$args['pta']` index. * * @param array $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * @return string */ protected function get_description_excerpt_from_args( $args ) { if ( $args['taxonomy'] ) { $excerpt = $this->get_archival_description_excerpt( \get_term( $args['id'], $args['taxonomy'] ) ); } elseif ( $args['pta'] ) { $excerpt = $this->get_archival_description_excerpt( \get_post_type_object( $args['pta'] ) ); } else { if ( $this->is_real_front_page_by_id( $args['id'] ) ) { $excerpt = $this->get_front_page_description_excerpt(); } elseif ( $this->is_home_as_page( $args['id'] ) ) { $excerpt = $this->get_blog_page_description_excerpt(); } else { $excerpt = $this->get_singular_description_excerpt( $args['id'] ); } } return $excerpt ?? '' ?: ''; } /** * Returns a description excerpt for the blog page. * * @since 3.1.0 * * @return string */ protected function get_blog_page_description_excerpt() { return $this->get_description_additions( [ 'id' => (int) \get_option( 'page_for_posts' ) ] ); } /** * Returns a description excerpt for the front page. * * @since 3.1.0 * * @return string */ protected function get_front_page_description_excerpt() { $id = $this->get_the_front_page_ID(); return ( $id ? $this->get_singular_description_excerpt( $id ) : '' ) ?: $this->get_description_additions( [ 'id' => $id ] ); } /** * Returns a description excerpt for archives. * * @since 3.1.0 * @since 4.0.0 Now processes HTML tags via s_excerpt_raw() for the author descriptions. * @since 4.2.0 Now uses post type archive descriptions to prefill meta descriptions. * * @param null|\WP_Term|\WP_Post_Type $object The term or post type object. * @return string */ protected function get_archival_description_excerpt( $object = null ) { if ( $object && \is_wp_error( $object ) ) return ''; if ( \is_null( $object ) ) { $in_the_loop = true; $object = \get_queried_object(); } else { $in_the_loop = false; } /** * @since 3.1.0 * @see `\tsf()->s_excerpt_raw()` to strip HTML tags neatly. * @param string $excerpt The short circuit excerpt. * @param \WP_Term|\WP_Post_Type $object The Term object or post type object. */ $excerpt = (string) \apply_filters_ref_array( 'the_seo_framework_generated_archive_excerpt', [ '', $object, ] ); if ( $excerpt ) return $excerpt; if ( $in_the_loop ) { if ( $this->is_category() || $this->is_tag() || $this->is_tax() ) { // WordPress DOES NOT allow HTML in term descriptions, not even if you're a super-administrator. // See https://wpvulndb.com/vulnerabilities/9445. We won't parse HTMl tags unless WordPress adds native support. $excerpt = $this->s_description_raw( $object->description ?? '' ); } elseif ( $this->is_author() ) { $excerpt = $this->s_excerpt_raw( \get_the_author_meta( 'description', (int) \get_query_var( 'author' ) ) ); } elseif ( \is_post_type_archive() ) { /** * @since 4.0.6 * @since 4.2.0 Now provides the post type object description, if assigned. * @param string $excerpt The archive description excerpt. * @param \WP_Term|\WP_Post_Type $object The post type object. */ $excerpt = (string) \apply_filters_ref_array( 'the_seo_framework_pta_description_excerpt', [ $this->s_description_raw( $object->description ?? '' ), $object, ] ); } else { /** * @since 4.0.6 * @since 4.1.0 Added the $object object parameter. * @param string $excerpt The fallback archive description excerpt. * @param \WP_Term $object The Term object. */ $excerpt = (string) \apply_filters_ref_array( 'the_seo_framework_fallback_archive_description_excerpt', [ '', $object, ] ); } } else { $excerpt = $this->s_description_raw( $object->description ?? '' ); } return $excerpt; } /** * Returns a description excerpt for singular post types. * * @since 3.1.0 * NOTE: Don't add memo; large memory heaps can occur. * It only runs twice on the post edit screen (post.php). * Front-end caller get_description_excerpt_from_query() uses memo. * * @param int $id The singular ID. * @return string */ protected function get_singular_description_excerpt( $id = null ) { $id = $id ?? $this->get_the_real_ID(); // If the post is protected, don't generate a description. if ( $this->is_protected( $id ) ) return ''; return $this->get_excerpt_by_id( '', $id, null, false ); } /** * Returns additions for "Title on Site Title". * * @since 3.1.0 * @since 3.2.0 1. Now no longer listens to options. * 2. Now only works for the front and blog pages. * @since 3.2.2 Now works for homepages from external requests. * @since 4.2.0 No longer adds "on Blogname". * @see $this->get_generated_description() * * @param array $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * This method should always have 'taxonomy' set to ''. * @return string The description additions. */ protected function get_description_additions( $args ) { if ( $this->is_real_front_page_by_id( $args['id'] ) ) { $title = $this->get_home_title_additions(); } elseif ( $this->is_home_as_page( $args['id'] ) ) { $title = sprintf( /* translators: %s = Blog page title. Front-end output. */ \__( 'Latest posts: %s', 'autodescription' ), $this->get_filtered_raw_generated_title( $args ) ); } return $title ?? '' ?: ''; } /** * Fetches or parses the excerpt of the post. * * @since 1.0.0 * @since 2.8.2 Added 4th parameter for escaping. * @since 3.1.0 1. No longer returns anything for terms. * 2. Now strips plausible embeds URLs. * @since 4.0.1 The second parameter `$id` now defaults to int 0, instead of an empty string. * TODO deprecate and simplify (remove $excerpt and $deprecated). * * @param string $excerpt The Excerpt. * @param int $id The Post ID. * @param null $deprecated No longer used. * @param bool $escape Whether to escape the excerpt. * @return string The trimmed excerpt. */ public function get_excerpt_by_id( $excerpt = '', $id = 0, $deprecated = null, $escape = true ) { if ( empty( $excerpt ) ) $excerpt = $this->fetch_excerpt( $id ); // No need to parse an empty excerpt. if ( ! $excerpt ) return ''; return $escape ? $this->s_excerpt( $excerpt ) : $this->s_excerpt_raw( $excerpt ); } /** * Fetches excerpt from post excerpt or fetches the full post content. * Determines if a page builder is used to return an empty string. * Does not sanitize output. * * @since 2.5.2 * @since 2.6.6 Detects Page builders. * @since 3.1.0 1. No longer returns anything for terms. * 2. Now strips plausible embeds URLs. * @since 4.0.1 Now fetches the real ID when no post is supplied. * Internally, this was never an issue. @see `$this->get_singular_description_excerpt()` * @since 4.2.8 1. Now tests for post type support of 'excerpt' before parsing the excerpt. * 2. Now tests for post type support of 'editor' before parsing the content. * * @param \WP_Post|int|null $post The Post or Post ID. Leave null to get current post. * @return string The excerpt. */ public function fetch_excerpt( $post = null ) { $post = \get_post( $post ?: $this->get_the_real_ID() ); if ( ! empty( $post->post_excerpt ) && \post_type_supports( $post->post_type, 'excerpt' ) ) { $excerpt = $post->post_excerpt; } elseif ( ! empty( $post->post_content ) && ! $this->uses_non_html_page_builder( $post->ID ) ) { // We should actually get the parsed content here... but that can be heavy on the server. // We could cache that parsed content, but that'd be asinine for a plugin. WordPress should've done that. $excerpt = $this->get_post_content( $post ); if ( $excerpt ) { $excerpt = $this->strip_newline_urls( $excerpt ); $excerpt = $this->strip_paragraph_urls( $excerpt ); } } return $excerpt ?? ''; } /** * Trims the excerpt by word and determines sentence stops. * * Warning: Returns with entities encoded. The output is not safe for printing. * * @since 2.6.0 * @since 3.1.0 1. Now uses smarter trimming. * 2. Deprecated 2nd parameter. * 3. Now has unicode support for sentence closing. * 4. Now strips last three words when preceded by a sentence closing separator. * 5. Now always leads with (inviting) dots, even if the excerpt is shorter than $max_char_length. * @since 4.0.0 1. Now stops parsing earlier on failure. * 2. Now performs faster queries. * 3. Now maintains last sentence with closing punctuations. * @since 4.0.5 1. Now decodes the excerpt input, improving accuracy, and so that HTML entities at * the end won't be transformed into gibberish. * @since 4.1.0 1. Now texturizes the excerpt input, improving accuracy with included closing & final punctuation support. * 2. Now performs even faster queries, in most situations. (0.2ms/0.02ms total (worst/best) @ PHP 7.3/PCRE 11). * Mind you, this method probably boots PCRE and wptexturize; so, it'll be slower than what we noted--it's * overhead that otherwise WP, the theme, or other plugin would cause anyway. So, deduct that. * 3. Now recognizes connector and final punctuations for preliminary sentence bounding. * 4. Leading punctuation now excludes symbols, special annotations, opening brackets and quotes, * and marks used in some latin languages like ¡¿. * 5. Is now able to always strip leading punctuation. * 6. It will now strip leading colon characters. * 7. It will now stop counting trailing words towards new sentences when a connector, dash, mark, or ¡¿ is found. * 8. Now returns encoded entities once more. So that the return value can be treated the same as anything else * revolving around descriptions--preventing double transcoding like `&amp; > & > &` instead of `&`. * @since 4.1.5 1. The second parameter now accepts values again. From "current description length" to minimum accepted char length. * 2. Can now return an empty string when the input string doesn't satisfy the minimum character length. * 3. The third parameter now defaults to 4096, so no longer unexpected results are created. * 4. Resolved some backtracking issues. * 5. Resolved an issue where a character followed by punctuation would cause the match to fail. * @since 4.2.0 Now enforces at least a character length of 1. This prevents needless processing. * @since 4.2.7 Now considers floating numerics as one word. * @see https://secure.php.net/manual/en/regexp.reference.unicode.php * * We use `[^\P{Po}\'\"]` because WordPress texturizes ' and " to fall under `\P{Po}`. * This is perfect. Please have the courtesy to credit us when taking it. :) * * @param string $excerpt The untrimmed excerpt. Expected not to contain any HTML operators. * @param int $min_char_length The minimum character length. Set to 0 to ignore the requirement. * This is read as a SUGGESTION. Multibyte characters will create inaccuracies. * @param int $max_char_length At what point to shave off the excerpt. * @return string The trimmed excerpt with encoded entities. Needs escaping prior printing. */ public function trim_excerpt( $excerpt, $min_char_length = 1, $max_char_length = 4096 ) { // At least 1. $min_char_length = max( 1, $min_char_length ); // We should _actually_ use mb_strlen, but that's wasteful on resources for something benign. // We'll rectify that later, somewhat, where characters are transformed. // We could also use preg_match_all( '/./u' ); or count( preg_split( '/./u', $excerpt, $min_char_length ) ); // But, again, that'll eat CPU cycles. if ( \strlen( $excerpt ) < $min_char_length ) return ''; // Decode to get a more accurate character length in Unicode. $excerpt = html_entity_decode( $excerpt, ENT_QUOTES, 'UTF-8' ); // Find all words with $max_char_length, and trim when the last word boundary or punctuation is found. preg_match( sprintf( '/.{0,%d}([^\P{Po}\'\":]|[\p{Pc}\p{Pd}\p{Pf}\p{Z}]|\Z){1}/su', $max_char_length ), trim( $excerpt ), $matches ); $excerpt = trim( $matches[0] ?? '' ?: '' ); if ( \strlen( $excerpt ) < $min_char_length ) return ''; // Texturize to recognize the sentence structure. Decode thereafter since we get HTML returned. $excerpt = html_entity_decode( \wptexturize( htmlentities( $excerpt, ENT_QUOTES, 'UTF-8' ) ), ENT_QUOTES, 'UTF-8' ); /** * Play with it here: https://regex101.com/r/u0DIgx/5/ (old) https://regex101.com/r/G92lUt/5 (new) * * TODO Group 4's match is repeated. However, referring to it as (4) will cause it to congeal into 3. * * TODO .+[\p{Pe}\p{Pf}](*THEN)\Z still backtracks; it should just find \Z and see if one char is in front of it. * -> [^\p{Pe}\p{Pf}]++.*?[\p{Pe}\p{Pf}]+?\Z would solve it... but I don't trust it; it's populating 4 and 5 in edge-cases. * * TODO we can futher optimize this by capturing the last 4 words and refer to that. Of thence more than 3 words * found, we could simply end the query, mitigating all forms of backtracking. For now, backtracking cannot * exceed step-count=($max_char_length*2+56) = 160*2+56 = 376, which is perfectly acceptable as a 'worst case'. * * Critically optimized, so the $matches don't make much sense. Bear with me: * * @param array $matches : { * 0 : Full excerpt. * 1 : Sentence after leading punctuation (if any), but including opening punctuation, marks, and ¡¿, before first punctuation (if any). * 2 : First one character following [1], always some form of punctuation. Won't be set if [3] is set. * 3 : Following [1] until last punctuation that isn't some sort of connecting punctiation that's leading a word-boundary. * 4 : First three words leading [3]. Connecting punctuations that splits words are included as non-countable. * 5 : All extraneous characters leading [3] and/or [4]. If this isn't set, forgo including 4--it won't be meaningful. * } */ preg_match( '/(?:\A[\p{P}\p{Z}]*?)?([\P{Po}\p{M}\xBF\xA1:\p{Z}]+[\p{Z}\w])(?:([^\P{Po}\p{M}\xBF\xA1:]\Z(*ACCEPT))|((?(?=.+(?:\w+[\p{Pc}\p{Pd}\p{Pf}\p{Z}]*){1,3}|[\p{Po}]\Z)(?:.+[\p{Pe}\p{Pf}](*THEN)\Z(*ACCEPT)|.*[^\P{Po}\p{M}\xBF\xA1:][^\P{Nd}\p{Z}]*)|.*\Z(*ACCEPT)))(?>(.+?\p{Z}*(?:\w+[\p{Pc}\p{Pd}\p{Pf}\p{Z}]*){1,3})|[^\p{Pc}\p{Pd}\p{M}\xBF\xA1:])?)(.+)?/su', $excerpt, $matches ); if ( isset( $matches[5] ) ) { $excerpt = "$matches[1]$matches[3]$matches[4]$matches[5]"; // Skip 4. It's useless content without 5. } elseif ( isset( $matches[3] ) ) { $excerpt = "$matches[1]$matches[3]"; } elseif ( isset( $matches[2] ) ) { $excerpt = "$matches[1]$matches[2]"; } elseif ( isset( $matches[1] ) ) { $excerpt = $matches[1]; } // else { TODO Should we empty excerpt here? Can we even reach this? } if ( \strlen( $excerpt ) < $min_char_length ) return ''; /** * @param array $matches: { * 1 : Full match until leading punctuation. * 2 : Spaces before (if any) and including closing leading punctuation (if any). * 3 : Non-closing leading punctuation and spaces (if any). * } */ preg_match( '/(.+[^\p{Pc}\p{Pd}\p{M}\xBF\xA1:;,\p{Z}\p{Po}])+?(\p{Z}*?[^\p{Pc}\p{Pd}\p{M}\xBF\xA1:;,\p{Z}]+)?([\p{Pc}\p{Pd}\p{M}\xBF\xA1:;,\p{Z}]+)?/su', $excerpt, $matches ); // Why can $matches[2] still be populated with 3 set? Does it populate empty results upward to last, always??? if ( isset( $matches[2] ) && \strlen( $matches[2] ) ) { $excerpt = "$matches[1]$matches[2]"; } elseif ( isset( $matches[1] ) && \strlen( $matches[1] ) ) { // Ignore useless [3], there's no [2], [1] is open-ended; so, add hellip. $excerpt = "$matches[1]..."; // This should be texturized later to …. } else { // If there's no matches[1], only some form of non-closing-leading punctuation was left in $excerpt. Empty it. $excerpt = ''; } if ( \strlen( $excerpt ) < $min_char_length ) return ''; return trim( htmlentities( $excerpt, ENT_QUOTES, 'UTF-8' ) ); } /** * Determines whether automated descriptions are enabled. * * @since 3.1.0 * @since 4.2.0 1. Now fixes the input arguments. * 2. Now supports the `$args['pta']` index. * @access private * @see $this->get_the_real_ID() * @see $this->get_current_taxonomy() * * @param array|null $args The query arguments. Accepts 'id', 'taxonomy', and 'pta'. * Leave null to autodetermine query. * @return bool */ public function is_auto_description_enabled( $args = null ) { if ( null !== $args ) $this->fix_generation_args( $args ); /** * @since 2.5.0 * @since 3.0.0 Now passes $args as the second parameter. * @since 3.1.0 Now listens to option. * @since 4.2.0 Now supports the `$args['pta']` index. * @param bool $autodescription Enable or disable the automated descriptions. * @param array|null $args The query arguments. Contains 'id', 'taxonomy', and 'pta'. * Is null when query is autodetermined. */ return (bool) \apply_filters_ref_array( 'the_seo_framework_enable_auto_description', [ $this->get_option( 'auto_description' ), $args, ] ); } }