$id, 'title' => '', 'description' => '', 'screen' => 'dashboard', 'area' => 'main', 'order' => 10, 'span' => 12, 'render' => null, 'actions' => null, 'kpi' => null, ) ); $registry[ $id ]['id'] = $id; } /** * Widgets for one screen area, sorted. * * @param string $screen Screen key. * @param string|null $area Area, or null for all areas. * @return array */ function swiftdash_get_widgets( $screen, $area = null ) { $widgets = array_filter( swiftdash_widget_registry(), function ( $w ) use ( $screen, $area ) { return $w['screen'] === $screen && ( null === $area || $w['area'] === $area ) && is_callable( $w['render'] ); } ); uasort( $widgets, function ( $a, $b ) { return $a['order'] <=> $b['order']; } ); return $widgets; } /** * KPI card style of a widget: its own 'kpi' args, else the built-in visitor KPIs * (the "an-" analytics copies share them). * * @param array $widget Widget args. * @return array|null { icon, tone } or null when the widget is not a KPI card. */ function swiftdash_kpi_card( $widget ) { if ( is_array( $widget['kpi'] ) && isset( $widget['kpi']['icon'], $widget['kpi']['tone'] ) ) { return $widget['kpi']; } $cards = array( 'visitors' => array( 'users-fill', 'blue' ), 'page-views' => array( 'chart-bar-fill', 'purple' ), 'sessions' => array( 'clock-fill', 'green' ), ); $key = preg_replace( '/^an-/', '', $widget['id'] ); return isset( $cards[ $key ] ) ? array_combine( array( 'icon', 'tone' ), $cards[ $key ] ) : null; } /** * Saved card order per screen for the current user. * * @return array Card ids per screen key, e.g. { dashboard: string[] }. */ function swiftdash_layout() { $saved = get_user_meta( get_current_user_id(), 'swiftdash_layout', true ); $layout = array( 'dashboard' => array() ); foreach ( is_array( $saved ) ? $saved : array() as $screen => $ids ) { if ( is_array( $ids ) ) { $layout[ sanitize_key( $screen ) ] = array_values( array_filter( array_map( 'sanitize_key', $ids ) ) ); } } return $layout; } /** * Register the per-user card order meta (saved through POST /wp/v2/users/me). */ function swiftdash_register_layout_meta() { $ids = array( 'type' => 'array', 'items' => array( 'type' => 'string', 'pattern' => '^[a-z0-9_-]+$', ), ); register_meta( 'user', 'swiftdash_layout', array( 'type' => 'object', 'single' => true, 'default' => array( 'dashboard' => array(), ), 'auth_callback' => function ( $allowed, $meta_key, $user_id ) { return current_user_can( 'edit_user', $user_id ); }, 'show_in_rest' => array( 'schema' => array( 'type' => 'object', 'properties' => array( 'dashboard' => $ids, ), 'additionalProperties' => $ids, ), ), ) ); } add_action( 'init', 'swiftdash_register_layout_meta' ); /** * Print every widget of a screen in one sortable 12-column grid (user order on top of the * default order: area by area, then widget order). * * @param string $screen_key Screen key, e.g. dashboard. */ function swiftdash_render_screen_widgets( $screen_key ) { $widgets = swiftdash_get_screen_widgets( $screen_key ); if ( ! $widgets ) { return; } $defaults = wp_list_pluck( $widgets, 'id' ); $layout = swiftdash_layout(); $saved = isset( $layout[ $screen_key ] ) ? array_flip( $layout[ $screen_key ] ) : array(); $count = count( $saved ); $rank = array(); foreach ( $defaults as $i => $id ) { // Saved cards keep their saved position; cards added later follow them in default order. $rank[ $id ] = isset( $saved[ $id ] ) ? $saved[ $id ] : $count + $i; } usort( $widgets, function ( $a, $b ) use ( $rank ) { return $rank[ $a['id'] ] <=> $rank[ $b['id'] ]; } ); $hidden = swiftdash_prefs()['hidden']; printf( '
', esc_attr( $screen_key ), esc_attr( implode( ',', $defaults ) ) ); foreach ( $widgets as $widget ) { swiftdash_render_widget( $widget, in_array( $widget['id'], $hidden, true ), true ); } echo '
'; echo '

' . esc_html__( 'Drag to move this card, or press the up and down arrow keys.', 'swiftwebdev-dashboard-analytics' ) . '

'; echo '
'; } /** * Card order assets on screens with a sortable widget grid. * * @param string $screen_key Current screen key. */ function swiftdash_enqueue_sortable( $screen_key ) { /** * Filters the screen keys that print a sortable widget grid. * * @param string[] $screens Default array( 'dashboard' ). */ if ( ! in_array( $screen_key, (array) apply_filters( 'swiftdash_sortable_screens', array( 'dashboard' ) ), true ) ) { return; } wp_enqueue_script( 'swiftdash-sortable-lib', SWIFTDASH_URL . 'assets/vendor/sortable/Sortable.min.js', array(), '1.15.7', true ); wp_enqueue_script( 'swiftdash-widgets-sortable', SWIFTDASH_URL . 'assets/js/widgets-sortable.js', array( 'swiftdash-app', 'wp-api-fetch', 'wp-i18n', 'swiftdash-sortable-lib' ), SWIFTDASH_VERSION, true ); wp_set_script_translations( 'swiftdash-widgets-sortable', 'swiftwebdev-dashboard-analytics' ); wp_add_inline_script( 'swiftdash-widgets-sortable', 'window.swiftdashLayout = ' . wp_json_encode( swiftdash_layout() ) . ';', 'before' ); } add_action( 'swiftdash_enqueue_assets', 'swiftdash_enqueue_sortable' ); /** * Print one widget card. * * @param array $widget Widget args. * @param bool $hidden Hidden by the user's customize panel. * @param bool $sortable Print a drag handle in the header. */ function swiftdash_render_widget( $widget, $hidden = false, $sortable = false ) { $widget = wp_parse_args( $widget, array( 'description' => '', 'span' => 12, 'actions' => null, 'kpi' => null, ) ); $id = 'sd-w-' . $widget['id']; $kpi = swiftdash_kpi_card( $widget ); $class = 'sd-card sd-widget sd-span-' . absint( $widget['span'] ) . ( $kpi ? ' sd-widget--kpi' : '' ); printf( '
', esc_attr( $class ), esc_attr( $id ), esc_attr( $widget['id'] ), $hidden ? ' hidden' : '' ); if ( $kpi ) { printf( '%s', esc_attr( $kpi['tone'] ), wp_kses( swiftdash_icon( $kpi['icon'] ), swiftdash_icon_kses() ) ); } echo '
'; printf( '

%s

', esc_attr( $id ), esc_html( $widget['title'] ) ); if ( $kpi && $widget['description'] ) { printf( '', /* translators: %s: metric name. */ esc_attr( sprintf( __( 'About %s', 'swiftwebdev-dashboard-analytics' ), $widget['title'] ) ), esc_attr( $id ), wp_kses( swiftdash_icon( 'info' ), swiftdash_icon_kses() ), esc_html( $widget['description'] ) ); } if ( is_callable( $widget['actions'] ) ) { echo '
'; call_user_func( $widget['actions'], $widget ); echo '
'; } if ( $sortable ) { printf( '', /* translators: %s: card title. */ esc_attr( sprintf( __( 'Move %s', 'swiftwebdev-dashboard-analytics' ), $widget['title'] ) ), wp_kses( swiftdash_icon( 'dots-six-vertical' ), swiftdash_icon_kses() ) ); } echo '
'; echo '
'; call_user_func( $widget['render'], $widget ); echo '
'; echo '
'; } /** * Whether the add-on notice should show for the current user. * * @return bool */ function swiftdash_show_upgrade() { /** * Filters whether the add-on notice is shown. An installed add-on can return false. * * @param bool $show Default true. */ $show = (bool) apply_filters( 'swiftdash_show_upgrade', true ); return $show && swiftdash_prefs()['upgrade_snooze'] < time(); } /** * Banner at the end of the dashboard about the separate PRO add-on: what it adds, one link * to learn more. Dismissible for 30 days; never shown elsewhere. * * @param string $screen_key Screen the banner is on, for the link's campaign tag. */ function swiftdash_render_upgrade( $screen_key = 'dashboard' ) { if ( ! swiftdash_show_upgrade() ) { return; } $url = add_query_arg( array( 'utm_source' => 'plugin', 'utm_medium' => sanitize_key( $screen_key ) . '-banner', ), SWIFTDASH_ADDON_URL ); $features = array( __( 'Real-time visitors', 'swiftwebdev-dashboard-analytics' ), __( 'Traffic sources and referrers', 'swiftwebdev-dashboard-analytics' ), __( 'Top pages and new visitors', 'swiftwebdev-dashboard-analytics' ), __( 'Countries, devices and browsers', 'swiftwebdev-dashboard-analytics' ), __( 'Form and click tracking', 'swiftwebdev-dashboard-analytics' ), __( 'Session recordings with private fields hidden', 'swiftwebdev-dashboard-analytics' ), __( 'Custom date ranges and CSV export', 'swiftwebdev-dashboard-analytics' ), __( 'Weekly speed tests with email alerts', 'swiftwebdev-dashboard-analytics' ), ); $title_id = 'sd-promo-title-' . sanitize_key( $screen_key ); ?>