<?php
|
namespace JVBase\ui;
|
|
use JVBase\managers\UserTermsManager;
|
use JVBase\meta\MetaForm;
|
use JVBase\meta\MetaManager;
|
use WP_User;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
/**
|
* CRUDSkeleton - Fluent builder for flexible CRUD interfaces
|
*
|
* Provides a reusable HTML skeleton for CRUD operations on any data type,
|
* not just WP posts. Can be used for custom tables, user data, etc.
|
*
|
* @example
|
* $crud = new CRUDSkeleton();
|
* $crud->title('Your Referrals', 'Track and manage your referral links')
|
* ->addFilter('date')
|
* ->addFilter('status', ['active', 'pending', 'expired'])
|
* ->addView('grid')
|
* ->addView('table')
|
* ->dataSource([$this, 'getReferrals'])
|
* ->render();
|
*/
|
class CRUDSkeleton {
|
protected WP_User $user;
|
protected int $user_id;
|
|
// Core configuration
|
protected string $title = '';
|
protected string $description = '';
|
protected string $dataType = '';
|
protected string $singular = '';
|
protected string $plural = '';
|
protected string $icon;
|
|
// Capabilities
|
protected array $caps = [];
|
private array $allowedCaps = ['view','edit', 'create', 'delete'];
|
protected bool $userCanPublish = false;
|
|
// Features
|
protected array $filters = [];
|
protected array $taxonomies = [];
|
protected array $views = [];
|
protected array $defaultViews = ['grid', 'list', 'table'];
|
protected string $defaultView = 'grid';
|
protected bool $hasSearch = false;
|
|
protected array $itemActions = [];
|
protected array $defaultItemActions = [
|
'edit' => [
|
'title' => 'Edit',
|
'icon' => 'pencil-simple'
|
],
|
'trash'=> [
|
'title' => 'Scrap',
|
'icon' => 'trash'
|
]
|
];
|
protected bool $isTimeline = false;
|
protected array $nonTimelineFields = [];
|
protected array $timelineSharedFields = [];
|
protected array $timelineUniqueFields = [];
|
protected bool $isCalendar = false;
|
protected bool $useCRUDjs = true;
|
//Bulk Actions
|
protected array $bulkActions = [];
|
private array $allowedBulkActions = ['edit', 'publish', 'draft', 'copy', 'trash'];
|
protected array $defaultBulkActions = [
|
'edit' => 'Edit',
|
'publish' => 'Show',
|
'draft' => 'Hide',
|
'copy' => 'Duplicate',
|
'trash' => 'Scrap'
|
];
|
protected array $fields = [];
|
protected array $sections = [];
|
protected array $statuses = [];
|
protected array $allowedStatuses = [
|
'all' => [
|
'label' => 'Everything',
|
'icon' => 'infinity'
|
],
|
'publish' => [
|
'label' => 'Visible',
|
'icon' => 'eye',
|
],
|
'draft' => [
|
'label' => 'Hidden',
|
'icon' => 'eye-slash'
|
],
|
'trash' => [
|
'label' => 'Deleted',
|
'icon' => 'trash'
|
],
|
|
'future' => [
|
'label' => 'Upcoming',
|
'icon' => 'clock-clockwise',
|
],
|
'past' => [
|
'label' => 'Past',
|
'icon' => 'clock-counter-clockwise',
|
],
|
'repeat' => [
|
'label' => 'Recurring',
|
'icon' => 'repeat',
|
]
|
];
|
protected array $defaultStatus = ['all', 'publish', 'draft', 'trash'];
|
protected array $defaultCalendarStatus = ['all', 'future', 'past', 'repeat', 'draft', 'trash'];
|
|
protected ?array $uploaderConfig = null;
|
|
// Data
|
protected $dataSourceCallback = null;
|
protected array $templates = [];
|
|
// Metadata handling
|
protected ?MetaManager $meta = null;
|
protected ?MetaForm $form = null;
|
|
// UI Options
|
protected array $stuck = []; // Fields that stick when scrolling
|
protected bool $showHeader = true;
|
protected bool $showBulkControls = true;
|
protected bool $showFilters = true;
|
protected array $customDateRanges = [];
|
protected array $additionalClasses = [];
|
|
public function __construct() {
|
$this->icon = jvbDefaultIcon();
|
$this->user = wp_get_current_user();
|
$this->user_id = $this->user->ID;
|
}
|
|
/**
|
* Set the title and optional description
|
*/
|
public function title(string $title, string $description = ''): self {
|
$this->title = $title;
|
$this->description = $description;
|
return $this;
|
}
|
|
/**
|
* Set content type information
|
*/
|
public function content(string $type, string $singular, string $plural): self {
|
$this->dataType = $type;
|
$this->singular = $singular;
|
$this->plural = $plural;
|
return $this;
|
}
|
|
/**
|
* Add a filter to the interface
|
*
|
* @param string $type Built-in types: 'status', 'date', 'author', or custom
|
* @param mixed $config Configuration array or callable
|
*/
|
public function addFilter(string $type, $config = []): self {
|
if ($type === 'status' && empty($config)) {
|
$config = $this->getDefaultStatuses();
|
} elseif ($type === 'date' && empty($config)) {
|
$config = [
|
'label' => 'Date',
|
'icon' => 'calendar'
|
];
|
}
|
|
$this->filters[$type] = $config;
|
return $this;
|
}
|
|
/**
|
* Add a date filter
|
*
|
* @param string $field The field to filter on (default: 'post_date')
|
* @param ?array $ranges Available date ranges
|
*/
|
public function addDateFilter(string $field = 'post_date', ?array $ranges = null): self {
|
if ($ranges === null) {
|
$ranges = ['today' => 'Today', 'week' => 'This Week', 'this-month' => 'This Month', 'last-month' => 'Last Month', 'quarter' => 'The Last 3 Months', 'past-year' => 'the Last Year', 'custom' => 'Custom Range'];
|
}
|
|
$this->filters['date'] = [
|
'type' => 'date',
|
'field' => $field,
|
'ranges' => $ranges,
|
'label' => 'Date',
|
'icon' => 'calendar'
|
];
|
|
return $this;
|
}
|
|
public function addCustomDateRange(array $ranges):self {
|
$ranges = array_filter($ranges);
|
$ranges = array_filter($ranges, function($range) {
|
return is_string($range);
|
});
|
$this->customDateRanges = $ranges;
|
|
return $this;
|
}
|
|
/**
|
* Add taxonomy filters
|
*
|
* @param array $taxonomies Array of taxonomy slugs to filter by
|
* @param string|null $limit 'user' to limit to current user's terms, null for all
|
*/
|
public function addTaxonomyFilter(array $taxonomies, ?string $limit = null): self {
|
foreach($taxonomies as $taxonomy) {
|
$this->taxonomies[$taxonomy] = [
|
'type' => 'taxonomy',
|
'taxonomy'=> $taxonomy,
|
'limit' => $limit,
|
'label' => JVB_TAXONOMY[$taxonomy]['plural']??'',
|
'icon' => JVB_TAXONOMY[$taxonomy]['icon']??''
|
];
|
}
|
|
return $this;
|
}
|
|
protected function taxConfig(string $taxonomy, string $label = ''):array
|
{
|
$isVerified = jvbUserIsVerified();
|
$label = ($label === '') ? JVB_TAXONOMY[$taxonomy]['plural'] : $label;
|
return [
|
'type' => 'taxonomy',
|
'label' => $label,
|
'taxonomy' => $taxonomy,
|
'createNew' => $isVerified,
|
'multiple' => true,
|
'mode' => 'append',
|
];
|
}
|
|
public function addSearch():self
|
{
|
$this->hasSearch = true;
|
return $this;
|
}
|
|
/**
|
* Add a view type (grid, table, list, timeline)
|
*/
|
public function addViews(?array $views = null):self
|
{
|
if (!$views) {
|
$views = $this->defaultViews;
|
}
|
$this->views = $views;
|
return $this;
|
}
|
|
/**
|
* Set the default view
|
*/
|
public function defaultView(string $type): self {
|
$this->defaultView = $type;
|
return $this;
|
}
|
|
/*****************************************************
|
* ITEM ACTIONS
|
*****************************************************/
|
public function addItemActions(array $actions = ['edit', 'trash']):self
|
{
|
if(!empty($actions)) {
|
$this->itemActions = $actions;
|
}
|
return $this;
|
}
|
public function defineItemAction(string $action, array $definition):self
|
{
|
$config = array_key_exists($action, $this->defaultItemActions) ? $this->defaultItemActions[$action] : [];
|
$config = array_merge($config, $definition);
|
$this->defaultItemActions[$action] = $config;
|
|
return $this;
|
}
|
/**
|
* Configure the uploader
|
*/
|
public function addUploader(array $config): self {
|
$this->uploaderConfig = array_merge([
|
'type' => 'upload',
|
'subtype' => 'image',
|
'mode' => 'selection',
|
'multiple' => true,
|
'label' => 'Upload Files',
|
], $config);
|
return $this;
|
}
|
|
public function useCRUDjs(bool $use = true):self
|
{
|
$this->useCRUDjs = false;
|
return $this;
|
}
|
|
public function setCalendar():self
|
{
|
$this->isCalendar = true;
|
return $this;
|
}
|
|
public function setDefaultStatus():self
|
{
|
if ($this->isCalendar) {
|
$this->statuses = $this->defaultCalendarStatus;
|
}else {
|
$this->statuses = $this->defaultStatus;
|
}
|
|
return $this;
|
}
|
/**************************************************
|
* TIMELINE SHORTCUTS
|
**************************************************/
|
public function setTimeline():self
|
{
|
$this->isTimeline = true;
|
|
return $this;
|
}
|
|
public function maybeSetupTimeline():void
|
{
|
if (!$this->isTimeline) {
|
return;
|
}
|
|
$this->timelineSharedFields = array_keys(array_filter($this->fields, function ($field) {
|
if (!array_key_exists('for_all', $field) || $field['for_all'] === false){
|
return true;
|
}
|
return false;
|
}));
|
array_unshift($this->timelineSharedFields, 'post_thumbnail');
|
array_unshift($this->timelineSharedFields, 'post_title');
|
array_unshift($this->timelineSharedFields, 'post_status');
|
|
$this->timelineUniqueFields = array_keys(array_filter($this->fields, function ($field) {
|
if (array_key_exists('for_all', $field) && $field['for_all'] === true) {
|
return true;
|
}
|
return false;
|
}));
|
|
$all = array_merge($this->timelineUniqueFields, $this->timelineSharedFields);
|
$this->nonTimelineFields = array_filter($this->fields, function ($field) use ($all) {
|
return !in_array($field, $all);
|
}, ARRAY_FILTER_USE_KEY);
|
}
|
/**************************************************
|
* CAPABILITIES
|
* Changes output depends on capabilities.
|
* View -> only lists data
|
* Edit -> can edit data
|
* Create -> can create data
|
* delete -> can delete data
|
*************************************************/
|
public function addCapabilities(?array $capabilities = null):self
|
{
|
if (!$capabilities) {
|
$capabilities = $this->allowedCaps;
|
}
|
$capabilities = array_filter($capabilities, function ($cap) {
|
return in_array($cap, $this->allowedCaps);
|
});
|
$this->caps = $capabilities;
|
return $this;
|
}
|
public function userCanPublish(bool $can = false):self
|
{
|
$this->userCanPublish = $can;
|
return $this;
|
}
|
/**************************************************
|
* BULK ACTIONS
|
* addBulkActions() -> adds default bulk actions
|
* addBulkActions(['edit','delete']) -> adds edit/delete
|
* setActionLabel('edit', 'Modify') -> change the edit action's label to 'Modify'
|
*************************************************/
|
public function addBulkActions(?array $actions = null):self
|
{
|
if ($actions === null) {
|
$actions = array_keys($this->defaultBulkActions);
|
}
|
$actions = array_filter($actions, function($item) {
|
return in_array($item, $this->allowedBulkActions);
|
});
|
$temp =[];
|
foreach ($actions as $action) {
|
$temp[$action] = $this->defaultBulkActions[$action];
|
}
|
$this->bulkActions = $temp;
|
return $this;
|
}
|
public function setActionLabel(string $key, string $label): self {
|
if (array_key_exists($key, $this->bulkActions)) {
|
$this->bulkActions[$key] = $label;
|
}
|
return $this;
|
}
|
|
/**
|
* Add a single field
|
*/
|
public function addField(string $name, array $config): self {
|
$this->fields[$name] = $config;
|
return $this;
|
}
|
|
/**
|
* Set all fields at once
|
*/
|
public function setFields(array $fields): self {
|
$this->fields = $fields;
|
$this->maybeSetupTimeline();
|
return $this;
|
}
|
|
/**
|
* Add a section for organizing fields
|
*/
|
public function addSection(string $id, array $config): self {
|
$this->sections[$id] = $config;
|
return $this;
|
}
|
|
/**
|
* Set custom statuses
|
*/
|
public function setStatuses(array $statuses): self {
|
$this->statuses = $statuses;
|
return $this;
|
}
|
|
|
/**
|
* Mark fields that should stick when scrolling
|
*/
|
public function stickFields(array $fieldNames): self {
|
$this->stuck = array_merge($this->stuck, $fieldNames);
|
return $this;
|
}
|
|
/**
|
* Set the data source callback
|
* Callback should accept filters and return array of items
|
*/
|
public function dataSource(callable $callback): self {
|
$this->dataSourceCallback = $callback;
|
return $this;
|
}
|
|
/**
|
* Add a custom template
|
*/
|
public function addTemplate(string $name, string $template): self {
|
$this->templates[$name] = $template;
|
return $this;
|
}
|
|
/**
|
* Add CSS classes to the wrapper
|
*/
|
public function addClasses(array $classes): self {
|
$this->additionalClasses = array_merge($this->additionalClasses, $classes);
|
return $this;
|
}
|
|
/**
|
* Toggle UI elements
|
*/
|
public function showHeader(bool $show = true): self {
|
$this->showHeader = $show;
|
return $this;
|
}
|
|
public function showBulkControls(bool $show = true): self {
|
$this->showBulkControls = $show;
|
return $this;
|
}
|
|
public function showFilters(bool $show = true): self {
|
$this->showFilters = $show;
|
return $this;
|
}
|
|
/**
|
* Initialize meta handling
|
*/
|
public function initMeta(string $objectType = 'post', ?string $content = null): self {
|
$this->meta = new MetaManager(null, $objectType, $content ?? $this->dataType);
|
$this->form = new MetaForm();
|
return $this;
|
}
|
|
/**
|
* Build the configuration array
|
*/
|
public function build(): array {
|
return [
|
'title' => $this->title,
|
'description' => $this->description,
|
'dataType' => $this->dataType,
|
'singular' => $this->singular,
|
'plural' => $this->plural,
|
'filters' => $this->filters,
|
'views' => $this->views,
|
'defaultView' => $this->defaultView,
|
'bulkActions' => $this->bulkActions,
|
'fields' => $this->fields,
|
'sections' => $this->sections,
|
'statuses' => $this->statuses,
|
'uploaderConfig' => $this->uploaderConfig,
|
'stuck' => $this->stuck,
|
'showHeader' => $this->showHeader,
|
'showBulkControls' => $this->showBulkControls,
|
'showFilters' => $this->showFilters,
|
'additionalClasses' => $this->additionalClasses,
|
];
|
}
|
|
/**
|
* Render the CRUD interface
|
*/
|
public function render(): void {
|
$config = $this->build();
|
$classes = array_merge(['dashboard-page', $this->dataType], $this->additionalClasses);
|
|
ob_start();
|
?>
|
<div class="<?= esc_attr(implode(' ', $classes)) ?>" data-type="<?= esc_attr($this->dataType) ?>">
|
<?php
|
if ($this->showHeader) {
|
$this->renderHeader();
|
}
|
$this->renderContent();
|
$this->renderModals();
|
$this->renderTemplates();
|
?>
|
</div>
|
<?php
|
echo ob_get_clean();
|
}
|
|
/**
|
* Render the header section
|
*/
|
protected function renderHeader(): void {
|
?>
|
<h1><?= esc_html($this->title) ?></h1>
|
<?php
|
if (!empty($this->description)) {
|
?>
|
<p class="page-description"><?= esc_html($this->description) ?></p>
|
<?php
|
}
|
|
if ($this->uploaderConfig) {
|
$this->renderUploader();
|
}
|
|
do_action('jvb_crud_after_header', $this->dataType, $this);
|
}
|
|
/**
|
* Render uploader section
|
*/
|
protected function renderUploader(): void {
|
if (!$this->meta) {
|
return;
|
}
|
?>
|
<details open class="uploader">
|
<summary class="row btw"><?= esc_html($this->uploaderConfig['label'] ?? 'Upload Files') ?></summary>
|
<?php
|
$this->meta->render(
|
'form',
|
'new_' . $this->dataType,
|
$this->uploaderConfig
|
);
|
?>
|
</details>
|
<?php
|
}
|
|
/**
|
* Render the main content area
|
*/
|
protected function renderContent(): void {
|
$dataIgnore = $this->useCRUDjs ? '' : ' data-ignore';
|
?>
|
<section class="items-list <?= esc_attr($this->dataType) ?> crud" data-content="<?= esc_attr($this->dataType) ?>" data-singular="<?=$this->singular?>" data-plural="<?=$this->plural?>" data-view="<?= $this->defaultView?>"<?=$dataIgnore?>>
|
<div class="wrap">
|
<?php
|
$this->renderControlsAndFilters();
|
|
if ($this->showBulkControls) {
|
$this->renderBulkActions();
|
}
|
?>
|
|
<div class="<?= esc_attr($this->dataType) ?> item-grid" role="grid"></div>
|
<div class="scroll-sentinel" aria-hidden="true"></div>
|
</div>
|
</section>
|
<?php
|
}
|
|
/**
|
* Render filters
|
*/
|
protected function renderControlsAndFilters(): void {
|
if (!$this->showFilters) {
|
return;
|
}
|
?>
|
<details class="all-filters col start" data-ignore>
|
<summary>Filters <button hidden data-action="clear-filters" data-ignore><?=jvbIcon('x')?><span>Clear Filters</span></span></button></summary>
|
<?php
|
|
$this->renderSearch();
|
$this->renderViewControls();
|
$this->renderStatusControls();
|
$this->renderOrderControls();
|
$this->renderFilters();
|
if (in_array('table', $this->views)) {
|
$this->renderColumnSelector();
|
}
|
?>
|
<button data-action="refresh" data-ignore><?=jvbIcon('arrows-clockwise')?><span>Hard Refresh</span></span></button>
|
</details>
|
<?php
|
}
|
|
protected function renderSearch():void
|
{
|
if (!$this->hasSearch){
|
return;
|
}
|
?>
|
<div class="search row start nowrap">
|
<span class="label">Search:</span>
|
<?= jvbSearch() ?>
|
</div>
|
<?php
|
}
|
|
protected function renderViewControls():void
|
{
|
if (empty($this->views) || count($this->views) === 1){
|
return;
|
}
|
?>
|
<div class="radio-options view row">
|
<span class="label">View:</span>
|
<?php
|
$views = [
|
'grid' => ['icon' => 'squares-four', 'label' => 'Grid View'],
|
'list' => ['icon' => 'rows', 'label' => 'List View'],
|
'table' => ['icon' => 'table', 'label' => 'Table View'],
|
];
|
foreach ($this->views as $index => $view) {
|
$first = $index === 0;
|
?>
|
<input type="radio"
|
data-view="<?=$view?>"
|
value="<?=$view?>"
|
class="btn"
|
name="view"
|
id="view-<?=$view?>"
|
<?= $first ? ' checked':''?>>
|
<label for="view-<?=$view?>"
|
title="<?=$views[$view]['label']?>">
|
<?= jvbDashIcon($views[$view]['icon']) ?>
|
<span class="screen-reader-text"><?=$views[$view]['label']?></span>
|
</label>
|
<?php
|
}
|
?>
|
</div>
|
<?php
|
}
|
|
protected function renderStatusControls():void
|
{
|
if (empty($this->statuses) || count($this->statuses) === 1) {
|
return;
|
}
|
?>
|
<div class="radio-options status row">
|
<span class="label">Status:</span>
|
<?php
|
$i = 1;
|
foreach ($this->statuses as $status) {
|
if (!array_key_exists($status, $this->allowedStatuses)) {
|
continue;
|
}
|
$config = $this->allowedStatuses[$status];
|
|
$checked = ($i == 1) ? ' checked' : '';
|
?>
|
<input type="radio" class="btn" data-filter="status" value="<?=$status?>" name="status" id="<?=$status?>"<?=$checked?>>
|
<label for="<?=$status?>" title="<?=$config['label']?>">
|
<?= jvbDashIcon($config['icon']) ?>
|
</label>
|
<?php
|
$i++;
|
}
|
?>
|
</div>
|
<?php
|
}
|
|
protected function renderOrderControls():void
|
{
|
?>
|
<div class="radio-options order row btw w-full">
|
<?php
|
$order = [
|
'orderby' => [
|
'date' => 'Order by date created',
|
'alphabetical' => 'Order alphabetically'
|
],
|
'order' => [
|
'sort-ascending' => 'In ascending order (Z-A, oldest to newest)',
|
'sort-descending' => 'In descending order (A-Z, newest to oldest)'
|
]
|
];
|
|
foreach ($order as $o => $option) {
|
?>
|
<div class="row start">
|
<span class="label"><?= ucfirst($o)?>:</span>
|
<?php
|
$i = 0;
|
foreach ($option as $opt => $label) {
|
$icon = $opt === 'date' ? 'calendar' : $opt;
|
$value = $opt;
|
$value = ($value === 'sort-ascending') ? 'asc' : $value;
|
$value = ($value === 'sort-descending') ? 'desc' : $value;
|
?>
|
<input id="<?=$opt?>" class="btn" type="radio" name="<?=$o?>" data-filter="<?=$o?>" value="<?=$value?>"<?=$i===0 ? ' checked':''?>>
|
|
<label for="<?=$opt?>" title="<?=$label?>"><?=jvbDashIcon($icon)?></label>
|
<?php
|
$i++;
|
}
|
?>
|
</div>
|
<?php
|
}
|
?>
|
</div>
|
<?php
|
}
|
|
protected function renderFilters(): void {
|
if (!$this->showFilters || empty($this->filters)) {
|
return;
|
}
|
?>
|
<div class="filters row start">
|
<span class="label">Filters:</span>
|
<?php
|
foreach ($this->filters as $key => $config) {
|
$type = $config['type'] ?? $key;
|
|
switch ($type) {
|
case 'date':
|
$this->renderDateFilter($config);
|
break;
|
|
default:
|
// Custom filter - allow override
|
do_action('jvb_crud_render_filter_' . $type, $config, $this);
|
break;
|
}
|
}
|
foreach ($this->taxonomies as $config) {
|
$this->renderTaxonomyFilter($config);
|
}
|
?>
|
|
<button type="button" class="clear-filters row" hidden>
|
<?= jvbDashIcon('x', ['title' => 'Clear Filters']) ?>
|
Clear All Filters
|
</button>
|
</div>
|
<?php
|
}
|
|
protected function renderDateFilter(array $config): void {
|
$field = $config['field'] ?? 'post_date';
|
$ranges = $config['ranges'] ?? [];
|
$label = $config['label'] ?? 'Date';
|
$icon = $config['icon'] ?? 'calendar';
|
?>
|
<div class="row nowrap">
|
<label for="filter-date"><?= jvbDashIcon($icon) ?> <span class="screen-reader-text">By <?= esc_html($label) ?>:</span></label>
|
<select id="filter-date" name="date-filter" data-filter="date">
|
<option value="">All Time</option>
|
<?php foreach ($ranges as $range => $rangeLabel):
|
?>
|
<option value="<?= esc_attr($range) ?>"><?= esc_html($rangeLabel) ?></option>
|
<?php endforeach; ?>
|
</select>
|
|
<?php
|
if (array_key_exists('custom', $ranges) && !empty($this->customDateRanges)) {
|
ob_start();
|
?>
|
<div class="custom-range row">
|
<label for="date-start" class="col">
|
From
|
</label>
|
<input type="date" id="date-start" class="date-start" name="date-start">
|
<label for="date-end" class="col">
|
To
|
</label>
|
<input type="date" id="date-end" class="date-end" name="date-end">
|
</div>
|
<div class="month-picker">
|
<label>
|
<span>Or select month</span>
|
<select class="month-select">
|
<?php
|
foreach ($this->customDateRanges as $name=> $label) {
|
echo sprintf(
|
'<option value="%s">%s</option>',
|
$name,
|
$label
|
);
|
}
|
?>
|
</select>
|
</label>
|
</div>
|
<?php
|
$form = ob_get_clean();
|
echo jvbNewModal(
|
'date-range',
|
'Filter Results by Date:',
|
$form
|
);
|
}
|
?>
|
</div>
|
<?php
|
}
|
|
protected function renderTaxonomyFilter(array $config): void {
|
$taxonomy = $config['taxonomy']??false;
|
|
if (!$taxonomy) {
|
return;
|
}
|
$limit = $config['limit'] ?? null;
|
$icon = $config['icon'] ?? 'folder';
|
$terms = $this->getCommonTerms($taxonomy, $limit);
|
$label = $config['label'] ?? 'Categories';
|
$out = '';
|
if (!empty($terms)) {
|
$out .= sprintf(
|
'<div class="row nowrap"><label class="m-0" for="filter-%s">%s<span class="screen-reader-text">Filter by %s</span></label>
|
<select id="filter-%s" class="filter %s" name="%s" data-filter="taxonomies" data-taxonomy="%s">
|
<option value="">by %s</option>',
|
$taxonomy,
|
jvbDashIcon($icon, ['title' => $label]),
|
$label,
|
$taxonomy,
|
$taxonomy,
|
$taxonomy,
|
$taxonomy,
|
$label
|
);
|
|
|
foreach ($terms as $term) {
|
$out .= sprintf(
|
'<option value="%s">%s</option>',
|
esc_attr($term['term_id']),
|
esc_html($term['name'])
|
);
|
}
|
$out .= '</select></div>';
|
}
|
echo $out;
|
}
|
|
/**
|
* Get common terms for taxonomy
|
* @param string $taxonomy
|
* @return array
|
*/
|
protected function getCommonTerms(string $taxonomy, ?string $limit = null):array {
|
if ($limit) {
|
if ($limit === 'user') {
|
$manager = new UserTermsManager();
|
return $manager->getUserTerms($this->user_id, $taxonomy);
|
} else {
|
$limit = (int)$limit;
|
}
|
}
|
|
$args = [
|
'taxonomy' => jvbCheckBase($taxonomy),
|
'hide_empty' => true,
|
'orderby' => 'name',
|
];
|
if ($limit) {
|
$args['number'] = $limit;
|
}
|
return get_terms($args);
|
}
|
|
protected function renderColumnSelector():void {
|
ob_start();
|
?>
|
<details class="multi-select" title="Select columns" hidden>
|
<summary class="row start nowrap">
|
<?= jvbDashIcon('columns') ?>
|
<span class="labels">Toggle Columns</span>
|
</summary>
|
<div class="column-list">
|
<?php foreach ($this->fields as $fieldName => $config):
|
if (array_key_exists('hidden', $config)){
|
continue;
|
}
|
?>
|
<input type="checkbox"
|
id="show-<?= esc_attr($fieldName) ?>"
|
class="column-toggle ch"
|
name="show-<?= esc_attr($fieldName) ?>"
|
checked>
|
<label for="show-<?= esc_attr($fieldName) ?>">
|
<?= esc_html($config['label']) ?>
|
</label>
|
<?php endforeach; ?>
|
</div>
|
</details>
|
<?php
|
echo ob_get_clean();
|
}
|
|
/**
|
* Render bulk controls
|
*/
|
protected function renderBulkActions(): void {
|
if (empty($this->bulkActions)) {
|
return;
|
}
|
?>
|
<div class="bulk-controls row nowrap btw">
|
<div class="bulk-select">
|
<input type="checkbox" id="select-all" class="select-all">
|
<label for="select-all" class="row"><span>Select All</span><span class="selected-count" hidden></span></label>
|
</div>
|
<div class="bulk-actions row nowrap" hidden>
|
<label for="bulk-action-select" class="screen-reader-text">
|
Select what to do with this selection.
|
</label>
|
<select class="bulk-action-select" id="bulk-action-select">
|
|
</select>
|
</div>
|
</div>
|
|
<template class="notTrashOptions">
|
<select class="wrap">
|
<option value="">Bulk Actions...</option>
|
<?php
|
foreach ($this->bulkActions as $control => $label) {
|
$disabled = ($control === 'publish' && !$this->userCanPublish) ? ' disabled' : '';
|
?>
|
<option value="<?=$control?>"<?=$disabled?>><?=$label?></option>
|
<?php
|
}
|
foreach ($this->taxonomies as $taxonomy => $config) {
|
?>
|
<option value="tax-<?=$taxonomy?>" data-type="selector" data-single="<?=JVB_TAXONOMY[$taxonomy]['singular']?>" data-plural="<?=JVB_TAXONOMY[$taxonomy]['plural']?>" data-taxonomy="<?=$taxonomy?>">Add to <?= JVB_TAXONOMY[$taxonomy]['singular']??$config['label'] ?></option>
|
<?php
|
}
|
?>
|
</select>
|
|
</template>
|
<template class="trashOptions">
|
<select class="wrap">
|
<option value="">Bulk Actions...</option>
|
<option value="restore">Restore</option>
|
<option value="delete">Permanently Delete</option>
|
</select>
|
</template>
|
<?php
|
}
|
|
/**
|
* Render modals (can be overridden)
|
*/
|
protected function renderModals(): void {
|
foreach ($this->caps as $cap) {
|
switch ($cap) {
|
case 'create':
|
$this->renderCreateModal();
|
break;
|
case 'edit':
|
$this->renderEditModal();
|
if (!empty($this->bulkActions)) {
|
$this->renderBulkEditModal();
|
}
|
break;
|
}
|
}
|
do_action('jvb_crud_render_modals', $this->dataType, $this);
|
}
|
|
/**
|
* Render templates (can be overridden)
|
*/
|
protected function renderTemplates(): void
|
{
|
$templates = $this->templates;
|
foreach ($this->views as $view) {
|
if (array_key_exists($view, $templates)) {
|
echo $templates[$view];
|
unset($templates[$view]);
|
} else {
|
switch ($view) {
|
case 'table':
|
$this->renderTableTemplate();
|
$this->renderTableRowTemplate();
|
break;
|
case 'grid':
|
$this->renderGridTemplate();
|
break;
|
case 'list':
|
$this->renderListTemplate();
|
break;
|
}
|
}
|
}
|
if ($this->isTimeline && !array_key_exists('timeline', $templates)) {
|
$temp = array_filter($this->fields, function ($field) {
|
return in_array($field, $this->timelineUniqueFields);
|
}, ARRAY_FILTER_USE_KEY);
|
$form = new MetaForm();
|
echo '<template class="timelineItem">';
|
$form->renderImagePreview(null,['fields' => $temp]);
|
echo '</template>';
|
}
|
if (!array_key_exists('empty', $templates)) {
|
$state = apply_filters('jvbEmptyState', $this->renderEmptyState(), $this->dataType);
|
echo '<template class="emptyState">' . $state . '</template>';
|
}
|
if (!array_key_exists('galleryPreview', $templates)) {
|
$this->renderGalleryPreviewTemplate();
|
}
|
foreach ($templates as $name => $template) {
|
echo $template;
|
}
|
do_action('jvb_crud_render_templates', $this->dataType, $this);
|
}
|
|
protected function renderEmptyStateTemplate():void
|
{
|
$state = apply_filters('jvbEmptyState', $this->renderEmptyState(), $this->dataType);
|
echo '<template class="emptyState">'.$state.'</template>';
|
}
|
protected function renderEmptyState():string
|
{
|
ob_start();
|
?>
|
<div class="empty-state">
|
<h3><?=jvbDashIcon($this->icon)?>Nothing here<?=jvbDashIcon($this->icon)?></h3>
|
<p>It doesn't look like you have any <?=$this->plural ?> yet.</p>
|
<p><small><i>Add many by uploading images above.</i>, or click the "<?=jvbDashIcon('plus-square')?>" button to add one at a time.</small></p>
|
</div>
|
<?php
|
return ob_get_clean();
|
}
|
|
protected function renderGalleryPreviewTemplate():void
|
{
|
echo '<template class="galleryPreview">
|
<div class="preview-item" draggable="true">
|
<img \>
|
<div class="upload-status">
|
<div class="upload-progress"></div>
|
</div>
|
<button type="button" class="remove-preview" title="Remove Image">'.jvbIcon('trash').'</button>
|
<button type="button" class="move-image" title="Reorder Image">'.jvbIcon('dots-six-vertical').'</button>
|
</div>
|
</template>';
|
}
|
|
protected function renderItemSelect():string
|
{
|
ob_start();
|
?>
|
<div class="item-select">
|
<input type="checkbox" class="select-item">
|
<label class="select-item-label">
|
<span class="screen-reader-text">Select this <?= $this->singular ?></span>
|
</label>
|
</div>
|
<?php
|
return ob_get_clean();
|
}
|
protected function renderImage():string
|
{
|
return '<img loading="lazy" alt="">';
|
}
|
|
protected function renderItemActions():string
|
{
|
if (empty($this->itemActions)) {
|
return '';
|
}
|
ob_start();
|
?>
|
<div class="item-actions row btw abs">
|
<?php
|
foreach ($this->itemActions as $action) {
|
$config = $this->defaultItemActions[$action];
|
$title = (array_key_exists('title', $config)) ? ' title="'.$config['title'].' '.$this->singular.'"' : '';
|
$icon = (array_key_exists('icon', $config)) ? jvbIcon($config['icon']) : '';
|
?>
|
<button type ="button" data-action="<?=$action?>"<?=$title?>>
|
<?=$icon?>
|
<span class="screen-reader-text"><?=$title?></span>
|
</button>
|
<?php
|
}
|
?>
|
</div>
|
<?php
|
return ob_get_clean();
|
}
|
|
protected function renderGridTemplate():void
|
{
|
?>
|
<template class="gridView">
|
<div class="item <?= $this->dataType ?>">
|
<input type="checkbox" class="select-item" name="select-item">
|
<label title="Select this <?= $this->singular?>" class="select-item-label">
|
<?= $this->renderImage() ?>
|
</label>
|
<?= $this->renderItemActions(); ?>
|
</div>
|
</template>
|
<?php
|
}
|
|
protected function renderListTemplate():void
|
{
|
?>
|
<template class="listView">
|
<div class="item <?=esc_attr($this->dataType)?> row nowrap">
|
<?= $this->renderItemSelect()?>
|
<?=$this->renderImage() ?>
|
<div class="col start w-full">
|
<h3 data-field="post_title"></h3>
|
<p data-attr="date"></p>
|
<p data-field="price"></p>
|
<div data-field="post_excerpt"></div>
|
<?= $this->renderItemActions()?>
|
</div>
|
</div>
|
</template>
|
<?php
|
}
|
|
protected function renderTableTemplate():void
|
{
|
if ($this->isTimeline) {
|
$this->renderTimelineTableView();
|
return;
|
}
|
$permissions = '';
|
foreach ($this->caps as $cap) {
|
$permissions .= ' data-'.$cap;
|
}
|
?>
|
<template class="contentTable">
|
<form class="table"
|
data-save="content"
|
data-content="<?= esc_attr($this->dataType) ?>"
|
data-form-id="content-table-<?= esc_attr($this->dataType) ?>"
|
<?= $permissions?>>
|
|
<?= jvbFormStatus() ?>
|
<?= $this->renderTableActions() ?>
|
|
<table>
|
<thead>
|
<?= $this->renderTableHeader() ?>
|
</thead>
|
<tbody>
|
<!-- Rows will be inserted here -->
|
</tbody>
|
<tfoot>
|
<?= $this->renderTableHeader() ?>
|
</tfoot>
|
</table>
|
</form>
|
</template>
|
<?php
|
}
|
/**
|
* Render table row template
|
*/
|
protected function renderTableRowTemplate(): void {
|
if ($this->isTimeline) {
|
$this->renderTimelineTableGroup();
|
return;
|
}
|
?>
|
<template class="tableView">
|
<tr class="item">
|
<td class="select">
|
<?= $this->renderItemSelect() ?>
|
</td>
|
<?php
|
if (array_key_exists('status', $this->fields)){
|
?>
|
<td class="status" data-field="post_status">
|
<?= $this->renderStatusRadios() ?>
|
</td>
|
<?php
|
}
|
?>
|
<?php
|
$makeDetails = [
|
'group',
|
'repeater',
|
'checkbox',
|
'radio'
|
];
|
foreach ($this->fields as $name => $config):
|
if (array_key_exists('hidden', $config) || $name === 'status'){
|
continue;
|
}
|
$makeThisDetailed = (in_array($config['type'], $makeDetails));
|
?>
|
<td class="field show-<?= esc_attr($name) ?>" data-field="<?= esc_attr($name) ?>" data-field-type="<?=$config['type']?>"<?=(in_array($name, $this->stuck)) ? ' data-stuck':''?>>
|
<?php
|
if (in_array('edit', $this->caps)) {
|
echo $makeThisDetailed ? '<details><summary class="row btw">See Value</summary>' : '';
|
echo $this->meta->render('form', $name, $config);
|
echo $makeThisDetailed ? '</details>' : '';
|
} else {
|
echo '<p></p>';
|
}
|
?>
|
</td>
|
<?php endforeach;
|
if (!empty($this->itemActions)) {
|
?>
|
<td class="field show-actions">
|
<?= $this->renderItemActions(); ?>
|
</td>
|
<?php
|
}
|
?>
|
|
</tr>
|
</template>
|
<?php
|
}
|
|
protected function renderTimelineTableView():void
|
{
|
?>
|
<template class="contentTable">
|
<form class="table"
|
data-save="content"
|
data-content="<?= esc_attr($this->dataType) ?>"
|
data-form-id="content-table-<?= esc_attr($this->dataType) ?>">
|
<?= jvbFormStatus() ?>
|
<?= $this->renderTableActions() ?>
|
|
<table>
|
<thead>
|
<?= $this->renderTimelineTableHeader() ?>
|
</thead>
|
<!-- Rows are inserted as tbody groups -->
|
<tfoot>
|
<?= $this->renderTimelineTableHeader() ?>
|
</tfoot>
|
</table>
|
</form>
|
</template>
|
<?php
|
}
|
|
protected function renderTimelineTableGroup():void
|
{
|
$makeDetails = [
|
'group',
|
'repeater',
|
'checkbox',
|
'radio'
|
];
|
?>
|
<template class="tableView">
|
<tbody class="item">
|
<tr class="shared">
|
<td class="select">
|
<?= $this->renderItemSelect() ?>
|
</td>
|
<td class="show-post_status field" data-field="post_status">
|
<?= $this->renderStatusRadios() ?>
|
</td>
|
<?php
|
foreach ($this->fields as $name => $config) {
|
if(array_key_exists('hidden', $config) || $name === 'post_status') {
|
continue;
|
}
|
if (!in_array($name, $this->timelineSharedFields)) {
|
echo '<td></td>';
|
continue;
|
}
|
$makeThisDetailed = (in_array($config['type'], $makeDetails));
|
?>
|
<td class="field show-<?= esc_attr($name) ?>" data-field="<?= esc_attr($name) ?>" data-field-type="<?=$config['type']?>"<?=(in_array($name, $this->stuck)) ? ' data-stuck':''?>>
|
<?= $makeThisDetailed ? '<details><summary class="row btw">See Value</summary>' : '' ?>
|
<?php $this->meta->render('form', $name, $config); ?>
|
<?= $makeThisDetailed ? '</details>' : '' ?>
|
</td>
|
<?php
|
}
|
|
?>
|
</tr>
|
<tr class="timeline-point">
|
<td class="select">
|
<button class="drag-handle" title="Drag to reorder" aria-label="Drag to reorder this timeline point"><?= jvbDashIcon('dots-six') ?></button>
|
</td>
|
<td class="show-post_status field" data-field="post_status">
|
<?= $this->renderStatusRadios() ?>
|
</td>
|
<?php
|
foreach ($this->fields as $name => $config) {
|
if(array_key_exists('hidden', $config) || $name === 'post_status') {
|
continue;
|
}
|
if (!in_array($name, $this->timelineUniqueFields)) {
|
echo '<td></td>';
|
continue;
|
}
|
$makeThisDetailed = (in_array($config['type'], $makeDetails));
|
?>
|
<td class="field show-<?= esc_attr($name) ?>" data-field="<?= esc_attr($name) ?>" data-field-type="<?=$config['type']?>"<?=(in_array($name, $this->stuck)) ? ' data-stuck':''?>>
|
<?= $makeThisDetailed ? '<details><summary class="row btw">See Value</summary>' : '' ?>
|
<?php $this->meta->render('form', $name, $config); ?>
|
<?= $makeThisDetailed ? '</details>' : '' ?>
|
</td>
|
<?php
|
}
|
?>
|
</tr>
|
</tbody>
|
</template>
|
<?php
|
}
|
|
/**
|
* Render table header
|
*/
|
protected function renderTableHeader(): string {
|
ob_start();
|
|
?>
|
<tr>
|
<th scope="col" class="select-header">
|
<input type="checkbox" id="select-all" name="select-all">
|
<label for="select-all">All</label>
|
</th>
|
<?php if (array_key_exists('status', $this->fields)) { ?>
|
<th scope="col" class="status-header">Status</th>
|
<?php } ?>
|
<?php foreach ($this->fields as $name => $config):
|
if (array_key_exists('hidden', $config) || $name === 'status'){
|
continue;
|
}
|
?>
|
<th scope="col" class="show-<?= esc_attr($name) ?>"<?= (in_array($name, $this->stuck)) ? ' data-stuck':''?>>
|
<?= esc_html($config['label']) ?>
|
</th>
|
<?php endforeach;
|
if (!empty($this->itemActions)) {
|
?>
|
<th scope="col" class="show-actions">
|
Actions
|
</th>
|
<?php
|
}
|
?>
|
|
</tr>
|
<?php
|
return ob_get_clean();
|
}
|
|
protected function renderTimelineTableHeader(): string {
|
ob_start();
|
|
?>
|
<tr>
|
<th scope="col" class="select-header">
|
<input type="checkbox" id="select-all" name="select-all">
|
<label for="select-all">All</label>
|
</th>
|
<th scope="col" class="show-post_status">
|
Status
|
</th>
|
<?php foreach ($this->fields as $name => $config):
|
if (array_key_exists('hidden', $config) || $name === 'post_status'){
|
continue;
|
}
|
?>
|
<th scope="col" class="show-<?= esc_attr($name) ?>"<?= (in_array($name, $this->stuck)) ? ' data-stuck':''?>>
|
<?= esc_html($config['label']) ?>
|
</th>
|
<?php endforeach; ?>
|
</tr>
|
<?php
|
return ob_get_clean();
|
}
|
|
/**
|
* Render table action controls
|
*/
|
protected function renderTableActions(): string {
|
ob_start();
|
?>
|
<div class="table-actions row btw nowrap">
|
<?php if (count(array_intersect(['create', 'edit'], $this->caps)) > 0) { ?>
|
<?= jvbRenderToggleTextField(
|
'vertical',
|
'TAB NAV:',
|
'',
|
jvbDashIcon('caret-double-down'),
|
jvbDashIcon('caret-double-right')
|
) ?>
|
<button type="button" class="add-row" title="Add new row">
|
<?= jvbDashIcon('plus-square') ?>
|
<span>Add Row</span>
|
</button>
|
<?php } ?>
|
</div>
|
<?php
|
return ob_get_clean();
|
}
|
|
protected function renderStatusRadios(): string {
|
ob_start();
|
?>
|
<div class="radio-options status-options row">
|
<?php foreach ($this->statuses as $status):
|
if ($status === 'all') continue;
|
if (!in_array($status, $this->allowedStatuses)) continue;
|
$config = $this->allowedStatuses[$status];
|
?>
|
|
<input type="radio"
|
name="post_status"
|
id="status-<?= esc_attr($status) ?>"
|
value="<?= esc_attr($status) ?>">
|
<label for="status-<?= esc_attr($status) ?>">
|
<?= jvbDashIcon($config['icon']) ?>
|
<span class="screen-reader-text"><?= esc_html($config['label']) ?></span>
|
</label>
|
<?php endforeach; ?>
|
</div>
|
<?php
|
return ob_get_clean();
|
}
|
|
|
|
/**
|
* Get default statuses
|
*/
|
protected function getDefaultStatuses(): array {
|
return [
|
'all' => [
|
'icon' => 'infinity',
|
'label' => 'All',
|
],
|
'active' => [
|
'icon' => 'check-circle',
|
'label' => 'Active',
|
],
|
'inactive' => [
|
'icon' => 'x-circle',
|
'label' => 'Inactive',
|
],
|
];
|
}
|
|
/**
|
* Get field configuration
|
*/
|
public function getFields(): array {
|
return $this->fields;
|
}
|
|
/**
|
* Get configuration value
|
*/
|
public function get(string $key) {
|
return $this->$key ?? null;
|
}
|
|
/***************************************************
|
* MODALS
|
***************************************************/
|
protected function renderCreateModal():void
|
{
|
echo jvbNewModal(
|
'create',
|
'Creating <span class="count"></span> New '.$this->singular,
|
str_replace('edit-form"', 'create-form" data-noautosave', $this->editForm())
|
);
|
}
|
|
protected function editForm():string
|
{
|
ob_start();
|
?>
|
<form class="edit-form" data-save="content" data-form-id="edit-<?=$this->dataType?>" data-autosave<?= ($this->isTimeline) ? ' data-timeline' : ''?>>
|
<?= jvbFormStatus() ?>
|
<input type="hidden" name="form-id" value="<?=uniqid('new-')?>" />
|
<input type="hidden" name="content" value="<?=$this->dataType?>" />
|
<div class="fields">
|
<div class="field-group radio-options row">
|
<span>Status:</span>
|
<?php
|
$this->getApplicableStatuses('edit');
|
?>
|
</div>
|
<?php if (!$this->userCanPublish) { ?>
|
<p class="description">Your account needs to be verified before you can publish content.</p>
|
<?php }
|
|
if (!empty($this->sections)) {
|
$tabs = [];
|
foreach ($this->sections as $slug => $config) {
|
$section = [];
|
if (array_key_exists('icon', $config)) {
|
$section = [
|
'icon' => $config['icon']
|
];
|
}
|
$tabs[$slug] = array_merge([
|
'title' => $config['label'],
|
'content' => '',
|
'description' => $config['description']??'',
|
], $section);
|
$icon = jvbSectionIcon($slug);
|
if ($icon !== '') {
|
$tabs[$slug]['icon'] = $icon;
|
}
|
}
|
} else {
|
$tabs = false;
|
}
|
|
|
$fields = $this->fields;
|
if (!$this->isTimeline) {
|
$first = ['post_thumbnail', 'post_title', 'price'];
|
|
foreach ($first as $f) {
|
if (array_key_exists($f, $fields)) {
|
if ($tabs) {
|
$tabs['basic']['content'] .= $this->meta->render('form', $f, $fields[$f], false, true);
|
} else {
|
$this->meta->render('form', $f, $fields[$f]);
|
}
|
|
unset($fields[$f]);
|
}
|
}
|
}
|
|
if ($this->isTimeline) {
|
$temp = array_filter($fields, function ($field) {
|
return in_array($field, $this->timelineUniqueFields);
|
}, ARRAY_FILTER_USE_KEY);
|
$config = [
|
'type' => 'gallery',
|
'subtype' => 'timeline',
|
'data' => 'timeline',
|
'label' => 'Progression',
|
'fields' => $temp
|
];
|
$content = '';
|
foreach ($fields as $slug=> $field) {
|
if (in_array($slug, $this->timelineSharedFields)) {
|
if (in_array($field['type'], ['taxonomy', 'selector'])) {
|
$field = array_merge($field, $this->taxConfig($field['taxonomy'], $field['label']));
|
}
|
$content .= $this->form->render($slug, null, $field, false, true);
|
}
|
}
|
|
|
$content .= $this->meta->render('form', 'timeline', $config, false,true);
|
|
$tabs['progression']['content'] = $content;
|
$fields = $this->nonTimelineFields;
|
}
|
foreach ($fields as $n => $config) {
|
if ($tabs) {
|
$section = (array_key_exists('section', $config)) ? $config['section'] : 'basic';
|
$tabs[$section]['content'] .= $this->meta->render('form', $n, $config, false, true);
|
} else {
|
if (in_array($config['type'], ['taxonomy', 'selector'])) {
|
$config = array_merge($config, $this->taxConfig($config['taxonomy'], $config['label']));
|
}
|
$this->meta->render('form', $n, $config);
|
}
|
}
|
|
if ($tabs) {
|
jvbRenderTabs($tabs);
|
}
|
?>
|
</div>
|
</form>
|
<?php
|
return ob_get_clean();
|
}
|
|
protected function renderEditModal():void
|
{
|
echo jvbNewModal(
|
'edit',
|
'Edit your '.$this->singular,
|
$this->editForm()
|
);
|
}
|
|
protected function renderBulkEditModal():void
|
{
|
if (empty($this->bulkActions)) return;
|
ob_start();
|
?>
|
<form class="bulk-edit-form" data-save="content" data-form-id="bulk-edit-<?=$this->dataType?>">
|
<?= jvbFormStatus() ?>
|
<div class="selected"></div>
|
<p class="description">You can unselect items by clicking the image here.</p>
|
<p class="hint"><strong>IMPORTANT: </strong> Whatever changes you make here will be applied to all selected <?=$this->plural?>.</p>
|
<div class="fields">
|
<div class="field-group radio-options row">
|
<?php
|
$this->getApplicableStatuses('bulk-');
|
?>
|
</div>
|
<?php
|
if (!empty($this->taxonomies)) {
|
?>
|
<div class="taxonomies">
|
<?php
|
foreach ($this->taxonomies as $taxonomy => $config) {
|
$this->form->renderSelectorField(
|
'bulk-edit-'.$taxonomy,
|
'',
|
$this->taxConfig($taxonomy, $config['label']),
|
'taxonomy'
|
);
|
}
|
?>
|
</div>
|
<?php
|
}
|
$fields = $this->fields;
|
$fields = array_filter($fields, function ($field) {
|
return array_key_exists('bulkEdit', $field);
|
});
|
foreach ($fields as $fieldName => $config) {
|
$this->meta->render('form', $fieldName, $config);
|
}
|
?>
|
</div>
|
</form>
|
<template class="bulkItem">
|
<label>
|
<input type="checkbox">
|
<img>
|
</label>
|
</template>
|
<?php
|
$form = ob_get_clean();
|
echo jvbNewModal(
|
'bulkEdit',
|
'Bulk Edit <span class="selected"></span> '.$this->plural,
|
$form
|
);
|
}
|
|
protected function getApplicableStatuses(string $prefix) {
|
foreach ($this->statuses as $status) {
|
if ($status === 'all' || !array_key_exists($status, $this->allowedStatuses)) {
|
continue;
|
}
|
$config = $this->allowedStatuses[$status];
|
if (in_array($status, ['future', 'past'])) {
|
if ($status === 'future') {
|
$status = 'publish';
|
$config = [
|
'icon' => 'eye',
|
'label' => 'Live',
|
];
|
} else {
|
continue;
|
}
|
}
|
$disabled = ($status === 'publish' && !$this->userCanPublish) ? ' disabled' : '';
|
?>
|
<input type ="radio"
|
name="post_status"
|
class="btn"
|
value="<?= esc_attr($status)?>"
|
id="<?=$prefix?>set-<?= esc_attr($status) ?>"
|
<?= $disabled?>>
|
<label for="<?=$prefix?>set-<?=esc_attr($status)?>" title="<?=esc_html($config['label'])?>">
|
<?= jvbDashIcon($config['icon']) ?>
|
</label>
|
<?php
|
}
|
}
|
}
|