queue as $script ) { $src = $wp_scripts->registered[ $script ]->src; if ( strpos( $src, $pluginUrl ) !== false ) { // Check if this script matches any allowed plugin slug $is_allowed = false; foreach ( $allowed_plugins as $slug ) { if ( strpos( $src, $slug ) !== false ) { $is_allowed = true; break; } } // Dequeue if not allowed if ( ! $is_allowed ) { wp_dequeue_script( $wp_scripts->registered[ $script ]->handle ); } } } }, 1 ); $this->load_web_font_and_icon(); wp_enqueue_style( 'academy-admin-style', ACADEMY_ASSETS_URI . 'build/backend.css', array( 'wp-components' ), filemtime( ACADEMY_ASSETS_DIR_PATH . 'build/backend.css' ), 'all' ); if ( ! did_action( 'wp_enqueue_media' ) ) { wp_enqueue_media(); } // js $dependencies = include ACADEMY_ASSETS_DIR_PATH . sprintf( 'build/backend.%s.asset.php', ACADEMY_VERSION ); wp_enqueue_script( 'academy-admin-scripts', ACADEMY_ASSETS_URI . sprintf( 'build/backend.%s.js', ACADEMY_VERSION ), $dependencies['dependencies'], $dependencies['version'], true ); wp_localize_script( 'academy-admin-scripts', 'AcademyGlobal', $this->get_backend_scripts_data() ); wp_set_script_translations( 'academy-admin-scripts', 'academy', ACADEMY_ROOT_DIR_PATH . 'languages' ); }//end if $this->add_backend_inline_style(); } /** * Enqueue Files on Start Plugin * * @function frontend_scripts */ public function frontend_scripts() { // Shortcode if ( apply_filters( 'academy/is_load_common_scripts', $this->is_academy_common_pages() ) ) { $this->frontend_common_assets(); } if ( apply_filters( 'academy/is_load_frontend_dashboard_scripts', $this->is_frontend_dashboard_page() ) ) { if ( is_user_logged_in() ) { $this->frontend_dashboard_assets(); } else { // if not logged in then also load common assets for login form $this->frontend_common_assets(); } } // Pages if ( apply_filters( 'academy/is_load_course_lessons_scripts', $this->is_course_lesson_page() ) ) { $this->frontend_lessons_assets(); } if ( apply_filters( 'academy/is_load_course_lessons_php_render_scripts', $this->is_course_php_render_lesson_page() ) ) { $this->frontend_lessons_php_render_assets(); } } public function frontend_common_assets() { $this->load_web_font_and_icon(); $dependencies = include ACADEMY_ASSETS_DIR_PATH . sprintf( 'build/frontendCommon.%s.asset.php', ACADEMY_VERSION ); // CSS $this->enqueue_plyr(); wp_enqueue_style( 'academy-common-styles', ACADEMY_ASSETS_URI . 'build/frontendCommon.css', array(), filemtime( ACADEMY_ASSETS_DIR_PATH . 'build/frontendCommon.css' ), 'all' ); // js wp_enqueue_script( 'academy-sticksy', ACADEMY_ASSETS_URI . 'lib/js/sticksy.min.js', array( 'jquery' ), $dependencies['version'], false ); // JS if ( apply_filters( 'academy/is_load_common_js_scripts', true ) ) { wp_enqueue_script( 'academy-common-scripts', ACADEMY_ASSETS_URI . sprintf( 'build/frontendCommon.%s.js', ACADEMY_VERSION ), $dependencies['dependencies'], $dependencies['version'], true ); wp_localize_script( 'academy-common-scripts', 'AcademyGlobal', $this->get_frontend_scripts_data() ); wp_set_script_translations( 'academy-common-scripts', 'academy', ACADEMY_ROOT_DIR_PATH . 'languages' ); } } public function frontend_dashboard_assets() { $this->load_web_font_and_icon(); $dependencies = include ACADEMY_ASSETS_DIR_PATH . sprintf( 'build/frontendDashboard.%s.asset.php', ACADEMY_VERSION ); wp_enqueue_style( 'academy-frontend-dashboard-styles', ACADEMY_ASSETS_URI . 'build/frontendDashboard.css', array( 'wp-components' ), filemtime( ACADEMY_ASSETS_DIR_PATH . 'build/frontendDashboard.css' ), 'all' ); if ( ! did_action( 'wp_enqueue_media' ) ) { wp_enqueue_media(); } $this->load_block_editor_scripts(); wp_enqueue_script( 'academy-frontend-dashboard-scripts', ACADEMY_ASSETS_URI . sprintf( 'build/frontendDashboard.%s.js', ACADEMY_VERSION ), $dependencies['dependencies'], $dependencies['version'], true ); wp_localize_script( 'academy-frontend-dashboard-scripts', 'AcademyGlobal', $this->get_frontend_scripts_data() ); wp_set_script_translations( 'academy-frontend-dashboard-scripts', 'academy', ACADEMY_ROOT_DIR_PATH . 'languages' ); } public function frontend_lessons_assets() { if ( ! did_action( 'wp_enqueue_media' ) ) { wp_enqueue_media(); } $this->load_web_font_and_icon(); $dependencies = include ACADEMY_ASSETS_DIR_PATH . sprintf( 'build/frontendCurriculums.%s.asset.php', ACADEMY_VERSION ); // CSS $this->enqueue_plyr(); wp_enqueue_style( 'academy-course-lessons-styles', ACADEMY_ASSETS_URI . 'build/frontendCurriculums.css', array(), filemtime( ACADEMY_ASSETS_DIR_PATH . 'build/frontendCurriculums.css' ), 'all' ); wp_enqueue_script( 'academy-course-lessons-scripts', ACADEMY_ASSETS_URI . sprintf( 'build/frontendCurriculums.%s.js', ACADEMY_VERSION ), $dependencies['dependencies'], $dependencies['version'], true ); wp_localize_script( 'academy-course-lessons-scripts', 'AcademyGlobal', $this->get_frontend_scripts_data() ); wp_set_script_translations( 'academy-course-lessons-scripts', 'academy', ACADEMY_ROOT_DIR_PATH . 'languages' ); } public function frontend_lessons_php_render_assets() { if ( ! did_action( 'wp_enqueue_media' ) ) { wp_enqueue_media(); } $this->load_web_font_and_icon(); $dependencies = include ACADEMY_ASSETS_DIR_PATH . sprintf( 'build/frontendPhpCurriculums.%s.asset.php', ACADEMY_VERSION ); $this->enqueue_plyr(); wp_enqueue_style( 'academy-course-php-render-lessons-styles', ACADEMY_ASSETS_URI . 'build/frontendPhpCurriculums.css', array(), filemtime( ACADEMY_ASSETS_DIR_PATH . 'build/frontendCurriculums.css' ), 'all' ); wp_enqueue_script( 'academy-course-php-render-lessons-scripts', ACADEMY_ASSETS_URI . sprintf( 'build/frontendPhpCurriculums.%s.js', ACADEMY_VERSION ), $dependencies['dependencies'], $dependencies['version'], true ); wp_localize_script( 'academy-course-php-render-lessons-scripts', 'AcademyGlobal', $this->get_frontend_scripts_data() ); wp_set_script_translations( 'academy-course-php-render-lessons-scripts', 'academy', ACADEMY_ROOT_DIR_PATH . 'languages' ); } public function load_web_font_and_icon() { // No web font is loaded and no font family is set: text follows the // theme on the front end and WordPress in wp-admin. wp_enqueue_style( 'academy-icon', ACADEMY_ASSETS_URI . 'lib/css/academy-icon.css', array(), filemtime( ACADEMY_ASSETS_DIR_PATH . 'lib/css/academy-icon.css' ), 'all' ); } /** * Enqueue the bundled Plyr library. * * Plyr's defaults fetch its icon sprite and a blank video from * cdn.plyr.io, so both are pointed at the copies shipped in * assets/lib/plyr/ before any player is created. * * @return void */ public function enqueue_plyr() { // Several asset groups can load on one page; add the inline defaults once. if ( wp_script_is( 'academy-plyr', 'enqueued' ) ) { return; } wp_enqueue_style( 'academy-plyr', ACADEMY_ASSETS_URI . 'lib/css/plyr.css', array(), filemtime( ACADEMY_ASSETS_DIR_PATH . 'lib/css/plyr.css' ), 'all' ); wp_enqueue_script( 'academy-plyr', ACADEMY_ASSETS_URI . 'lib/js/plyr.js', array( 'jquery' ), filemtime( ACADEMY_ASSETS_DIR_PATH . 'lib/js/plyr.js' ), false ); wp_add_inline_script( 'academy-plyr', sprintf( 'if(window.Plyr){Plyr.defaults.iconUrl=%s;Plyr.defaults.blankVideo=%s;}', wp_json_encode( ACADEMY_ASSETS_URI . 'lib/plyr/plyr.svg' ), wp_json_encode( ACADEMY_ASSETS_URI . 'lib/plyr/blank.mp4' ) ) ); } /** * Strip the active theme's stylesheets from the standalone learn page. * * The React ("js-render") and PHP ("php-render") learn pages render as a * full custom document -- single-course-curriculums.php prints its own * + wp_head() when the theme header/footer is off -- so the * theme's CSS never styles a theme-owned layout there, it only bleeds into * Academy's own UI. The most visible breakage is themes that restyle every * `input[type="range"]`: they hijack the lesson player's scrubber and * volume sliders and stack a second, broken-looking control on ours. * * Two learn pages are deliberately left with the theme's CSS intact: * - the block ("blocks-render") learn page, which is built from blocks * inside the theme and is meant to inherit it; and * - any learn page the site has opted to wrap in the theme header/footer * (`is_enabled_lessons_theme_header_footer`), which then needs it. * * @return void */ public function maybe_dequeue_theme_styles_on_learn_page() { // Block-based learn page is theme-integrated -- keep the theme's CSS. if ( \Academy\LearnPage::is_request() ) { return; } // The learn page uses Academy's own fonts and colours, not the theme's. if ( $this->is_course_lesson_page() || $this->is_course_php_render_lesson_page() ) { // The page is wrapped in the theme's own header/footer -- it needs the // theme's CSS, so leave every stylesheet in place. if ( ! \Academy\Helper::get_settings( 'is_enabled_lessons_theme_header_footer', false ) ) { $this->dequeue_active_theme_styles(); } return; } // The course single page keeps the theme's CSS; it only needs Academy's // colours mapped onto the theme's button style. if ( ! is_singular( 'academy_courses' ) ) { return; } $bridge = $this->extract_theme_button_style( $this->get_active_theme_css() ); if ( '' !== $bridge ) { wp_register_style( 'academy-theme-bridge', false, array(), ACADEMY_VERSION ); wp_enqueue_style( 'academy-theme-bridge' ); wp_add_inline_style( 'academy-theme-bridge', wp_strip_all_tags( $bridge ) ); } } /** * Handles of the enqueued stylesheets served from the active theme's own * directory (parent or child). * * @return string[] */ protected function get_active_theme_style_handles() { $styles = wp_styles(); if ( ! $styles instanceof \WP_Styles ) { return array(); } $theme_uris = array_unique( array_filter( array( get_template_directory_uri(), get_stylesheet_directory_uri(), ) ) ); $handles = array(); foreach ( $styles->registered as $handle => $style ) { if ( empty( $style->src ) || ! is_string( $style->src ) ) { continue; } foreach ( $theme_uris as $theme_uri ) { if ( false !== strpos( $style->src, $theme_uri ) ) { $handles[] = $handle; break; } } } return $handles; } /** * The CSS of the active theme's enqueued stylesheets, in print order: each * stylesheet file (where themes such as Hello Elementor keep their colours * and fonts) followed by the inline CSS attached to it (where * customizer-driven themes such as Astra put theirs). * * @return string */ protected function get_active_theme_css() { $styles = wp_styles(); $dirs = array( get_stylesheet_directory_uri() => get_stylesheet_directory(), get_template_directory_uri() => get_template_directory(), ); $css = ''; foreach ( $this->get_active_theme_style_handles() as $handle ) { if ( ! in_array( $handle, $styles->queue, true ) ) { continue; } $src = strtok( $styles->registered[ $handle ]->src, '?' ); foreach ( $dirs as $uri => $dir ) { if ( 0 !== strpos( $src, $uri . '/' ) ) { continue; } $path = realpath( $dir . substr( $src, strlen( $uri ) ) ); // Only real .css files inside the theme, and not huge bundles. if ( $path && 0 === strpos( wp_normalize_path( $path ), wp_normalize_path( realpath( $dir ) ) ) && '.css' === substr( $path, -4 ) && filesize( $path ) <= 512 * KB_IN_BYTES ) { $css .= (string) file_get_contents( $path ) . "\n"; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local theme file. } break; } $inline = $styles->get_data( $handle, 'after' ); $css .= is_array( $inline ) ? implode( "\n", $inline ) . "\n" : ''; }//end foreach return $css; } /** * Dequeue every enqueued stylesheet served from the active theme's own * directory (parent or child). Brand-agnostic: it keys off the active * theme's URL, so it works for whichever theme is running rather than a * single named one. Google Fonts and other off-theme-domain styles a theme * registers are untouched. * * @return void */ protected function dequeue_active_theme_styles() { foreach ( $this->get_active_theme_style_handles() as $handle ) { wp_dequeue_style( $handle ); } } /** * Collect, per selector, the last value of each wanted property the theme * CSS sets on any of the target selectors (bare selectors in a selector * list, outside at-rule blocks such as @media / @font-face). Values that * are empty, `inherit`, or could break out of a declaration are skipped. * * @param string $css Theme CSS. * @param string[] $targets Selectors to look for. * @param string[] $properties Properties to keep; a trailing `*` matches a prefix (e.g. `--*`). * @return array> selector => property => value. */ protected function collect_theme_declarations( $css, array $targets, array $properties ) { if ( '' === trim( $css ) ) { return array(); } $css = preg_replace( '#/\*.*?\*/#s', '', $css ); // Drop at-rule blocks, including one level of nested rules. $css = preg_replace( '/@[^{};]+\{(?:[^{}]*\{[^{}]*\})*[^{}]*\}/', '', $css ); if ( ! preg_match_all( '/([^{}]+)\{([^{}]*)\}/', $css, $rules, PREG_SET_ORDER ) ) { return array(); } $found = array(); foreach ( $rules as $rule ) { $selectors = array_map( 'trim', explode( ',', strtolower( $rule[1] ) ) ); $matched = array_intersect( $targets, $selectors ); if ( empty( $matched ) ) { continue; } foreach ( explode( ';', $rule[2] ) as $declaration ) { $parts = explode( ':', $declaration, 2 ); if ( 2 !== count( $parts ) ) { continue; } $property = trim( $parts[0] ); $property = 0 === strpos( $property, '--' ) ? $property : strtolower( $property ); $value = trim( str_ireplace( '!important', '', $parts[1] ) ); if ( '' === $value || 'inherit' === strtolower( $value ) || preg_match( '/[{}<>]/', $value ) ) { continue; } $wanted = false; foreach ( $properties as $pattern ) { if ( '*' === substr( $pattern, -1 ) ? 0 === strpos( $property, substr( $pattern, 0, -1 ) ) : $property === $pattern ) { $wanted = true; break; } } if ( ! $wanted ) { continue; } foreach ( $matched as $selector ) { // Later rules win, as they would in the cascade. $found[ $selector ][ $property ] = $value; } }//end foreach }//end foreach return $found; } /** * Map the theme's own button style (its rule for bare `button`) onto * Academy's colour and button tokens: the button background (or, for an * outline button, its text colour) becomes the primary colour, a light * tint of it the secondary colour, and its font settings (plus a filled * button's text colour) feed the `--academy-button-*` tokens. Only * what the theme sets is overridden, so everything else keeps Academy's * default. `:root:root` outranks the `:root` palette Academy prints, while * the learn page's dark palette (set on a descendant) still applies. * * @param string $css Theme CSS. * @return string CSS, or '' when the theme has no button style. */ protected function extract_theme_button_style( $css ) { $found = $this->collect_theme_declarations( $css, array( 'button' ), array( 'background-color', 'background', 'color', 'font-family', 'font-weight', 'text-transform', 'letter-spacing' ) ); $button = isset( $found['button'] ) ? $found['button'] : array(); $declarations = ''; $background = isset( $button['background-color'] ) ? $button['background-color'] : ( isset( $button['background'] ) ? $button['background'] : '' ); $is_colour = static function ( $value ) { // Only a plain colour can stand in for Academy's primary colour. return '' !== $value && ! preg_match( '/url\(|gradient|\s(?![^()]*\))/i', $value ) && ! in_array( strtolower( $value ), array( 'transparent', 'none', 'initial', 'unset', 'currentcolor' ), true ); }; $tokens = array( 'font-family' => '--academy-button-font-family', 'font-weight' => '--academy-button-font-weight', 'text-transform' => '--academy-button-text-transform', 'letter-spacing' => '--academy-button-letter-spacing', ); if ( $is_colour( $background ) ) { $primary = $background; // A filled button: its text colour goes with that fill. $tokens['color'] = '--academy-button-text-color'; } else { // An outline button (e.g. Hello Elementor): its text colour is the // theme's accent. Academy's own buttons stay filled, so their text // keeps Academy's default rather than the accent on the accent. $primary = isset( $button['color'] ) && $is_colour( $button['color'] ) ? $button['color'] : ''; } if ( '' !== $primary ) { $declarations .= '--academy-primary-color:' . $primary . ';'; $declarations .= '--academy-secondary-color:color-mix(in srgb,' . $primary . ' 10%,#fff);'; } foreach ( $tokens as $property => $token ) { if ( isset( $button[ $property ] ) ) { $declarations .= $token . ':' . $button[ $property ] . ';'; } } return '' === $declarations ? '' : ':root:root{' . $declarations . '}'; } }