wecobble/Subtitles

View on GitHub
public/class-subtitles.php

Summary

Maintainability
C
7 hrs
Test Coverage
<?php
/**
* @package Subtitles
*/
 
/**
* Do not load this file directly
*
* @since 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
die( '-1' );
}
 
/**
* Checks for the existence of Subtitles before defining it.
*
* @link http://www.php.net//manual/en/function.class-exists.php
*
* @since 1.0.0
*/
if ( ! class_exists( 'Subtitles' ) ) {
/**
* Define primary Subtitles class.
*
* Within classes constants and variables are called properties.
* Within classes functions are called methods.
*
* @link http://php.net/manual/en/language.oop5.php
*
* @since 1.0.0
*/
class Subtitles {
/**
* Constant used for plugin versioning and enqueues.
*
* Constants differ from normal variables in that you don't use the $ symbol
* to declare or use them. The value must be a constant expression, not (for example)
* a variable, a property, a result of a mathematical operation, or a function call.
*
* Semantic versioning is used in this plugin.
*
* @link http://semver.org/
*
* @since 1.0.0
*/
const VERSION = '4.0.0';
 
/**
* Constant used when referencing the plugin in load text domain calls and other
* functionality that relies on the slug of the plugin being present.
*
* @since 1.0.0
*/
const PLUGIN_SLUG = 'subtitles';
 
/**
* Constant used in subtitle support checks for custom post types.
*
* @link http://codex.wordpress.org/Post_Types
*
* @since 1.0.0
*/
const SUBTITLE_FEATURE_SUPPORT = 'subtitles';
 
/**
* Constant used in nonce checks when saving custom subtitles data.
*
* @link http://codex.wordpress.org/WordPress_Nonces
*
* @since 1.0.0
*/
const SUBTITLE_NONCE_NAME = '_subtitle_data_nonce';
 
/**
* Constant used for subtitle meta key.
*
* Note the underscore before the custom meta for subtitles. This ensures that
* the meta key doesn't appear in the custom fields section on the new post
* and page screens and is only used within our classes. This is considered
* protected meta.
*
* @link http://codex.wordpress.org/Custom_Fields
*
* @since 1.0.0
*/
const SUBTITLE_META_KEY = '_subtitle';
 
/**
* If no instance of Subtitles has been made then let's make it.
*
* Declaring class properties or methods as static makes them accessible
* without needing an instantiation of the class Subtitles.
*
* For example, this is how you would access a static property:
* Subtitles::$instance
*
* And this is how you would access a static method:
* Subtitles::getinstance()
*
* @link http://www.php.net/manual/en/language.oop5.late-static-bindings.php
* @var object $instance
* @access private
* @static
*
* @since 1.0.0
*/
private static $instance = null;
 
/**
* Kick off the class if it hasn't been instantiated.
*
* There is a lot of information about the Singleton design pattern
* and singleton implementations for WordPress and plugins. I am not (at all!)
* an expert on this and may very well be instantiating Subtitles in the
* wrong way. If there's a better way to do this, or if it's not necessary
* to use this design pattern with the plugin, please let me know. The reason
* I've done it this way is because plugins should never be instantiated twice
* in WordPress. In general, I assume that this won't happen under normal circumstances,
* but using this design pattern ensures that if someone tries to instantiate
* Subtitles twice, then it won't be possible to do so.
*
* For more reading, see the following links.
*
* @link http://en.wikipedia.org/wiki/Singleton_pattern
* @link http://hardcorewp.com/2013/using-singleton-classes-for-wordpress-plugins/
* @link http://eamann.com/tech/the-case-for-singletons/
* @link http://www.toppa.com/2013/the-case-against-singletons-in-wordpress/
* @link http://eamann.com/tech/making-singletons-safe-in-php/
*
* @staticvar Singleton $instance The Singleton instance of this class.
* @return Singleton The Singleton instance of this class.
* @access public
* @static
*
* @since 1.0.0
*/
public static function getinstance() {
if ( ! self::$instance ) {
self::$instance = new Subtitles;
}
 
return self::$instance;
} // end method getinstance
 
/**
* Declare constructor methods for the class Subtitles.
*
* Classes which have a constructor method call this method on each newly-created object,
* so it is suitable for any initialization that the object may need before it is used.
*
* Access is set to protected to prevent outside access to the method for anything other than
* the class or a child class. Otherwise `new` could be used to kick off this constructor if
* it were public.
*
* @access protected
*
* @since 1.0.0
*/
protected function __construct() {
/**
* Add default support for subtitles on posts and pages.
*
* To find out the number and name of arguments for any action in WordPress,
* search Core for the matching do_action call. For example, searching for
* "do_action( 'save_post'" reveals that two arguments, $post_id and $post are used.
*
* add_action accepts:
*
* - $tag The name of the action to which the $function_to_add is hooked.
* - callback function The name of the function you wish to be called.
* - int $priority optional. Used to specify the order in which the functions
* associated with a particular action are executed (default: 10).
* Lower numbers correspond with earlier execution, and functions with the
* same priority are executed in the order in which they were added to the action.
* - int $accepted_args optional. The number of arguments the function accept (default: 1).
*
* Support for post types needs to be fired on `init`.
*
* @see add_action()
* @link http://codex.wordpress.org/Function_Reference/add_action
*
* @since 1.0.0
*/
add_action( 'init', array( &$this, 'add_subtitles_support' ) );
 
/**
* Make Subtitles available for translation.
*
* Translations can be added into the /languages/ directory.
* @link http://codex.wordpress.org/Translating_WordPress
*
* @since 1.0.0
*/
add_action( 'init', array( &$this, 'load_subtitles_textdomain' ) );
 
/**
* Output front-end styles for Subtitles via wp_head
*
* @see add_action()
* @since 2.0.0
*/
add_action( 'wp_head', array( &$this, 'subtitle_styling' ) );
 
/**
* Filter post titles to display subtitles properly.
*
* add_filter accepts:
*
* - $tag The name of the filter to hook the $function_to_add callback to.
* - $function_to_add The callback to be run when the filter is applied.
* - $priority (optional) The order in which the functions associated with a
* particular action are executed. Lower numbers correspond with
* earlier execution, and functions with the same priority are
* executed in the order in which they were added to the action.
* Default: 10.
* $accepted_args (optional) The number of arguments the function accepts.
* Default: 1.
*
* @see add_filter()
* @link http://codex.wordpress.org/Function_Reference/add_filter
*
* @since 1.0.0
*/
if ( ! is_admin() ) { // Don't touch anything inside of the WordPress Dashboard, yet.
add_filter( 'the_title', array( &$this, 'the_subtitle' ), 10, 2 );
 
/**
* Let's also filter the dedicated function for single post titles
*
* @link https://github.com/wecobble/Subtitles/issues/2
*
* @since 1.0.1
*/
add_filter( 'single_post_title', array( &$this, 'the_subtitle' ), 10, 2 );
 
/**
* Make sure that Subtitles plays nice with WordPress SEO plugin by Yoast
*
* @link https://wordpress.org/plugins/wordpress-seo/
* @link https://github.com/wecobble/Subtitles/issues/5
*
* @since 1.0.1
* @since 4.0.0 - Deprecate the wp_seo_get_bc_title filter and use
* wpseo_breadcrumb_single_link_info instead.
* See https://yoast.com/yoast-seo-5-8/
*/
add_filter( 'wpseo_breadcrumb_single_link_info', array( &$this, 'plugin_compat_wordpress_seo' ) );
}
} // end method __construct
 
/**
* Make sure that Subtitles plays nice with WordPress SEO plugin by Yoast.
*
* The plugin features breadcrumb functionality, which users can add into their themes
* by using functionality that's specific to the plugin. Because it's so popular and
* I'm not sure where or how people will insert their breadcrumbs into their template
* files, it's best avoid messing with the plugin altogether. We'll filter the output
* and make sure that subtitles isn't included in any of the breadcrumb titles.
*
* @link https://wordpress.org/plugins/wordpress-seo/
* @link https://github.com/wecobble/Subtitles/issues/5
* @link http://us1.php.net//manual/en/function.strlen.php
* @see get_the_subtitle()
*
* @since 1.0.1
*/
Function `plugin_compat_wordpress_seo` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.
public function plugin_compat_wordpress_seo( $breadcrumb_text ) {
/**
* This issue only arrises when breadcrumbs are placed inside of The Loop,
* so we'll first check to see if we're in The Loop, and if not, just bail
* on this altogether.
*
* @link http://codex.wordpress.org/Function_Reference/in_the_loop
*
* @since 1.0.1
*/
$in_the_loop = (bool) in_the_loop();
if ( ! $in_the_loop ) {
return $breadcrumb_text;
}
 
/**
* Grab the post subtitle.
*
* Example: (string) "Subtitle"
*
* @see get_the_subtitle()
*
* @since 1.0.1
*/
$post_subtitle = get_the_subtitle();
 
/**
* Grab the length of the post subtitle.
*
* We're also using a negative value of the legnth of the subtitle.
*
* Example: (int) 8
*
* @link http://us1.php.net//manual/en/function.strlen.php
*
* @since 1.0.1
*/
$post_subtitle_length = (int) strlen( $post_subtitle );
$post_subtitle_length_neg = -1 * $post_subtitle_length;
 
/**
* Grab the already filtered post title.
*
* Example: (string) "Post TitleSubtitle"
*/
$post_title = (string) $breadcrumb_text['text'];
 
/**
* Grab the length of the filtered post title.
*
* Example: (int) 18
*/
$post_title_length = (int) strlen( $post_title );
 
/**
* Check for a few specific cases:
*
* 1. Does the subtitle of the current post equal the title of any of its ancestors?
* 2. Is the post title that's being checked empty?
*
* If so, then bail out on this.
*
* Example: If the subtitle of the page is "Features" and one of the parent breadcrumbs also
* has the name "Features", then we don't want to mess that up, so we'll make sure that we bail out
* early on this function.
*
* @since 1.0.1
*/
if ( ( $post_title == $post_subtitle ) || ( '' == $post_title ) ) {
return $breadcrumb_text;
}
 
/**
* Remove the subtitle from the "title" string so that it shows up properly.
*
* Example: If the post title that we've brought into the function is called "Post TitleSubtitle",
* then all we really want is the the title to say "Post Title", so we'll use the length of the subtitle
* to cut that many characters off of the end of the post title, and hope to end up with "Post Title".
*
* @see apply_filters()
* @link http://us2.php.net//manual/en/function.substr.php
*
* @since 1.0.1
*/
$post_title = substr( $post_title, 0, $post_subtitle_length_neg );
$post_title = apply_filters( 'compat_wordpress_seo', $post_title );
 
/**
* Make sure that the new title and its subtitle is the right string that
* we want to manipulate, by comparing the post title + subtitle against
* the original title that we brought into this function.
*
* Example: The original title brought in was "Post TitleSubtitle", so we'll
* make sure that "Post Title" + "Subtitle" is actually "Post TitleSubtitle".
*
* @since 1.0.1
*/
$reconstructed_title = $post_title . $post_subtitle;
 
if ( $reconstructed_title == $breadcrumb_text['text'] ) {
$breadcrumb_text['text'] = $post_title;
return $breadcrumb_text;
}
 
// else just return the title that was brought into the function
return $breadcrumb_text;
} // end plugin_compat_wordpress_seo()
 
/**
* Add default support for subtitles on posts and pages.
*
* @since 1.0.0
*/
public function add_subtitles_support() {
/**
* Automatically enable subtitles support on posts.
*
* This can be overridden within themes.
*
* @see add_post_type_support()
* @link http://codex.wordpress.org/Function_Reference/add_post_type_support
* @see SUBTITLE_FEATURE_SUPPORT
*
* @since 1.0.0
*/
add_post_type_support( 'post', self::SUBTITLE_FEATURE_SUPPORT );
 
/**
* Automatically enable subtitles support on pages.
*
* This can be overridden within themes.
*
* @see add_post_type_support()
* @link http://codex.wordpress.org/Function_Reference/add_post_type_support
* @see SUBTITLE_FEATURE_SUPPORT
*
* @since 1.0.0
*/
add_post_type_support( 'page', self::SUBTITLE_FEATURE_SUPPORT );
 
/**
* Automatically enable subtitles support for Jetpack Portfolios
*
* This can be overridden within themes.
*
* @see add_post_type_support()
* @link http://codex.wordpress.org/Function_Reference/add_post_type_support
* @link https://jetpack.com/support/custom-content-types/
* @see SUBTITLE_FEATURE_SUPPORT
*
* @since 1.0.7
*/
add_post_type_support( 'jetpack-portfolio', self::SUBTITLE_FEATURE_SUPPORT );
 
/**
* Automatically enable subtitles support for Jetpack Testimonials
*
* This can be overridden within themes.
*
* @see add_post_type_support()
* @link http://codex.wordpress.org/Function_Reference/add_post_type_support
* @link https://jetpack.com/support/custom-content-types/
* @see SUBTITLE_FEATURE_SUPPORT
*
* @since 2.2.0
*/
add_post_type_support( 'jetpack-testimonial', self::SUBTITLE_FEATURE_SUPPORT );
} // end add_subtitles_support()
 
/**
* Declare __clone as private to prevent cloning an instance of the class via `clone`.
*
* @link http://www.php.net/manual/en/language.oop5.cloning.php
* @access protected
*
* @since 1.0.0
*/
protected function __clone() {
 
} // end method __clone()
 
/**
* Prevent unserializing.
*
* @link http://php.net/function.unserialize
* @access public
*
* @since 1.0.0
*/
public function __wakeup() {
// i18n won't work on this exception so there's no need to add it into the language pack
throw new Exception( 'This Singleton cannot be unserialized.' );
} // end method __wakeup()
 
/**
* Make Subtitles available for translation.
*
* @link https://ulrich.pogson.ch/load-theme-plugin-translations
* @link http://ottopress.com/2013/language-packs-101-prepwork/
*
* @since 1.0.0
*/
public function load_subtitles_textdomain() {
$domain = self::PLUGIN_SLUG;
$locale = apply_filters( 'plugin_locale', get_locale(), $domain );
 
/**
* Load a .mo file into the text domain $domain.
*
* If the text domain already exists, the translations will be merged. If both
* sets have the same string, the translation from the original value will be taken.
*
* On success, the .mo file will be placed in the $l10n global by $domain
* and will be a MO object.
*
* @param string $domain Text domain. Unique identifier for retrieving translated strings.
* @param string $mofile Path to the .mo file.
* @return bool True on success, false on failure.
*
* @since 1.0.0
*/
load_textdomain( $domain, trailingslashit( WP_LANG_DIR ) . $domain . '/' . $domain . '-' . $locale . '.mo' );
 
/**
* Load a plugin's translated strings.
*
* If the path is not given then it will be the root of the plugin directory.
*
* The .mo file should be named based on the text domain with a dash, and then the locale exactly.
*
* @param string $domain Unique identifier for retrieving translated strings
* @param string $deprecated Use the $plugin_rel_path parameter instead.
* @param string $plugin_rel_path Optional. Relative path to WP_PLUGIN_DIR where the .mo file resides.
*
* @since 1.0.0
*/
load_plugin_textdomain( $domain, false, basename( plugin_dir_path( dirname( __FILE__ ) ) ) . '/languages/' );
}
 
/**
* Output front-end styles for Subtitles via wp_head
*
* @access public
* @since 2.0.0
*/
public function subtitle_styling() {
?>
<style type="text/css" media="screen">
/**
* Plugin Name: Subtitles
* Plugin URI: http://wordpress.org/plugins/subtitles/
* Description: Easily add subtitles into your WordPress posts, pages, custom post types, and themes.
* Author: <a href="https://philip.blog/">Philip Arthur Moore</a>, <a href="https://wecobble.com">We Cobble</a>
* Author URI: https://wecobble.com/
* Version: 4.0.0
* License: GNU General Public License v2 or later
* License URI: http://www.gnu.org/licenses/gpl-2.0.html
*/
 
/**
* Be explicit about this styling only applying to spans,
* since that's the default markup that's returned by
* Subtitles. If a developer overrides the default subtitles
* markup with another element or class, we don't want to stomp
* on that.
*
* @since 1.0.0
*/
span.entry-subtitle {
display: block; /* Put subtitles on their own line by default. */
font-size: 0.53333333333333em; /* Sensible scaling. It's assumed that post titles will be wrapped in heading tags. */
}
/**
* If subtitles are shown in comment areas, we'll hide them by default.
*
* @since 1.0.5
*/
#comments .comments-title span.entry-subtitle {
display: none;
}
</style><?php
} // end function subtitle_styling
 
/**
* Output the subtitle
*
* @since 1.0.0
*/
Function `the_subtitle` has a Cognitive Complexity of 19 (exceeds 5 allowed). Consider refactoring.
Method `the_subtitle` has 46 lines of code (exceeds 25 allowed). Consider refactoring.
public function the_subtitle( $title, $id = null ) {
/**
* Which globals will we need?
*
* @since 1.0.0
*/
global $post;
 
/**
* Check if $post is set. There's a chance that this can
* be NULL on search results pages with zero results.
*
* @link https://github.com/wecobble/Subtitles/issues/12
*
* @since 1.0.2
*/
if ( ! isset( $post ) ) {
return $title;
}
 
/**
* Make sure we're not touching any of the titles in the Dashboard
* This filtering should only happen on the front end of the site.
*
* @since 1.0.0
*/
if ( is_admin() ) {
return $title;
}
 
/**
* Do not show subtitles in RSS feeds, but give devs. the option
* to turn this off.
*
* @since 2.0.1
*/
$is_feed = apply_filters( 'subtitles_is_feed', is_feed() );
if ( $is_feed ) {
return $title;
}
 
/**
* Bail early if no subtitle has been set for the post.
*
* @since 1.0.0
*/
$post_id = (int) absint( $post->ID ); // post ID should always be a non-negative integer
$subtitle = (string) html_entity_decode( wp_unslash( esc_html( get_post_meta( $post_id, self::SUBTITLE_META_KEY, true ) ) ), ENT_QUOTES );
 
/**
* Allow theme and plugin authors to override the early return if no subtitle exists.
*
* @see https://github.com/wecobble/Subtitles/issues/79
* @since 2.2.0
*/
$subtitle_exists = ! empty( $subtitle );
$subtitle_exists = apply_filters( 'subtitle_exists', $subtitle_exists );
 
if ( ! $subtitle_exists ) {
return $title;
}
 
/**
* Bail if the title being filtered isn't the actual primary post title.
*
* This can happen when something is used within the loop that outputs titles,
* like navigation between single posts on a blog.
*
* $id should not be an object, and if it is then the single_post_title() filter is likely
* being invoked, so we'll need a check against that.
*
* Here's a more detailed explanation:
*
* 1. the_title() can be filtered with apply_filters( 'the_title', $title, $id ).
* This means that the second argument needs to be an integer, which will be the ID of a post.
* 2. single_post_title() can be filtered with apply_filters( 'single_post_title', $_post->post_title, $_post ).
* This means that the second argument needs to be the entire post object.
*
* So if we're checking $id and it's an object, then there's a good chance that single_post_title()
* is being used and we need to reassign $id to be ID of the object WP_Post.
*
* @see the_title()
* @see single_post_title()
* @see get_the_ID()
* @see is_object()
*
* @since 1.0.0
*/
if ( isset( $id ) && is_object( $id ) ) { // single_post_title() is being used.
$id = $id->ID; // grab the ID from the post object.
}
if ( isset( $post->post_title ) && get_the_ID() != $id ) {
Avoid too many `return` statements within this method.
return $title;
}
 
/**
* Make sure we're in The Loop. If a theme maker wants to create
* a custom loop via WP_Query, then he can filter subtitle_view_supported.
*
* @see in_the_loop()
* @link http://codex.wordpress.org/Function_Reference/in_the_loop
*
* @since 1.0.0
*/
$subtitle_view_supported = true;
 
$in_the_loop = (bool) in_the_loop();
if ( ! $in_the_loop ) {
$subtitle_view_supported = false;
}
 
/**
* Allow subtitle views to be filtered by theme developers.
*
* @see apply_filters()
* @link http://codex.wordpress.org/Function_Reference/apply_filters
*
* @since 1.0.0
*/
$subtitle_view_supported = (bool) apply_filters( 'subtitle_view_supported', $subtitle_view_supported );
 
/**
* If no subtitle support is active for the given view
* then simply return the post title.
*
* @since 1.0.0
*/
if ( ! $subtitle_view_supported ) {
Avoid too many `return` statements within this method.
return $title;
}
 
/**
* Bail if the current post type doesn't support Subtitles.
*
* The good news here is that if Subtitles have been entered in for a user,
* but support for a specific post type has been removed, then the subtitles
* won't be displayed on the front-end of the site but will still be retained
* in the database. This is useful for child themes and such who want to override
* the look and feel of a theme that features subtitles.
*
* @since 1.0.0
*/
if ( ! post_type_supports( $post->post_type, self::SUBTITLE_FEATURE_SUPPORT ) ) {
Avoid too many `return` statements within this method.
return $title;
}
 
/**
* Let theme authors modify the subtitle markup, in case spans aren't appropriate
* for what they are trying to do with their themes.
*
* The reason that spans are being used is because HTML does not have a dedicated
* mechanism for marking up subheadings, alternative titles, or taglines. There
* are suggested alternatives from the World Wide Web Consortium (W3C); among them
* are spans, which work well for what we're trying to do with titles in WordPress.
* See the linked documentation for more information.
*
* @link http://www.w3.org/html/wg/drafts/html/master/common-idioms.html#sub-head
*
* @since 1.0.0
*/
$subtitle_markup = apply_filters(
'subtitle_markup',
array(
'before' => '<span class="entry-subtitle">',
'after' => '</span>',
)
);
 
$subtitle = $subtitle_markup['before'] . $subtitle . $subtitle_markup['after'];
 
/**
* Put together the final title and subtitle set
*
* @since 1.0.0
*/
$title = '<span class="entry-title-primary">' . $title . '</span> ' . $subtitle;
 
/**
* Filter the post subtitle, if necessary.
*
* @param string $title The post title.
* @param int $id The post ID.
*
* @since 1.0.0
*/
Avoid too many `return` statements within this method.
return apply_filters( 'the_subtitle', $title );
}
 
/**
* Get the Subtitle.
*
* This is a helper method used to grab the subtitle for any given post,
* which theme authors can use with the template tag get_the_subtitle().
*
* @since 1.0.0
*/
public static function get_the_subtitle( $post = 0 ) {
/**
* Bail early if no subtitle has been set for the post.
*
* @since 1.0.0
*/
$post = get_post( $post ); // this will be returned as an object or NULL
$post_id = ( isset( $post ) ) ? $post->ID : 0; // post ID should always be a non-negative integer
$subtitle = (string) html_entity_decode( wp_unslash( esc_html( get_post_meta( $post_id, self::SUBTITLE_META_KEY, true ) ) ), ENT_QUOTES );
 
if ( '' === $subtitle ) {
return $subtitle;
}
 
/**
* Filter the post subtitle.
*
* @since 1.0.0
*
* @param string $subtitle The post subtitle.
* @param int $id The post ID.
*/
return apply_filters( 'the_subtitle', $subtitle );
} // end get_the_subtitle()
} // end class Subtitles
} // End if().