You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
6.7 KiB
170 lines
6.7 KiB
<?php |
|
/** |
|
* 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 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. |
|
* |
|
* 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. 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 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'; |
|
|
|
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 ) { |
|
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 );
|
|
|