'', 'icon' => '', 'title' => '', 'heading' => 'h3', 'description' => '', 'footnote' => '', 'toolbar' => '', 'wrap' => true, 'card_class' => '', ), (array) $config ); $boxed = $c['wrap'] || '' !== (string) $c['title']; if ( $boxed ) { $shell = self::card_shell( array_merge( array_intersect_key( $c, array_flip( array( 'col', 'span', 'min', 'movable', 'essential', 'meta' ) ) ), array( 'anchor' => $c['anchor'], 'icon' => $c['icon'], 'title' => $c['title'], 'level' => 'h2' === $c['heading'] && $c['wrap'] ? 2 : 3, 'description' => $c['description'], 'footnote' => $c['footnote'], 'tools' => $c['toolbar'], 'class' => $c['card_class'], 'flush' => true, ) ) ); } else { $shell = array( '' !== (string) $c['description'] ? '

' . esc_html( $c['description'] ) . '

' : '', '' !== (string) $c['footnote'] ? '

' . esc_html( $c['footnote'] ) . '

' : '', ); } echo $shell[0] . self::get_html( $config ) . $shell[1]; } public static function card_shell( $c ) { $c = array_merge( array( 'anchor' => '', 'icon' => '', 'title' => '', 'level' => 2, 'description' => '', 'footnote' => '', 'tools' => '', 'class' => '', 'footer' => '', 'flush' => false, ), (array) $c ); $sub = 3 === (int) $c['level']; $anchor = (string) $c['anchor']; $title = (string) $c['title']; if ( '' !== $title && class_exists( 'AIEO_Chrome' ) && AIEO_Chrome::on() && function_exists( 'rooster_admin_card_open_html' ) ) { $opts = array( 'dom_id' => '' !== $anchor ? 'aieo-card-' . $anchor : '', 'description' => (string) $c['description'], 'tools' => (string) $c['tools'], 'class' => trim( ( $sub ? 'is-sub ' : '' ) . (string) $c['class'] ), 'level' => $sub ? 3 : 2, 'flush' => ! empty( $c['flush'] ), 'icon' => (string) $c['icon'], 'attrs' => array_merge( is_array( $c['attrs'] ?? null ) ? $c['attrs'] : array(), '' !== (string) $c['icon'] ? array( 'data-aieo-icon' => (string) $c['icon'] ) : array() ), 'meta' => (string) ( $c['meta'] ?? '' ), ); foreach ( array( 'col', 'span', 'min', 'movable', 'essential' ) as $key ) { if ( ! $sub && array_key_exists( $key, $c ) ) { $opts[ $key ] = $c[ $key ]; } } $open = rooster_admin_card_open_html( '', $title, $opts ); $foot = (string) ( $c['footer'] ?? '' ) . ( '' !== (string) $c['footnote'] ? '

' . esc_html( $c['footnote'] ) . '

' : '' ); $close = ( '' !== $foot ? rooster_admin_card_footer_html() . $foot : '' ) . rooster_admin_card_close_html(); return array( $open, $close ); } $tag = $sub ? 'h3' : 'h2'; $open = ''; if ( '' !== $title ) { $open .= '<' . $tag . ' class="aieo-t-card-h">' . esc_html( $title ) . ( '' !== (string) $c['tools'] ? ' ' . $c['tools'] : '' ) . ''; } if ( '' !== (string) $c['description'] ) { $open .= '

' . esc_html( $c['description'] ) . '

'; } $close = (string) $c['footer'] . ( '' !== (string) $c['footnote'] ? '

' . esc_html( $c['footnote'] ) . '

' : '' ) . ''; return array( $open, $close ); } private static function config( $config ) { return array_merge( array( 'columns' => array(), 'rows' => array(), 'row_key' => '', 'row_label' => '', 'row_type' => '', 'actions' => array(), 'bulk' => array(), 'page_id' => '', 'empty' => '', 'building' => false, 'totals' => array(), 'totals_at' => 'below', 'totals_label' => '', 'sortable' => true, 'head' => true, 'row_header' => false, 'bare' => false, 'narrow' => false, 'density' => 'normal', 'cap' => 0, 'capped' => false, 'total' => null, 'id' => '', 'tbody_id' => '', 'group_by' => '', 'table_data' => array(), 'class' => '', 'row_class' => null, 'row_data' => array(), 'detail_row' => null, 'report' => '', 'meta' => array(), 'caps' => array(), 'nonce' => '', 'search' => '', 'params' => array(), 'filter_options' => array(), 'filter_autoapply' => false, 'filter_columns' => array(), 'context' => 'admin', 'per_page' => null, 'view_prefix' => '', 'caption' => '', 'edit' => array(), 'scroll' => null, 'sticky_first' => false, 'registry' => '', 'phone' => '', 'items' => '', 'filters' => array(), 'chooser' => false, 'text_size' => false, 'export' => array(), 'move' => array(), 'sizes' => null, 'remote' => array(), 'filter_values' => null, 'answer_extra' => null, ), (array) $config ); } public static function get_html( $config ) { $c = self::config( $config ); $is_t1 = ( '' !== (string) $c['report'] ); if ( $is_t1 ) { $c = self::t1_defaults( $c ); } if ( $is_t1 && ! empty( $c['remote']['url'] ) ) { return self::library_remote_html( $c ); } $columns = array_map( array( 'AIEO_Table_Columns', 'normalise' ), (array) $c['columns'] ); if ( ! $columns ) { return ''; } $rows = array_values( (array) $c['rows'] ); $legacy = ''; if ( ! $is_t1 && 'storefront' !== (string) $c['context'] ) { $legacy = self::library_ready( $c, $columns ); $library = '' === $legacy ? self::library_html( $c, $columns, $rows ) : null; if ( null !== $library ) { return $library; } if ( '' === $legacy ) { $legacy = self::$declined; } $legacy = '-' === $legacy ? '' : $legacy; $c['sortable'] = false; $c['caps'] = array_merge( (array) $c['caps'], array( 'search' => false ) ); } if ( $c['actions'] && self::actions_column_earned( $rows, $c, $is_t1 ) ) { $columns[] = AIEO_Table_Columns::normalise( array( 'key' => '__actions', 'label' => __( 'Actions', 'ai-eshop-optimizer' ), 'type' => 'actions', 'sortable' => false, 'align' => 'right', ) ); } if ( '' === $c['id'] ) { self::$seq++; $c['id'] = 'aieo-t-' . self::$seq; } self::prime_object_columns( $columns, $rows ); if ( empty( $c['head'] ) ) { $c['sortable'] = false; } $classes = array( 'aieo-t' ); if ( apply_filters( 'aieo_table_legacy_chrome', false, $c ) ) { array_unshift( $classes, 'widefat', 'striped' ); } if ( 'compact' === $c['density'] ) { $classes[] = 'aieo-t--compact'; } if ( $c['sortable'] ) { $classes[] = 'aieo-t--sortable'; } if ( empty( $c['head'] ) ) { $classes[] = 'aieo-t--panel'; } if ( ! empty( $c['bare'] ) ) { $classes[] = 'aieo-t--bare'; } if ( ! empty( $c['narrow'] ) ) { $classes[] = 'aieo-t--narrow'; } if ( $c['class'] ) { $classes[] = $c['class']; } if ( $is_t1 ) { $classes = array_values( array_diff( $classes, array( 'aieo-t--sortable' ) ) ); if ( 'server' === $c['caps']['sort'] ) { $classes[] = 'aieo-t--server'; } } $out = ''; if ( $c['totals'] && 'above' === $c['totals_at'] ) { $out .= self::totals_strip( $columns, $c['totals'] ); } if ( $is_t1 || ! empty( $c['caps']['search'] ) ) { $out = self::toolbar( $c ) . self::filter_panel( $c, $columns ) . $out; } $out .= self::bulk_bar( $c, $columns, $rows ); $table_attrs = ''; foreach ( (array) $c['table_data'] as $tk => $tv ) { if ( null === $tv || '' === $tv ) { continue; } $table_attrs .= ' data-' . esc_attr( str_replace( '_', '-', sanitize_key( (string) $tk ) ) ) . '="' . esc_attr( (string) $tv ) . '"'; } $frame = 'aieo-dmm-scroll-x' . ( empty( $c['bare'] ) ? ' aieo-t-frame' : '' ); $out .= '
'; $out .= ''; $out .= self::head( $columns, $c ); $out .= ''; if ( ! $rows ) { $out .= self::empty_row( $columns, $c ); } else { $groups = self::group_runs( $rows, $c ); foreach ( $rows as $i => $row ) { $out .= self::row( $row, $columns, $c, $i, isset( $groups[ $i ] ) ? $groups[ $i ] : null ); } } $out .= ''; $out .= self::foot( $columns, $c ); $out .= '
'; if ( $c['totals'] && ! in_array( $c['totals_at'], array( 'above', 'foot' ), true ) ) { $out .= self::totals_strip( $columns, $c['totals'] ); } if ( $is_t1 && ! empty( $c['caps']['paginate'] ) ) { $out .= self::pager( $c ); } else { if ( $is_t1 ) { $shown = count( $rows ); $c['total'] = (int) self::meta( $c, 'filtered', 0 ); $c['cap'] = (int) self::meta( $c, 'per_page', $shown ); $c['capped'] = ( $c['total'] > $shown ); } $out .= self::cap_notice( $c, count( $rows ) ); } if ( $is_t1 ) { $out = '
' . $out . '
'; } elseif ( ! empty( $c['caps']['search'] ) ) { $out = '
' . $out . '
'; } return $out; } private static function t1_defaults( $c ) { $caps = array( 'sort' => 'server', 'paginate' => true, 'search' => true, 'filters' => false, 'export' => false, 'bulk' => false, ); $rid = ''; if ( class_exists( 'AIEO_Report_Registry' ) ) { $def = AIEO_Report_Registry::get( $c['report'] ); $rid = $def ? (string) $def->get( 'registry_id', '' ) : ''; } if ( $rid && class_exists( 'AIEO_Table_Registry' ) ) { $row = AIEO_Table_Registry::get( $rid ); if ( $row ) { foreach ( array_keys( $caps ) as $k ) { if ( isset( $row['caps'][ $k ] ) ) { $caps[ $k ] = $row['caps'][ $k ]; } } } } $c['caps'] = array_merge( $caps, (array) $c['caps'] ); if ( '' === $c['id'] ) { $c['id'] = 'aieo-t-' . preg_replace( '/[^a-z0-9_-]/', '', strtolower( (string) $c['report'] ) ); } return $c; } private static function scope_param_keys( $report_id ) { if ( '' === (string) $report_id || ! class_exists( 'AIEO_Report_Registry' ) ) { return array(); } $def = AIEO_Report_Registry::get( $report_id ); return $def ? array_keys( (array) $def->get( 'params', array() ) ) : array(); } private static function meta( $c, $key, $default = null ) { return isset( $c['meta'][ $key ] ) ? $c['meta'][ $key ] : $default; } private static function toolbar( $c ) { $bits = ''; if ( ! empty( $c['caps']['search'] ) ) { $bits .= '' . '' . ''; } if ( ! empty( $c['caps']['paginate'] ) ) { $current = (int) self::meta( $c, 'per_page', 25 ); $opts = array( 10, 25, 50, 100, 200 ); if ( ! in_array( $current, $opts, true ) ) { $opts[] = $current; sort( $opts ); } $sel = ''; foreach ( $opts as $n ) { $sel .= ''; } $bits .= '' . ''; } if ( '' === $bits ) { return ''; } return '
' . $bits . '
'; } private static function bulk_bar( $c, $columns, $rows ) { $actions = (array) $c['bulk']; if ( ! $actions ) { return ''; } $is_t1 = ( '' !== (string) $c['report'] ); if ( $is_t1 && empty( $c['caps']['bulk'] ) ) { return ''; } if ( ! $is_t1 && ! $rows ) { return ''; } $has_selector = false; foreach ( $columns as $col ) { if ( 'selector' === (string) $col['type'] ) { $has_selector = true; break; } } if ( ! $has_selector ) { return ''; } $bits = ''; $any_filter = false; foreach ( $actions as $a ) { $a = array_merge( array( 'id' => '', 'label' => '', 'capability' => '', 'destructive' => false, 'confirm' => '', 'scope' => 'selection', 'param' => array(), ), (array) $a ); if ( '' === (string) $a['id'] || '' === (string) $a['label'] ) { continue; } if ( $a['capability'] && ! current_user_can( $a['capability'] ) ) { continue; } if ( 'filter' === (string) $a['scope'] ) { $any_filter = true; } $button = ' '; $picker = self::bulk_param_control( $a ); $bits .= ( '' !== $picker ) ? '' . $picker . ' ' . $button . ' ' : $button; } if ( '' === $bits ) { return ''; } $all = ''; if ( $is_t1 && $any_filter ) { $filtered = (int) self::meta( $c, 'filtered', 0 ); if ( $filtered > count( $rows ) ) { $all = ''; } } return '
' . trim( $bits ) . $all . '' . '
'; } private static function bulk_param_control( $a ) { $param = isset( $a['param'] ) && is_array( $a['param'] ) ? $a['param'] : array(); if ( ! $param || empty( $param['type'] ) ) { return ''; } $type = (string) $param['type']; $label = ! empty( $param['label'] ) ? (string) $param['label'] : (string) $a['label']; $paint = in_array( $type, array( 'select', 'term', 'options' ), true ) ? 'aieo-c-select' : 'aieo-c-input'; $attrs = ' class="' . $paint . ' aieo-c-input--sm aieo-t-bulk-param"' . ' data-for="' . esc_attr( (string) $a['id'] ) . '"' . ( ! empty( $param['required'] ) ? ' data-required="1"' : '' ) . ' aria-label="' . esc_attr( $label ) . '"'; switch ( $type ) { case 'select': $out = ''; foreach ( (array) $param['options'] as $value => $text ) { $out .= ''; } return $out . ''; case 'term': $terms = get_terms( array( 'taxonomy' => (string) $param['taxonomy'], 'hide_empty' => false, 'number' => 500, 'fields' => 'id=>name', ) ); $out = '' . ''; if ( is_array( $terms ) ) { foreach ( $terms as $tid => $tname ) { $out .= ''; } } return $out . ''; case 'options': return '' . ''; case 'number': return ''; default: return ''; } } private static function filter_panel( $c, $columns ) { if ( empty( $c['caps']['filters'] ) ) { return ''; } $active = (array) $c['params']; $bits = ''; if ( ! empty( $c['filter_columns'] ) && is_array( $c['filter_columns'] ) ) { $columns = $c['filter_columns']; } foreach ( $columns as $col ) { $kind = isset( $col['filter'] ) ? (string) $col['filter'] : ''; if ( '' === $kind ) { continue; } $key = (string) $col['key']; $label = (string) $col['label']; $val = isset( $active[ $key ] ) ? $active[ $key ] : ''; $bits .= '' . ''; switch ( $kind ) { case 'select': case 'multiselect': $is_multi = ( 'multiselect' === $kind ); $chosen = $is_multi ? (array) $val : array( (string) $val ); $given = isset( $c['filter_options'][ $key ] ) && is_array( $c['filter_options'][ $key ] ) ? $c['filter_options'][ $key ] : null; $bits .= ''; break; case 'number_range': case 'date_range': $type = ( 'date_range' === $kind ) ? 'date' : 'number'; $from = is_array( $val ) && isset( $val['from'] ) ? (string) $val['from'] : ''; $to = is_array( $val ) && isset( $val['to'] ) ? (string) $val['to'] : ''; $bits .= '' . '–' . ''; break; case 'number': $bits .= ''; break; default: $bits .= ''; break; } $bits .= ''; } if ( '' === $bits ) { return ''; } $bits .= '' . ( empty( $c['filter_autoapply'] ) ? '' : '' ) . '' . ''; return '
' . $bits . '
'; } private static function pager( $c ) { if ( empty( $c['caps']['paginate'] ) ) { return ''; } $filtered = (int) self::meta( $c, 'filtered', 0 ); $per_page = max( 1, (int) self::meta( $c, 'per_page', 25 ) ); $page = max( 1, (int) self::meta( $c, 'page', 1 ) ); $pages = self::page_count( $c ); if ( $filtered <= 0 ) { return ''; } $first = ( ( $page - 1 ) * $per_page ) + 1; $last = min( $filtered, $page * $per_page ); $info = '' . esc_html( sprintf( __( '%1$s–%2$s of %3$s', 'ai-eshop-optimizer' ), number_format_i18n( $first ), number_format_i18n( $last ), number_format_i18n( $filtered ) ) ) . ''; $total = (int) self::meta( $c, 'total', 0 ); if ( $total > 0 && $filtered !== $total ) { $info .= ' ' . esc_html( sprintf( __( '(filtered from %s)', 'ai-eshop-optimizer' ), number_format_i18n( $total ) ) ) . ''; } $links = ''; if ( $pages > 1 ) { $btn = static function ( $target, $label, $disabled ) { return ''; }; $links = '' . $btn( 1, '«', $page <= 1 ) . $btn( $page - 1, '‹', $page <= 1 ) . '' . esc_html( sprintf( __( 'Page %1$s of %2$s', 'ai-eshop-optimizer' ), number_format_i18n( $page ), number_format_i18n( $pages ) ) ) . '' . $btn( $page + 1, '›', $page >= $pages ) . $btn( $pages, '»', $page >= $pages ) . ''; } return '
' . $info . $links . '
'; } private static function head( $columns, $c ) { if ( empty( $c['head'] ) ) { return ''; } $is_t1 = ( '' !== (string) $c['report'] ); $server_sort = $is_t1 && 'server' === $c['caps']['sort']; $active = $is_t1 ? (string) self::meta( $c, 'sort', '' ) : ''; $active_dir = ( 'DESC' === strtoupper( (string) self::meta( $c, 'dir', 'ASC' ) ) ) ? 'desc' : 'asc'; $sortable_on = $is_t1 ? $server_sort : (bool) $c['sortable']; $out = ''; foreach ( $columns as $col ) { $cls = array( 'aieo-t-h', 'aieo-t-h--' . $col['type'] ); if ( $sortable_on && $col['sortable'] && 'actions' !== $col['type'] && 'selector' !== $col['type'] ) { $cls[] = 'aieo-t-sortable'; } $aria = ''; if ( $server_sort && '' !== $active ) { $key = ( '' !== (string) $col['sort_key'] ) ? (string) $col['sort_key'] : (string) $col['key']; if ( $key === $active ) { $cls[] = 'aieo-t-' . $active_dir; $aria = ' aria-sort="' . ( 'desc' === $active_dir ? 'descending' : 'ascending' ) . '"'; } } if ( '' === $aria && $sortable_on && $col['sortable'] && ! in_array( (string) $col['type'], array( 'actions', 'selector' ), true ) ) { $aria = ' aria-sort="none"'; } $align = $col['align']; if ( '' === $align && in_array( $col['type'], array( 'id', 'integer', 'decimal', 'money', 'percent' ), true ) ) { $align = 'right'; } if ( '' !== $align ) { $cls[] = 'aieo-t-a--' . $align; } $style = $col['width'] ? ' style="width:' . esc_attr( $col['width'] ) . '"' : ''; $sort_key = ( '' !== (string) $col['sort_key'] ) ? (string) $col['sort_key'] : (string) $col['key']; $tip = ( '' !== (string) $col['header_tip'] ) ? ' title="' . esc_attr( (string) $col['header_tip'] ) . '"' : ''; $drawn = ( '' !== (string) $col['header_html'] ) ? (string) $col['header_html'] : esc_html( (string) $col['label'] ); if ( 'selector' === (string) $col['type'] && '' === (string) $col['header_html'] ) { $drawn = ''; } $out .= '' . $drawn . ''; } return $out . ''; } private static function group_runs( $rows, $c ) { $key = isset( $c['group_by'] ) ? (string) $c['group_by'] : ''; if ( '' === $key || ! $rows ) { return array(); } $vals = array(); foreach ( $rows as $row ) { $vals[] = (string) AIEO_Table_Columns::value( $row, $key, '' ); } $out = array(); $n = count( $vals ); $i = 0; while ( $i < $n ) { $j = $i + 1; while ( $j < $n && $vals[ $j ] === $vals[ $i ] ) { $j++; } $size = $j - $i; for ( $k = $i; $k < $j; $k++ ) { $out[ $k ] = array( 'start' => ( $k === $i ), 'size' => $size ); } $i = $j; } return $out; } private static function row( $row, $columns, $c, $index, $group = null ) { $key = self::row_key_value( $row, $c['row_key'] ); $attrs = ''; if ( '' !== $key ) { $attrs .= ' data-row-key="' . esc_attr( $key ) . '"'; } if ( $c['row_type'] ) { $attrs .= ' data-row-type="' . esc_attr( (string) AIEO_Table_Columns::value( $row, $c['row_type'], '' ) ) . '"'; } $rc = AIEO_Table_Columns::is_fn( $c['row_class'] ) ? (string) call_user_func( $c['row_class'], $row ) : ''; if ( $group && ! empty( $group['start'] ) ) { $rc = trim( $rc . ' aieo-t-group-start' ); } if ( '' !== $rc ) { $attrs .= ' class="' . esc_attr( $rc ) . '"'; } $attrs .= self::data_attrs( $c['row_data'], $row ); $ctx = array( 'page_id' => $c['page_id'], 'row_key_value' => $key, 'index' => $index, ); $out = ''; $skip = 0; foreach ( $columns as $i => $col ) { if ( $skip > 0 ) { $skip--; continue; } $rowspan = 1; if ( $group && ! empty( $col['group_span'] ) ) { if ( empty( $group['start'] ) ) { continue; } $rowspan = max( 1, (int) $group['size'] ); } $span = 1; if ( AIEO_Table_Columns::is_fn( $col['span'] ) ) { $span = max( 1, (int) call_user_func( $col['span'], $row ) ); $span = min( $span, count( $columns ) - $i ); $skip = $span - 1; } $inner = ( 'actions' === $col['type'] ) ? self::action_cell( $row, $c, $key ) : AIEO_Table_Columns::render( $col, $row, $ctx ); $tag = ( $c['row_header'] && 0 === $i ) ? 'th' : 'td'; $sc = ( 'th' === $tag ) ? ' scope="row"' : ''; $cs = ( $span > 1 ) ? ' colspan="' . (int) $span . '"' : ''; $rs = ( $rowspan > 1 ) ? ' rowspan="' . (int) $rowspan . '"' : ''; $out .= '<' . $tag . ' ' . AIEO_Table_Columns::cell_attrs( $col, $row ) . $sc . $cs . $rs . '>' . $inner . ''; } $out .= ''; if ( AIEO_Table_Columns::is_fn( $c['detail_row'] ) ) { $panel = call_user_func( $c['detail_row'], $row ); $attrs = ' class="aieo-t-detail"'; $html = ''; if ( is_array( $panel ) ) { $html = isset( $panel['html'] ) ? (string) $panel['html'] : ''; $cls = 'aieo-t-detail' . ( isset( $panel['class'] ) ? ' ' . (string) $panel['class'] : '' ); $attrs = ' class="' . esc_attr( $cls ) . '"'; $attrs .= isset( $panel['id'] ) && '' !== $panel['id'] ? ' id="' . esc_attr( (string) $panel['id'] ) . '"' : ''; $attrs .= ! empty( $panel['hidden'] ) ? ' hidden' : ''; } else { $html = (string) $panel; } if ( '' !== $html ) { $out .= '' . '' . $html . ''; } } return $out; } private static function data_attrs( $map, $row ) { $reserved = array( 'row', 'action', 'confirm' ); $out = ''; foreach ( (array) $map as $dk => $dv ) { $key = str_replace( '_', '-', sanitize_key( (string) $dk ) ); if ( in_array( $key, $reserved, true ) ) { _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'The table action data key "%1$s" is reserved by the table component (%2$s) and was ignored. A duplicate attribute is dropped by the HTML parser, so the value would never reach your JavaScript. Rename it, e.g. "payload".', 'ai-eshop-optimizer' ), esc_html( $key ), esc_html( implode( ', ', $reserved ) ) ), '11.1.10' ); continue; } $dval = AIEO_Table_Columns::is_fn( $dv ) ? call_user_func( $dv, $row ) : AIEO_Table_Columns::value( $row, $dv, $dv ); if ( null === $dval || '' === $dval ) { continue; } $out .= ' data-' . esc_attr( $key ) . '="' . esc_attr( (string) $dval ) . '"'; } return $out; } private static function row_key_value( $row, $row_key ) { if ( ! $row_key ) { return ''; } if ( is_array( $row_key ) ) { $parts = array(); foreach ( $row_key as $k ) { $parts[] = (string) AIEO_Table_Columns::value( $row, $k, '' ); } return implode( ':', $parts ); } return (string) AIEO_Table_Columns::value( $row, $row_key, '' ); } private static function actions_column_earned( $rows, $c, $is_t1 ) { if ( $is_t1 && self::page_count( $c ) > 1 ) { return true; } foreach ( (array) $c['actions'] as $a ) { $a = (array) $a; $cap = isset( $a['capability'] ) ? (string) $a['capability'] : ''; if ( '' !== $cap && ! current_user_can( $cap ) ) { continue; } $visible = isset( $a['visible'] ) ? $a['visible'] : null; $intent = ! empty( $a['intent'] ) ? $a['intent'] : null; if ( ! AIEO_Table_Columns::is_fn( $visible ) && null === $intent ) { return true; } foreach ( $rows as $row ) { if ( AIEO_Table_Columns::is_fn( $visible ) && ! call_user_func( $visible, $row ) ) { continue; } if ( null !== $intent && null === AIEO_Action_Intents::resolve( $intent, $row ) ) { continue; } return true; } } return false; } private static function page_count( $c ) { $filtered = (int) self::meta( $c, 'filtered', 0 ); $per_page = max( 1, (int) self::meta( $c, 'per_page', 25 ) ); return (int) self::meta( $c, 'pages', (int) ceil( $filtered / $per_page ) ); } private static function action_cell( $row, $c, $key ) { $out = ''; foreach ( (array) $c['actions'] as $a ) { $a = array_merge( array( 'id' => '', 'label' => '', 'capability' => '', 'destructive' => false, 'confirm' => '', 'visible' => null, 'when' => array(), 'class' => '', 'variant' => '', 'hook' => '', 'intent' => array(), 'href' => '', 'external' => false, 'data' => array(), ), (array) $a ); if ( $a['capability'] && ! current_user_can( $a['capability'] ) ) { continue; } if ( AIEO_Table_Columns::is_fn( $a['visible'] ) && ! call_user_func( $a['visible'], $row ) ) { continue; } if ( ! empty( $a['when']['key'] ) ) { $have = (string) AIEO_Table_Columns::value( $row, $a['when']['key'], '' ); if ( array_key_exists( 'is', $a['when'] ) && $have !== (string) $a['when']['is'] ) { continue; } if ( array_key_exists( 'not', $a['when'] ) && $have === (string) $a['when']['not'] ) { continue; } } $blocked = ''; $intent = $a['intent']; if ( $intent ) { $res = AIEO_Action_Intents::resolve( $intent, $row ); if ( null === $res ) { continue; } if ( isset( $res['blocked'] ) ) { $blocked = (string) $res['blocked']; } elseif ( isset( $res['url'] ) ) { $a['href'] = (string) $res['url']; } } $label = AIEO_Table_Columns::is_fn( $a['label'] ) ? call_user_func( $a['label'], $row ) : $a['label']; $conf = AIEO_Table_Columns::is_fn( $a['confirm'] ) ? call_user_func( $a['confirm'], $row ) : $a['confirm']; $attr = ( $c['page_id'] && class_exists( 'AIEO_DMM_UI' ) ) ? ' ' . AIEO_DMM_UI::action_attr( $c['page_id'], $a['id'], ! empty( $a['destructive'] ) ) : ''; $extra = self::data_attrs( $a['data'], $row ); $cls = '' !== (string) $a['class'] ? (string) $a['class'] : AIEO_Variants::wp_button_class( '' !== (string) $a['variant'] ? $a['variant'] : 'secondary' ); if ( '' !== (string) $a['hook'] ) { $cls .= ' ' . (string) $a['hook']; } if ( $a['destructive'] ) { $cls .= ' aieo-t-danger'; } if ( '' !== $blocked ) { $cls .= ' aieo-t-blocked'; } $common = ' class="' . esc_attr( $cls ) . '"' . ' data-action="' . esc_attr( $a['id'] ) . '"' . ( '' !== $key ? ' data-row="' . esc_attr( $key ) . '"' : '' ) . ( '' !== (string) $conf ? ' data-confirm="' . esc_attr( $conf ) . '"' : '' ) . $extra . $attr; if ( '' !== $blocked ) { $out .= ' '; continue; } $href = AIEO_Table_Columns::is_fn( $a['href'] ) ? call_user_func( $a['href'], $row ) : self::action_href( $a['href'], $row ); $tgt = ! empty( $a['external'] ) ? ' target="_blank" rel="noopener noreferrer"' : ''; $out .= $href ? '' . esc_html( $label ) . ' ' : ' '; } $out = trim( $out ); return '' !== $out ? '' . $out . '' : ''; } private static function action_href( $href, $row ) { $href = (string) $href; if ( '' === $href || false === strpos( $href, '{' ) ) { return $href; } if ( preg_match( '/^\{([a-zA-Z0-9_]+)\}$/', $href, $whole ) ) { $v = class_exists( 'AIEO_Table_Columns' ) ? AIEO_Table_Columns::value( $row, $whole[1], '' ) : ( is_array( $row ) && isset( $row[ $whole[1] ] ) ? $row[ $whole[1] ] : '' ); return (string) $v; } return (string) preg_replace_callback( '/\{([a-zA-Z0-9_]+)\}/', static function ( $m ) use ( $row ) { switch ( $m[1] ) { case 'admin_url': return admin_url(); case 'home_url': return home_url( '/' ); case 'order_edit_url': if ( class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ) { return admin_url( 'admin.php?page=wc-orders&action=edit&id=' ); } return admin_url( 'post.php?action=edit&post=' ); } $v = class_exists( 'AIEO_Table_Columns' ) ? AIEO_Table_Columns::value( $row, $m[1], '' ) : ( is_array( $row ) && isset( $row[ $m[1] ] ) ? $row[ $m[1] ] : '' ); return rawurlencode( (string) $v ); }, $href ); } private static function empty_row( $columns, $c ) { $text = ''; if ( is_array( $c['empty'] ) ) { $text = ! empty( $c['building'] ) && ! empty( $c['empty']['building'] ) ? $c['empty']['building'] : ( isset( $c['empty']['text'] ) ? $c['empty']['text'] : '' ); } else { $text = (string) $c['empty']; } if ( '' === $text ) { $text = __( 'Nothing to show yet.', 'ai-eshop-optimizer' ); } return '' . '' . esc_html( $text ) . ''; } private static function totals_strip( $columns, $totals ) { $out = '
'; foreach ( $columns as $col ) { if ( ! array_key_exists( $col['key'], (array) $totals ) ) { continue; } $fake = array( $col['key'] => $totals[ $col['key'] ] ); $out .= '' . '' . esc_html( (string) $col['label'] ) . '' . '' . AIEO_Table_Columns::render( $col, $fake ) . '' . ''; } return $out . '
'; } private static function foot( $columns, $c ) { if ( ! $c['totals'] || 'foot' !== $c['totals_at'] ) { return ''; } $totals = (array) $c['totals']; $label = ( '' !== (string) $c['totals_label'] ) ? (string) $c['totals_label'] : __( 'Total', 'ai-eshop-optimizer' ); $out = ''; $labelled = false; foreach ( $columns as $i => $col ) { $key = $col['key']; $has = array_key_exists( $key, $totals ); if ( ! $has && ! $labelled ) { $out .= '' . esc_html( $label ) . ''; $labelled = true; continue; } if ( ! $has ) { $out .= ''; continue; } $fake = array( $key => $totals[ $key ] ); $out .= '' . AIEO_Table_Columns::render( $col, $fake ) . ''; } return $out . ''; } private static function cap_notice( $c, $shown ) { $cap = (int) $c['cap']; if ( $cap <= 0 || ( ! $c['capped'] && $shown < $cap ) ) { return ''; } $msg = ( null !== $c['total'] ) ? sprintf( __( 'Showing the first %1$s of %2$s rows.', 'ai-eshop-optimizer' ), number_format_i18n( $shown ), number_format_i18n( (int) $c['total'] ) ) : sprintf( __( 'Showing the first %s rows.', 'ai-eshop-optimizer' ), number_format_i18n( $shown ) ); return '

' . esc_html( $msg ) . '

'; } private static function prime_object_columns( $columns, $rows ) { if ( ! $rows || ! function_exists( '_prime_post_caches' ) ) { return; } $ids = array(); $users = array(); foreach ( $columns as $col ) { if ( 'object' !== $col['type'] && 'user' !== $col['type'] ) { continue; } foreach ( $rows as $row ) { $id = (int) AIEO_Table_Columns::value( $row, $col['key'], 0 ); if ( $id > 0 ) { if ( 'user' === $col['type'] ) { $users[ $id ] = true; } else { $ids[ $id ] = true; } } } } if ( $ids ) { _prime_post_caches( array_keys( $ids ), false, false ); } if ( $users && function_exists( 'cache_users' ) ) { cache_users( array_keys( $users ) ); } } const LIBRARY_NUM_TYPES = array( 'id', 'integer', 'decimal', 'money', 'percent', 'duration' ); private static function library_html( $c, $columns, $rows ) { $details = array(); if ( AIEO_Table_Columns::is_fn( $c['detail_row'] ) ) { foreach ( $rows as $i => $row ) { $panel = call_user_func( $c['detail_row'], $row ); if ( is_array( $panel ) ) { if ( isset( $panel['id'] ) || isset( $panel['class'] ) || ! empty( $panel['hidden'] ) ) { self::$declined = 'a detail panel the screen opens by its own control: waits for a detail row the product opens'; return null; } $panel = $panel['html'] ?? ''; } if ( '' !== (string) $panel ) { $details[ $i ] = (string) $panel; } } } $ui = '\Rooster\Admin\Ui'; $states = '\Rooster\Admin\States'; $id = '' !== (string) $c['id'] ? (string) $c['id'] : 'aieo-t-' . ( self::$seq + 1 ); self::prime_object_columns( $columns, $rows ); $prefix = preg_replace( '/[^a-z0-9_]/', '', strtolower( (string) $c['view_prefix'] ) ); $args = array( 's' => $prefix . 's', 'orderby' => $prefix . 'orderby', 'order' => $prefix . 'order', 'paged' => $prefix . 'paged', ); $size = null === $c['per_page'] ? self::library_per_page() : max( 0, (int) $c['per_page'] ); $edit = self::library_edit( $c ); $search = ! empty( $c['caps']['search'] ); $group = (string) $c['group_by']; if ( '' !== $group ) { $c['sortable'] = false; $size = 0; $search = false; } $defs = array(); $select = false; foreach ( $columns as $i => $col ) { if ( in_array( $col['type'], array( 'selector', 'expander' ), true ) ) { $select = $select || 'selector' === $col['type']; continue; } $key = (string) $col['key']; $key = ( '' === $key || isset( $defs[ $key ] ) || '__actions' === $key ) ? $key . '__' . $i : $key; $defs[ $key ] = array( 'col' => $col, 'type' => self::library_type( $col ), 'sortable' => ! empty( $c['sortable'] ) && $col['sortable'] && ( ! in_array( $col['type'], array( 'actions', 'toggle', 'control', 'html', 'image' ), true ) || ( 'control' === $col['type'] && ! empty( $col['sort_key'] ) ) ), ); } $select = $select && $rows && '' !== self::row_key_value( $rows[0], $c['row_key'] ); if ( $c['actions'] && self::actions_column_earned( $rows, $c, false ) ) { $defs['__actions'] = array( 'col' => AIEO_Table_Columns::normalise( array( 'key' => '__actions', 'label' => __( 'Actions', 'ai-eshop-optimizer' ), 'type' => 'actions' ) ), 'type' => 'actions', 'sortable' => false, ); } $keys = array(); foreach ( $defs as $key => $def ) { $keys += array( (string) $def['col']['key'] => $key ); } $figs = array(); $carry = array(); foreach ( $defs as $key => $def ) { $figs[ $key ] = 'actions' === $def['type'] ? array() : self::library_figure( $def['col'], $keys, true, array_key_exists( (string) $def['col']['key'], (array) $c['totals'] ) ); if ( is_array( $figs[ $key ]['total'] ?? null ) ) { $carry += array_fill_keys( $figs[ $key ]['total']['ratio'], true ); } } $filters = '' === $group ? self::library_filters( $c, $columns, $rows, $prefix ) : array(); $decls = array(); if ( '' === $group && is_array( $c['filters'] ) ) { foreach ( $c['filters'] as $d ) { if ( ! is_array( $d ) || '' === (string) ( $d['key'] ?? '' ) ) { continue; } $field = (string) ( $d['field'] ?? $d['key'] ); if ( 'values' === ( $d['kind'] ?? 'values' ) && empty( $d['options'] ) ) { $seen = array(); foreach ( $rows as $row ) { foreach ( self::library_filter_values( $row, $field ) as $v ) { $seen[ $v ] = $v; } } natcasesort( $seen ); $d['options'] = $seen; } $decls[ (string) $d['key'] ] = $d; } if ( $decls ) { $filters = array(); } } $grid = false; $lrows = array(); $metas = array(); $dialogs = array(); foreach ( $rows as $i => $row ) { $rid = self::row_key_value( $row, $c['row_key'] ); $rid = '' !== $rid ? $rid : (string) ( $i + 1 ); $ctx = array( 'page_id' => $c['page_id'], 'row_key_value' => $rid, 'index' => $i, ); $name = self::library_row_name( $columns, $row, $ctx ); $lrow = array( '_id' => $rid, '_name' => $name, ); if ( isset( $details[ $i ] ) ) { $lrow['_detail'] = $details[ $i ]; } $text = array(); $sort = array(); $covered = 0; $left = count( $defs ); foreach ( $defs as $key => $def ) { --$left; if ( $covered > 0 ) { --$covered; $lrow[ $key ] = ''; continue; } if ( 'actions' === $def['type'] ) { $lrow[ $key ] = self::library_actions( $row, $c, $rid, $id, $dialogs ); continue; } list( $cell, $plain ) = self::library_cell( $def['col'], $row, $ctx, $edit, $rid, $name ); if ( AIEO_Table_Columns::is_fn( $def['col']['span'] ) && is_array( $cell ) ) { $n = min( (int) call_user_func( $def['col']['span'], $row ), $left + 1 ); if ( $n > 1 ) { $cell['colspan'] = $n; $covered = $n - 1; } } $lrow[ $key ] = $cell; if ( 'control' === $def['col']['type'] && is_array( $cell ) && preg_match( '/<(input|select|textarea)\b/i', (string) ( $cell['html'] ?? '' ) ) ) { $grid = true; } if ( '' !== $plain ) { $text[] = $plain; } if ( $def['sortable'] ) { $sort[ $key ] = self::library_sort_value( $def['col'], $row ); } elseif ( isset( $carry[ $key ] ) ) { $v = self::library_sort_value( $def['col'], $row ); $sort[ $key ] = is_numeric( $v ) ? 0 + $v : ''; } } $rc = AIEO_Table_Columns::is_fn( $c['row_class'] ) ? (string) call_user_func( $c['row_class'], $row ) : ''; if ( preg_match( '/\baieo-t-row--(danger|warning|info|success)\b/', $rc, $m ) ) { $lrow['_tone'] = 'danger' === $m[1] ? 'destructive' : $m[1]; } $f = array(); foreach ( $filters as $fkey => $filter ) { $f[ $fkey ] = self::library_filter_values( $row, $filter['key'] ); } foreach ( $decls as $fkey => $d ) { $f[ $fkey ] = self::library_filter_values( $row, (string) ( $d['field'] ?? $fkey ) ); } $metas[ $i ] = array( 'f' => $f, 's' => $sort, 'text' => implode( "\n", $text ), ); $lrow['_attrs'] = array( 'data-ra-f' => $f, 'data-ra-s' => $sort, 'data-ra-text' => $metas[ $i ]['text'], 'data-row-key' => '' !== self::row_key_value( $row, $c['row_key'] ) ? $rid : null, 'data-row-type' => $c['row_type'] ? (string) AIEO_Table_Columns::value( $row, $c['row_type'], '' ) : null, ) + self::data_attr_values( $c['row_data'], $row ); $lrows[ $i ] = $lrow; } $sorts = array(); foreach ( $defs as $key => $def ) { if ( ! $def['sortable'] ) { continue; } $numeric = true; foreach ( $metas as $meta ) { $v = $meta['s'][ $key ]; if ( '' !== $v && ! is_int( $v ) && ! is_float( $v ) && ! is_numeric( $v ) ) { $numeric = false; break; } } $sorts[ $key ] = $numeric ? 'number' : ( in_array( $def['col']['type'], array( 'date', 'datetime', 'relative' ), true ) ? 'date' : 'text' ); } if ( $sorts ) { foreach ( $lrows as $i => $lrow ) { foreach ( $sorts as $key => $how ) { if ( 'number' === $how && '' !== $lrows[ $i ]['_attrs']['data-ra-s'][ $key ] ) { $lrows[ $i ]['_attrs']['data-ra-s'][ $key ] = 0 + $lrows[ $i ]['_attrs']['data-ra-s'][ $key ]; $metas[ $i ]['s'][ $key ] = $lrows[ $i ]['_attrs']['data-ra-s'][ $key ]; } } } } $sizes = array(); if ( false !== $c['sizes'] && ! $grid && '' === $group && $size > 0 ) { $sizes = array_values( array_filter( array_map( 'intval', is_array( $c['sizes'] ) ? $c['sizes'] : array( 25, 50, 100 ) ), static function ( $n ) { return $n > 0; } ) ); if ( count( $sizes ) < 2 || count( $rows ) <= $sizes[0] ) { $sizes = array(); } } if ( $sizes ) { $asked = isset( $_GET[ $prefix . 'per_page' ] ) && is_scalar( $_GET[ $prefix . 'per_page' ] ) ? (int) $_GET[ $prefix . 'per_page' ] : 0; $size = in_array( $asked, $sizes, true ) ? $asked : ( in_array( $size, $sizes, true ) ? $size : $sizes[0] ); } $pseudo = $filters; foreach ( $decls as $fkey => $d ) { $pseudo[ $fkey ] = array( 'arg' => $prefix . preg_replace( '/[^a-z0-9_]/', '', strtolower( $fkey ) ), 'options' => 'boolean' === ( $d['kind'] ?? '' ) ? array( '1' => '', '0' => '' ) : (array) ( $d['options'] ?? array() ), ); } $state = self::library_state( $args, $pseudo, $sorts ); $fvalues = array(); if ( $decls && class_exists( '\Rooster\Admin\Filters' ) ) { $fvalues = \Rooster\Admin\Filters::values( array_values( $decls ), $prefix ); foreach ( $decls as $fkey => $d ) { if ( 'range' === ( $d['kind'] ?? '' ) && is_array( $fvalues[ $fkey ] ?? null ) && ( '' !== $fvalues[ $fkey ]['from'] || '' !== $fvalues[ $fkey ]['to'] ) ) { $state['ranges'][ $fkey ] = $fvalues[ $fkey ]; } } } $view = self::library_view( $metas, $state, $sorts, $size ); $shown = array(); foreach ( $view['rows'] as $i ) { $shown[] = $lrows[ $i ]; } $all = count( $rows ); if ( '' !== $group ) { $shown = array(); $was = null; foreach ( $rows as $i => $row ) { $value = (string) AIEO_Table_Columns::value( $row, $group, '' ); if ( $value !== $was ) { $shown[] = array( '_group' => '' !== $value ? $value : AIEO_Table_Columns::EMPTY_MARKER, '_attrs' => array( 'data-ra-text' => $value ), ); $was = $value; } $shown[] = $lrows[ $i ]; } $lrows = $shown; } $ra_cols = array(); foreach ( $defs as $key => $def ) { $col = array( 'key' => $key, 'label' => (string) $def['col']['label'], 'type' => $def['type'], ); $align = array( 'left' => 'start', 'right' => 'end', 'center' => 'center', ); if ( isset( $align[ (string) $def['col']['align'] ] ) ) { $col['align'] = $align[ (string) $def['col']['align'] ]; } if ( in_array( (string) $def['col']['width'], array( 'min', 'xs', 'sm', 'md', 'lg' ), true ) ) { $col['width'] = (string) $def['col']['width']; } if ( 'low' === (string) ( $def['col']['priority'] ?? '' ) ) { $col['priority'] = 'low'; } if ( '' !== (string) ( $def['col']['group'] ?? '' ) ) { $col['group'] = (string) $def['col']['group']; } if ( 'image' === $def['type'] ) { $px = isset( $def['col']['format']['size'] ) ? (int) $def['col']['format']['size'] : 40; $col['size'] = $px <= 32 ? 'sm' : ( $px <= 40 ? 'md' : ( $px <= 56 ? 'lg' : 'xl' ) ); } if ( $def['sortable'] ) { $on = $key === $state['orderby']; $col['sort'] = $on ? $state['order'] : 'none'; $col['sort_type'] = $sorts[ $key ]; $col['sort_url'] = self::library_url( array( $args['orderby'] => $key, $args['order'] => $on && 'asc' === $state['order'] ? 'desc' : 'asc', $args['paged'] => '', ) ); } $ra_cols[] = $col + $figs[ $key ]; } $toolbar = ''; if ( ! $decls && ( $search || $filters ) ) { $fields = ''; if ( $search ) { $fields .= $ui::filter( __( 'Search', 'ai-eshop-optimizer' ), $ui::input( $grid ? '' : $args['s'], $state['typed'], array( 'type' => 'search', 'icon' => 'search', 'id' => $id . '-search', 'attrs' => array( 'data-ra-search' => '' ), ) ), $id . '-search' ); } foreach ( $filters as $fkey => $filter ) { $fid = $id . '-filter-' . sanitize_html_class( $fkey ); $fields .= $ui::filter( $filter['label'], $ui::select( $grid ? '' : $filter['arg'], array( '' => __( 'Any', 'ai-eshop-optimizer' ) ) + $filter['shown'], isset( $state['filters'][ $fkey ] ) ? (string) $state['filters'][ $fkey ] : '', array( 'id' => $fid, 'attrs' => array( 'data-ra-filter' => $fkey ), ) ), $fid ); } $toolbar = $grid ? '' : ''; } $pager = ! $grid && $view['pages'] > 1 ? $ui::pager( $view['page'], $view['pages'], static function ( $n ) use ( $args ) { return self::library_url( array( $args['paged'] => $n > 1 ? $n : '' ) ); }, array( 'total' => $view['total'] ) ) : ''; $footer = $pager; $empty_text = is_array( $c['empty'] ) ? (string) ( $c['empty'][ ! empty( $c['building'] ) && ! empty( $c['empty']['building'] ) ? 'building' : 'text' ] ?? '' ) : (string) $c['empty']; $first_use = $states::state( 'first-use', array( 'title' => '' !== $empty_text ? $empty_text : __( 'Nothing to show yet.', 'ai-eshop-optimizer' ), 'compact' => true, ) ); $clear = array( $args['s'] => '', $args['paged'] => '' ); foreach ( $filters as $filter ) { $clear[ $filter['arg'] ] = ''; } $no_results = $states::state( 'no-results', array( 'compact' => true, 'action' => $ui::button( __( 'Clear filters', 'ai-eshop-optimizer' ), array( 'icon' => 'x', 'href' => self::library_url( $clear ), 'attrs' => array( 'data-ra-clear' => '' ), ) ), ) ); $caption = '' !== (string) $c['caption'] ? (string) $c['caption'] : (string) ( $c['title'] ?? '' ); $phone = self::library_phone( $c ); $opts = array( 'id' => $id, 'caption' => $caption, 'dense' => 'compact' === $c['density'], 'toolbar' => $toolbar, 'footer' => $footer, 'empty' => 0 === $all ? $first_use : $no_results, 'local' => array( 'size' => $size, 'words' => true, 'keys' => $args, ), 'rows_template' => $lrows, 'sticky_first' => ! empty( $c['sticky_first'] ), ); if ( 'list' === $phone ) { $opts['cards'] = true; } if ( null !== $c['scroll'] ) { $opts['scroll'] = $c['scroll']; } if ( $sizes ) { $opts['local']['sizes'] = $sizes; $opts['local']['keys']['per_page'] = $prefix . 'per_page'; } if ( $decls ) { $opts['filters'] = array_values( $decls ); if ( $search ) { $opts['search'] = true; } $opts['local']['prefix'] = $prefix; $opts['filter_values'] = $fvalues; } if ( $c['chooser'] ) { $opts['chooser'] = $c['chooser']; } if ( $c['export'] ) { $opts['export'] = (array) $c['export']; } if ( $c['move'] ) { $opts['move'] = $c['move']; } if ( $details ) { $opts['details'] = true; } if ( $select ) { $opts['selectable'] = true; $opts['bulk'] = self::library_bulk( $c, $id, $dialogs ); } if ( $c['totals'] ) { $opts['totals'] = array(); foreach ( $defs as $key => $def ) { $ck = (string) $def['col']['key']; if ( array_key_exists( $ck, (array) $c['totals'] ) && ! in_array( $def['type'], array( 'actions', 'image' ), true ) ) { $opts['totals'][ $key ] = self::library_total( $def['col'], $figs[ $key ], $c['totals'][ $ck ] ); } } if ( '' !== (string) $c['totals_label'] ) { $opts['totals_label'] = (string) $c['totals_label']; } } $cap = (int) $c['cap']; if ( $cap > 0 && ( $c['capped'] || $all >= $cap ) ) { $opts['capped'] = null !== $c['total'] && (int) $c['total'] > $all ? array( 'shown' => $all, 'total' => (int) $c['total'], ) : array( 'shown' => $all ); } if ( $edit && $all > 0 ) { $opts['edit'] = array( 'url' => add_query_arg( 'nonce', rawurlencode( (string) ( $edit['nonce'] ?? '' ) ), rest_url( (string) $edit['route'] ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'open' => in_array( $edit['open'] ?? '', array( 'click', 'dblclick' ), true ) ? $edit['open'] : 'button', ); } if ( $grid ) { $opts['local']['form'] = true; unset( $opts['rows_template'] ); $shown = array_values( $lrows ); } foreach ( (array) $c['table_data'] as $tk => $tv ) { if ( null !== $tv && '' !== $tv ) { $opts['attrs'][ 'data-' . str_replace( '_', '-', sanitize_key( (string) $tk ) ) ] = (string) $tv; } } $html = $ui::table( $ra_cols, $shown, $opts ) . implode( '', $dialogs ); if ( '' === (string) $c['id'] ) { ++self::$seq; } return $html; } private static function library_phone( $c ) { $own = (string) ( $c['phone'] ?? '' ); if ( in_array( $own, array( 'grid', 'list', 'wide' ), true ) ) { return $own; } if ( ! class_exists( 'AIEO_Table_Registry' ) ) { return ''; } $row = null; $rid = (string) ( $c['registry'] ?? '' ); if ( '' === $rid && '' !== (string) ( $c['report'] ?? '' ) && class_exists( 'AIEO_Report_Registry' ) ) { $def = AIEO_Report_Registry::get( $c['report'] ); $rid = $def ? (string) $def->get( 'registry_id', '' ) : ''; } if ( '' !== $rid ) { $row = AIEO_Table_Registry::get( $rid ); } if ( ! $row ) { $anchor = (string) ( $c['anchor'] ?? '' ); if ( '' === $anchor && class_exists( 'AIEO_DMM_UI' ) ) { $anchor = AIEO_DMM_UI::current_anchor(); } $row = AIEO_Table_Registry::for_anchor( $anchor, (string) ( $c['page_id'] ?? '' ) ); } return $row ? (string) ( $row['phone'] ?? '' ) : ''; } private static function library_ready( $c, $columns ) { if ( ! class_exists( '\Rooster\Admin\Ui' ) || ! class_exists( '\Rooster\Admin\States' ) || ! class_exists( 'AIEO_Chrome' ) || ! AIEO_Chrome::on() ) { return '-'; } if ( ! apply_filters( 'aieo_table_library', true, $c ) ) { return '-'; } if ( wp_doing_ajax() ) { return 'drawn into the page by a script after the library has wired it: waits for the screen to ask for the library\'s table'; } if ( empty( $c['head'] ) ) { return 'a label and value panel with no head: waits for a table without a visible head'; } if ( ! empty( $c['bare'] ) ) { return 'a layout grid of a settings form: waits for the form grid'; } if ( '' !== (string) $c['tbody_id'] ) { return 'its rows are drawn by the screen\'s script: waits for the script to draw through the library'; } foreach ( $columns as $col ) { if ( in_array( $col['type'], array( 'editable', 'toggle' ), true ) && empty( $c['edit']['route'] ) ) { return 'cells the screen\'s script saves: waits for the declared save'; } if ( ! empty( $col['group_span'] ) ) { return 'a cell spanning rows: not built in the library table'; } if ( preg_match( '/<(input|select|button|textarea)\b/i', (string) $col['header_html'] ) ) { return 'a control in a column\'s head: waits for the library\'s selection'; } } return ''; } private static function action_hooks( $a ) { $hooks = array(); foreach ( preg_split( '/\s+/', trim( (string) ( $a['class'] ?? '' ) . ' ' . (string) ( $a['hook'] ?? '' ) ) ) as $cls ) { if ( '' !== $cls && ! preg_match( '/^(button(-[a-z-]+)?|aieo-c-btn(--[a-z-]+)?|aieo-t-[a-z-]+|delete)$/', $cls ) ) { $hooks[] = $cls; } } return $hooks; } private static function library_per_page() { if ( class_exists( 'AIEO_Table_Registry' ) && is_callable( array( 'AIEO_Table_Registry', 'site_defaults' ) ) ) { $d = AIEO_Table_Registry::site_defaults(); if ( ! empty( $d['per_page'] ) ) { return max( 1, (int) $d['per_page'] ); } } return 25; } private static function library_edit( $c ) { $edit = is_array( $c['edit'] ) ? $c['edit'] : array(); if ( '' === (string) ( $edit['route'] ?? '' ) ) { return array(); } if ( '' !== (string) ( $edit['capability'] ?? '' ) && ! current_user_can( (string) $edit['capability'] ) ) { return array(); } return $edit; } private static function library_type( $col ) { $given = isset( $col['ra_type'] ) ? (string) $col['ra_type'] : ''; if ( in_array( $given, array( 'text', 'num', 'date', 'status', 'progress', 'html' ), true ) ) { return $given; } $t = (string) $col['type']; if ( in_array( $t, self::LIBRARY_NUM_TYPES, true ) ) { return 'num'; } if ( in_array( $t, array( 'diff', 'range' ), true ) ) { $as = isset( $col['format']['as'] ) ? (string) $col['format']['as'] : ( 'range' === $t ? 'decimal' : 'text' ); return in_array( $as, self::LIBRARY_NUM_TYPES, true ) ? 'num' : 'text'; } if ( 'editable' === $t ) { return 'number' === ( $col['format']['as'] ?? '' ) ? 'num' : 'text'; } $map = array( 'code' => 'code', 'boolean' => 'boolean', 'date' => 'date', 'datetime' => 'date', 'relative' => 'date', 'badge' => 'status', 'bar' => 'progress', 'actions' => 'actions', 'text' => 'text', 'link' => 'text', 'image' => 'image', ); return isset( $map[ $t ] ) ? $map[ $t ] : 'html'; } private static function library_row_name( $columns, $row, $ctx ) { $first = null; foreach ( $columns as $col ) { if ( ! in_array( $col['type'], array( 'image', 'selector', 'expander', 'actions', 'boolean', 'bar' ), true ) ) { $first = $col; break; } } if ( ! $first ) { return ''; } if ( in_array( $first['type'], array( 'control', 'html', 'prose' ), true ) ) { $html = 'control' === $first['type'] ? AIEO_Table_Columns::render( $first, $row, $ctx ) : (string) AIEO_Table_Columns::value( $row, $first['key'], '' ); if ( preg_match( '#<(strong|b)[^>]*>(.*?)#s', $html, $m ) ) { $html = $m[2]; } return self::library_plain( $html ); } $raw = AIEO_Table_Columns::value( $row, $first['key'] ); return is_scalar( $raw ) ? trim( (string) $raw ) : ''; } private static function library_plain( $html ) { $text = html_entity_decode( wp_strip_all_tags( str_replace( '<', ' <', (string) $html ) ), ENT_QUOTES, 'UTF-8' ); return trim( (string) preg_replace( '/\s+/u', ' ', $text ) ); } private static function library_cell( $col, $row, $ctx, $edit, $rid, $rname ) { list( $cell, $plain ) = self::library_cell_value( $col, $row, $ctx, $edit, $rid, $rname ); if ( is_array( $cell ) && ! self::library_is_empty( $col, $row ) && ! in_array( $col['type'], array( 'badge', 'toggle', 'editable' ), true ) && ( '' !== (string) $col['tone'] || $col['bands'] || '' !== (string) $col['tone_key'] ) && preg_match( '/\baieo-t-tone--([a-z]+)\b/', AIEO_Table_Columns::cell_attrs( $col, $row ), $m ) ) { $tone = self::library_tone( $m[1] ); if ( 'mono' !== $tone ) { $cell['tone'] = $tone; } } return array( $cell, $plain ); } private static function library_is_empty( $col, $row ) { $type = (string) $col['type']; return ! in_array( $type, AIEO_Table_Columns::INTERACTIVE_TYPES, true ) && 'html' !== $type && AIEO_Table_Columns::is_empty( AIEO_Table_Columns::value( $row, $col['key'] ), $col ); } private static function library_cell_value( $col, $row, $ctx, $edit, $rid, $rname ) { $type = (string) $col['type']; $raw = AIEO_Table_Columns::value( $row, $col['key'] ); if ( 'toggle' === $type || 'editable' === $type ) { return self::library_edit_cell( $col, $row, $raw, $edit, $rid, $rname ); } if ( 'image' === $type ) { $f = is_array( $col['format'] ) ? $col['format'] : array(); $url = is_scalar( $raw ) ? trim( (string) $raw ) : ''; $path = '' !== $url && ! empty( $f['rehost'] ) ? wp_parse_url( $url, PHP_URL_PATH ) : null; $cell = array( 'src' => is_string( $path ) && '' !== $path ? home_url( $path ) : $url ); if ( ! empty( $f['alt'] ) ) { $cell['alt'] = (string) AIEO_Table_Columns::value( $row, (string) $f['alt'], '' ); } if ( isset( $f['href'] ) ) { $href = (string) AIEO_Table_Columns::resolve( $row, $f['href'], $f['href'] ); if ( '' !== (string) $href ) { $cell['url'] = (string) $href; } } return array( $cell, '' ); } $sub = ''; if ( ! empty( $col['subline'] ) ) { $sub = AIEO_Table_Columns::resolve( $row, $col['subline'] ); $sub = null === $sub ? '' : (string) $sub; } $suffix = ''; if ( ! empty( $col['suffix'] ) ) { $suffix = AIEO_Table_Columns::is_fn( $col['suffix'] ) ? call_user_func( $col['suffix'], $row ) : AIEO_Table_Columns::value( $row, $col['suffix'], $col['suffix'] ); $suffix = null === $suffix ? '' : (string) $suffix; } $after = ( '' !== $suffix ? ' ' . esc_html( $suffix ) : '' ) . ( '' !== $sub ? '' . esc_html( $sub ) . '' : '' ); if ( self::library_is_empty( $col, $row ) ) { $marker = null === $col['empty'] ? AIEO_Table_Columns::EMPTY_MARKER : (string) $col['empty']; return array( array( 'html' => esc_html( $marker ) . $after ), trim( $marker . ' ' . $sub ) ); } if ( 'badge' === $type ) { $f = is_array( $col['format'] ) ? $col['format'] : array(); $tone = isset( $f['map'][ (string) $raw ] ) ? $f['map'][ (string) $raw ] : 'neutral'; $label = isset( $f['labels'][ (string) $raw ] ) ? (string) $f['labels'][ (string) $raw ] : (string) $raw; if ( ! empty( $f['tone_key'] ) && '' !== (string) AIEO_Table_Columns::value( $row, $f['tone_key'], '' ) ) { $tone = (string) AIEO_Table_Columns::value( $row, $f['tone_key'], '' ); } $tone = self::library_tone( $tone ); if ( '' === $after ) { return array( array( 'label' => $label, 'tone' => $tone, ), $label, ); } return array( array( 'html' => \Rooster\Admin\Ui::badge( $label, $tone, array( 'dot' => true ) ) . $after ), trim( $label . ' ' . $sub ) ); } $f = is_array( $col['format'] ) ? $col['format'] : array(); $simple = '' === $suffix && empty( $col['tooltip'] ) && empty( $col['truncate'] ); if ( $simple && 'text' === $type ) { return array( array( 'title' => (string) $raw, 'sub' => $sub, ), trim( $raw . ' ' . $sub ), ); } if ( $simple && 'link' === $type && empty( $f['external'] ) && empty( $f['text'] ) && empty( $f['class'] ) && empty( $f['attrs'] ) ) { $href = ''; if ( isset( $f['href'] ) ) { $href = (string) AIEO_Table_Columns::resolve( $row, $f['href'], $f['href'] ); } return array( array( 'title' => (string) $raw, 'url' => (string) $href, 'sub' => $sub, ), trim( $raw . ' ' . $sub ), ); } if ( 'code' === $type ) { if ( '' === $after && empty( $col['tooltip'] ) && empty( $col['truncate'] ) ) { return array( array( 'value' => (string) $raw ), (string) $raw ); } $tip = empty( $col['tooltip'] ) ? '' : ( true === $col['tooltip'] ? (string) $raw : (string) AIEO_Table_Columns::value( $row, $col['tooltip'], '' ) ); $code = '' . esc_html( (string) $raw ) . ''; $html = ( '' !== $tip ? '' . $code . '' : $code ) . $after; return array( array( 'html' => $html ), trim( $raw . ' ' . $sub ) ); } if ( 'boolean' === $type && ! isset( $f['true'] ) && ! isset( $f['false'] ) && '' === $after ) { return array( array( 'value' => ! empty( $raw ) && 'no' !== $raw && '0' !== (string) $raw ), '' ); } if ( 'bar' === $type ) { $max = 0.0; if ( isset( $f['max'] ) ) { $max = is_numeric( $f['max'] ) ? (float) $f['max'] : (float) AIEO_Table_Columns::value( $row, $f['max'], 0 ); } $pct = $max > 0 ? max( 0, min( 100, ( (float) $raw / $max ) * 100 ) ) : 0; if ( ! empty( $f['invert'] ) ) { $pct = 100 - $pct; } $label = isset( $f['label'] ) && false === $f['label'] ? '' : number_format_i18n( (float) $raw, isset( $f['precision'] ) ? (int) $f['precision'] : 0 ); return array( array( 'value' => $pct, 'label' => $label, ), $label, ); } if ( 'list' === $type ) { $items = is_array( $raw ) ? $raw : array_filter( array_map( 'trim', explode( ',', (string) $raw ) ), 'strlen' ); $titles = array(); if ( ! empty( $f['titles'] ) ) { $titles = is_array( $f['titles'] ) ? $f['titles'] : (array) AIEO_Table_Columns::value( $row, (string) $f['titles'], array() ); } $html = ''; foreach ( $items as $item ) { $title = isset( $titles[ (string) $item ] ) ? trim( (string) $titles[ (string) $item ] ) : ''; $html .= \Rooster\Admin\Ui::badge( (string) $item, 'mono', array( 'sm' => true, 'attrs' => '' !== $title ? array( 'title' => $title ) : array() ) ) . ' '; } return array( array( 'html' => trim( $html ) . $after ), trim( implode( ' ', array_map( 'strval', $items ) ) . ' ' . $sub ) ); } $bare = $col; $bare['subline'] = ''; $bare['suffix'] = ''; $html = AIEO_Table_Columns::render( $bare, $row, $ctx ) . $after; return array( array( 'html' => $html ), self::library_plain( $html ) ); } private static function library_edit_cell( $col, $row, $raw, $edit, $rid, $rname ) { $as = 'toggle' === $col['type'] ? 'toggle' : ( isset( $col['format']['as'] ) ? (string) $col['format']['as'] : 'text' ); $options = isset( $col['format']['options'] ) ? (array) $col['format']['options'] : array(); $value = 'toggle' === $as ? ( ! empty( $raw ) ? '1' : '0' ) : (string) $raw; $shown = 'select' === $as && isset( $options[ $value ] ) ? (string) $options[ $value ] : $value; $may = (bool) $edit; if ( $may && isset( $col['format']['editable_if'] ) && AIEO_Table_Columns::is_fn( $col['format']['editable_if'] ) ) { $may = (bool) call_user_func( $col['format']['editable_if'], $row ); } if ( ! $may ) { if ( 'toggle' === $as ) { $html = AIEO_Table_Columns::render( array( 'key' => $col['key'], 'type' => 'boolean' ), $row ); return array( array( 'html' => $html ), '' ); } return array( array( 'html' => esc_html( $shown ) ), $shown ); } $opts = array( 'type' => in_array( $as, array( 'select', 'number', 'toggle' ), true ) ? $as : 'text', 'row' => $rid, 'key' => (string) $col['key'], 'value' => $value, 'label' => sprintf( __( '%1$s of %2$s', 'ai-eshop-optimizer' ), (string) $col['label'], $rname ), ); if ( 'select' === $as ) { $opts['options'] = $options; } foreach ( array( 'min', 'max', 'step', 'maxlength', 'pattern', 'required' ) as $rule ) { if ( isset( $col['format'][ $rule ] ) ) { $opts[ $rule ] = $col['format'][ $rule ]; } } $cell = \Rooster\Admin\Ui::editable( 'toggle' === $as ? '' : esc_html( $shown ), $opts ); return array( array( 'html' => $cell ), 'toggle' === $as ? '' : $shown ); } private static function library_tone( $tone ) { $tone = class_exists( 'AIEO_Variants' ) ? AIEO_Variants::badge( $tone ) : (string) $tone; return in_array( $tone, array( 'primary', 'success', 'warning', 'destructive', 'info' ), true ) ? $tone : 'mono'; } private static function library_figure( $col, $keys, $local, $given = false ) { $out = array(); if ( '' !== (string) $col['header_tip'] ) { $out['tip'] = (string) $col['header_tip']; } if ( 'money' === $col['type'] && empty( $col['format']['currency'] ) ) { $out['format'] = 'money'; } $bands = self::library_bands( $col ); if ( $bands ) { $out['bands'] = $bands; } $total = $col['total'] ?? null; if ( is_array( $total ) && is_array( $total['ratio'] ?? null ) && 2 === count( $total['ratio'] ) ) { $of = array_values( $total['ratio'] ); $a = $keys[ (string) $of[0] ] ?? ''; $b = $keys[ (string) $of[1] ] ?? ''; if ( '' === $a || '' === $b ) { $out['total'] = 'none'; return $out; } $out['total'] = array( 'ratio' => array( $a, $b ), 'percent' => ! empty( $total['percent'] ), ); $d = $total['decimals'] ?? ( $col['format']['precision'] ?? ( 'percent' === $col['type'] ? 1 : null ) ); if ( null !== $d ) { $out['total']['decimals'] = (int) $d; } if ( 'percent' === $col['type'] ) { $out['format'] = array( 'suffix' => '%' ); } } elseif ( in_array( $total, array( 'sum', 'none', 'given' ), true ) ) { $out['total'] = $total; } elseif ( $local && ( 'percent' === $col['type'] || in_array( strtoupper( (string) ( $col['aggregate'] ?? '' ) ), array( 'AVG', 'MIN', 'MAX', 'COUNT_DISTINCT' ), true ) ) ) { $out['total'] = $given ? 'given' : 'none'; } return $out; } private static function library_bands( $col ) { if ( empty( $col['bands'] ) || ! is_array( $col['bands'] ) ) { return array(); } $edges = array(); foreach ( $col['bands'] as $b ) { if ( ! is_array( $b ) || ! isset( $b['tone'] ) ) { continue; } if ( isset( $b['min'] ) ) { $edges[] = (float) $b['min']; } if ( isset( $b['max'] ) ) { $edges[] = (float) $b['max'] + 1e-9; } } $edges = array_values( array_unique( $edges ) ); sort( $edges ); if ( ! $edges ) { return array(); } $stretches = array(); foreach ( array_merge( array( $edges[0] - 1 ), $edges ) as $i => $at ) { $tone = AIEO_Table_Columns::band_tone( $at, $col['bands'] ); $tone = '' === $tone ? '' : self::library_tone( $tone ); $tone = 'mono' === $tone ? '' : $tone; $end = $edges[ $i ] ?? null; if ( $stretches && $stretches[ count( $stretches ) - 1 ]['tone'] === $tone ) { $stretches[ count( $stretches ) - 1 ]['below'] = $end; continue; } $stretches[] = array( 'below' => $end, 'tone' => $tone ); } $out = array(); foreach ( $stretches as $s ) { $band = array( 'tone' => $s['tone'] ); if ( null !== $s['below'] ) { $band = array( 'below' => $s['below'] ) + $band; } $out[] = $band; } return $out; } private static function library_total( $col, $fig, $value ) { if ( 'money' === ( $fig['format'] ?? '' ) && is_numeric( $value ) ) { return (float) $value; } return array( 'html' => AIEO_Table_Columns::render( $col, array( (string) $col['key'] => $value ) ) ); } private static function library_sort_value( $col, $row ) { $v = '' !== (string) $col['sort_key'] ? AIEO_Table_Columns::value( $row, $col['sort_key'] ) : AIEO_Table_Columns::value( $row, $col['key'] ); if ( is_bool( $v ) ) { return $v ? 1 : 0; } if ( null === $v || is_array( $v ) || is_object( $v ) ) { return ''; } return is_int( $v ) || is_float( $v ) ? $v : (string) $v; } private static function library_filters( $c, $columns, $rows, $prefix ) { $declared = array(); foreach ( array_merge( $columns, (array) $c['filter_columns'] ) as $col ) { if ( is_array( $col ) && '' !== (string) ( $col['filter'] ?? '' ) && '' !== (string) ( $col['key'] ?? '' ) && ! isset( $declared[ (string) $col['key'] ] ) ) { $declared[ (string) $col['key'] ] = $col; } } $out = array(); foreach ( $declared as $key => $col ) { $labels = isset( $col['format']['labels'] ) && is_array( $col['format']['labels'] ) ? $col['format']['labels'] : array(); $options = isset( $c['filter_options'][ $key ] ) && is_array( $c['filter_options'][ $key ] ) ? $c['filter_options'][ $key ] : null; $counts = array(); foreach ( $rows as $row ) { foreach ( self::library_filter_values( $row, $key ) as $v ) { $counts[ $v ] = ( $counts[ $v ] ?? 0 ) + 1; } } if ( null === $options ) { $options = array(); foreach ( array_keys( $counts ) as $v ) { $options[ (string) $v ] = isset( $labels[ $v ] ) ? (string) $labels[ $v ] : (string) $v; } } elseif ( array_is_list( $options ) ) { $options = array_combine( array_map( 'strval', $options ), array_map( 'strval', $options ) ); } $shown = array(); foreach ( $options as $v => $label ) { if ( empty( $counts[ (string) $v ] ) ) { continue; } $shown[ (string) $v ] = $label . ' (' . number_format_i18n( (int) $counts[ (string) $v ] ) . ')'; } $arg = $prefix . preg_replace( '/[^a-z0-9_]/', '', strtolower( $key ) ); if ( in_array( $arg, array( 'page', 'dmm_tab', 'action', 'post_type', 'taxonomy', 'tab', 's', 'orderby', 'order', 'paged' ), true ) ) { $arg = 'f_' . $arg; } $out[ $key ] = array( 'key' => $key, 'label' => (string) ( $col['label'] ?? $key ), 'arg' => $arg, 'options' => $options, 'shown' => $shown, ); } return $out; } private static function library_filter_values( $row, $key ) { $v = AIEO_Table_Columns::value( $row, $key ); if ( is_bool( $v ) ) { return array( $v ? '1' : '0' ); } if ( is_array( $v ) ) { return array_values( array_map( 'strval', array_filter( $v, 'is_scalar' ) ) ); } return null === $v || '' === (string) $v ? array() : array( (string) $v ); } private static function data_attr_values( $map, $row ) { $out = array(); foreach ( (array) $map as $dk => $dv ) { $key = str_replace( '_', '-', sanitize_key( (string) $dk ) ); if ( in_array( $key, array( 'row', 'action', 'confirm' ), true ) ) { continue; } $dval = AIEO_Table_Columns::is_fn( $dv ) ? call_user_func( $dv, $row ) : AIEO_Table_Columns::value( $row, $dv, $dv ); if ( null !== $dval && '' !== $dval ) { $out[ 'data-' . $key ] = (string) $dval; } } return $out; } private static function library_actions( $row, $c, $rid, $table_id, &$dialogs ) { $items = array(); foreach ( (array) $c['actions'] as $a ) { $a = array_merge( array( 'id' => '', 'label' => '', 'capability' => '', 'destructive' => false, 'confirm' => '', 'confirm_level' => '', 'confirm_body' => '', 'confirm_name' => '', 'confirm_noun' => '', 'visible' => null, 'when' => array(), 'intent' => array(), 'href' => '', 'external' => false, 'icon' => '', 'data' => array(), ), (array) $a ); if ( $a['capability'] && ! current_user_can( $a['capability'] ) ) { continue; } if ( AIEO_Table_Columns::is_fn( $a['visible'] ) && ! call_user_func( $a['visible'], $row ) ) { continue; } if ( ! empty( $a['when']['key'] ) ) { $have = (string) AIEO_Table_Columns::value( $row, $a['when']['key'], '' ); if ( ( array_key_exists( 'is', $a['when'] ) && $have !== (string) $a['when']['is'] ) || ( array_key_exists( 'not', $a['when'] ) && $have === (string) $a['when']['not'] ) ) { continue; } } $blocked = ''; if ( $a['intent'] ) { $res = AIEO_Action_Intents::resolve( $a['intent'], $row ); if ( null === $res ) { continue; } if ( isset( $res['blocked'] ) ) { $blocked = (string) $res['blocked']; } elseif ( isset( $res['url'] ) ) { $a['href'] = (string) $res['url']; } } $label = (string) ( AIEO_Table_Columns::is_fn( $a['label'] ) ? call_user_func( $a['label'], $row ) : $a['label'] ); $conf = (string) ( AIEO_Table_Columns::is_fn( $a['confirm'] ) ? call_user_func( $a['confirm'], $row ) : $a['confirm'] ); $attrs = array( 'data-action' => (string) $a['id'], 'data-row' => '' !== $rid ? $rid : null, 'data-aieo-t-action' => '', ) + self::data_attr_values( $a['data'], $row ); foreach ( self::action_hooks( $a ) as $hook ) { $attrs[ 'data-' . preg_replace( '/^aieo-dmm-/', 'aieo-', strtolower( sanitize_html_class( $hook ) ) ) ] = ''; } $href = '' !== $blocked ? '' : ( AIEO_Table_Columns::is_fn( $a['href'] ) ? call_user_func( $a['href'], $row ) : self::action_href( $a['href'], $row ) ); if ( $href && ! empty( $a['external'] ) ) { $attrs['target'] = '_blank'; $attrs['rel'] = 'noopener noreferrer'; } $attrs += self::library_action_confirm( $a, $label, $conf, $row, $table_id, $dialogs, self::row_name( $row, $c, $a, $rid ) ); $items[] = array( 'label' => $label, 'url' => (string) $href, 'destructive' => ! empty( $a['destructive'] ) || 'destructive' === (string) ( $a['variant'] ?? '' ), 'icon' => (string) $a['icon'], 'disabled' => '' !== $blocked, 'reason' => $blocked, 'description' => (string) ( $a['description'] ?? '' ), 'attrs' => $attrs, ); } return $items ? $items : ''; } private static function library_bulk( $c, $table_id, &$dialogs ) { $html = ''; foreach ( (array) $c['bulk'] as $a ) { $a = array_merge( array( 'id' => '', 'label' => '', 'capability' => '', 'destructive' => false, 'confirm' => '', 'param' => array(), ), (array) $a ); if ( '' === (string) $a['id'] || '' === (string) $a['label'] || ( $a['capability'] && ! current_user_can( $a['capability'] ) ) ) { continue; } $key = $table_id . '-' . sanitize_html_class( (string) $a['id'] ); $attrs = array( 'data-aieo-t-bulk' => (string) $a['id'] ); $param = is_array( $a['param'] ) ? $a['param'] : array(); $pick = ''; if ( in_array( (string) ( $param['type'] ?? '' ), array( 'select', 'term', 'number', 'text' ), true ) ) { $named = array( 'id' => $key . '-param', 'attrs' => array( 'data-aieo-t-bulk-param' => (string) $a['id'], 'aria-label' => '' !== (string) ( $param['label'] ?? '' ) ? (string) $param['label'] : (string) $a['label'], ), ); if ( in_array( $param['type'], array( 'select', 'term' ), true ) ) { $options = 'term' === $param['type'] ? get_terms( array( 'taxonomy' => (string) ( $param['taxonomy'] ?? '' ), 'hide_empty' => false, 'number' => 500, 'fields' => 'id=>name', ) ) : (array) ( $param['options'] ?? array() ); $pick = \Rooster\Admin\Ui::select( '', array( '' => __( 'Pick…', 'ai-eshop-optimizer' ) ) + ( is_array( $options ) ? $options : array() ), '', $named ); } else { $pick = \Rooster\Admin\Ui::input( '', '', $named + array( 'type' => $param['type'] ) ); } if ( ! empty( $param['required'] ) ) { $attrs['data-aieo-t-bulk-required'] = ''; } } if ( '' !== (string) $a['confirm'] ) { $dialog = $key . '-ask'; $count = '0'; $value = ''; $body = '

' . strtr( esc_html( (string) $a['confirm'] ), array( '%1$s' => $count, '%1$d' => $count, '%2$s' => $value, '%s' => $count, '%d' => $count, ) ) . '

'; $dialogs[ $dialog ] = \Rooster\Admin\States::confirm( $dialog, $a['destructive'] ? 'loss' : 'apply', (string) $a['label'], $body, array( 'confirm' => (string) $a['label'] ) ); $attrs += \Rooster\Admin\States::confirm_attrs( $dialog ); } $html .= $pick . \Rooster\Admin\Ui::button( (string) $a['label'], array( 'size' => 'sm', 'class' => $a['destructive'] ? 'is-destructive' : '', 'attrs' => $attrs, ) ); } return $html; } private static function library_state( $args, $filters, $sorts ) { $get = static function ( $name ) { return (string) \Rooster\Admin\Request::text( $name ); }; $state = array( 'filters' => array(), 'typed' => $get( $args['s'] ), 'orderby' => '', 'order' => 'asc', 'paged' => max( 1, (int) $get( $args['paged'] ) ), ); $by = $get( $args['orderby'] ); if ( isset( $sorts[ $by ] ) ) { $state['orderby'] = $by; $state['order'] = 'desc' === strtolower( $get( $args['order'] ) ) ? 'desc' : 'asc'; } foreach ( $filters as $key => $filter ) { $v = $get( $filter['arg'] ); if ( '' !== $v && isset( $filter['options'][ $v ] ) ) { $state['filters'][ $key ] = $v; } } return $state; } private static function library_view( $metas, $state, $sorts, $size ) { $needles = array_filter( explode( ' ', self::library_fold( preg_replace( '/\s+/u', ' ', trim( (string) $state['typed'] ) ) ) ), 'strlen' ); $hits = array(); foreach ( $metas as $i => $meta ) { foreach ( (array) $state['filters'] as $key => $want ) { if ( ! in_array( (string) $want, isset( $meta['f'][ $key ] ) ? $meta['f'][ $key ] : array(), true ) ) { continue 2; } } foreach ( (array) ( $state['ranges'] ?? array() ) as $key => $range ) { $v = (string) ( $meta['s'][ $key ] ?? '' ); if ( '' === $v ) { continue 2; } foreach ( array( 'from' => -1, 'to' => 1 ) as $end => $sign ) { if ( '' === $range[ $end ] ) { continue; } $by = is_numeric( $v ) && is_numeric( $range[ $end ] ) ? $v <=> $range[ $end ] : strncmp( $v, $range[ $end ], min( strlen( $v ), strlen( $range[ $end ] ) ) ); if ( ( $by <=> 0 ) === $sign ) { continue 3; } } } $text = self::library_fold( $meta['text'] ); foreach ( $needles as $needle ) { if ( false === strpos( $text, $needle ) ) { continue 2; } } $hits[] = $i; } if ( '' !== $state['orderby'] ) { $key = $state['orderby']; $how = $sorts[ $key ]; $sign = 'desc' === $state['order'] ? -1 : 1; $collator = class_exists( 'Collator' ) ? new Collator( get_user_locale() ) : null; if ( $collator ) { $collator->setStrength( Collator::PRIMARY ); } $place = array_flip( $hits ); usort( $hits, static function ( $x, $y ) use ( $metas, $key, $how, $sign, $collator, $place ) { $a = $metas[ $x ]['s'][ $key ] ?? ''; $b = $metas[ $y ]['s'][ $key ] ?? ''; if ( '' === $a || '' === $b ) { if ( '' !== $a || '' !== $b ) { return '' === $a ? 1 : -1; } return $place[ $x ] - $place[ $y ]; } if ( 'number' === $how ) { $by = $a <=> $b; } elseif ( 'date' === $how ) { $by = strcmp( (string) $a, (string) $b ); } else { $by = $collator ? (int) $collator->compare( (string) $a, (string) $b ) : strcmp( self::library_fold( (string) $a ), self::library_fold( (string) $b ) ); } $by = $sign * ( $by <=> 0 ); return 0 !== $by ? $by : $place[ $x ] - $place[ $y ]; } ); } $total = count( $hits ); $per = $size > 0 ? $size : max( 1, $total ); $pages = max( 1, (int) ceil( $total / $per ) ); $page = min( max( 1, (int) $state['paged'] ), $pages ); $start = ( $page - 1 ) * $per; return array( 'rows' => array_slice( $hits, $start, $per ), 'total' => $total, 'page' => $page, 'pages' => $pages, 'first' => $total ? $start + 1 : 0, 'last' => min( $total, $start + $per ), ); } private static function library_fold( $text ) { $s = function_exists( 'mb_strtolower' ) ? mb_strtolower( (string) $text, 'UTF-8' ) : strtolower( (string) $text ); if ( class_exists( 'Normalizer' ) ) { $n = Normalizer::normalize( $s, Normalizer::FORM_D ); if ( false !== $n ) { $s = (string) preg_replace( '/\p{M}+/u', '', $n ); } } return str_replace( 'ς', 'σ', $s ); } private static function library_url( $changes ) { $set = array(); foreach ( $changes as $name => $value ) { $set[ $name ] = ( null === $value || '' === (string) $value ) ? false : $value; } return \Rooster\Admin\Request::view_url( add_query_arg( $set ) ); } private static function library_hidden_args( $own ) { $html = ''; foreach ( (array) $_GET as $name => $value ) { if ( ! is_scalar( $value ) || in_array( (string) $name, $own, true ) || 1 !== preg_match( '/^[a-zA-Z0-9_-]+$/', (string) $name ) ) { continue; } $html .= ''; } return $html; } public static function remote_ready() { return class_exists( '\Rooster\Admin\Ui' ) && class_exists( '\Rooster\Admin\Filters' ) && class_exists( '\Rooster\Admin\States' ) && class_exists( 'AIEO_Chrome' ) && AIEO_Chrome::on() && ! wp_doing_ajax() && apply_filters( 'aieo_table_library', true, array() ); } public static function remote_answer( $config ) { $c = self::t1_defaults( self::config( $config ) ); $parts = self::library_remote_parts( $c ); $meta = (array) $c['meta']; $dialog_extra = $parts['dialogs'] ? array_merge( (array) $c['answer_extra'], array( 'dialogs' => implode( '', $parts['dialogs'] ) ) ) : $c['answer_extra']; return \Rooster\Admin\Ui::answer( $parts['columns'], $parts['rows'], array( 'total' => (int) ( $meta['total'] ?? 0 ), 'filtered' => (int) ( $meta['filtered'] ?? 0 ), 'page' => (int) ( $meta['page'] ?? 1 ), 'per_page' => (int) ( $meta['per_page'] ?? 25 ), 'orderby' => (string) ( $meta['sort'] ?? '' ), 'order' => 'DESC' === strtoupper( (string) ( $meta['dir'] ?? '' ) ) ? 'desc' : 'asc', ), array( 'selectable' => $parts['select'], 'totals' => $parts['totals'], 'extra' => $dialog_extra, 'cards' => 'list' === self::library_phone( $c ), ) ); } private static function library_remote_html( $c ) { $ui = '\Rooster\Admin\Ui'; $states = '\Rooster\Admin\States'; $id = (string) $c['id']; $parts = self::library_remote_parts( $c ); $dialogs = $parts['dialogs']; $remote = (array) $c['remote']; $prefix = (string) ( $remote['prefix'] ?? '' ); $meta = (array) $c['meta']; $per = max( 1, (int) ( $meta['per_page'] ?? 25 ) ); $page = max( 1, (int) ( $meta['page'] ?? 1 ) ); $filtered = max( 0, (int) ( $meta['filtered'] ?? count( $parts['rows'] ) ) ); $all = max( 0, (int) ( $meta['total'] ?? $filtered ) ); $pages = max( 1, (int) ceil( $filtered / $per ) ); $paginate = ! empty( $c['caps']['paginate'] ); $search = ! empty( $c['caps']['search'] ); foreach ( (array) $c['actions'] as $a ) { if ( is_array( $a ) && ( empty( $a['capability'] ) || current_user_can( (string) $a['capability'] ) ) && is_string( $a['label'] ?? '' ) && is_string( $a['confirm'] ?? '' ) ) { self::library_action_confirm( $a, (string) ( $a['label'] ?? '' ), (string) ( $a['confirm'] ?? '' ), null, $id, $dialogs, '' ); } } $sizes = ! empty( $remote['sizes'] ) ? array_values( array_filter( array_map( 'intval', (array) $remote['sizes'] ) ) ) : array( 10, 25, 50, 100, 200 ); if ( ! in_array( $per, $sizes, true ) ) { $sizes[] = $per; sort( $sizes ); } $footer = ''; if ( $paginate && $pages > 1 ) { $footer .= $ui::pager( $page, $pages, static function ( $n ) use ( $prefix ) { return self::library_url( array( $prefix . 'paged' => $n > 1 ? $n : '' ) ); }, array( 'total' => $filtered ) ); } $empty_text = is_array( $c['empty'] ) ? (string) ( $c['empty']['text'] ?? '' ) : (string) $c['empty']; if ( 0 === $all ) { $said = preg_split( '/(?<=[.!?])\s+/u', '' !== $empty_text ? $empty_text : __( 'Nothing to show yet.', 'ai-eshop-optimizer' ), 2 ); return '
' . $states::state( 'first-use', array( 'title' => (string) $said[0], 'text' => (string) ( $said[1] ?? '' ), ) ) . '
'; } $empty = 0 === $all ? $states::state( 'first-use', array( 'title' => '' !== $empty_text ? $empty_text : __( 'Nothing to show yet.', 'ai-eshop-optimizer' ), 'compact' => true, ) ) : $states::state( 'no-results', array( 'compact' => true, 'action' => $ui::button( __( 'Clear filters', 'ai-eshop-optimizer' ), array( 'icon' => 'x', 'attrs' => array( 'data-ra-clear' => '' ), ) ), ) ); $default = '' !== (string) ( $remote['sort']['key'] ?? '' ) ? array( 'key' => (string) $remote['sort']['key'], 'dir' => 'desc' === strtolower( (string) ( $remote['sort']['dir'] ?? '' ) ) ? 'desc' : 'asc', ) : null; $opts = array( 'id' => $id, 'caption' => (string) $c['caption'], 'dense' => 'compact' === $c['density'], 'footer' => $footer, 'empty' => $empty, 'remote' => array( 'url' => (string) $remote['url'], 'nonce' => (string) ( $remote['nonce'] ?? '' ), 'scope' => (array) ( $remote['scope'] ?? array() ), 'context' => 'storefront' === (string) $c['context'] ? 'storefront' : 'admin', 'options' => (string) ( $remote['options'] ?? '' ), 'size' => $per, 'sizes' => $paginate ? $sizes : array(), 'total' => $filtered, 'paged' => $page, 'sort' => $default, 'prefix' => $prefix, ), ); if ( ! empty( $remote['options_current'] ) ) { $opts['remote']['options_current'] = true; } if ( is_array( $c['filters'] ) && $c['filters'] ) { $opts['filters'] = array_values( $c['filters'] ); if ( is_array( $c['filter_values'] ) ) { $opts['filter_values'] = $c['filter_values']; } $opts['search'] = $search; $opts['typed'] = (string) $c['search']; } elseif ( $search ) { $opts['toolbar'] = \Rooster\Admin\Filters::bar( $id, array(), array(), array( 'prefix' => $prefix, 'search' => true, 'typed' => (string) $c['search'], 'keys' => array( 's' => $prefix . 's' ), ) ); } if ( $c['export'] ) { $opts['export'] = (array) $c['export']; } if ( $c['chooser'] ) { $opts['chooser'] = $c['chooser']; } if ( $c['text_size'] ) { $opts['text_size'] = true; } $phone = self::library_phone( $c ); if ( 'list' === $phone ) { $opts['cards'] = true; } if ( ! empty( $c['sticky_first'] ) ) { $opts['sticky_first'] = true; } if ( null !== $c['scroll'] ) { $opts['scroll'] = $c['scroll']; } if ( $parts['select'] ) { $opts['selectable'] = true; if ( ! empty( $c['caps']['bulk'] ) && $c['bulk'] ) { $bulk = self::library_bulk( $c, $id, $dialogs ); if ( '' !== $bulk ) { $opts['bulk'] = $bulk . ''; } } } if ( $parts['totals'] ) { $opts['totals'] = $parts['totals']; $opts['totals_label'] = '' !== (string) $c['totals_label'] ? (string) $c['totals_label'] : __( 'Total', 'ai-eshop-optimizer' ); } if ( $parts['edit'] ) { $opts['edit'] = array( 'url' => (string) $parts['edit']['url'], 'nonce' => (string) ( $parts['edit']['nonce'] ?? '' ), 'open' => 'button', ); } if ( ! $paginate && $filtered > count( $parts['rows'] ) ) { $opts['capped'] = array( 'shown' => count( $parts['rows'] ), 'total' => $filtered, ); } return '
' . $ui::table( $parts['columns'], $parts['rows'], $opts ) . implode( '', $dialogs ) . '
'; } private static function library_remote_parts( $c ) { $columns = array_map( array( 'AIEO_Table_Columns', 'normalise' ), (array) $c['columns'] ); $rows = array_values( (array) $c['rows'] ); $edit = is_array( $c['edit'] ) && '' !== (string) ( $c['edit']['url'] ?? '' ) ? $c['edit'] : array(); $server = 'server' === (string) ( $c['caps']['sort'] ?? '' ); $meta = (array) $c['meta']; $sort_on = (string) ( $meta['sort'] ?? '' ); $dir = 'DESC' === strtoupper( (string) ( $meta['dir'] ?? '' ) ) ? 'desc' : 'asc'; $prefix = (string) ( $c['remote']['prefix'] ?? '' ); $group = (string) $c['group_by']; $id = (string) $c['id']; self::prime_object_columns( $columns, $rows ); $defs = array(); $select = false; foreach ( $columns as $i => $col ) { if ( 'selector' === $col['type'] ) { $select = '' !== (string) $c['row_key']; continue; } if ( 'expander' === $col['type'] || ( '' !== $group && ! empty( $col['group_span'] ) ) ) { continue; } $base = $col; if ( $edit && ! empty( $col['editable'] ) && ! in_array( $col['type'], array( 'editable', 'toggle' ), true ) ) { $number = in_array( $col['type'], self::LIBRARY_NUM_TYPES, true ); $col['format'] = array_merge( array( 'as' => $number ? 'number' : 'text' ), 'integer' === $col['type'] ? array( 'step' => 1 ) : array(), (array) $col['format'] ); $col['type'] = 'editable'; } $key = (string) $col['key']; $key = ( '' === $key || isset( $defs[ $key ] ) || '__actions' === $key ) ? $key . '__' . $i : $key; $defs[ $key ] = array( 'col' => $col, 'base' => $base, 'type' => self::library_type( $col ), 'sortable' => $server && ! empty( $base['sortable'] ) && ! in_array( $base['type'], array( 'actions', 'toggle' ), true ), ); } if ( $c['actions'] ) { $defs['__actions'] = array( 'col' => AIEO_Table_Columns::normalise( array( 'key' => '__actions', 'label' => __( 'Actions', 'ai-eshop-optimizer' ), 'type' => 'actions' ) ), 'base' => array(), 'type' => 'actions', 'sortable' => false, ); } $keys = array(); foreach ( $defs as $key => $def ) { $keys += array( (string) ( $def['base']['key'] ?? '' ) => $key ); } $figs = array(); foreach ( $defs as $key => $def ) { $figs[ $key ] = 'actions' === $def['type'] ? array() : self::library_figure( $def['base'], $keys, false ); } $ra_cols = array(); foreach ( $defs as $key => $def ) { $col = array( 'key' => $key, 'label' => (string) $def['col']['label'], 'type' => $def['type'], ); $align = array( 'left' => 'start', 'right' => 'end', 'center' => 'center', ); if ( isset( $align[ (string) $def['col']['align'] ] ) ) { $col['align'] = $align[ (string) $def['col']['align'] ]; } if ( in_array( (string) $def['col']['width'], array( 'min', 'xs', 'sm', 'md', 'lg' ), true ) ) { $col['width'] = (string) $def['col']['width']; } if ( 'low' === (string) ( $def['base']['priority'] ?? '' ) ) { $col['priority'] = 'low'; } if ( '' !== (string) ( $def['base']['group'] ?? '' ) ) { $col['group'] = (string) $def['base']['group']; } if ( 'image' === $def['type'] ) { $px = isset( $def['col']['format']['size'] ) ? (int) $def['col']['format']['size'] : 40; $col['size'] = $px <= 32 ? 'sm' : ( $px <= 40 ? 'md' : ( $px <= 56 ? 'lg' : 'xl' ) ); } if ( $def['sortable'] ) { $t = (string) $def['base']['type']; $on = $key === $sort_on; $col['sort'] = $on ? $dir : 'none'; $col['sort_type'] = in_array( $t, array_merge( self::LIBRARY_NUM_TYPES, array( 'bar' ) ), true ) ? 'number' : ( in_array( $t, array( 'date', 'datetime', 'relative' ), true ) ? 'date' : 'text' ); $col['sort_url'] = self::library_url( array( $prefix . 'orderby' => $key, $prefix . 'order' => $on && 'asc' === $dir ? 'desc' : 'asc', $prefix . 'paged' => '', ) ); } $ra_cols[] = $col + $figs[ $key ]; } $dialogs = array(); $lrows = array(); $was = null; foreach ( $rows as $i => $row ) { if ( '' !== $group ) { $value = (string) AIEO_Table_Columns::value( $row, $group, '' ); if ( $value !== $was ) { $lrows[] = array( '_group' => '' !== $value ? $value : AIEO_Table_Columns::EMPTY_MARKER ); $was = $value; } } $rid = self::row_key_value( $row, $c['row_key'] ); $rid = '' !== $rid ? $rid : (string) ( $i + 1 ); $ctx = array( 'page_id' => $c['page_id'], 'row_key_value' => $rid, 'index' => $i, ); $name = self::library_row_name( $columns, $row, $ctx ); $lrow = array( '_id' => $rid, '_name' => $name, ); foreach ( $defs as $key => $def ) { if ( 'actions' === $def['type'] ) { $lrow[ $key ] = self::library_actions( $row, $c, $rid, $id, $dialogs ); continue; } list( $cell ) = self::library_cell( $def['col'], $row, $ctx, $edit, $rid, $name ); $lrow[ $key ] = $cell; } $rc = AIEO_Table_Columns::is_fn( $c['row_class'] ) ? (string) call_user_func( $c['row_class'], $row ) : ''; if ( preg_match( '/\baieo-t-row--(danger|warning|info|success)\b/', $rc, $m ) ) { $lrow['_tone'] = 'danger' === $m[1] ? 'destructive' : $m[1]; } $lrow['_attrs'] = array( 'data-row-key' => '' !== self::row_key_value( $row, $c['row_key'] ) ? $rid : null, 'data-row-type' => $c['row_type'] ? (string) AIEO_Table_Columns::value( $row, $c['row_type'], '' ) : null, ) + self::data_attr_values( $c['row_data'], $row ); $lrows[] = $lrow; } $totals = array(); foreach ( $defs as $key => $def ) { $ck = (string) ( $def['base']['key'] ?? '' ); if ( '' !== $ck && array_key_exists( $ck, (array) $c['totals'] ) && ! in_array( $def['type'], array( 'actions', 'image' ), true ) ) { $base = $def['base']; $base['type'] = in_array( $base['type'], array( 'editable', 'toggle' ), true ) ? 'decimal' : $base['type']; $totals[ $key ] = self::library_total( $base, $figs[ $key ], $c['totals'][ $ck ] ); } } return array( 'columns' => $ra_cols, 'rows' => $lrows, 'dialogs' => $dialogs, 'select' => $select, 'totals' => $totals, 'edit' => $edit, ); } private static function row_name( $row, $c, $a, $rid ) { $name = ''; foreach ( array( $a['confirm_name'] ?? '', $c['row_label'] ?? '' ) as $src ) { if ( AIEO_Table_Columns::is_fn( $src ) ) { $name = (string) call_user_func( $src, $row ); } elseif ( is_string( $src ) && '' !== $src ) { $name = (string) AIEO_Table_Columns::value( $row, $src, '' ); } if ( '' !== trim( wp_strip_all_tags( $name ) ) ) { break; } } if ( '' === trim( wp_strip_all_tags( $name ) ) ) { foreach ( (array) ( $c['columns'] ?? array() ) as $col ) { if ( is_array( $col ) && in_array( $col['type'] ?? 'text', array( 'text', 'link' ), true ) && ! empty( $col['key'] ) ) { $name = (string) AIEO_Table_Columns::value( $row, (string) $col['key'], '' ); if ( '' !== trim( wp_strip_all_tags( $name ) ) ) { break; } } } } $name = trim( preg_replace( '/\s+/', ' ', wp_strip_all_tags( $name ) ) ); if ( '' === $name ) { $name = '#' . $rid; } return mb_strlen( $name ) > 80 ? mb_substr( $name, 0, 79 ) . '…' : $name; } private static function library_action_confirm( $a, $label, $conf, $row, $table_id, &$dialogs, $name = '' ) { $ask = isset( $a['ask'] ) && is_array( $a['ask'] ) ? $a['ask'] : array(); if ( '' === $conf && ! $ask ) { return array(); } $level = in_array( $a['confirm_level'] ?? '', array( 'reset', 'off', 'replace', 'loss', 'delete' ), true ) ? $a['confirm_level'] : ( ! empty( $a['destructive'] ) ? ( preg_match( '/delete|remove|drop|trash|discard/i', (string) ( $a['id'] ?? '' ) . ' ' . $label ) ? 'delete' : 'loss' ) : ( $ask ? 'apply' : 'reset' ) ); $title = '' !== $conf ? $conf : $label; $body = (string) ( $a['confirm_body'] ?? '' ); if ( 'delete' === $level ) { if ( null === $row ) { return array(); } $noun = (string) ( $a['confirm_noun'] ?? '' ); $title = '' !== $noun ? sprintf( __( 'Delete the %1$s "%2$s"?', 'ai-eshop-optimizer' ), $noun, $name ) : sprintf( __( 'Delete "%s"?', 'ai-eshop-optimizer' ), $name ); if ( '' === $body && '' !== $conf ) { $body = $conf; } if ( '' !== $body && 0 !== strpos( ltrim( $body ), '<' ) ) { $body = '

' . esc_html( $body ) . '

'; } } $dialog = $table_id . '-ask-' . substr( md5( ( $a['id'] ?? '' ) . '|' . $label . '|' . $conf . ( 'delete' === $level ? '|' . $title : '' ) ), 0, 10 ); if ( ! isset( $dialogs[ $dialog ] ) ) { $opts = array( 'confirm' => $label ); if ( $ask ) { $opts['field'] = array( 'name' => (string) ( $ask['name'] ?? 'value' ), 'label' => (string) ( $ask['label'] ?? $label ), 'min' => $ask['min'] ?? null, 'step' => $ask['step'] ?? 1, 'unit' => (string) ( $ask['unit'] ?? '' ), 'required' => true, ); } $dialogs[ $dialog ] = \Rooster\Admin\States::confirm( $dialog, $level, $title, $body, $opts ); } $own = array(); if ( $ask && null !== $row ) { foreach ( array( 'value', 'min', 'max' ) as $k ) { if ( isset( $ask[ $k ] ) && '' !== (string) $ask[ $k ] ) { $own[ $k ] = is_numeric( $ask[ $k ] ) ? $ask[ $k ] : AIEO_Table_Columns::value( $row, (string) $ask[ $k ], '' ); } } } return \Rooster\Admin\States::confirm_attrs( $dialog, $own ); } public static function local( $id, array $columns, array $rows, array $opts = array() ) { if ( ! class_exists( '\Rooster\Admin\Ui' ) && ( ! function_exists( 'rooster_admin_builders' ) || ! rooster_admin_builders() ) ) { return ''; } $id = (string) $id; $prefix = preg_replace( '/[^A-Za-z0-9_-]/', '', (string) ( $opts['prefix'] ?? '' ) ); $size = isset( $opts['size'] ) ? max( 0, (int) $opts['size'] ) : 25; $search = $opts['search'] ?? ( ! empty( $opts['caps']['search'] ) ? true : false ); $keys = array( 's' => $prefix . 's', 'orderby' => $prefix . 'orderby', 'order' => $prefix . 'order', 'paged' => $prefix . 'paged', ); $all = ! empty( $opts['sortable'] ); $how = array(); foreach ( $columns as $i => $col ) { $key = (string) ( $col['key'] ?? '' ); $type = (string) ( $col['type'] ?? 'text' ); $way = ''; if ( isset( $col['sort_type'] ) ) { $way = (string) $col['sort_type']; } elseif ( isset( $col['sort'] ) && in_array( (string) $col['sort'], array( 'number', 'date', 'text', '' ), true ) ) { $way = (string) $col['sort']; unset( $columns[ $i ]['sort'] ); } elseif ( $all && 'actions' !== $type ) { $way = 'num' === $type ? 'number' : ( 'date' === $type ? 'date' : 'text' ); } if ( '' !== $key && in_array( $way, array( 'number', 'date', 'text' ), true ) ) { $how[ $key ] = $way; } } $base = wp_doing_ajax() ? (string) wp_get_referer() : ''; $by = isset( $_GET[ $keys['orderby'] ] ) && is_scalar( $_GET[ $keys['orderby'] ] ) ? sanitize_text_field( wp_unslash( (string) $_GET[ $keys['orderby'] ] ) ) : ''; $dir = isset( $_GET[ $keys['order'] ] ) && 'desc' === strtolower( (string) wp_unslash( $_GET[ $keys['order'] ] ) ) ? 'desc' : 'asc'; foreach ( $columns as $i => $col ) { $key = (string) ( $col['key'] ?? '' ); if ( ! isset( $how[ $key ] ) || ! $rows ) { continue; } $on = $key === $by; $columns[ $i ]['sort'] = $on ? $dir : 'none'; $columns[ $i ]['sort_type'] = $how[ $key ]; $columns[ $i ]['sort_url'] = add_query_arg( array( $keys['orderby'] => $key, $keys['order'] => $on && 'asc' === $dir ? 'desc' : 'asc', ), '' !== $base ? $base : false ); } $operands = array(); foreach ( $columns as $col ) { if ( is_array( $col['total'] ?? null ) && is_array( $col['total']['ratio'] ?? null ) ) { $operands += array_fill_keys( array_map( 'strval', $col['total']['ratio'] ), true ); } } $list = array(); foreach ( array_values( $rows ) as $i => $row ) { $s = is_array( $row['_s'] ?? null ) ? $row['_s'] : array(); foreach ( $how as $key => $way ) { if ( ! array_key_exists( $key, $s ) ) { $cell = $row[ $key ] ?? ''; $s[ $key ] = is_array( $cell ) ? wp_strip_all_tags( (string) ( $cell['title'] ?? $cell['label'] ?? $cell['value'] ?? ( $cell['html'] ?? '' ) ) ) : (string) $cell; } if ( 'number' === $way && is_numeric( $s[ $key ] ) ) { $s[ $key ] = 0 + $s[ $key ]; } } $row['_id'] = isset( $row['_id'] ) ? $row['_id'] : (string) ( $i + 1 ); $s['_id'] = $row['_id']; $text = isset( $row['_text'] ) ? (string) $row['_text'] : implode( ' ', array_map( 'strval', array_filter( array_diff_key( $s, array( '_id' => 1 ) ), 'is_scalar' ) ) ); foreach ( $operands as $key => $on ) { if ( ! array_key_exists( $key, $s ) && isset( $row[ $key ] ) && is_numeric( $row[ $key ] ) ) { $s[ $key ] = 0 + $row[ $key ]; } } $attrs = ( is_array( $row['_attrs'] ?? null ) ? $row['_attrs'] : array() ) + array( 'data-ra-s' => (object) $s, 'data-ra-text' => $text, ); if ( isset( $row['_f'] ) ) { $attrs['data-ra-f'] = (object) $row['_f']; } if ( isset( $row['_data'] ) ) { $attrs = array_merge( $attrs, (array) $row['_data'] ); } unset( $row['_s'], $row['_text'], $row['_f'], $row['_data'] ); $row['_attrs'] = $attrs; $list[] = $row; } $sizes = array(); if ( $size > 0 && false !== ( $opts['sizes'] ?? null ) ) { $sizes = array_values( array_filter( array_map( 'intval', is_array( $opts['sizes'] ?? null ) ? $opts['sizes'] : array( 25, 50, 100 ) ) ) ); if ( count( $sizes ) < 2 || count( $list ) <= $sizes[0] ) { $sizes = array(); } } if ( $sizes ) { $asked = isset( $_GET[ $prefix . 'per_page' ] ) && is_scalar( $_GET[ $prefix . 'per_page' ] ) ? (int) $_GET[ $prefix . 'per_page' ] : 0; $size = in_array( $asked, $sizes, true ) ? $asked : ( in_array( $size, $sizes, true ) ? $size : $sizes[0] ); } $local = array( 'size' => $size, 'words' => true, 'keys' => $keys, ); if ( '' !== $prefix ) { $local['prefix'] = $prefix; } if ( $sizes ) { $local['sizes'] = $sizes; $local['keys']['per_page'] = $prefix . 'per_page'; } foreach ( array( 'sort', 'label', 'tie' ) as $k ) { if ( ! empty( $opts[ $k ] ) ) { $local[ $k ] = $opts[ $k ]; } } if ( is_array( $opts['local'] ?? null ) ) { $local = array_merge( $local, $opts['local'] ); } $table = array_diff_key( $opts, array_flip( array( 'prefix', 'size', 'sizes', 'sort', 'label', 'tie', 'sortable', 'search', 'caps', 'filters', 'words', 'local' ) ) ); $table = array( 'id' => $id, 'local' => $local, ) + $table; $filters = is_array( $opts['filters'] ?? null ) ? $opts['filters'] : array(); if ( $filters ) { $table['filters'] = $filters; $table['search'] = $search; } elseif ( $search ) { $table['toolbar'] = \Rooster\Admin\Filters::bar( $id, array(), array(), array( 'search' => $search, 'prefix' => $prefix, 'keys' => array( 's' => $keys['s'] ), ) ) . (string) ( $opts['toolbar'] ?? '' ); } $shown = $list; if ( empty( $local['form'] ) && $size > 0 && count( $list ) > $size ) { $table['rows_template'] = $list; $shown = array_slice( $list, 0, $size ); } return \Rooster\Admin\Ui::table( $columns, $shown, $table ); } public static function local_answer( $id, array $columns, array $rows, array $opts = array() ) { $html = self::local( $id, $columns, $rows, $opts ); return array( 'html' => $html, 'icons' => '' !== $html ? \Rooster\Admin\Ui::icons_of( $html ) : '', ); } public static function table_assets( array $parts = array() ) { if ( function_exists( 'rooster_admin_table_assets' ) ) { rooster_admin_table_assets( array_values( array_unique( array_merge( array( 'local' ), $parts ) ) ) ); } } }