文件操作 - DropdownFilter.php
返回文件管理
返回主菜单
删除本文件
文件: /home/www/public/wp-content/plugins/tutor/components/DropdownFilter.php
编辑文件内容
<?php /** * DropdownFilter Component Class. * * @package Tutor\Components * @author Themeum * @link https://themeum.com * @since 4.0.0 */ namespace Tutor\Components; use Tutor\Components\Constants\Size; use Tutor\Components\Constants\Variant; use Tutor\Components\Constants\Positions; use Tutor\Components\Constants\Color; use Tutor\Components\Popover; use TUTOR\Icon; use TUTOR\Input; defined( 'ABSPATH' ) || exit; /** * Class DropdownFilter * * Example Usage: * ```php * // Basic dropdown bound to URL query param (link-based navigation) * DropdownFilter::make() * ->options( $options ) * ->query_param( 'status' ) * ->render(); * * // With custom variant, size and search box * DropdownFilter::make() * ->options( $options ) * ->query_param( 'status' ) * ->variant( Variant::PRIMARY ) * ->size( Size::SMALL ) * ->search( true ) * ->placeholder( __( 'Search status...', 'tutor' ) ) * ->render(); * * // JS callback on change (Alpine.js method name) instead of link navigation * DropdownFilter::make() * ->options( $options ) * ->on_change( 'changeStatus' ) * ->bind_active_value( 'currentStatus' ) * ->render(); * * // Course filter convenience use-case * DropdownFilter::make() * ->options( CourseFilter::get_course_filter_options() ) * ->count( $total_items ) * ->query_param( 'course-id' ) * ->variant( Variant::PRIMARY_SOFT ) * ->size( Size::X_SMALL ) * ->popover_size( Size::MEDIUM ) * ->position( Positions::BOTTOM_END ) * ->placeholder( __( 'Search Course', 'tutor' ) ) * ->search( true ) * ->render(); * * // Cumulative filtering — preserve existing query params via base_url * DropdownFilter::make() * ->options( $options ) * ->query_param( 'category' ) * ->base_url( remove_query_arg( 'current_page' ) ) * ->render(); * * // Outline variant with custom button CSS * DropdownFilter::make() * ->options( $options ) * ->variant( Variant::OUTLINE ) * ->button_class( 'tutor-btn tutor-btn-outline tutor-btn-small my-custom-btn' ) * ->render(); * * // Popover positioned bottom-end with large width * DropdownFilter::make() * ->options( $options ) * ->query_param( 'type' ) * ->position( Positions::BOTTOM_END ) * ->popover_size( Size::LARGE ) * ->render(); * * // Custom icon size for the chevron * DropdownFilter::make() * ->options( $options ) * ->variant( Variant::PRIMARY ) * ->icon_size( 14 ) * ->render(); * ``` * * @since 4.0.0 */ class DropdownFilter extends BaseComponent { /** * Options for the dropdown * * @var array */ protected $options = array(); /** * Whether to show search box * * @var bool */ protected $show_search = false; /** * Trigger button variant * * @var string */ protected $variant = Variant::LINK; /** * Trigger button size * * @var string */ protected $size = Size::MEDIUM; /** * Custom button CSS classes * * @var string */ protected $button_class = ''; /** * Search placeholder * * @var string */ protected $placeholder = ''; /** * Query parameter name to manage URL and active state * * @var string */ protected $query_param = ''; /** * Count to display * * @var int|null */ protected $count = null; /** * Popover size * * @var string */ protected $popover_size = Size::SMALL; /** * On change callback function name * * @var string */ protected $on_change = ''; /** * Alpine.js variable name for active state * * @var string */ protected $active_value = ''; /** * Base URL for building filter links (preserves other query params when set). * * @var string */ protected $base_url = ''; /** * Icon size * * @var int|null */ protected $icon_size = null; /** * Popover placement position * * @var string */ protected $position = Positions::BOTTOM_START; /** * Set options list * * @param array $options list of options. Each item: array( 'label' => string, 'url' => string, 'count' => int|null, 'active' => bool, 'value' => mixed ). * * @return self */ public function options( array $options ): self { $this->options = $options; return $this; } /** * Set query parameter name * * @param string $key query parameter name. * * @return self */ public function query_param( string $key ): self { $this->query_param = $key; return $this; } /** * Set search visibility * * @param bool $show whether to show search. * * @return self */ public function search( bool $show ): self { $this->show_search = $show; return $this; } /** * Set button variant * * @param string $variant button variant. * * @return self */ public function variant( string $variant ): self { $this->variant = $variant; return $this; } /** * Set button size * * @param string $size button size. * * @return self */ public function size( string $size ): self { $this->size = $size; return $this; } /** * Set button CSS classes * * @param string $classes CSS classes for the button. * * @return self */ public function button_class( string $classes ): self { $this->button_class = $classes; return $this; } /** * Set search placeholder * * @param string $placeholder search placeholder. * * @return self */ public function placeholder( string $placeholder ): self { $this->placeholder = $placeholder; return $this; } /** * Set popover width * * @param string $size popover width size (Size::SMALL, Size::MEDIUM, Size::LARGE). * * @return self */ public function popover_size( string $size ): self { $this->popover_size = $size; return $this; } /** * Set icon size * * @param int $size icon size. * * @return self */ public function icon_size( int $size ): self { $this->icon_size = $size; return $this; } /** * Set popover placement position. * * @param string $position placement position (Positions::TOP, Positions::BOTTOM, etc). * * @return self */ public function position( string $position ): self { $this->position = $position; return $this; } /** * Get popover width in pixels based on size constant * * @param string $size Size constant. * * @return string Width in pixels. */ protected function get_popover_width( string $size ): string { switch ( $size ) { case Size::MEDIUM: return '275px'; case Size::LARGE: return '350px'; case Size::SMALL: default: return '172px'; } } /** * Set count * * @param int $count count to display. * * @return self */ public function count( int $count ): self { $this->count = $count; return $this; } /** * Set JS callback on change * * @param string $method_name JS method name. * * @return self */ public function on_change( string $method_name ): self { $this->on_change = $method_name; return $this; } /** * Set Alpine.js active state binding * * @param string $active_value Alpine variable name. * * @return self */ public function bind_active_value( string $active_value ): self { $this->active_value = $active_value; return $this; } /** * Set base URL for filter links so other query params are preserved (cumulative filtering). * * @param string $url Full current page URL including query string. * * @return self */ public function base_url( string $url ): self { $this->base_url = $url; return $this; } /** * Get the base URL used for auto-generated filter links. * * Removes transient pagination state so changing filters resets pagination. * * @return string */ protected function get_filter_base_url(): string { if ( ! empty( $this->base_url ) ) { return remove_query_arg( 'current_page', $this->base_url ); } return remove_query_arg( 'current_page' ); } /** * Get component content * * @return string */ public function get(): string { $options = $this->options; if ( empty( $options ) ) { return ''; } // Automatically manage active state and URL if query_param is set. if ( ! empty( $this->query_param ) ) { $current_val = Input::get( $this->query_param, '' ); $base = $this->get_filter_base_url(); foreach ( $options as &$option ) { $val = isset( $option['value'] ) ? $option['value'] : ''; if ( ! isset( $option['active'] ) ) { $option['active'] = (string) $val === (string) $current_val; } if ( ! isset( $option['url'] ) ) { if ( ! empty( $base ) ) { $option['url'] = ! empty( $val ) ? add_query_arg( $this->query_param, $val, $base ) : remove_query_arg( $this->query_param, $base ); } else { $option['url'] = ! empty( $val ) ? add_query_arg( $this->query_param, $val ) : remove_query_arg( $this->query_param ); } } } unset( $option ); } $active_option = null; foreach ( $options as $option ) { if ( ! empty( $option['active'] ) ) { $active_option = $option; break; } } // Fallback to first option if none is active. if ( ! $active_option && ! empty( $options ) ) { $active_option = $options[0]; } $label = $active_option['label'] ?? ''; $count = $active_option['count'] ?? null; // If count is set via count() method, use it for the active option. if ( null !== $this->count ) { $count = $this->count; } $btn_class = $this->button_class; if ( empty( $btn_class ) ) { $size_class = 'tutor-btn-medium'; if ( Size::SMALL === $this->size ) { $size_class = 'tutor-btn-small'; } elseif ( Size::X_SMALL === $this->size ) { $size_class = 'tutor-btn-x-small'; } elseif ( Size::LARGE === $this->size ) { $size_class = 'tutor-btn-large'; } if ( Variant::PRIMARY === $this->variant ) { $btn_class = 'tutor-btn tutor-btn-primary ' . $size_class; } elseif ( Variant::PRIMARY_SOFT === $this->variant ) { $btn_class = 'tutor-btn tutor-btn-primary-soft ' . $size_class; } elseif ( Variant::OUTLINE === $this->variant ) { $btn_class = 'tutor-btn tutor-btn-outline ' . $size_class; } else { $btn_class = 'tutor-btn tutor-btn-link'; } } $origin = Popover::TRANSFORM_ORIGIN_MAP[ $this->position ] ?? 'left.top'; ob_start(); ?> <div class="tutor-dropdown-filter" x-data="{ ...tutorPopover({ placement: '<?php echo esc_js( $this->position ); ?>', offset: 4, }), <?php if ( $this->active_value ) : ?> labels: <?php echo esc_attr( wp_json_encode( array_column( $options, 'label', 'value' ) ) ); ?>, counts: <?php echo esc_attr( wp_json_encode( array_column( $options, 'count', 'value' ) ) ); ?> <?php endif; ?> }" > <button type="button" x-ref="trigger" @click="toggle()" class="<?php echo esc_attr( $btn_class ); ?>" > <span class="tutor-truncate <?php echo Variant::LINK === $this->variant ? esc_attr( 'tutor-text-secondary' ) : ''; ?>" style="max-width: 150px;" <?php if ( $this->active_value ) : ?> x-text="labels[<?php echo esc_attr( $this->active_value ); ?>] || '<?php echo esc_js( $label ); ?>'" <?php endif; ?> > <?php echo esc_html( $label ); ?> </span> <?php if ( null !== $count || $this->active_value ) : ?> <span class="tutor-font-medium <?php echo Variant::LINK === $this->variant ? esc_attr( 'tutor-text-primary' ) : ''; ?>" <?php if ( $this->active_value ) : ?> x-text="'(' + (counts[<?php echo esc_attr( $this->active_value ); ?>] ?? '0') + ')'" x-show="counts[<?php echo esc_attr( $this->active_value ); ?>] !== null" <?php endif; ?> > <?php echo null !== $count ? '(' . esc_html( $count ) . ')' : ''; ?> </span> <?php endif; ?> <?php $icon_size = $this->icon_size; if ( null === $icon_size ) { $icon_size = in_array( $this->variant, array( Variant::PRIMARY, Variant::PRIMARY_SOFT ), true ) ? 16 : 20; } SvgIcon::make()->name( Icon::CHEVRON_DOWN_2 )->size( $icon_size )->color( Variant::PRIMARY_SOFT === $this->variant ? Color::BRAND : Color::SECONDARY )->render(); ?> </button> <div x-ref="content" x-show="open" x-cloak x-transition.<?php echo esc_attr( $origin ); ?> @click.outside="handleClickOutside()" class="tutor-popover" style="width: <?php echo esc_attr( $this->get_popover_width( $this->popover_size ) ); ?>; max-width: unset;" > <div class="tutor-popover-menu" x-data="{ search: '', get hasResults() { return this.search === '' || [...$el.querySelectorAll('.tutor-popover-menu-item')].some(el => el.style.display !== 'none' && !el.hasAttribute('hidden')); } }"> <?php if ( $this->show_search ) : ?> <div class="tutor-input-field tutor-px-5 tutor-pt-2 tutor-pb-4"> <div class="tutor-input-wrapper"> <div class="tutor-input-content tutor-input-content-left"> <?php SvgIcon::make()->name( Icon::SEARCH_2 )->size( 16 )->color( Color::IDLE )->render(); ?> </div> <input type="text" class="tutor-input tutor-input-sm tutor-input-content-left" placeholder="<?php echo esc_attr( $this->placeholder ? $this->placeholder : __( 'Search...', 'tutor' ) ); ?>" x-model="search" @click.stop > </div> </div> <?php endif; ?> <div class="tutor-overflow-y-auto" style="max-height: 200px;"> <?php foreach ( $options as $option ) : ?> <?php $opt_label = $option['label'] ?? ''; $opt_url = $option['url'] ?? '#'; $opt_count = $option['count'] ?? null; $is_active = ! empty( $option['active'] ); $val = isset( $option['value'] ) ? $option['value'] : ''; ?> <?php if ( $this->on_change ) : ?> <button type="button" @click="<?php echo esc_js( $this->on_change ); ?>('<?php echo esc_attr( $val ); ?>'); hide()" class="tutor-popover-menu-item <?php echo esc_attr( ! $this->active_value && $is_active ? ' tutor-active' : '' ); ?>" <?php if ( $this->active_value ) : ?> :class="{ 'tutor-active': (<?php echo esc_attr( $this->active_value ); ?> || '') === '<?php echo esc_attr( $val ); ?>' }" <?php endif; ?> <?php if ( $this->show_search ) : ?> x-show="search === '' || '<?php echo esc_js( $opt_label ); ?>'.toLowerCase().includes(search.toLowerCase())" <?php endif; ?> > <span class="tutor-flex tutor-gap-2 tutor-items-center tutor-w-full"> <span class="tutor-truncate"> <?php echo esc_html( $opt_label ); ?> </span> <?php if ( null !== $opt_count ) : ?> <span> (<?php echo esc_html( $opt_count ); ?>) </span> <?php endif; ?> </span> </button> <?php else : ?> <a href="<?php echo esc_url( $opt_url ); ?>" class="tutor-popover-menu-item <?php echo $is_active ? 'tutor-active' : ''; ?>" <?php if ( $this->show_search ) : ?> x-show="search === '' || '<?php echo esc_js( $opt_label ); ?>'.toLowerCase().includes(search.toLowerCase())" <?php endif; ?> > <span class="tutor-flex tutor-gap-2 tutor-items-center tutor-w-full"> <span class="tutor-truncate"> <?php echo esc_html( $opt_label ); ?> </span> <?php if ( null !== $opt_count ) : ?> <span> (<?php echo esc_html( $opt_count ); ?>) </span> <?php endif; ?> </span> </a> <?php endif; ?> <?php endforeach; ?> <?php if ( $this->show_search ) : ?> <div x-show="search !== '' && !hasResults" class="tutor-p-5 tutor-text-center tutor-text-secondary tutor-p2" > <?php esc_html_e( 'No results found', 'tutor' ); ?> </div> <?php endif; ?> </div> </div> </div> </div> <?php $this->component_string = ob_get_clean(); return $this->component_string; } }
修改文件时间
将文件时间修改为当前时间的前一年
删除文件