diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 73da1815ea08528e216196746a390d7132a9e129..b44537577a8e94c6de0a4df2a5f46957f6cfc335 100755 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,2 +1,13 @@ include: - remote: https://git-cdn.e15r.co/gitlab/ci/wordpress/-/raw/main/plugins/default.yml + +# Plugin does not support 5.6 or 7.0. +PHPunit:PHP5.6:MySQL: + rules: + - if: $PIPELINE_PHP_5_6 != '1' + when: never + +PHPunit:PHP7.0:MySQL: + rules: + - if: $PIPELINE_PHP_7_0 != '1' + when: never diff --git a/eth-embed-anchor-fm.php b/eth-embed-anchor-fm.php index bb370d507efeffd83971d6bc716855fc762b5ee2..89de486694b5dcff420244f8fceb5b279f0bb01d 100644 --- a/eth-embed-anchor-fm.php +++ b/eth-embed-anchor-fm.php @@ -27,3 +27,23 @@ */ namespace ETH_Embed_Anchor_FM; + +/** + * Perform setup actions after plugin loads. + * + * @return void + */ +function action_plugins_loaded() { + load_plugin_textdomain( + 'eth-embed-anchor-fm', + false, + dirname( plugin_basename( __FILE__ ) ) . '/languages/' + ); +} +add_action( 'plugins_loaded', __NAMESPACE__ . '\action_plugins_loaded' ); + +/** + * Load plugin classes. + */ +require_once __DIR__ . '/inc/class-plugin.php'; +Plugin::get_instance(); diff --git a/inc/class-plugin.php b/inc/class-plugin.php new file mode 100644 index 0000000000000000000000000000000000000000..ef8c6d6548d6ed47e7b7747af9934468b09e8329 --- /dev/null +++ b/inc/class-plugin.php @@ -0,0 +1,186 @@ +<?php +/** + * Plugin functionality. + * + * @package ETH_Embed_Anchor_FM + */ + +namespace ETH_Embed_Anchor_FM; + +/** + * Class Plugin. + */ +class Plugin { + /** + * Regex pattern to match URL to be oEmbedded. + * + * @var string + */ + private const OEMBED_FORMAT = '#^https://anchor\.fm/(?!api)([^/]+)/episodes/([^/\s]+)/?#i'; + + /** + * Anchor oEmbed endpoint with placeholder. + * + * @var string + */ + private const OEMBED_ENDPOINT = 'https://anchor.fm/api/v3/episodes/__EPISODE_ID__/oembed'; + + /** + * Placeholder in self::OEMBED_ENDPOINT to be replaced with episode ID. + * + * @var string + */ + private const EPISODE_ID_PLACEHOLDER = '__EPISODE_ID__'; + + /** + * Shortcode tag. + * + * @var string + */ + private const SHORTCODE_TAG = 'eth_anchor_fm'; + + /** + * Singleton. + * + * @var Plugin + */ + private static $_instance = null; + + /** + * Implement singleton. + * + * @return Plugin + */ + public static function get_instance(): Plugin { + if ( ! is_a( self::$_instance, __CLASS__ ) ) { + self::$_instance = new self(); + self::$_instance->_setup(); + } + + return self::$_instance; + } + + /** + * Silence is golden! + */ + private function __construct() { + // Add nothing here. + } + + /** + * Register hooks. + * + * @return void + */ + private function _setup(): void { + add_action( 'init', [ $this, 'action_init' ] ); + + add_filter( + 'oembed_fetch_url', + [ $this, 'filter_oembed_fetch_url' ], + 10, + 3 + ); + } + + /** + * Register oEmbed handler. + * + * @return void + */ + public function action_init(): void { + wp_oembed_add_provider( + self::OEMBED_FORMAT, + self::OEMBED_ENDPOINT, + true + ); + + add_shortcode( + self::SHORTCODE_TAG, + [ $this, 'do_shortcode' ] + ); + } + + /** + * Filter oEmbed URL. + * + * Anchor.fm's oEmbed endpoint is specific to an episode ID, which must be + * extracted from the episode URL. + * + * @param string $provider URL of the oEmbed provider. + * @param string $url URL of the content to be embedded. + * @param array $args Optional. Additional arguments for retrieving + * embed HTML. + * @return string + */ + public function filter_oembed_fetch_url( + string $provider, + string $url, + array $args = [] + ): string { + if ( 0 !== stripos( $provider, self::OEMBED_ENDPOINT ) ) { + return $provider; + } + + if ( ! preg_match( self::OEMBED_FORMAT, $url, $matches ) ) { + return ''; + } + + $episode_slug_parts = explode( '-', $matches[2] ); + $id = array_pop( $episode_slug_parts ); + + $provider = str_replace( + self::EPISODE_ID_PLACEHOLDER, + $id, + self::OEMBED_ENDPOINT + ); + + // Anchor.fm's oEmbed endpoint offers limited support for arguments. + if ( isset( $args['width'], $args['height'] ) ) { + $provider = add_query_arg( + [ + 'maxwidth' => (int) $args['width'], + 'maxheight' => (int) $args['height'], + ], + $provider + ); + } + + return $provider; + } + + /** + * Render Anchor.fm iframe embed via a shortcode. + * + * @param array $attrs Shortcode attributes. + * @return string + */ + public function do_shortcode( array $attrs ): string { + $attrs = shortcode_atts( + [ + 'src' => null, + 'url' => null, + 'width' => '400px', + 'height' => '102px', + ], + $attrs, + self::SHORTCODE_TAG + ); + + // Fallback in case one passes `url` rather than `src`. + if ( empty( $attrs['src'] ) && ! empty( $attrs['url'] ) ) { + $attrs['src'] = $attrs['url']; + } + + if ( empty( $attrs['src'] ) ) { + return ''; + } + + return sprintf( + '<iframe src="%1$s" width="%2$s" height="%3$s" frameborder="0" scrolling="no"></iframe>', + esc_url( $attrs['src'] ), + esc_attr( $attrs['width'] ), + esc_attr( $attrs['height'] ) + ); + } +} diff --git a/phpcs.xml b/phpcs.xml index fc325fa8ef728942a9dbc570c845c1d0a968a881..ff47dc708d326f35790b403ba2915293ecd02306 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -26,7 +26,9 @@ <!-- https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards --> <!-- https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties --> <config name="minimum_supported_wp_version" value="4.7"/> - <rule ref="WordPress" /> + <rule ref="WordPress"> + <exclude name="Generic.Arrays.DisallowShortArraySyntax" /> + </rule> <rule ref="WordPressVIPMinimum" /> <rule ref="WordPress-VIP-Go" /> <rule ref="WordPress.NamingConventions.PrefixAllGlobals">