'',
'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'] : '' )
. '' . $tag . '>';
}
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 = '';
case 'term':
$terms = get_terms(
array(
'taxonomy' => (string) $param['taxonomy'],
'hide_empty' => false,
'number' => 500,
'fields' => 'id=>name',
)
);
$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 = '';
}
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 . '' . $tag . '>';
}
$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 = '';
}
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
? '' . $fields . '
'
: '';
}
$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)[^>]*>(.*?)\1>#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 ) ) ) );
}
}
}