dispatch_hook(); Template\Loader::init(); } public function dispatch_hook() { add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ), 30 ); // Priority 5 — must run before the other template_redirect gates below // (and before any theme/page routing), since an active lockout should // pre-empt them rather than let a redirect happen first. add_action( 'template_redirect', array( $this, 'maintenance_mode_template_redirect' ), 5 ); add_action( 'template_redirect', array( $this, 'archive_course_template_redirect' ) ); add_action( 'template_redirect', array( $this, 'course_curriculum_learn_page_redirect' ) ); add_action( 'template_redirect', array( $this, 'frontend_dashboard_template_redirect' ) ); add_filter( 'pre_get_document_title', array( $this, 'pre_get_document_title' ), 30, 1 ); add_filter( 'post_type_archive_title', array( $this, 'archive_course_document_title' ), 30, 2 ); add_action( 'init', [ $this, 'register_block_styles' ] ); add_filter( 'render_block', [ $this, 'custom_featured_image_with_default' ], 10, 2 ); // Add the filter here add_filter( 'render_block', [ $this, 'add_category_images_to_post_terms' ], 10, 2 ); // Safe to leave attached everywhere (admin and REST included): it acts // only on queries that opted in via Helper::apply_sticky_course_ordering(). add_filter( 'posts_orderby', [ Helper::class, 'filter_sticky_posts_orderby' ], 10, 2 ); } /** * Hook into pre_get_posts to do the main product query. * * @param WP_Query $q Query instance. */ public function pre_get_posts( $q ) { $per_page = (int) \Academy\Helper::get_settings( 'course_archive_courses_per_page', 12 ); if ( $q->is_main_query() && ! $q->is_feed() && ! is_admin() ) { if ( ! empty( $q->query['author_name'] ) && Academy\Helper::get_settings( 'is_show_public_profile' ) ) { $user = get_user_by( 'login', $q->query['author_name'] ); if ( $user ) { if ( current( $user->roles ) === 'academy_instructor' || current( $user->roles ) === 'administrator' ) { $q->set( 'author', $q->query['author_name'] ); $q->set( 'posts_per_page', $per_page ); $q->set( 'post_type', 'academy_courses' ); } } } elseif ( is_post_type_archive( 'academy_courses' ) && ! $q->is_tax( 'academy_courses_category' ) ) { $paged = ( get_query_var( 'paged' ) ) ? absint( get_query_var( 'paged' ) ) : 1; $orderby = ( get_query_var( 'orderby' ) ) ? get_query_var( 'orderby' ) : Academy\Helper::get_settings( 'course_archive_courses_order' ); $q->set( 'post_type', apply_filters( 'academy/course_archive_post_types', array( 'academy_courses' ) ) ); // phpcs:ignore WordPressVIPMinimum.Hooks.PreGetPosts.PreGetPosts -- guarded by is_main_query() && ! is_admin() above $q->set( 'posts_per_page', $per_page ); // phpcs:ignore WordPressVIPMinimum.Hooks.PreGetPosts.PreGetPosts -- guarded by is_main_query() && ! is_admin() above $q->set( 'paged', $paged ); // phpcs:ignore WordPressVIPMinimum.Hooks.PreGetPosts.PreGetPosts -- guarded by is_main_query() && ! is_admin() above $this->set_sticky_course_ordering( $q, $orderby ); } elseif ( $q->is_tax( 'academy_courses_category' ) || ( $q->is_search() && isset( $q->query['academy_courses_category'] ) ) ) { $q->set( 'posts_per_page', $per_page ); // phpcs:ignore WordPressVIPMinimum.Hooks.PreGetPosts.PreGetPosts -- guarded by is_main_query() && ! is_admin() above $orderby = ( get_query_var( 'orderby' ) ) ? get_query_var( 'orderby' ) : Academy\Helper::get_settings( 'course_archive_courses_order' ); $this->set_sticky_course_ordering( $q, $orderby ); }//end if }//end if } /** * Apply the archive's configured sort to a course query and pin featured * courses above the rest. * * @param WP_Query $q The query being prepared. * @param string $orderby The archive's configured sort key. */ private function set_sticky_course_ordering( $q, $orderby ) { $order_field = 'name' === $orderby ? 'title' : $orderby; $order_dir = 'name' === $orderby ? 'ASC' : 'DESC'; foreach ( Helper::apply_sticky_course_ordering( $order_field, $order_dir ) as $key => $value ) { $q->set( $key, $value ); } } public function archive_course_template_redirect() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_GET['page_id'] ) && '' === get_option( 'permalink_structure' ) && (int) \Academy\Helper::get_settings( 'course_page' ) === absint( $_GET['page_id'] ) ) { $archive_link = $this->get_post_type_archive_link( 'academy_courses' ); if ( $archive_link ) { wp_safe_redirect( $this->get_post_type_archive_link( 'academy_courses' ) ); exit; } } } public function course_curriculum_learn_page_redirect() { global $post; if ( $post && (int) Helper::get_settings( 'is_enabled_lessons_php_render' ) && (int) Helper::get_settings( 'lessons_page' ) === (int) $post->ID ) { $course_id = Helper::get_last_course_id(); if ( $course_id ) { wp_safe_redirect( Helper::get_start_course_permalink( $course_id ) ); exit; } } } public function frontend_dashboard_template_redirect() { if ( ! is_user_logged_in() && (int) \Academy\Helper::get_settings( 'frontend_dashboard_page' ) === get_the_ID() ) { if ( ! \Academy\Helper::get_settings( 'is_enabled_academy_login', true ) && wp_safe_redirect( wp_login_url( get_the_permalink() ) ) ) { exit; } } } /** * Maintenance mode — a frontend-only lockout for the LMS surfaces (course * archive/single/learn pages), never wp-admin. Scope is either the whole * LMS ('full_site') or a specific set of courses ('course'), configured * in Settings → General. Administrators and instructors always bypass it * so they can keep working while it's active. */ public function maintenance_mode_template_redirect() { $scope = Helper::get_settings( 'academy_maintenance_scope', 'off' ); if ( 'off' === $scope ) { return; } if ( current_user_can( 'manage_options' ) || current_user_can( 'manage_academy_instructor' ) ) { return; } $end_time = Helper::get_settings( 'academy_maintenance_end_time' ); if ( ! empty( $end_time ) && strtotime( $end_time ) <= time() ) { // The window has passed — treat the lockout as expired without // having to flip the stored setting back (no cron needed). return; } if ( ! $this->is_maintenance_locked_request( $scope ) ) { return; } status_header( 503 ); nocache_headers(); Helper::get_template( 'maintenance-mode.php', array( 'message' => Helper::get_settings( 'academy_maintenance_message' ), 'end_time' => $end_time, ) ); exit; } /** * @param string $scope 'full_site' or 'course'. */ private function is_maintenance_locked_request( $scope ) { $is_course_archive = is_post_type_archive( 'academy_courses' ) || is_tax( 'academy_courses_category' ) || is_tax( 'academy_courses_tag' ); $is_single_course = is_singular( 'academy_courses' ); // The learn/curriculum page (lesson, quiz, assignment, …) doesn't // have its own singular template — it's always this one page with a // `curriculum_type` query var, so its presence IS "on a learn page". $is_learn_page = '' !== (string) get_query_var( 'curriculum_type' ); if ( 'full_site' === $scope ) { return $is_course_archive || $is_single_course || $is_learn_page; } if ( 'course' === $scope ) { // The archive listing isn't tied to any single course, so it's // gated by its own standalone toggle instead of the per-course // page-type selection below. if ( $is_course_archive ) { return (bool) Helper::get_settings( 'academy_maintenance_lock_archive', false ); } $locked_pages = (array) Helper::get_settings( 'academy_maintenance_locked_pages', array( 'single', 'learn' ) ); $current_course_id = 0; if ( $is_single_course && in_array( 'single', $locked_pages, true ) ) { $current_course_id = get_the_ID(); } elseif ( $is_learn_page && in_array( 'learn', $locked_pages, true ) ) { $current_course_id = (int) Helper::get_the_current_course_id(); } if ( ! $current_course_id ) { return false; } $locked_course_ids = array_map( 'intval', (array) Helper::get_settings( 'academy_maintenance_course_ids', array() ) ); return in_array( (int) $current_course_id, $locked_course_ids, true ); }//end if return false; } public function get_post_type_archive_link( $post_type ) { global $wp_rewrite; $post_type_obj = get_post_type_object( $post_type ); if ( ! $post_type_obj ) { return false; } if ( 'post' === $post_type ) { $show_on_front = get_option( 'show_on_front' ); $page_for_posts = get_option( 'page_for_posts' ); if ( 'page' === $show_on_front && $page_for_posts ) { $link = get_permalink( $page_for_posts ); } else { $link = get_home_url(); } /** This filter is documented in wp-includes/link-template.php */ return apply_filters( 'post_type_archive_link', $link, $post_type ); } if ( ! $post_type_obj->has_archive ) { return false; } if ( get_option( 'permalink_structure' ) && is_array( $post_type_obj->rewrite ) ) { $struct = ( true === $post_type_obj->has_archive ) ? $post_type_obj->rewrite['slug'] : $post_type_obj->has_archive; if ( $post_type_obj->rewrite['with_front'] ) { $struct = $wp_rewrite->front . $struct; } else { $struct = $wp_rewrite->root . $struct; } $link = home_url( user_trailingslashit( $struct, 'post_type_archive' ) ); } else { $link = home_url( '?post_type=' . $post_type ); } return apply_filters( 'academy/frontend/post_type_archive_link', $link, $post_type ); } public function pre_get_document_title( $title ) { $items = [ 'lesson', 'quiz', 'assignment', 'booking', 'zoom', 'meeting' ]; if ( class_exists( 'RankMath' ) && ! in_array( get_query_var( 'curriculum_type' ), $items, true ) ) { $page_id = (int) get_queried_object_id(); $course_page = (int) \Academy\Helper::get_settings( 'course_page' ); if ( $page_id === $course_page ) { // Empty = let core (and the SEO plugin) build the title. return ''; } } elseif ( get_query_var( 'name' ) && get_query_var( 'curriculum_type' ) ) { if ( 'lesson' === get_query_var( 'curriculum_type' ) ) { $lesson = helper::get_lesson_by_slug( get_query_var( 'name' ) ); if ( $lesson ) { return $lesson['lesson_title']; } return get_query_var( 'name' ); } $post = get_page_by_path( get_query_var( 'name' ), OBJECT, self::get_post_type_name( get_query_var( 'curriculum_type' ) ) ); if ( $post ) { return $post->post_title; } return get_query_var( 'name' ); }//end if return $title; } public function archive_course_document_title( $name, $post_type ) { if ( 'academy_courses' === $post_type ) { $course_page = (int) \Academy\Helper::get_settings( 'course_page' ); return get_the_title( $course_page ); } return $name; } public function get_post_type_name( $type ) { if ( 'quiz' === $type ) { return 'academy_quiz'; } elseif ( 'booking' === $type ) { return 'academy_booking'; } elseif ( 'meeting' === $type ) { return 'academy_meeting'; } elseif ( 'assignment' === $type ) { return 'academy_assignments'; } return $type; } public function register_block_styles() { // Define block styles $block_custom_styles = [ [ 'block' => 'core/columns', 'styles' => [ [ 'name' => 'academylms', 'label' => __( 'AcademyLms', 'academy' ), ], ], ], [ 'block' => 'core/column', 'styles' => [ [ 'name' => 'academylmssticky', 'label' => __( 'AcademyLms Sticky', 'academy' ), ], ], ], // Add more blocks and styles as needed ]; foreach ( $block_custom_styles as $block_custom_style ) { foreach ( $block_custom_style['styles'] as $style ) { register_block_style( $block_custom_style['block'], $style ); } } } public function custom_featured_image_with_default( $block_content, $block ) { if ( 'core/post-featured-image' !== $block['blockName'] || ! is_singular( 'academy_courses' ) ) { return $block_content; } $course_id = get_the_ID(); $video_output = self::get_course_fsc_preview_videos( $course_id ); if ( ! empty( $video_output ) ) { return $video_output; } if ( has_post_thumbnail() ) { return $block_content; } $image_url = ACADEMY_ASSETS_URI . 'images/thumbnail-placeholder.png'; return sprintf( '%s', esc_url( $image_url ), esc_attr__( 'Default Featured Image', 'academy' ) ); } /** * FSE/block-theme single course template (templates/block-templates/single-academy_courses.html) * shows categories via the core `wp:post-terms` block, which has no concept of a category * image. This injects the existing category image (see includes/traits/courses.php * get_the_course_category_image_url(), the same helper the classic-theme templates use) into * each term link's output, without altering the block's own markup/attrs. * * @param mixed $block_content * @param \WP_Block $block */ public function add_category_images_to_post_terms( $block_content, $block ) { if ( 'core/post-terms' !== $block['blockName'] || ! is_singular( 'academy_courses' ) || 'academy_courses_category' !== ( $block['attrs']['term'] ?? '' ) ) { return $block_content; } $categories = \Academy\Helper::get_the_course_category( get_the_ID() ); if ( empty( $categories ) || is_wp_error( $categories ) ) { return $block_content; } foreach ( $categories as $category ) { $term_link = get_term_link( $category->term_id ); if ( is_wp_error( $term_link ) ) { continue; } $image = sprintf( '', esc_url( \Academy\Helper::get_the_course_category_image_url( $category->term_id ) ) ); $block_content = preg_replace_callback( '/]*>/', function ( $matches ) use ( $image ) { return $matches[0] . $image; }, $block_content, 1 ); }//end foreach return $block_content; } public static function get_course_fsc_preview_videos( $id ) { $output = ''; $intro_video = get_post_meta( $id, 'academy_course_intro_video', true ); if ( $intro_video && is_array( $intro_video ) && count( $intro_video ) > 1 && ! empty( $intro_video[1] ) ) { $type = $intro_video[0]; if ( 'html5' === $type ) { $attachment_id = (int) $intro_video[1]; $att_url = wp_get_attachment_url( $attachment_id ); $thumb_id = get_post_thumbnail_id( $attachment_id ); $thumb_url = wp_get_attachment_url( $thumb_id ); $output .= sprintf( '', esc_url( $thumb_url ), esc_url( $att_url ) ); } elseif ( 'embedded' === $type ) { $embed = Helper::parse_embedded_url( $intro_video[1] ); $output .= sprintf( '
', esc_url( $embed['url'] ) ); } elseif ( 'youtube' === $type || 'vimeo' === $type ) { $embed = Helper::get_basic_url_to_embed_url( $intro_video[1] ); $output .= sprintf( '
', esc_url( $embed['url'] ) ); } elseif ( 'shortcode' === $type ) { $output .= do_shortcode( $intro_video[1] ); } else { $embed = Helper::get_basic_url_to_embed_url( $intro_video[1] ); $output .= sprintf( '
', esc_url( $embed['url'] ) ); }//end if }//end if return $output; } }