1 changed files with 147 additions and 22 deletions
@ -1,45 +1,170 @@ |
|||||||
<?php |
<?php |
||||||
/** |
/** |
||||||
* Plugin Name: H5P core fonts fix |
* Plugin Name: H5P core version reconciliation |
||||||
* Description: Re-adds styles/h5p-fonts.css to H5PCore::$styles, which Pressbooks' vendored h5p-core 1.27 omits. |
* 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 |
* Pressbooks pins h5p/h5p-core 1.27.0, and that package declares its classes under |
||||||
* autoload_files.php, so it defines H5PCore before the H5P plugin's lazy autoloader |
* Composer's autoload.files, so h5p.classes.php is eagerly required when Pressbooks' |
||||||
* (h5p/autoloader.php) ever gets a chance to. H5PCore::$styles is therefore always the |
* autoloader boots. The H5P WordPress plugin registers the same classes through a lazy |
||||||
* 1.27 list, while H5P_Plugin::add_core_assets() resolves those filenames against the |
* spl_autoload_register classmap (h5p/autoloader.php) and never requires the file |
||||||
* H5P plugin's own h5p-php-library (1.28+), where the icon @font-face was moved out of |
* directly. Eager autoload wins, the plugin's autoloader never fires, and no redeclare |
||||||
* h5p.css into h5p-fonts.css. |
* 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 |
* Two symptoms follow, both fixed here by reconciling the statics with what is on disk: |
||||||
* 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, |
* 1. $styles omits three stylesheets 1.28 ships. add_core_assets() resolves the 1.27 list |
||||||
* being icon-only, disappears entirely). |
* 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 |
* 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' ) ) { |
if ( ! defined( 'ABSPATH' ) ) { |
||||||
exit; |
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 () { |
add_action( 'plugins_loaded', function () { |
||||||
if ( ! class_exists( 'H5PCore' ) ) { |
if ( ! class_exists( 'H5PCore' ) ) { |
||||||
return; |
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/'; |
$library_dir = WP_PLUGIN_DIR . '/h5p/h5p-php-library/'; |
||||||
|
$classes_file = $library_dir . 'h5p.classes.php'; |
||||||
|
|
||||||
$missing = []; |
if ( ! is_readable( $classes_file ) ) { |
||||||
foreach ( [ 'styles/h5p-fonts.css' ] as $style ) { |
return; |
||||||
if ( ! in_array( $style, H5PCore::$styles, true ) && file_exists( $library_dir . $style ) ) { |
|
||||||
$missing[] = $style; |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
|
$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 ) { |
if ( $missing ) { |
||||||
// Prepend, mirroring upstream ordering. |
H5PCore::$styles = array_merge( $missing, $loaded ); |
||||||
H5PCore::$styles = array_merge( $missing, H5PCore::$styles ); |
} |
||||||
|
} |
||||||
|
|
||||||
|
// 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 ); |
}, 100 ); |
||||||
|
|||||||
Loading…
Reference in new issue