Constants
4 days ago
Accordion.php
4 days ago
Alert.php
4 days ago
AttachmentCard.php
4 days ago
Avatar.php
4 days ago
Badge.php
4 days ago
BaseComponent.php
4 days ago
Button.php
4 days ago
ConfirmationModal.php
4 days ago
CourseFilter.php
4 days ago
DateFilter.php
4 days ago
DropdownFilter.php
4 days ago
EmptyState.php
4 days ago
FileUploader.php
4 days ago
InputField.php
4 days ago
Modal.php
4 days ago
Nav.php
4 days ago
Pagination.php
4 days ago
Popover.php
4 days ago
PreviewTrigger.php
4 days ago
Progress.php
4 days ago
SearchFilter.php
4 days ago
Sorting.php
4 days ago
StarRating.php
4 days ago
StarRatingInput.php
4 days ago
StatusSelect.php
4 days ago
SvgIcon.php
4 days ago
Table.php
4 days ago
Tabs.php
4 days ago
Tooltip.php
4 days ago
WPEditor.php
4 days ago
DateFilter.php
423 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Date Filter Component. |
| 4 | * |
| 5 | * @package Tutor\Components |
| 6 | * @author Themeum |
| 7 | * @link https://themeum.com |
| 8 | * @since 4.0.0 |
| 9 | */ |
| 10 | |
| 11 | namespace Tutor\Components; |
| 12 | |
| 13 | use TUTOR\Icon; |
| 14 | use TUTOR\Input; |
| 15 | use Tutor\Components\Constants\Size; |
| 16 | use Tutor\Components\Popover; |
| 17 | |
| 18 | defined( 'ABSPATH' ) || exit; |
| 19 | |
| 20 | /** |
| 21 | * DateFilter Component Class. |
| 22 | * |
| 23 | * Example usage: |
| 24 | * |
| 25 | * ```php |
| 26 | * DateFilter::make() |
| 27 | * ->type( DateFilter::TYPE_RANGE ) |
| 28 | * ->placement( DateFilter::PLACEMENT_BOTTOM_START ) |
| 29 | * ->render(); |
| 30 | * ``` |
| 31 | * |
| 32 | * ```php |
| 33 | * DateFilter::make() |
| 34 | * ->type( DateFilter::TYPE_SINGLE ) |
| 35 | * ->placement( DateFilter::PLACEMENT_BOTTOM_END ) |
| 36 | * ->render(); |
| 37 | * ``` |
| 38 | * |
| 39 | * @since 4.0.0 |
| 40 | */ |
| 41 | class DateFilter extends BaseComponent { |
| 42 | |
| 43 | /** |
| 44 | * Date Filter Type Single |
| 45 | */ |
| 46 | const TYPE_SINGLE = 'single'; |
| 47 | |
| 48 | /** |
| 49 | * Date Filter Type Range |
| 50 | */ |
| 51 | const TYPE_RANGE = 'range'; |
| 52 | |
| 53 | /** |
| 54 | * Placement Bottom Start |
| 55 | */ |
| 56 | const PLACEMENT_BOTTOM_START = 'bottom-start'; |
| 57 | |
| 58 | /** |
| 59 | * Placement Bottom End |
| 60 | */ |
| 61 | const PLACEMENT_BOTTOM_END = 'bottom-end'; |
| 62 | |
| 63 | /** |
| 64 | * Component Type |
| 65 | * |
| 66 | * @var string |
| 67 | */ |
| 68 | protected $type = self::TYPE_SINGLE; |
| 69 | |
| 70 | /** |
| 71 | * Button Label |
| 72 | * |
| 73 | * @var string |
| 74 | */ |
| 75 | protected $label = ''; |
| 76 | |
| 77 | /** |
| 78 | * Popover placement |
| 79 | * |
| 80 | * @var string |
| 81 | */ |
| 82 | protected $placement = self::PLACEMENT_BOTTOM_START; |
| 83 | |
| 84 | /** |
| 85 | * Button size. |
| 86 | * |
| 87 | * @var string |
| 88 | */ |
| 89 | protected $size = Size::SMALL; |
| 90 | |
| 91 | /** |
| 92 | * Icon size. |
| 93 | * |
| 94 | * @var integer |
| 95 | */ |
| 96 | protected $icon_size = Size::SIZE_16; |
| 97 | |
| 98 | /** |
| 99 | * Whether to display the label text. |
| 100 | * |
| 101 | * @since 4.0.0 |
| 102 | * |
| 103 | * @var bool |
| 104 | */ |
| 105 | protected $show_label = true; |
| 106 | |
| 107 | /** |
| 108 | * Whether to hide the initial label when no date range is selected. |
| 109 | * |
| 110 | * @since 4.0.0 |
| 111 | * |
| 112 | * @var bool |
| 113 | */ |
| 114 | protected $hide_initial_label = false; |
| 115 | |
| 116 | /** |
| 117 | * Query params to clear when a date filter is applied or cleared (e.g. 'current_page'). |
| 118 | * |
| 119 | * @since 4.0.0 |
| 120 | * |
| 121 | * @var array |
| 122 | */ |
| 123 | protected $clear_params = array(); |
| 124 | |
| 125 | /** |
| 126 | * Set filter type. |
| 127 | * |
| 128 | * @param string $type Filter type (single|range). |
| 129 | * |
| 130 | * @return self |
| 131 | */ |
| 132 | public function type( string $type ): self { |
| 133 | $this->type = $type; |
| 134 | return $this; |
| 135 | } |
| 136 | |
| 137 | /** |
| 138 | * Set Button Size. |
| 139 | * |
| 140 | * @since 4.0.0 |
| 141 | * |
| 142 | * @param string $size the size type (x-small|small|medium|large). |
| 143 | * |
| 144 | * @return self |
| 145 | */ |
| 146 | public function trigger_size( string $size ): self { |
| 147 | $allowed_sizes = array( Size::X_SMALL, Size::SMALL, Size::MEDIUM, Size::LARGE ); |
| 148 | if ( in_array( $size, $allowed_sizes, true ) ) { |
| 149 | $this->size = $size; |
| 150 | } |
| 151 | return $this; |
| 152 | } |
| 153 | |
| 154 | /** |
| 155 | * Set the button icon size. |
| 156 | * |
| 157 | * @since 4.0.0 |
| 158 | * |
| 159 | * @param integer $size the icon size. |
| 160 | * |
| 161 | * @return self |
| 162 | */ |
| 163 | public function icon_size( int $size ): self { |
| 164 | $this->icon_size = $size; |
| 165 | return $this; |
| 166 | } |
| 167 | |
| 168 | /** |
| 169 | * Set button label. |
| 170 | * |
| 171 | * @param string $label Button label. |
| 172 | * |
| 173 | * @return self |
| 174 | */ |
| 175 | public function label( string $label ): self { |
| 176 | $this->label = $label; |
| 177 | return $this; |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Set popover placement. |
| 182 | * |
| 183 | * @param string $placement Popover placement. |
| 184 | * |
| 185 | * @return self |
| 186 | */ |
| 187 | public function placement( string $placement ): self { |
| 188 | $this->placement = $placement; |
| 189 | return $this; |
| 190 | } |
| 191 | |
| 192 | /** |
| 193 | * Enable or disable the display of the label text. |
| 194 | * |
| 195 | * @since 4.0.0 |
| 196 | * |
| 197 | * @param bool $show_label True to show the label, false to hide it. |
| 198 | * |
| 199 | * @return $this |
| 200 | */ |
| 201 | public function show_label( bool $show_label ) { |
| 202 | $this->show_label = $show_label; |
| 203 | return $this; |
| 204 | } |
| 205 | |
| 206 | /** |
| 207 | * Hide or show the initial label text before a date range is selected. |
| 208 | * |
| 209 | * @since 4.0.0 |
| 210 | * |
| 211 | * @return self |
| 212 | */ |
| 213 | public function hide_initial_label(): self { |
| 214 | $this->hide_initial_label = true; |
| 215 | return $this; |
| 216 | } |
| 217 | |
| 218 | /** |
| 219 | * Set query params to clear when a date filter is applied or cleared. |
| 220 | * |
| 221 | * Useful for resetting pagination (e.g. 'current_page') or other |
| 222 | * context-specific params whenever the date selection changes. |
| 223 | * |
| 224 | * @since 4.0.0 |
| 225 | * |
| 226 | * @param array $params List of query param keys to remove on apply/clear. |
| 227 | * |
| 228 | * @return self |
| 229 | */ |
| 230 | public function clear_params( array $params ): self { |
| 231 | $this->clear_params = $params; |
| 232 | return $this; |
| 233 | } |
| 234 | |
| 235 | /** |
| 236 | * Render the component. |
| 237 | * |
| 238 | * @return string |
| 239 | */ |
| 240 | public function get(): string { |
| 241 | $is_range = self::TYPE_RANGE === $this->type; |
| 242 | |
| 243 | if ( empty( $this->label ) ) { |
| 244 | $this->label = $this->calculate_label(); |
| 245 | } |
| 246 | |
| 247 | // Default settings based on type. |
| 248 | $calendar_options = array( |
| 249 | 'type' => 'default', |
| 250 | 'clearParams' => $this->clear_params, |
| 251 | ); |
| 252 | |
| 253 | $button_classes = 'tutor-btn tutor-btn-outline'; |
| 254 | |
| 255 | if ( $this->size ) { |
| 256 | $button_classes .= ' tutor-btn-' . $this->size; |
| 257 | } |
| 258 | |
| 259 | $popover_classes = 'tutor-popover'; |
| 260 | $icon = Icon::CALENDAR_2; |
| 261 | |
| 262 | if ( $is_range ) { |
| 263 | $calendar_options = array( |
| 264 | 'type' => 'multiple', |
| 265 | 'selectionDatesMode' => 'multiple-ranged', |
| 266 | 'clearParams' => $this->clear_params, |
| 267 | ); |
| 268 | $popover_classes .= ' tutor-range-calendar-popover'; |
| 269 | } |
| 270 | |
| 271 | if ( empty( $this->label ) ) { |
| 272 | $button_classes .= ' tutor-btn-icon'; |
| 273 | } else { |
| 274 | $button_classes .= ' tutor-gap-2'; |
| 275 | } |
| 276 | |
| 277 | $options_json = wp_json_encode( $calendar_options ); |
| 278 | |
| 279 | $origin = Popover::TRANSFORM_ORIGIN_MAP[ $this->placement ] ?? 'center.top'; |
| 280 | |
| 281 | ob_start(); |
| 282 | ?> |
| 283 | <div x-data="tutorPopover({ placement: '<?php echo esc_attr( $this->placement ); ?>' })" <?php echo $this->get_attributes_string(); //phpcs:ignore -- Sanitization is performed inside get_attributes_string. ?>> |
| 284 | |
| 285 | <button |
| 286 | type="button" |
| 287 | x-ref="trigger" |
| 288 | @click="toggle()" |
| 289 | class="<?php echo esc_attr( $button_classes ); ?>" |
| 290 | <?php if ( empty( $this->label ) ) : ?> |
| 291 | aria-label="<?php echo $is_range ? esc_attr__( 'Filter by date range', 'tutor' ) : esc_attr__( 'Filter by date', 'tutor' ); ?>" |
| 292 | <?php endif; ?> |
| 293 | > |
| 294 | <?php SvgIcon::make()->name( $icon )->size( $this->icon_size )->render(); ?> |
| 295 | <?php if ( ! empty( $this->label ) ) : ?> |
| 296 | <span><?php echo esc_html( $this->label ); ?></span> |
| 297 | <?php endif; ?> |
| 298 | |
| 299 | <?php if ( $this->has_selection() ) : ?> |
| 300 | <span @click.stop="$dispatch('tutor-calendar:clear')" class="tutor-cursor-pointer tutor-icon-secondary tutor-flex tutor-align-center"> |
| 301 | <?php SvgIcon::make()->name( Icon::CROSS_2 )->render(); ?> |
| 302 | </span> |
| 303 | <?php endif; ?> |
| 304 | </button> |
| 305 | |
| 306 | <div |
| 307 | x-ref="content" |
| 308 | x-show="open" |
| 309 | x-cloak |
| 310 | x-transition.origin.<?php echo esc_attr( $origin ); ?> |
| 311 | @click.outside="handleClickOutside()" |
| 312 | class="<?php echo esc_attr( $popover_classes ); ?>" |
| 313 | > |
| 314 | <div x-data="tutorCalendar({ options: <?php echo esc_attr( $options_json ); ?>, hidePopover: () => hide() })"></div> |
| 315 | </div> |
| 316 | </div> |
| 317 | <?php |
| 318 | return ob_get_clean(); |
| 319 | } |
| 320 | |
| 321 | /** |
| 322 | * Check if filter has active selection. |
| 323 | * |
| 324 | * @return bool |
| 325 | */ |
| 326 | protected function has_selection(): bool { |
| 327 | if ( self::TYPE_SINGLE === $this->type ) { |
| 328 | return ! empty( Input::get( 'date' ) ); |
| 329 | } |
| 330 | return ! empty( Input::get( 'start_date' ) ) && ! empty( Input::get( 'end_date' ) ); |
| 331 | } |
| 332 | |
| 333 | /** |
| 334 | * Calculate dynamic label based on URL parameters. |
| 335 | * |
| 336 | * @return string |
| 337 | */ |
| 338 | protected function calculate_label(): string { |
| 339 | if ( self::TYPE_SINGLE === $this->type ) { |
| 340 | return Input::get( 'date', '' ); |
| 341 | } |
| 342 | |
| 343 | if ( ! $this->show_label ) { |
| 344 | return ''; |
| 345 | } |
| 346 | |
| 347 | $start_date = Input::get( 'start_date' ); |
| 348 | $end_date = Input::get( ( 'end_date' ) ); |
| 349 | |
| 350 | if ( ! $start_date || ! $end_date ) { |
| 351 | return $this->hide_initial_label ? '' : __( 'All Time', 'tutor' ); |
| 352 | } |
| 353 | |
| 354 | $presets = array( |
| 355 | 'today' => __( 'Today', 'tutor' ), |
| 356 | 'yesterday' => __( 'Yesterday', 'tutor' ), |
| 357 | 'last-7' => __( 'Last 7 Days', 'tutor' ), |
| 358 | 'last-14' => __( 'Last 14 Days', 'tutor' ), |
| 359 | 'last-30' => __( 'Last 30 Days', 'tutor' ), |
| 360 | 'this-month' => __( 'This Month', 'tutor' ), |
| 361 | 'last-month' => __( 'Last Month', 'tutor' ), |
| 362 | 'last-year' => __( 'Last Year', 'tutor' ), |
| 363 | ); |
| 364 | |
| 365 | $current_time = time(); |
| 366 | |
| 367 | foreach ( $presets as $key => $label ) { |
| 368 | $range = $this->get_preset_dates( $key, $current_time ); |
| 369 | if ( $range && $range[0] === $start_date && $range[1] === $end_date ) { |
| 370 | return $label; |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | $start_format = date_i18n( 'M j', strtotime( $start_date ) ); |
| 375 | $end_format = date_i18n( 'M j', strtotime( $end_date ) ); |
| 376 | |
| 377 | return sprintf( '%s - %s', $start_format, $end_format ); |
| 378 | } |
| 379 | |
| 380 | /** |
| 381 | * Get preset dates. |
| 382 | * |
| 383 | * @param string $preset Preset key. |
| 384 | * @param int $current_time Current timestamp. |
| 385 | * |
| 386 | * @return array|null |
| 387 | */ |
| 388 | protected function get_preset_dates( $preset, $current_time ) { |
| 389 | $today = gmdate( 'Y-m-d', $current_time ); |
| 390 | |
| 391 | switch ( $preset ) { |
| 392 | case 'today': |
| 393 | return array( $today, $today ); |
| 394 | case 'yesterday': |
| 395 | $d = gmdate( 'Y-m-d', strtotime( '-1 day', $current_time ) ); |
| 396 | return array( $d, $d ); |
| 397 | case 'last-7': |
| 398 | $start = gmdate( 'Y-m-d', strtotime( '-6 days', $current_time ) ); |
| 399 | return array( $start, $today ); |
| 400 | case 'last-14': |
| 401 | $start = gmdate( 'Y-m-d', strtotime( '-13 days', $current_time ) ); |
| 402 | return array( $start, $today ); |
| 403 | case 'last-30': |
| 404 | $start = gmdate( 'Y-m-d', strtotime( '-29 days', $current_time ) ); |
| 405 | return array( $start, $today ); |
| 406 | case 'this-month': |
| 407 | $start = gmdate( 'Y-m-01', $current_time ); |
| 408 | $end = gmdate( 'Y-m-t', $current_time ); |
| 409 | return array( $start, $end ); |
| 410 | case 'last-month': |
| 411 | $first_day_last_month = strtotime( 'first day of previous month', $current_time ); |
| 412 | $start = gmdate( 'Y-m-d', $first_day_last_month ); |
| 413 | $end = gmdate( 'Y-m-t', $first_day_last_month ); |
| 414 | return array( $start, $end ); |
| 415 | case 'last-year': |
| 416 | $start = gmdate( 'Y-01-01', strtotime( '-1 year', $current_time ) ); |
| 417 | $end = gmdate( 'Y-12-31', strtotime( '-1 year', $current_time ) ); |
| 418 | return array( $start, $end ); |
| 419 | } |
| 420 | return null; |
| 421 | } |
| 422 | } |
| 423 |