1 changed files with 147 additions and 22 deletions
@ -1,45 +1,170 @@
|
||||
<?php |
||||
/** |
||||
* Plugin Name: H5P core fonts fix |
||||
* Description: Re-adds styles/h5p-fonts.css to H5PCore::$styles, which Pressbooks' vendored h5p-core 1.27 omits. |
||||
* Plugin Name: H5P core version reconciliation |
||||
* Description: Reconciles H5PCore's static properties with the h5p-php-library actually installed, which Pressbooks' vendored h5p-core 1.27 misreports. |
||||
* |
||||
* Pressbooks pins h5p/h5p-core 1.27.0 and requires it eagerly through Composer's |
||||
* autoload_files.php, so it defines H5PCore before the H5P plugin's lazy autoloader |
||||
* (h5p/autoloader.php) ever gets a chance to. H5PCore::$styles is therefore always the |
||||
* 1.27 list, while H5P_Plugin::add_core_assets() resolves those filenames against the |
||||
* H5P plugin's own h5p-php-library (1.28+), where the icon @font-face was moved out of |
||||
* h5p.css into h5p-fonts.css. |
||||
* Pressbooks pins h5p/h5p-core 1.27.0, and that package declares its classes under |
||||
* Composer's autoload.files, so h5p.classes.php is eagerly required when Pressbooks' |
||||
* autoloader boots. The H5P WordPress plugin registers the same classes through a lazy |
||||
* spl_autoload_register classmap (h5p/autoloader.php) and never requires the file |
||||
* directly. Eager autoload wins, the plugin's autoloader never fires, and no redeclare |
||||
* fatal occurs — so H5PCore silently describes 1.27 while the library actually installed, |
||||
* executed and served to browsers is the H5P plugin's 1.28 at a different path. |
||||
* |
||||
* Net effect: h5p-fonts.css never makes it into H5PIntegration.core.styles, so the |
||||
* document.write()n content iframe has no 'h5p' icon font and the action bar renders |
||||
* Reuse/Embed/Rights of use with tofu boxes instead of icons (and the H5P logo link, |
||||
* being icon-only, disappears entirely). |
||||
* Two symptoms follow, both fixed here by reconciling the statics with what is on disk: |
||||
* |
||||
* 1. $styles omits three stylesheets 1.28 ships. add_core_assets() resolves the 1.27 list |
||||
* against plugins_url('h5p/h5p-php-library'), so the content iframe is built without |
||||
* the icon @font-face (action bar renders as tofu) and without the theme layer that |
||||
* 1.28-era libraries depend on (content collapses — see PB_H5P_RESTORE_STYLES). |
||||
* |
||||
* 2. $coreApi reports 1.27. H5PValidator::isValidH5pData() compares an imported package's |
||||
* declared coreApi against it, so importing an .h5p archive containing a library that |
||||
* requires 1.28 fails with "requires a newer version of the H5P plugin ... currently |
||||
* running version 1.27". Authoring new content is unaffected, because the editor never |
||||
* runs the package validator. |
||||
* |
||||
* Reporting the shipped values is accurate, not a workaround: coreApi describes the |
||||
* client-side H5P core JS, and the JS actually served is the shipped one. The PHP delta |
||||
* between 1.27 and 1.28 is confined to Hub endpoints and one sanitization fix, with no |
||||
* change to content validation or storage. |
||||
* |
||||
* Remove this once Pressbooks' vendored h5p-core catches up with the H5P plugin's |
||||
* h5p-php-library. |
||||
* h5p-php-library. Removing the file is a complete rollback: nothing here is persisted |
||||
* beyond a cache of values read from disk. |
||||
*/ |
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) { |
||||
exit; |
||||
} |
||||
|
||||
/** |
||||
* Stylesheets 1.28 ships that 1.27's $styles omits, used as a fallback if the shipped |
||||
* $styles array can't be parsed. |
||||
* |
||||
* h5p-fonts.css carries the icon @font-face. The other two carry H5P's 1.28 theme layer: |
||||
* h5p-theme-variables.css defines ~82 --h5p-theme-* custom properties, and h5p-theme.css |
||||
* consumes them. Libraries built against 1.28 — H5P.Components, and current builds of the |
||||
* shared H5P.Question / H5P.JoubelUI / H5P.DragText — reference those variables heavily, |
||||
* and without the definitions every reference resolves to an empty string and the content |
||||
* collapses. Importing any recent .h5p package upgrades those shared libraries site-wide, |
||||
* so this is not opt-in. |
||||
* |
||||
* Restoring all three is safe for older content: h5p-theme.css is scoped entirely to |
||||
* .h5p-theme / .h5p-content:has(.h5p-theme), which only 1.28-era libraries apply to |
||||
* themselves, and h5p-theme-variables.css just declares custom properties on :root and |
||||
* .h5peditor. Neither affects a library that does not reference them. |
||||
*/ |
||||
const PB_H5P_RESTORE_STYLES = [ |
||||
'styles/h5p-fonts.css', |
||||
'styles/h5p-theme.css', |
||||
'styles/h5p-theme-variables.css', |
||||
]; |
||||
|
||||
/** |
||||
* Read the coreApi version and core stylesheet list declared by the h5p-php-library on |
||||
* disk. |
||||
* |
||||
* Parsed rather than hardcoded so this keeps telling the truth across H5P plugin updates, |
||||
* and so the stylesheet list keeps upstream's ordering by construction. Cached against the |
||||
* file's mtime. Either value may come back null, in which case callers fall back. |
||||
* |
||||
* @param string $classes_file Absolute path to the shipped h5p.classes.php. |
||||
* @return array{coreApi:?array,styles:?array} |
||||
*/ |
||||
function pb_h5p_shipped_core_values( $classes_file ) { |
||||
$mtime = @filemtime( $classes_file ); |
||||
$cached = get_site_transient( 'pb_h5p_shipped_core_values' ); |
||||
|
||||
if ( is_array( $cached ) && isset( $cached['mtime'] ) && $cached['mtime'] === $mtime ) { |
||||
return $cached['values']; |
||||
} |
||||
|
||||
$source = @file_get_contents( $classes_file ); |
||||
$values = [ 'coreApi' => null, 'styles' => null ]; |
||||
|
||||
if ( $source ) { |
||||
if ( preg_match( |
||||
'/public\s+static\s+\$coreApi\s*=\s*(?:array\s*\(|\[)\s*' |
||||
. "'majorVersion'\s*=>\s*(\d+)\s*,\s*'minorVersion'\s*=>\s*(\d+)/", |
||||
$source, |
||||
$m |
||||
) ) { |
||||
$values['coreApi'] = [ |
||||
'majorVersion' => (int) $m[1], |
||||
'minorVersion' => (int) $m[2], |
||||
]; |
||||
} |
||||
|
||||
if ( preg_match( '/public\s+static\s+\$styles\s*=\s*(?:array\s*\(|\[)(.*?)(?:\)|\])\s*;/s', $source, $m ) |
||||
&& preg_match_all( "/'([^']+\.css)'/", $m[1], $found ) |
||||
) { |
||||
$values['styles'] = $found[1]; |
||||
} |
||||
} |
||||
|
||||
set_site_transient( |
||||
'pb_h5p_shipped_core_values', |
||||
[ 'mtime' => $mtime, 'values' => $values ], |
||||
DAY_IN_SECONDS |
||||
); |
||||
|
||||
return $values; |
||||
} |
||||
|
||||
add_action( 'plugins_loaded', function () { |
||||
if ( ! class_exists( 'H5PCore' ) ) { |
||||
return; |
||||
} |
||||
|
||||
// add_core_assets() resolves these against plugins_url('h5p/h5p-php-library'). |
||||
// add_core_assets() resolves style paths against plugins_url('h5p/h5p-php-library'). |
||||
$library_dir = WP_PLUGIN_DIR . '/h5p/h5p-php-library/'; |
||||
$classes_file = $library_dir . 'h5p.classes.php'; |
||||
|
||||
$missing = []; |
||||
foreach ( [ 'styles/h5p-fonts.css' ] as $style ) { |
||||
if ( ! in_array( $style, H5PCore::$styles, true ) && file_exists( $library_dir . $style ) ) { |
||||
$missing[] = $style; |
||||
} |
||||
if ( ! is_readable( $classes_file ) ) { |
||||
return; |
||||
} |
||||
|
||||
$shipped = pb_h5p_shipped_core_values( $classes_file ); |
||||
$loaded = H5PCore::$styles; |
||||
|
||||
// 1. Adopt the shipped stylesheet list, which preserves upstream's ordering — the |
||||
// theme layer has to load after h5p.css to override it. Only files actually |
||||
// present are included, and anything the loaded list has that the shipped one |
||||
// doesn't is kept, so this can only ever add. |
||||
if ( $shipped['styles'] ) { |
||||
$styles = array_values( array_filter( |
||||
$shipped['styles'], |
||||
function ( $style ) use ( $library_dir ) { |
||||
return file_exists( $library_dir . $style ); |
||||
} |
||||
) ); |
||||
H5PCore::$styles = array_merge( $styles, array_values( array_diff( $loaded, $styles ) ) ); |
||||
} else { |
||||
// Fallback: prepend whichever known-missing sheets are present. |
||||
$missing = array_values( array_filter( |
||||
PB_H5P_RESTORE_STYLES, |
||||
function ( $style ) use ( $library_dir, $loaded ) { |
||||
return ! in_array( $style, $loaded, true ) && file_exists( $library_dir . $style ); |
||||
} |
||||
) ); |
||||
if ( $missing ) { |
||||
// Prepend, mirroring upstream ordering. |
||||
H5PCore::$styles = array_merge( $missing, H5PCore::$styles ); |
||||
H5PCore::$styles = array_merge( $missing, $loaded ); |
||||
} |
||||
} |
||||
|
||||
// 2. Report the coreApi the shipped library actually implements. Only ever raised, |
||||
// so a future vendor copy that overtakes the H5P plugin is left untouched. |
||||
if ( ! $shipped['coreApi'] ) { |
||||
return; |
||||
} |
||||
|
||||
$current = H5PCore::$coreApi; |
||||
$is_newer = $shipped['coreApi']['majorVersion'] > $current['majorVersion'] |
||||
|| ( $shipped['coreApi']['majorVersion'] === $current['majorVersion'] |
||||
&& $shipped['coreApi']['minorVersion'] > $current['minorVersion'] ); |
||||
|
||||
if ( $is_newer ) { |
||||
H5PCore::$coreApi = $shipped['coreApi']; |
||||
} |
||||
}, 100 ); |
||||
|
||||
Loading…
Reference in new issue