Something went wrong. Try again.
Plugins for CMSs providing rich, customisable embeds for various developer sites and services. vale.rocks/portfolio/developer-embeds
Something went wrong. Try again.
11 kB · 373 lines
PHP
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374<?php/** * Plugin Name: Developer Embeds * Description: Rich embeds for npm, PyPI, and code repositories. * Version: 0.1.0 * Requires at least: 6.3 * Requires PHP: 7.4 * Author: Declan Chidlow * Author URI: https://vale.rocks * License: GPLv2 or later * License URI: https://www.gnu.org/licenses/gpl-2.0.html * Text Domain: developer-embeds * * @package Developer_Embeds */
defined( 'ABSPATH' ) || exit;
/** * Main plugin class. */final class Developer_Embeds { /** * Plugin version, used as a fallback asset version. * * @var string */ private const VERSION = '0.1.0';
/** * Registers the plugin hooks. * * @return void */ public static function init() { add_action( 'init', array( __CLASS__, 'register_assets' ), 9 ); add_action( 'init', array( __CLASS__, 'register_blocks' ) ); add_action( 'init', array( __CLASS__, 'register_shortcodes' ) ); add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_style' ) ); add_action( 'enqueue_block_assets', array( __CLASS__, 'enqueue_editor_assets' ) ); add_action( 'admin_init', array( __CLASS__, 'add_privacy_policy_content' ) ); }
/** * Registers the component and editor assets. * * @return void */ public static function register_assets() { $editor_dependencies = array( 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n' );
foreach ( self::components() as $tag => $component ) { wp_register_script( self::script_handle( $tag ), plugins_url( 'assets/components/' . $component['file'], __FILE__ ), array(), self::asset_version( 'components/' . $component['file'] ), true );
$editor_dependencies[] = self::script_handle( $tag ); }
wp_register_script( 'developer-embeds-editor', plugins_url( 'assets/js/editor.js', __FILE__ ), $editor_dependencies, self::asset_version( 'js/editor.js' ), true );
wp_set_script_translations( 'developer-embeds-editor', 'developer-embeds' );
wp_register_style( 'developer-embeds', plugins_url( 'assets/css/developer-embeds.css', __FILE__ ), array(), self::asset_version( 'css/developer-embeds.css' ) );
$blocks = array();
foreach ( self::components() as $tag => $component ) { $blocks[] = array( 'name' => self::block_name( $tag ), 'tag' => $tag, 'title' => $component['title'], 'description' => $component['description'], 'icon' => $component['icon'], 'keywords' => $component['keywords'], 'attributes' => $component['attributes'], 'fields' => $component['fields'], ); }
wp_localize_script( 'developer-embeds-editor', 'developerEmbeds', array( 'blocks' => $blocks ) ); }
/** * Registers the blocks, preferring block.json metadata when available. * * @return void */ public static function register_blocks() { foreach ( self::components() as $tag => $component ) { $block_directory = __DIR__ . '/blocks/' . $tag;
if ( file_exists( $block_directory . '/block.json' ) ) { register_block_type( $block_directory, array( 'render_callback' => array( __CLASS__, 'render_block' ), ) );
continue; }
register_block_type( self::block_name( $tag ), array( 'api_version' => 3, 'title' => $component['title'], 'description' => $component['description'], 'category' => 'embed', 'icon' => $component['icon'], 'keywords' => $component['keywords'], 'attributes' => $component['attributes'], 'supports' => array( 'html' => false ), 'textdomain' => 'developer-embeds', 'editor_script' => 'developer-embeds-editor', 'editor_style' => 'developer-embeds', 'render_callback' => array( __CLASS__, 'render_block' ), ) ); } }
/** * Registers the shortcodes. * * @return void */ public static function register_shortcodes() { foreach ( array_keys( self::components() ) as $tag ) { add_shortcode( $tag, array( __CLASS__, 'render_shortcode' ) ); } }
/** * Enqueues the front-end stylesheet. * * The stylesheet is small and applies to any page that may contain an * embed, including shortcode-only pages and widgets. The component * scripts are loaded only when an embed is rendered. * * @return void */ public static function enqueue_style() { wp_enqueue_style( 'developer-embeds' ); }
/** * Enqueues the component scripts in the block editor. * * The block editor iframe needs the component scripts inside the iframe, * not just in the parent editor window. Assets enqueued on * enqueue_block_assets are included in the iframed editor as of WordPress 6.3. * * @return void */ public static function enqueue_editor_assets() { if ( ! is_admin() ) { return; }
wp_enqueue_style( 'developer-embeds' );
foreach ( array_keys( self::components() ) as $tag ) { wp_enqueue_script( self::script_handle( $tag ) ); } }
/** * Adds suggested privacy policy content. * * @return void */ public static function add_privacy_policy_content() { if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) { return; }
$content = '<p>' . __( 'Developer Embeds displays public package and repository data. When an embed is displayed on the front end, or previewed in the editor, the browser requests data from the npm registry and downloads API, PyPI, Shields.io, and the selected repository forge. Tangled lookups may also contact api.tangled.org and public.api.bsky.app; GitHub repository preview images may be loaded from opengraph.githubassets.com. If a custom host or API URL is configured, the browser contacts that host. These services may receive the browser\'s IP address and the requested package or repository name. The plugin itself does not collect, store, or share personal data, and does not set cookies or use analytics. API responses are cached in the browser\'s sessionStorage for 15 minutes.', 'developer-embeds' ) . '</p>'; $content .= '<p>' . __( 'Privacy policies and terms:', 'developer-embeds' ) . ' '; $content .= '<a href="https://www.npmjs.com/policies/privacy">npm</a>, '; $content .= '<a href="https://pypi.org/policy/terms-of-use/">PyPI</a>, '; $content .= '<a href="https://www.python.org/privacy/">Python Software Foundation</a>, '; $content .= '<a href="https://shields.io/privacy">Shields.io</a>, '; $content .= '<a href="https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement">GitHub</a>, '; $content .= '<a href="https://about.gitlab.com/privacy/">GitLab</a>, '; $content .= '<a href="https://www.atlassian.com/legal/privacy-policy">Bitbucket/Atlassian</a>, '; $content .= '<a href="https://tangled.org/privacy">Tangled</a>, '; $content .= '<a href="https://bsky.social/about/support/privacy-policy">Bluesky</a>, '; $content .= '<a href="https://man.sr.ht/privacy.md">SourceHut</a>, '; $content .= '<a href="https://codeberg.org/Codeberg/org/src/branch/main/PrivacyPolicy.md">Codeberg</a>, '; $content .= '<a href="https://privacy.microsoft.com/privacystatement">Azure DevOps</a>.'; $content .= '</p>';
wp_add_privacy_policy_content( 'Developer Embeds', wp_kses_post( $content ) ); }
/** * Renders a block on the front end. * * @param array $attributes Block attributes. * @param string $content Block content. * @param WP_Block $block Block instance. * @return string */ public static function render_block( $attributes, $content, $block ) { $tag = str_replace( 'developer-embeds/', '', $block->name ); $component = self::components()[ $tag ] ?? null;
if ( ! $component ) { return ''; }
$embed = self::render_embed( $tag, $attributes, $component );
return '' === $embed ? '' : '<div ' . get_block_wrapper_attributes() . '>' . $embed . '</div>'; }
/** * Renders a shortcode. * * @param array|string $atts Shortcode attributes. * @param string|null $content Enclosed content. * @param string $tag Shortcode tag. * @return string */ public static function render_shortcode( $atts, $content, $tag ) { $component = self::components()[ $tag ] ?? null;
if ( ! $component ) { return ''; }
$raw = is_array( $atts ) ? $atts : array(); $defaults = array_fill_keys( array_keys( $component['attributes'] ), '' ); $atts = shortcode_atts( $defaults, $raw, $tag );
if ( isset( $raw['details'] ) && is_string( $raw['details'] ) && '' === trim( $raw['details'] ) ) { $atts['details'] = 'none'; }
return self::render_embed( $tag, $atts, $component ); }
/** * Builds the custom element markup for an embed. * * @param string $tag Component tag. * @param array $attributes Embed attributes. * @param array $component Component metadata. * @return string */ private static function render_embed( $tag, $attributes, $component ) { $required = $component['required']; $value = $attributes[ $required ] ?? '';
if ( ! is_scalar( $value ) || '' === trim( (string) $value ) ) { return ''; }
wp_enqueue_script( self::script_handle( $tag ) );
$html = '<' . $tag;
foreach ( array_keys( $component['attributes'] ) as $name ) { if ( ! array_key_exists( $name, $attributes ) ) { continue; }
$value = $attributes[ $name ];
if ( ! is_scalar( $value ) ) { continue; }
$value = (string) $value;
if ( 'details' === $name && 'none' === $value ) { $value = ''; } elseif ( '' === $value ) { continue; }
$html .= sprintf( ' %s="%s"', $name, esc_attr( $value ) ); }
return $html . '></' . $tag . '>'; }
/** * Returns the script handle for a component. * * @param string $tag Component tag. * @return string */ private static function script_handle( $tag ) { return 'developer-embeds-' . $tag; }
/** * Returns the block name for a component. * * @param string $tag Component tag. * @return string */ private static function block_name( $tag ) { return 'developer-embeds/' . $tag; }
/** * Returns an asset version, falling back to the plugin version. * * @param string $file Asset file name. * @return string */ private static function asset_version( $file ) { $path = __DIR__ . '/assets/' . $file;
return file_exists( $path ) ? (string) filemtime( $path ) : self::VERSION; }
/** * Returns the component metadata keyed by tag. * * @return array */ private static function components() { static $components = null;
if ( null !== $components ) { return $components; }
$path = __DIR__ . '/data/component-metadata.json'; $data = file_exists( $path ) ? wp_json_file_decode( $path, array( 'associative' => true ) ) : null;
$components = array();
if ( is_array( $data ) ) { foreach ( $data as $component ) { if ( is_array( $component ) && ! empty( $component['tag'] ) ) { $components[ $component['tag'] ] = $component; } } }
return $components; }}
Developer_Embeds::init();