$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_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( '