$item ) {
$key = $item['key'];
if ( ! isset( $items[ $key ] ) ) {
continue;
}
$items[ $key ]['priority'] = ( $index + 1 ) * 10;
if ( '' !== $item['label'] ) {
$items[ $key ]['label'] = $item['label'];
}
if ( empty( $item['enabled'] ) && ! in_array( $key, Settings::REQUIRED_MENU, true ) ) {
$items[ $key ]['public'] = false;
}
}
return $items;
}
/**
* Pages added to the menu on Customize → Dashboard. Each one shows a
* WordPress page inside the dashboard, so it can hold any block or
* shortcode. They are registered like any other item, so they can be
* reordered, renamed and switched off with the rest of the menu.
*
* @param array $items Menu items.
* @return array
*/
public static function add_pages( $items ) {
if ( ! is_array( $items ) ) {
return $items;
}
foreach ( Settings::get()['pages'] as $index => $page ) {
if ( isset( $items[ $page['key'] ] ) ) {
continue;
}
$items[ $page['key'] ] = [
'label' => $page['label'],
'area' => $page['area'],
'icon' => 'academy-icon academy-icon--' . $page['icon'],
'public' => true,
'priority' => 90 + $index,
];
add_action( 'academy_frontend_dashboard_' . $page['key'] . '_endpoint', [ __CLASS__, 'render_page' ] );
}
return $items;
}
/**
* A page added to the menu, found by its menu key.
*
* @param string $key Menu key.
* @return array|null
*/
public static function find_page( $key ) {
foreach ( Settings::get()['pages'] as $page ) {
if ( $page['key'] === $key ) {
return $page;
}
}
return null;
}
/**
* Show a page added to the menu: its content, blocks and shortcodes
* included, inside the dashboard.
*
* @param string $key Menu key.
* @return void
*/
public static function render_page( $key ) {
static $depth = 0;
$page = self::find_page( (string) $key );
$post = $page && $page['page'] ? get_post( $page['page'] ) : null;
// The dashboard page itself would draw the dashboard again, and again.
$usable = $post && 'page' === $post->post_type && in_array( $post->post_status, [ 'publish', 'private' ], true )
&& (int) Helper::get_settings( 'frontend_dashboard_page' ) !== (int) $post->ID;
if ( ! $usable || $depth > 0 ) {
if ( current_user_can( 'manage_options' ) ) {
echo '
' . esc_html__( 'Choose a page for this menu item on Academy → Customize → Dashboard.', 'academy' ) . '
';
}
return;
}
++$depth;
// Blocks that read the current post (title, featured image…) see this page.
$previous = $GLOBALS['post'] ?? null;
$GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
setup_postdata( $post );
$content = apply_filters( 'the_content', $post->post_content ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core filter.
$GLOBALS['post'] = $previous; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
if ( $previous instanceof \WP_Post ) {
setup_postdata( $previous );
}
--$depth;
?>
card.
* @param string $view learning|teaching|family.
* @return array
*/
public static function apply_cards( $cards, $view = '' ) {
$saved = Settings::get()['cards'];
if ( ! $saved || ! is_array( $cards ) ) {
return $cards;
}
$ordered = [];
foreach ( $saved as $item ) {
if ( isset( $cards[ $item['key'] ] ) ) {
if ( $item['enabled'] ) {
$ordered[ $item['key'] ] = $cards[ $item['key'] ];
}
unset( $cards[ $item['key'] ] );
}
}
// Cards an add-on added since the list was saved go last.
return $ordered + $cards;
}
/**
* Boxed or full width. "Auto" keeps Academy's behaviour: full width for
* instructors, boxed for students.
*
* @param string $class Container class.
* @param int $page_id Page ID.
* @return string
*/
public static function container_class( $class, $page_id ) {
if ( (int) Helper::get_settings( 'frontend_dashboard_page' ) !== (int) $page_id ) {
return $class;
}
$width = Settings::get()['width'];
if ( 'full' === $width ) {
return 'academy-container-fluid';
}
if ( 'boxed' === $width ) {
return 'academy-container';
}
return $class;
}
/**
* The dashboard's own container, inside the page: full width when set.
*
* @return string
*/
public static function inner_container_class() {
// The dashboard's layout rules hang on .academy-container, so full width
// is a modifier on it rather than another container.
return self::is_full_width() ? 'academy-container academy-dashboard-container--full' : 'academy-container';
}
/**
* Classes for the dashboard's outer wrapper.
*
* Block themes cap everything in the post content at their content width
* (often 600-700px), far too narrow for the dashboard. WordPress's own
* alignment classes lift that cap using the theme's sizes: `alignwide`
* takes the theme's wide width, `alignfull` the whole page. Classic
* themes don't apply those rules the same way, so they're left as before.
*
* @return string
*/
public static function wrapper_class() {
$classes = array( 'academy-frontend-dashboard' );
if ( self::is_full_width() ) {
$classes[] = 'academy-frontend-dashboard--full';
}
if ( wp_is_block_theme() ) {
$classes[] = self::is_full_width() ? 'alignfull' : 'alignwide';
}
/**
* Filters the dashboard wrapper's classes.
*
* @param string[] $classes Classes.
*/
$classes = (array) apply_filters( 'academy/frontend_dashboard/wrapper_classes', $classes );
return implode( ' ', array_map( 'sanitize_html_class', $classes ) );
}
/**
* Whether the dashboard runs the full width of the page.
*
* @return bool
*/
public static function is_full_width() {
// "Automatic" keeps the dashboard as it has always been.
return 'full' === Settings::get()['width'];
}
/**
* Classes the dashboard's styles hang on.
*
* @param string[] $classes Body classes.
* @return string[]
*/
public static function body_class( $classes ) {
if ( ! self::is_page() ) {
return $classes;
}
$settings = Settings::get();
$classes[] = 'academy-dashboard-body';
if ( 'bare' === $settings['layout'] && is_user_logged_in() ) {
$classes[] = 'academy-dashboard-body--bare';
}
if ( $settings['sidebarCollapsed'] ) {
$classes[] = 'academy-dashboard-body--collapsed';
}
return $classes;
}
/**
* Sidebar width and the colours set on Customize, for light and dark mode.
*
* @return void
*/
public static function print_styles() {
if ( ! self::is_page() ) {
return;
}
$settings = Settings::get();
$css = sprintf( 'body.academy-dashboard-body{--academy-dashboard-sidebar-width:%dpx;}', $settings['sidebarWidth'] );
// Dark mode reaches the page around the dashboard too. On the "theme"
// layout that's only the `.academy-frontend-dashboard` wrapper, or text
// outside the cards (the welcome line) turns light on the theme's light
// page. Printed before the Customize colours so a set one still wins.
$css .= 'body.academy-dashboard-body[data-academy-user-theme="dark"] .academy-canvas,body.academy-dashboard-body--bare[data-academy-user-theme="dark"],body.academy-dashboard-body[data-academy-user-theme="dark"] .academy-frontend-dashboard{background:var(--academy-body-background-color);color:var(--academy-text-color)}';
// Only hex colours get here (Settings::colors()).
foreach ( [ 'light', 'dark' ] as $mode ) {
$scope = 'dark' === $mode ? 'body.academy-dashboard-body[data-academy-user-theme="dark"]' : 'body.academy-dashboard-body';
$colors = $settings['colors'][ $mode ];
$rules = [
// `.academy-canvas` only exists on the "academy"/"bare" page templates
// (`template()` above); `--bare` covers the bare layout's own body
// modifier. Neither is present on the default "theme" layout, where
// the shortcode's own `.academy-frontend-dashboard` wrapper is the
// only element behind the whole dashboard — include it too, or this
// colour never has anything to paint on the most common layout.
'background' => '%1$s .academy-canvas,%1$s.academy-dashboard-body--bare,%1$s .academy-frontend-dashboard{background:%2$s}',
'sidebar' => '%1$s .academy-dashboard-menu,%1$s .academy-frontend-dashboard__user{background:%2$s}',
'active' => '%1$s .academy-dashboard-menu__item-current>a{background:%2$s}',
'topbar' => '%1$s .academy-frontend-dashboard .academy-topbar{background:%2$s}',
'card' => '%1$s .academy-analytics-cards--card,%1$s .academy-dashboard-continue__course{background:%2$s}',
];
foreach ( $rules as $key => $rule ) {
if ( '' !== $colors[ $key ] ) {
$css .= sprintf( $rule, $scope, $colors[ $key ] );
}
}
}//end foreach
printf( "\n", wp_strip_all_tags( $css ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CSS body is built from colour settings validated above; wp_strip_all_tags guards against a stray tag ever reaching the