diff --git a/.gitattributes b/.gitattributes index 1929f83..abb9643 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,5 +4,6 @@ .gitattributes export-ignore .wordpress-org export-ignore .editorconfig export-ignore -phpcs.xml export-ignore +composer.json export-ignore docker-compose.yml export-ignore +phpcs.xml export-ignore diff --git a/.github/workflows/phpcs.yml b/.github/workflows/phpcs.yml new file mode 100644 index 0000000..0959f33 --- /dev/null +++ b/.github/workflows/phpcs.yml @@ -0,0 +1,31 @@ +name: PHP_CodeSniffer +on: + push: + pull_request: +jobs: + phpcs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + coverage: none + tools: composer, cs2pr + - name: Get Composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Setup cache + uses: pat-s/always-upload-cache@v1.1.4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + # Use the hash of composer.json as the key for your cache if you do not commit composer.lock. + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + #key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + - name: Install dependencies + run: composer install --prefer-dist --no-progress + - name: Detect coding standard violations + run: ./vendor/bin/phpcs \ No newline at end of file diff --git a/.gitignore b/.gitignore index bd16e8f..e9850c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/vendor/ .DS_Store -.vscode/settings.json +.vscode +composer.lock \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ede36b5 --- /dev/null +++ b/composer.json @@ -0,0 +1,28 @@ +{ + "name": "pfefferle/wordpress-opengraph", + "description": "Adds Open Graph metadata to your posts and pages so that they look great when shared on sites like Facebook and Twitter.", + "type": "wordpress-plugin", + "license": "Apache License, Version 2.0", + "require": { + "php": ">=7.0", + "composer/installers": "^1.0 || ^2.0" + }, + "require-dev": { + "phpcompatibility/php-compatibility": "*", + "phpcompatibility/phpcompatibility-wp": "*", + "squizlabs/php_codesniffer": "3.*", + "wp-coding-standards/wpcs": "dev-develop", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", + "sirbrillig/phpcs-variable-analysis": "^2.11", + "phpcsstandards/phpcsextra": "^1.1.0" + }, + "config": { + "allow-plugins": true + }, + "allow-plugins": { + "composer/installers": true + }, + "extra": { + "installer-name": "opengraph" + } +} diff --git a/opengraph.php b/opengraph.php index 25c94b4..88f67ba 100644 --- a/opengraph.php +++ b/opengraph.php @@ -5,13 +5,14 @@ * Description: Adds Open Graph metadata to your pages * Author: Will Norris & Matthias Pfefferle * Author URI: https://github.com/pfefferle/wordpress-opengraph - * Version: 1.12.2 + * Version: 2.0.0 * License: Apache License, Version 2.0 * License URI: http://www.apache.org/licenses/LICENSE-2.0.html * Text Domain: opengraph + * + * @package opengraph */ - // If you have the opengraph plugin running alongside jetpack, we assume you'd // rather use our opengraph support, so disable jetpack's opengraph functionality. add_filter( 'jetpack_enable_opengraph', '__return_false' ); @@ -19,15 +20,17 @@ // Disable strict mode by default. -if ( ! defined( 'OPENGRAPH_STRICT_MODE' ) ) { - define( 'OPENGRAPH_STRICT_MODE', false ); -} +defined( 'OPENGRAPH_STRICT_MODE' ) || define( 'OPENGRAPH_STRICT_MODE', false ); /** * Add Open Graph XML prefix to element. * * @uses apply_filters calls 'opengraph_prefixes' filter on RDFa prefix array + * + * @param string $output The current list of prefixes. + * + * @return string The updated list of prefixes. */ function opengraph_add_prefix( $output ) { $prefixes = array( @@ -54,6 +57,10 @@ function opengraph_add_prefix( $output ) { /** * Add additional prefix namespaces that are supported by the opengraph plugin. + * + * @param array $prefixes The current list of prefixes. + * + * @return array The updated list of prefixes. */ function opengraph_additional_prefixes( $prefixes ) { if ( is_author() ) { @@ -77,45 +84,70 @@ function opengraph_additional_prefixes( $prefixes ) { function opengraph_metadata() { $metadata = array(); - // default properties defined at http://ogp.me/ + // Default properties defined at http://ogp.me/. $properties = array( - // required - 'title', - 'type', - 'image', - 'url', - - // optional - 'audio', - 'description', - 'determiner', - 'locale', - 'site_name', - 'video', + // Required properties. + 'title' => '', + 'type' => '', + 'image' => array(), + 'url' => '', + + // Optional properties. + 'audio' => array(), + 'description' => '', + 'determiner' => '', + 'locale' => '', + 'site_name' => '', + 'video' => array(), ); - foreach ( $properties as $property ) { + foreach ( $properties as $property => $default ) { $filter = 'opengraph_' . $property; - $metadata[ "og:$property" ] = apply_filters( $filter, '' ); + /** + * Filter the Open Graph metadata. + * + * @param array $default The default value. + */ + $metadata[ "og:$property" ] = apply_filters( $filter, $default ); } - $twitter_properties = array( 'card', 'creator' ); + $twitter_properties = array( + 'card' => '', + 'creator' => '', + ); - foreach ( $twitter_properties as $property ) { + foreach ( $twitter_properties as $property => $default ) { $filter = 'twitter_' . $property; - $metadata[ "twitter:$property" ] = apply_filters( $filter, '' ); + /** + * Filter the Twitter Card metadata. + * + * @param array $default The default value. + */ + $metadata[ "twitter:$property" ] = apply_filters( $filter, $default ); } - $fediverse_properties = array( 'creator' ); + $fediverse_properties = array( + 'creator' => array(), + ); - foreach ( $fediverse_properties as $property ) { + foreach ( $fediverse_properties as $property => $default ) { $filter = 'fediverse_' . $property; - $metadata[ "fediverse:$property" ] = apply_filters( $filter, '' ); + /** + * Filter the Fediverse metadata. + * + * @param array $default The default value. + */ + $metadata[ "fediverse:$property" ] = apply_filters( $filter, $default ); } + /** + * Filter the Open Graph metadata. + * + * @param array $metadata The metadata array. + */ return apply_filters( 'opengraph_metadata', $metadata ); } @@ -124,32 +156,38 @@ function opengraph_metadata() { * Register filters for default Open Graph metadata. */ function opengraph_default_metadata() { - // core metadata attributes + // Core metadata attributes. add_filter( 'opengraph_title', 'opengraph_default_title', 5 ); add_filter( 'opengraph_type', 'opengraph_default_type', 5 ); - add_filter( 'opengraph_image', 'opengraph_default_image', 5 ); add_filter( 'opengraph_url', 'opengraph_default_url', 5 ); + // Image metadata attributes with fallbacks. + add_filter( 'opengraph_image', 'opengraph_default_image', 5 ); + add_filter( 'opengraph_image', 'opengraph_block_image', 15 ); + add_filter( 'opengraph_image', 'opengraph_parsed_image', 25 ); + add_filter( 'opengraph_image', 'opengraph_attached_image', 25 ); + add_filter( 'opengraph_image', 'opengraph_fallback_image', 35 ); + add_filter( 'opengraph_description', 'opengraph_default_description', 5 ); add_filter( 'opengraph_locale', 'opengraph_default_locale', 5 ); add_filter( 'opengraph_site_name', 'opengraph_default_sitename', 5 ); add_filter( 'opengraph_audio', 'opengraph_default_audio', 5 ); add_filter( 'opengraph_video', 'opengraph_default_video', 5 ); - // additional prefixes + // Additional prefixes. add_filter( 'opengraph_prefixes', 'opengraph_additional_prefixes' ); - // additional profile metadata + // Additional profile metadata. add_filter( 'opengraph_metadata', 'opengraph_profile_metadata' ); - // additional article metadata + // Additional article metadata. add_filter( 'opengraph_metadata', 'opengraph_article_metadata' ); - // twitter card metadata + // twitter card metadata. add_filter( 'twitter_card', 'twitter_default_card', 5 ); add_filter( 'twitter_creator', 'twitter_default_creator', 5 ); - // fediverse creator metadata + // fediverse creator metadata. add_filter( 'fediverse_creator', 'fediverse_default_creator', 5 ); } add_action( 'wp', 'opengraph_default_metadata' ); @@ -157,20 +195,24 @@ function opengraph_default_metadata() { /** * Default title property, using the page title. + * + * @param string $title The current title. + * + * @return string The title. */ function opengraph_default_title( $title ) { if ( $title ) { return $title; } - // set default title, because twitter is requiring one + // Set default title, because twitter is requiring one. $title = __( 'Untitled', 'opengraph' ); if ( is_home() || is_front_page() ) { $title = get_bloginfo( 'name' ); } elseif ( is_singular() ) { $title = get_the_title( get_queried_object_id() ); - // fall back to description + // Fall back to description. if ( empty( $title ) ) { $title = opengraph_default_description( null, 5 ); } @@ -183,7 +225,11 @@ function opengraph_default_title( $title ) { $title = single_tag_title( '', false ); } elseif ( is_archive() && get_post_format() ) { $title = get_post_format_string( get_post_format() ); - } elseif ( is_archive() && function_exists( 'get_the_archive_title' ) && get_the_archive_title() ) { // new in version 4.1 to get all other archive titles + } elseif ( + is_archive() && + function_exists( 'get_the_archive_title' ) && + get_the_archive_title() + ) { // New in version 4.1 to get all other archive titles. $title = get_the_archive_title(); } @@ -193,8 +239,12 @@ function opengraph_default_title( $title ) { /** * Default type property. + * + * @param string $type The current type. + * + * @return string The type. */ -function opengraph_default_type( $type ) { +function opengraph_default_type( $type = '' ) { if ( empty( $type ) ) { if ( is_singular( array( 'post', 'page' ) ) ) { $type = 'article'; @@ -211,98 +261,240 @@ function opengraph_default_type( $type ) { /** * Default image property, using the post-thumbnail and any attached images. + * + * @param array $image The current list of images. + * + * @return array The list of images. */ -function opengraph_default_image( $image ) { - if ( $image ) { +function opengraph_default_image( $image = array() ) { + // Show avatar on profile pages. + if ( is_author() ) { + return array( get_avatar_url( get_the_author_meta( 'ID' ), array( 'size' => 512 ) ) ); + } + + if ( count( $image ) >= opengraph_max_images() ) { return $image; } - // show avatar on profile pages - if ( is_author() ) { - return get_avatar_url( get_the_author_meta( 'ID' ), array( 'size' => 512 ) ); + if ( is_attachment() && wp_attachment_is_image() ) { + $id = get_queried_object_id(); + $image[] = current( wp_get_attachment_image_src( $id, 'large' ) ?: array() ); // phpcs:ignore + } elseif ( is_singular() && ! is_attachment() ) { + $id = get_queried_object_id(); + + // List post thumbnail first if this post has one. + if ( function_exists( 'has_post_thumbnail' ) && has_post_thumbnail( $id ) ) { + $thumbnail_id = get_post_thumbnail_id( $id ); + $image[] = current( wp_get_attachment_image_src( $thumbnail_id, 'large' ) ?: array() ); // phpcs:ignore + } } - // As of July 2014, Facebook seems to only let you select from the first 3 images - $max_images = apply_filters( 'opengraph_max_images', 3 ); + return array_unique( $image ); +} - // max images can't be negative or zero - if ( $max_images <= 0 ) { - $max_images = 1; + +/** + * Block image property, using the first image in the post content. + * + * @param array $image The current list of images. + * + * @return array The list of images. + */ +function opengraph_block_image( $image = array() ) { + if ( + ! opengraph_site_supports_blocks() || + count( $image ) >= opengraph_max_images() || + ! is_singular() || + is_attachment() + ) { + return $image; } - if ( is_singular() && ! is_attachment() ) { - $id = get_queried_object_id(); - $image_ids = array(); + // Get the first image in the post content. + $blocks = parse_blocks( get_the_content( null, false ) ); + foreach ( $blocks as $block ) { + if ( + 'core/image' === $block['blockName'] || + 'core/cover' === $block['blockName'] + ) { + $id = $block['attrs']['id']; + $image[] = current( wp_get_attachment_image_src( $id, 'large' ) ?: array() ); // phpcs:ignore + } + } - // list post thumbnail first if this post has one - if ( function_exists( 'has_post_thumbnail' ) && has_post_thumbnail( $id ) ) { - $image_ids[] = get_post_thumbnail_id( $id ); - } else { - // otherwise list any image attachments - $query = new WP_Query( - array( - 'post_parent' => $id, - 'post_status' => 'inherit', - 'post_type' => 'attachment', - 'post_mime_type' => 'image', - 'order' => 'ASC', - 'orderby' => 'menu_order ID', - 'posts_per_page' => $max_images, - ) - ); - - foreach ( $query->get_posts() as $attachment ) { - if ( ! in_array( $attachment->ID, $image_ids, true ) ) { - $image_ids[] = $attachment->ID; - } - } + return array_unique( $image ); +} + + +/** + * Parse images in the HTML content. + * + * @param array $image The current list of images. + * + * @return array The list of images. + */ +function opengraph_parsed_image( $image = array() ) { + // If someone calls that function directly, bail. + if ( + ! \class_exists( 'WP_HTML_Tag_Processor' ) || + ! opengraph_site_supports_blocks() || + count( $image ) >= opengraph_max_images() || + ! is_singular() || + is_attachment() + ) { + return $image; + } + + $post_id = get_queried_object_id(); + $base = wp_upload_dir()['baseurl']; + $content = get_post_field( 'post_content', $post_id ); + $tags = new WP_HTML_Tag_Processor( $content ); + + // This linter warning is a false positive - we have to re-count each time here as we modify $images. + // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found + while ( $tags->next_tag( 'img' ) && ( count( $image ) <= opengraph_max_images() ) ) { + $src = $tags->get_attribute( 'src' ); + + /* + * If the img source is in our uploads dir, get the + * associated ID. Note: if there's a -500x500 + * type suffix, we remove it, but we try the original + * first in case the original image is actually called + * that. Likewise, we try adding the -scaled suffix for + * the case that this is a small version of an image + * that was big enough to get scaled down on upload: + * https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/ + */ + if ( null === $src || ! str_starts_with( $src, $base ) ) { + continue; } - // get URLs for each image - $image = array(); - foreach ( $image_ids as $id ) { - $thumbnail = wp_get_attachment_image_src( $id, 'full' ); - if ( $thumbnail ) { - $image[] = $thumbnail[0]; + $img_id = attachment_url_to_postid( $src ); + + if ( 0 === $img_id ) { + $count = 0; + $src = strtok( $src, '?' ); + $img_id = attachment_url_to_postid( $src ); + } + + if ( 0 === $img_id ) { + $count = 0; + $src = preg_replace( '/-(?:\d+x\d+)(\.[a-zA-Z]+)$/', '$1', $src, 1, $count ); + if ( $count > 0 ) { + $img_id = attachment_url_to_postid( $src ); } } - } elseif ( is_attachment() && wp_attachment_is_image() ) { - $id = get_queried_object_id(); - $image = array( wp_get_attachment_url( $id ) ); + + if ( 0 === $img_id ) { + $src = preg_replace( '/(\.[a-zA-Z]+)$/', '-scaled$1', $src ); + $img_id = attachment_url_to_postid( $src ); + } + + if ( 0 !== $img_id ) { + $image[] = current( wp_get_attachment_image_src( $img_id, 'large' ) ?: array() ); // phpcs:ignore + } } - if ( empty( $image ) ) { - $image = array(); + return array_unique( $image ); +} - // add header images - if ( function_exists( 'get_uploaded_header_images' ) ) { - if ( is_random_header_image() ) { - foreach ( get_uploaded_header_images() as $header_image ) { - $image[] = $header_image['url']; - if ( sizeof( $image ) >= $max_images ) { - break; - } - } - } elseif ( get_header_image() ) { - $image[] = get_header_image(); - } +/** + * Attached images. + * + * @param array $image The current list of images. + * + * @return array The list of images. + */ +function opengraph_attached_image( $image = array() ) { + $max_images = opengraph_max_images(); + + if ( + count( $image ) >= $max_images || + ! is_singular() || is_attachment() + ) { + return $image; + } + + $id = get_queried_object_id(); + + $query = new WP_Query( + array( + 'post_parent' => $id, + 'post_status' => 'inherit', + 'post_type' => 'attachment', + 'post_mime_type' => 'image', + 'order' => 'ASC', + 'orderby' => 'menu_order ID', + 'fields' => 'ids', + 'posts_per_page' => $max_images, + ) + ); + + $image_ids = $query->get_posts(); + + // Get URLs for each image. + foreach ( $image_ids as $id ) { + $thumbnail = wp_get_attachment_image_src( $id, 'large' ); + if ( $thumbnail ) { + $image[] = $thumbnail[0]; } + } - // add site icon - if ( empty( $image ) && function_exists( 'get_site_icon_url' ) && has_site_icon() ) { - $image[] = get_site_icon_url( 512 ); + return array_unique( $image ); +} + +/** + * Fallback image property, using the site icon, custom logo, or header images. + * + * @param array $image The current list of images. + * + * @return array The list of images. + */ +function opengraph_fallback_image( $image = array() ) { + if ( $image ) { + return $image; + } + + $max_images = opengraph_max_images(); + + // Try site icon. + if ( function_exists( 'get_site_icon_url' ) && has_site_icon() ) { + $image[] = get_site_icon_url( 512 ); + } + + // Try custom logo second. + if ( empty( $image ) ) { + $custom_logo = get_theme_mod( 'custom_logo' ); + $image[] = wp_get_attachment_image_src( $custom_logo, 'large' ); + } + + // Try header images. + if ( empty( $image ) && function_exists( 'get_uploaded_header_images' ) ) { + if ( is_random_header_image() ) { + foreach ( get_uploaded_header_images() as $header_image ) { + $image[] = $header_image['url']; + if ( count( $image ) >= $max_images ) { + break; + } + } + } elseif ( get_header_image() ) { + $image[] = get_header_image(); } } - return $image; + return array_unique( $image ); } /** * Default audio property, using get_attached_media. + * + * @param array $audio The current list of audio files. + * + * @return array The list of audio files. */ -function opengraph_default_audio( $audio ) { +function opengraph_default_audio( $audio = array() ) { $id = get_queried_object_id(); $attachments = get_attached_media( 'audio', $id ); @@ -310,10 +502,6 @@ function opengraph_default_audio( $audio ) { return $audio; } - if ( empty( $audio ) ) { - $audio = array(); - } - foreach ( $attachments as $attachment ) { $audio[] = wp_get_attachment_url( $attachment->ID ); } @@ -324,8 +512,12 @@ function opengraph_default_audio( $audio ) { /** * Default video property, using get_attached_media. + * + * @param array $video The current list of video files. + * + * @return array The list of video files. */ -function opengraph_default_video( $video ) { +function opengraph_default_video( $video = array() ) { $id = get_queried_object_id(); $attachments = get_attached_media( 'video', $id ); @@ -333,10 +525,6 @@ function opengraph_default_video( $video ) { return $video; } - if ( empty( $video ) ) { - $video = array(); - } - foreach ( $attachments as $attachment ) { $video[] = wp_get_attachment_url( $attachment->ID ); } @@ -347,8 +535,12 @@ function opengraph_default_video( $video ) { /** * Default url property, using the permalink for the page. + * + * @param string $url The current URL. + * + * @return string The URL. */ -function opengraph_default_url( $url ) { +function opengraph_default_url( $url = '' ) { if ( empty( $url ) ) { if ( is_singular() ) { $url = get_permalink(); @@ -363,8 +555,12 @@ function opengraph_default_url( $url ) { /** * Default site_name property, using the bloginfo name. + * + * @param string $name The current site name. + * + * @return string The site name. */ -function opengraph_default_sitename( $name ) { +function opengraph_default_sitename( $name = '' ) { if ( empty( $name ) ) { $name = get_bloginfo( 'name' ); } @@ -376,8 +572,13 @@ function opengraph_default_sitename( $name ) { /** * Default description property, using the excerpt or content for posts, or the * bloginfo description. + * + * @param string $description The current description. + * @param int $length The maximum length of the description. + * + * @return string The description. */ -function opengraph_default_description( $description, $length = 55 ) { +function opengraph_default_description( $description = '', $length = 55 ) { if ( $description ) { return $description; } @@ -385,8 +586,8 @@ function opengraph_default_description( $description, $length = 55 ) { if ( is_singular() ) { $post = get_queried_object(); if ( post_password_required( $post ) ) { - $description = __( 'This content is password protected.' ); - } else if ( ! empty( $post->post_excerpt ) ) { + $description = __( 'This content is password protected.', 'opengraph' ); + } elseif ( ! empty( $post->post_excerpt ) ) { $description = $post->post_excerpt; } else { $description = $post->post_content; @@ -398,14 +599,18 @@ function opengraph_default_description( $description, $length = 55 ) { $description = category_description(); } elseif ( is_tag() && tag_description() ) { $description = tag_description(); - } elseif ( is_archive() && function_exists( 'get_the_archive_description' ) && get_the_archive_description() ) { // new in version 4.1 to get all other archive descriptions + } elseif ( + is_archive() && + function_exists( 'get_the_archive_description' ) && + get_the_archive_description() + ) { // New in version 4.1 to get all other archive descriptions. $description = get_the_archive_description(); } else { $description = get_bloginfo( 'description' ); } // strip description to first 55 words. - $description = strip_tags( strip_shortcodes( $description ) ); + $description = wp_strip_all_tags( strip_shortcodes( $description ) ); $description = opengraph_trim_text( $description, $length ); return wp_strip_all_tags( $description ); @@ -414,8 +619,12 @@ function opengraph_default_description( $description, $length = 55 ) { /** * Default locale property, using the WordPress locale. + * + * @param string $locale The current locale. + * + * @return string The locale. */ -function opengraph_default_locale( $locale ) { +function opengraph_default_locale( $locale = '' ) { if ( empty( $locale ) ) { $locale = get_locale(); } @@ -426,23 +635,27 @@ function opengraph_default_locale( $locale ) { /** * Default twitter-card type. + * + * @param string $card The current card type. + * + * @return string The card type. */ -function twitter_default_card( $card ) { +function twitter_default_card( $card = '' ) { if ( $card ) { return $card; } $card = 'summary'; - $images = opengraph_default_image( null ); + $images = apply_filters( 'opengraph_image', array() ); - // show large image on... + // Show large image on... if ( is_singular() ) { if ( - // gallery and image posts + // Gallery and image posts. in_array( get_post_format(), array( 'image', 'gallery' ), true ) || - // posts with more than one image + // Posts with more than one image. ( is_array( $images ) && count( $images ) > 1 ) || - // posts with a post-thumbnail + // Posts with a post-thumbnail. ( function_exists( 'has_post_thumbnail' ) && has_post_thumbnail() ) ) { $card = 'summary_large_image'; @@ -455,8 +668,14 @@ function twitter_default_card( $card ) { /** * Default twitter-card creator. + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started + * + * @param string $creator The current creator. + * + * @return string The creator. */ -function twitter_default_creator( $creator ) { +function twitter_default_creator( $creator = '' ) { if ( $creator || ! is_singular() ) { return $creator; } @@ -469,10 +688,10 @@ function twitter_default_creator( $creator ) { return $creator; } - // check if twitter-account matches "http://twitter.com/username" + // Check if twitter-account matches "http://twitter.com/username". if ( preg_match( '/^http:\/\/twitter\.com\/(#!\/)?(\w+)/i', $twitter, $matches ) ) { $creator = '@' . $matches[2]; - } elseif ( preg_match( '/^@?(\w+)$/i', $twitter, $matches ) ) { // check if twitter-account matches "(@)username" + } elseif ( preg_match( '/^@?(\w+)$/i', $twitter, $matches ) ) { // Check if twitter-account matches "(@)username". $creator = '@' . $matches[1]; } @@ -484,8 +703,12 @@ function twitter_default_creator( $creator ) { * Default fediverse creator. * * @see https://github.com/mastodon/mastodon/pull/30398 + * + * @param string $creator The current creator. + * + * @return string The creator. */ -function fediverse_default_creator( $creator ) { +function fediverse_default_creator( $creator = '' ) { if ( ! is_singular() ) { return $creator; } @@ -517,9 +740,14 @@ function opengraph_meta_tags() { $value = (array) $value; foreach ( $value as $v ) { - // check if "strict mode" is enabled + // Skip empty values. + if ( empty( $v ) ) { + continue; + } + + // Check if "strict mode" is enabled. if ( OPENGRAPH_STRICT_MODE === true ) { - if ( // use "name" attribute for Twitter Cards + if ( // Use "name" attribute for Twitter Cards. str_starts_with( $key, 'twitter:' ) || str_starts_with( $key, 'fediverse:' ) ) { @@ -528,7 +756,7 @@ function opengraph_meta_tags() { esc_attr( $key ), esc_attr( $v ) ); - } else { // use "property" attribute for Open Graph + } else { // Use "property" attribute for Open Graph. printf( '' . PHP_EOL, esc_attr( $key ), @@ -536,7 +764,7 @@ function opengraph_meta_tags() { ); } } else { - // use "property" and "name" + // Use the "property" and "name" attributes. printf( '' . PHP_EOL, esc_attr( $key ), @@ -553,6 +781,10 @@ function opengraph_meta_tags() { * Include profile metadata for author pages. * * @link http://ogp.me/#type_profile + * + * @param array $metadata The current metadata. + * + * @return array The updated metadata. */ function opengraph_profile_metadata( $metadata ) { if ( is_author() ) { @@ -571,6 +803,10 @@ function opengraph_profile_metadata( $metadata ) { * Include article metadata for posts and pages. * * @link http://ogp.me/#type_article + * + * @param array $metadata The current metadata. + * + * @return array The updated metadata. */ function opengraph_article_metadata( $metadata ) { if ( ! is_singular() ) { @@ -580,7 +816,7 @@ function opengraph_article_metadata( $metadata ) { $post = get_queried_object(); $author = $post->post_author; - // check if page/post has tags + // Check if page/post has tags. $tags = wp_get_object_terms( $post->ID, 'post_tag' ); if ( $tags && is_array( $tags ) ) { foreach ( $tags as $tag ) { @@ -588,7 +824,7 @@ function opengraph_article_metadata( $metadata ) { } } - // check if page/post has categories + // Check if page/post has categories. $categories = wp_get_object_terms( $post->ID, 'category' ); if ( $categories && is_array( $categories ) ) { $metadata['article:section'][] = current( $categories )->name; @@ -610,10 +846,14 @@ function opengraph_article_metadata( $metadata ) { /** * Add "twitter" as a contact method + * + * @param array $user_contactmethods The current list of contact methods. + * + * @return array The updated list of contact methods. */ -function opengraph_user_contactmethods( $user_contactmethods ) { - $user_contactmethods['twitter'] = __( 'Twitter', 'opengraph' ); - $user_contactmethods['facebook'] = __( 'Facebook (Profile URL)', 'opengraph' ); +function opengraph_user_contactmethods( $user_contactmethods = array() ) { + $user_contactmethods['twitter'] = __( 'Twitter', 'opengraph' ); + $user_contactmethods['facebook'] = __( 'Facebook (Profile URL)', 'opengraph' ); $user_contactmethods['fediverse'] = __( 'Fediverse (username@host.tld)', 'opengraph' ); return $user_contactmethods; @@ -624,8 +864,9 @@ function opengraph_user_contactmethods( $user_contactmethods ) { /** * Add 512x512 icon size * - * @param array $sizes sizes available for the site icon - * @return array updated list of icons + * @param array $sizes Sizes available for the site icon. + * + * @return array updated list of icons. */ function opengraph_site_icon_image_sizes( $sizes ) { $sizes[] = 512; @@ -638,6 +879,11 @@ function opengraph_site_icon_image_sizes( $sizes ) { /** * Helper function to trim text using the same default values for length and * 'more' text as wp_trim_excerpt. + * + * @param string $text The text to trim. + * @param int $length The maximum number of words to include. + * + * @return string The trimmed text. */ function opengraph_trim_text( $text, $length = 55 ) { $excerpt_length = apply_filters( 'excerpt_length', $length ); @@ -647,11 +893,68 @@ function opengraph_trim_text( $text, $length = 55 ) { } /** - * str_starts_with function for PHP < 8.0 + * Get the maximum number of images to include in Open Graph metadata. + * + * @return int The maximum number of images to include. + */ +function opengraph_max_images() { + /** + * Filter the maximum number of images to include in Open Graph metadata. + * + * As of July 2014, Facebook seems to only let you select from the first 3 images. + * + * @param int $max_images The maximum number of images to include. + */ + $max_images = apply_filters( 'opengraph_max_images', 3 ); + + // Max images can't be negative or zero. + if ( $max_images <= 0 ) { + $max_images = 1; + } + + return $max_images; +} + +/** + * Check if a site supports the block editor. * - * @see https://www.php.net/manual/en/function.str-starts-with + * @return boolean True if the site supports the block editor, false otherwise. */ +function opengraph_site_supports_blocks() { + $return = true; + + if ( version_compare( get_bloginfo( 'version' ), '5.9', '<' ) ) { + $return = false; + } elseif ( function_exists( 'classicpress_version' ) ) { + $return = false; + } elseif ( + ! function_exists( 'register_block_type_from_metadata' ) || + ! function_exists( 'do_blocks' ) + ) { + $return = false; + } + + /** + * Allow plugins to disable block editor support, + * thus disabling blocks registered by the OpenGraph plugin. + * + * @param boolean $supports_blocks True if the site supports the block editor, false otherwise. + */ + return apply_filters( 'opengraph_site_supports_blocks', $return ); +} + + if ( ! function_exists( 'str_starts_with' ) ) { + /** + * `str_starts_with` function for PHP < 8.0. + * + * @see https://www.php.net/manual/en/function.str-starts-with + * + * @param string $haystack The string to search in. + * @param string $needle The string to search for. + * + * @return bool True if the string starts with the needle, false otherwise. + */ function str_starts_with( $haystack, $needle ) { return 0 === strncmp( $haystack, $needle, \strlen( $needle ) ); } diff --git a/phpcs.xml b/phpcs.xml index 6c9d3b6..d81057a 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,5 +1,33 @@ - + WordPress Coding Standard. - + . + + *\.(inc|css|js|svg) + */vendor/* + */node_modules/* + *.asset.php + + + + + + + + + + + + + + + + + + + + + + + diff --git a/readme.md b/readme.md index bfc1aca..bf4502b 100644 --- a/readme.md +++ b/readme.md @@ -4,7 +4,7 @@ - Tags: social, opengraph, ogp, facebook - Requires at least: 2.3 - Tested up to: 6.7 -- Stable tag: 1.12.2 +- Stable tag: 2.0.0 - License: Apache License, Version 2.0 - License URI: https://www.apache.org/licenses/LICENSE-2.0.html @@ -63,6 +63,13 @@ The plugin populates the meta 'name' attribute alongside the 'property' attribut Project maintained on github at [pfefferle/wordpress-opengraph](https://github.com/pfefferle/wordpress-opengraph). +### 2.0.0 (Nov XX. 2024) + + - complete rewrite of image handling + - added support for block images + - added parsing of HTML `` tags + - improved WordPress Coding Standards compliance + ### version 1.12.2 (Nov 17, 2024) - optimized readme and updated dependencies