Edit file File name : Datepicker.php Content :<?php namespace WPForms\Admin\Helpers; use DateTimeImmutable; /** * Timespan and popover date-picker helper methods. * * @since 1.8.2 */ class Datepicker { /** * Number of timespan days by default. * "Last 30 Days", by default. * * @since 1.8.2 */ const TIMESPAN_DAYS = '30'; /** * Timespan (date range) delimiter. * * @since 1.8.2 */ const TIMESPAN_DELIMITER = ' - '; /** * Default date format. * * @since 1.8.2 */ const DATE_FORMAT = 'Y-m-d'; /** * Default date-time format. * * @since 1.8.2 */ const DATETIME_FORMAT = 'Y-m-d H:i:s'; /** * Sets the timespan (or date range) selected. * * Includes: * 1. Start date object in WP timezone. * 2. End date object in WP timezone. * 3. Number of "Last X days", if applicable, otherwise returns "custom". * 4. Label associated with the selected date filter choice. @see "get_date_filter_choices". * * @since 1.8.2 * * @return array */ public static function process_timespan() { $dates = (string) filter_input( INPUT_GET, 'date', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); // Return default timespan if dates are empty. if ( empty( $dates ) ) { return self::get_timespan_dates( self::TIMESPAN_DAYS ); } $dates = self::maybe_validate_string_timespan( $dates ); list( $start_date, $end_date ) = explode( self::TIMESPAN_DELIMITER, $dates ); // Return default timespan if start date is more recent than end date. if ( strtotime( $start_date ) > strtotime( $end_date ) ) { return self::get_timespan_dates( self::TIMESPAN_DAYS ); } $timezone = wp_timezone(); // Retrieve the timezone string for the site. $start_date = date_create_immutable( $start_date, $timezone ); $end_date = date_create_immutable( $end_date, $timezone ); // Return default timespan if date creation fails. if ( ! $start_date || ! $end_date ) { return self::get_timespan_dates( self::TIMESPAN_DAYS ); } // Set time to 0:0:0 for start date and 23:59:59 for end date. $start_date = $start_date->setTime( 0, 0, 0 ); $end_date = $end_date->setTime( 23, 59, 59 ); $days_diff = ''; $current_date = date_create_immutable( 'now', $timezone )->setTime( 23, 59, 59 ); // Calculate days difference only if end date is equal to current date. if ( ! $current_date->diff( $end_date )->format( '%a' ) ) { $days_diff = $end_date->diff( $start_date )->format( '%a' ); } list( $days, $timespan_label ) = self::get_date_filter_choices( $days_diff ); return [ $start_date, // WP timezone. $end_date, // WP timezone. $days, // e.g., 22. $timespan_label, // e.g., Custom. ]; } /** * Sets the timespan (or date range) for performing mysql queries. * * Includes: * 1. Start date object in WP timezone. * 2. End date object in WP timezone. * * @param null|array $timespan Given timespan (dates) preferably in WP timezone. * * @since 1.8.2 * * @return array */ public static function process_timespan_mysql( $timespan = null ) { // Retrieve and validate timespan if none is given. if ( empty( $timespan ) || ! is_array( $timespan ) ) { $timespan = self::process_timespan(); } list( $start_date, $end_date ) = $timespan; // Ideally should be in WP timezone. // If the time period is not a date object, return empty values. if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) { return [ '', '' ]; } // If given timespan is already in UTC timezone, return as it is. if ( date_timezone_get( $start_date )->getName() === 'UTC' && date_timezone_get( $end_date )->getName() === 'UTC' ) { return [ $start_date, // UTC timezone. $end_date, // UTC timezone. ]; } $mysql_timezone = timezone_open( 'UTC' ); return [ $start_date->setTimezone( $mysql_timezone ), // UTC timezone. $end_date->setTimezone( $mysql_timezone ), // UTC timezone. ]; } /** * Helper method to generate WP and UTC based date-time instances. * * Includes: * 1. Start date object in WP timezone. * 2. End date object in WP timezone. * 3. Start date object in UTC timezone. * 4. End date object in UTC timezone. * * @since 1.8.2 * * @param string $dates Given timespan (dates) in string. i.e. "2023-01-16 - 2023-02-15". * * @return bool|array */ public static function process_string_timespan( $dates ) { $dates = self::maybe_validate_string_timespan( $dates ); list( $start_date, $end_date ) = explode( self::TIMESPAN_DELIMITER, $dates ); // Return false if the start date is more recent than the end date. if ( strtotime( $start_date ) > strtotime( $end_date ) ) { return false; } $timezone = wp_timezone(); // Retrieve the timezone object for the site. $start_date = date_create_immutable( $start_date, $timezone ); $end_date = date_create_immutable( $end_date, $timezone ); // Return false if the date creation fails. if ( ! $start_date || ! $end_date ) { return false; } // Set the time to 0:0:0 for the start date and 23:59:59 for the end date. $start_date = $start_date->setTime( 0, 0, 0 ); $end_date = $end_date->setTime( 23, 59, 59 ); // Since we will need the initial datetime instances after the query, // we need to return new objects when modifications made. // Convert the dates to UTC timezone. $mysql_timezone = timezone_open( 'UTC' ); $utc_start_date = $start_date->setTimezone( $mysql_timezone ); $utc_end_date = $end_date->setTimezone( $mysql_timezone ); return [ $start_date, // WP timezone. $end_date, // WP timezone. $utc_start_date, // UTC timezone. $utc_end_date, // UTC timezone. ]; } /** * Sets the timespan (or date range) for performing mysql queries. * * Includes: * 1. A list of date filter options for the datepicker module. * 2. Currently selected filter or date range values. Last "X" days, or i.e. Feb 8, 2023 - Mar 9, 2023. * 3. Assigned timespan dates. * * @param null|array $timespan Given timespan (dates) preferably in WP timezone. * * @since 1.8.2 * * @return array */ public static function process_datepicker_choices( $timespan = null ) { // Retrieve and validate timespan if none is given. if ( empty( $timespan ) || ! is_array( $timespan ) ) { $timespan = self::process_timespan(); } list( $start_date, $end_date, $days ) = $timespan; $filters = self::get_date_filter_choices(); $selected = isset( $filters[ $days ] ) ? $days : 'custom'; $value = self::concat_dates( $start_date, $end_date ); $chosen_filter = $selected === 'custom' ? $value : $filters[ $selected ]; $choices = []; foreach ( $filters as $choice => $label ) { $timespan_dates = self::get_timespan_dates( $choice ); $checked = checked( $selected, $choice, false ); $choices[] = sprintf( '<label class="%s">%s<input type="radio" aria-hidden="true" name="timespan" value="%s" %s></label>', $checked ? 'is-selected' : '', esc_html( $label ), esc_attr( self::concat_dates( ...$timespan_dates ) ), esc_attr( $checked ) ); } return [ $choices, $chosen_filter, $value, ]; } /** * Based on the specified date-time range, calculates the comparable prior time period to estimate trends. * * Includes: * 1. Start date object in the given (original) timezone. * 2. End date object in the given (original) timezone. * * @since 1.8.2 * @since 1.8.8 Added $days_diff optional parameter. * * @param DateTimeImmutable $start_date Start date for the timespan. * @param DateTimeImmutable $end_date End date for the timespan. * @param null|int $days_diff Optional. Number of days in the timespan. If provided, it won't be calculated. * * @return bool|array */ public static function get_prev_timespan_dates( $start_date, $end_date, $days_diff = null ) { if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) { return false; } // Calculate $days_diff if not provided. if ( ! is_numeric( $days_diff ) ) { $days_diff = $end_date->diff( $start_date )->format( '%a' ); } // If $days_diff is non-positive, set $days_modifier to 1; otherwise, use $days_diff. $days_modifier = max( (int) $days_diff, 1 ); return [ $start_date->modify( "-{$days_modifier} day" ), $start_date->modify( '-1 second' ), ]; } /** * Get the site's date format from WordPress settings and convert it to a format compatible with Moment.js. * * @since 1.8.5.4 * * @return string */ public static function get_wp_date_format_for_momentjs() { // Get the date format from WordPress settings. $date_format = get_option( 'date_format', 'F j, Y' ); // Define a mapping of PHP date format characters to Moment.js format characters. $format_mapping = [ 'd' => 'DD', 'D' => 'ddd', 'j' => 'D', 'l' => 'dddd', 'S' => '', // PHP's S (English ordinal suffix) is not directly supported in Moment.js. 'w' => 'd', 'z' => '', // PHP's z (Day of the year) is not directly supported in Moment.js. 'W' => '', // PHP's W (ISO-8601 week number of year) is not directly supported in Moment.js. 'F' => 'MMMM', 'm' => 'MM', 'M' => 'MMM', 'n' => 'M', 't' => '', // PHP's t (Number of days in the given month) is not directly supported in Moment.js. 'L' => '', // PHP's L (Whether it's a leap year) is not directly supported in Moment.js. 'o' => 'YYYY', 'Y' => 'YYYY', 'y' => 'YY', 'a' => 'a', 'A' => 'A', 'B' => '', // PHP's B (Swatch Internet time) is not directly supported in Moment.js. 'g' => 'h', 'G' => 'H', 'h' => 'hh', 'H' => 'HH', 'i' => 'mm', 's' => 'ss', 'u' => '', // PHP's u (Microseconds) is not directly supported in Moment.js. 'e' => '', // PHP's e (Timezone identifier) is not directly supported in Moment.js. 'I' => '', // PHP's I (Whether or not the date is in daylight saving time) is not directly supported in Moment.js. 'O' => '', // PHP's O (Difference to Greenwich time (GMT) without colon) is not directly supported in Moment.js. 'P' => '', // PHP's P (Difference to Greenwich time (GMT) with colon) is not directly supported in Moment.js. 'T' => '', // PHP's T (Timezone abbreviation) is not directly supported in Moment.js. 'Z' => '', // PHP's Z (Timezone offset in seconds) is not directly supported in Moment.js. 'c' => 'YYYY-MM-DD', // PHP's c (ISO 8601 date) is not directly supported in Moment.js. 'r' => 'ddd, DD MMM YYYY', // PHP's r (RFC 2822 formatted date) is not directly supported in Moment.js. 'U' => '', // PHP's U (Seconds since the Unix Epoch) is not directly supported in Moment.js. ]; // Convert PHP format to JavaScript format. $momentjs_format = strtr( $date_format, $format_mapping ); // Use 'MMM D, YYYY' as a fallback if the conversion is not available. return empty( $momentjs_format ) ? 'MMM D, YYYY' : $momentjs_format; } /** * The number of days is converted to the start and end date range. * * @since 1.8.2 * * @param string $days Timespan days. * * @return array */ private static function get_timespan_dates( $days ) { list( $timespan_key, $timespan_label ) = self::get_date_filter_choices( $days ); // Bail early, if the given number of days is NOT a number nor a numeric string. if ( ! is_numeric( $days ) ) { return [ '', '', $timespan_key, $timespan_label ]; } $end_date = date_create_immutable( 'now', wp_timezone() ); $start_date = $end_date; if ( (int) $days > 0 ) { $start_date = $start_date->modify( "-{$days} day" ); } $start_date = $start_date->setTime( 0, 0, 0 ); $end_date = $end_date->setTime( 23, 59, 59 ); return [ $start_date, // WP timezone. $end_date, // WP timezone. $timespan_key, // i.e. 30. $timespan_label, // i.e. Last 30 days. ]; } /** * Check the delimiter to see if the end date is specified. * We can assume that the start and end dates are the same if the end date is missing. * * @since 1.8.2 * * @param string $dates Given timespan (dates) in string. i.e. "2023-01-16 - 2023-02-15" or "2023-01-16". * * @return string */ private static function maybe_validate_string_timespan( $dates ) { // "-" (en dash) is used as a delimiter for the datepicker module. if ( strpos( $dates, self::TIMESPAN_DELIMITER ) !== false ) { return $dates; } return $dates . self::TIMESPAN_DELIMITER . $dates; } /** * Returns a list of date filter options for the datepicker module. * * @since 1.8.2 * * @param string|null $key Optional. Key associated with available filters. * * @return array */ private static function get_date_filter_choices( $key = null ) { // Available date filters. $choices = [ '0' => esc_html__( 'Today', 'wpforms-lite' ), '1' => esc_html__( 'Yesterday', 'wpforms-lite' ), '7' => esc_html__( 'Last 7 days', 'wpforms-lite' ), '30' => esc_html__( 'Last 30 days', 'wpforms-lite' ), '90' => esc_html__( 'Last 90 days', 'wpforms-lite' ), '365' => esc_html__( 'Last 1 year', 'wpforms-lite' ), 'custom' => esc_html__( 'Custom', 'wpforms-lite' ), ]; // Bail early, and return the full list of options. if ( is_null( $key ) ) { return $choices; } // Return the "Custom" filter if the given key is not found. $key = isset( $choices[ $key ] ) ? $key : 'custom'; return [ $key, $choices[ $key ] ]; } /** * Concatenate given dates into a single string. i.e. "2023-01-16 - 2023-02-15". * * @since 1.8.2 * * @param DateTimeImmutable $start_date Start date. * @param DateTimeImmutable $end_date End date. * @param int|string $fallback Fallback value if dates are not valid. * * @return string */ private static function concat_dates( $start_date, $end_date, $fallback = '' ) { // Bail early, if the given dates are not valid. if ( ! ( $start_date instanceof DateTimeImmutable ) || ! ( $end_date instanceof DateTimeImmutable ) ) { return $fallback; } return implode( self::TIMESPAN_DELIMITER, [ $start_date->format( self::DATE_FORMAT ), $end_date->format( self::DATE_FORMAT ), ] ); } } Save