| @@ -1,4 +1,4 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | 3 | * Working Time settings and availability helpers. |
| 4 | 4 | * |
| @@ -94,9 +94,9 @@ | ||
| 94 | 94 | * @param mixed $weekdays Weekday intervals. |
| 95 | 95 | * |
| 96 | 96 | * @return array |
| 97 | 97 | */ |
| 98 | -function wpbc_working_time__normalize_weekday_intervals( $weekdays ) { | |
| 98 | +function wpbc_working_time__normalize_weekday_intervals( $weekdays ) { | |
| 99 | 99 | |
| 100 | 100 | $normalized = array(); |
| 101 | 101 | |
| 102 | 102 | for ( $day = 0; $day <= 6; $day++ ) { |
| @@ -131,10 +131,127 @@ | ||
| 131 | 131 | } |
| 132 | 132 | ); |
| 133 | 133 | } |
| 134 | 134 | |
| 135 | - return $normalized; | |
| 136 | -} | |
| 135 | + return $normalized; | |
| 136 | +} | |
| 137 | + | |
| 138 | +/** | |
| 139 | + * Validate a complete canonical weekly Working Time schedule. | |
| 140 | + * | |
| 141 | + * This strict validator complements the compatibility normalizer above. It is | |
| 142 | + * intended for administration mutation boundaries, where silently dropping an | |
| 143 | + * invalid or overlapping interval would make the saved schedule differ from | |
| 144 | + * what the user submitted. Intervals that touch at one boundary are valid. | |
| 145 | + * | |
| 146 | + * @param mixed $weekdays Untrusted weekday interval records. | |
| 147 | + * @param int $max_intervals_per_day Maximum intervals accepted for one weekday. | |
| 148 | + * | |
| 149 | + * @return array<int,array<int,array{start_second:int,end_second:int}>>|WP_Error Validated weekdays or an error. | |
| 150 | + */ | |
| 151 | +function wpbc_working_time__validate_weekday_intervals( $weekdays, $max_intervals_per_day = 8 ) { | |
| 152 | + | |
| 153 | + if ( ! is_array( $weekdays ) ) { | |
| 154 | + return new WP_Error( 'wpbc_working_time_weekdays_invalid', __( 'Enter a valid weekly Working Time schedule.', 'booking' ) ); | |
| 155 | + } | |
| 156 | + | |
| 157 | + foreach ( array_keys( $weekdays ) as $day_key ) { | |
| 158 | + if ( ! preg_match( '/^[0-6]$/', (string) $day_key ) ) { | |
| 159 | + return new WP_Error( 'wpbc_working_time_weekday_invalid', __( 'The Working Time schedule contains an invalid weekday.', 'booking' ) ); | |
| 160 | + } | |
| 161 | + } | |
| 162 | + | |
| 163 | + $max_intervals_per_day = max( 1, min( 48, absint( $max_intervals_per_day ) ) ); | |
| 164 | + $normalized_weekdays = array(); | |
| 165 | + | |
| 166 | + for ( $day_number = 0; $day_number <= 6; $day_number++ ) { | |
| 167 | + $raw_intervals = isset( $weekdays[ $day_number ] ) && is_array( $weekdays[ $day_number ] ) | |
| 168 | + ? array_values( $weekdays[ $day_number ] ) | |
| 169 | + : array(); | |
| 170 | + | |
| 171 | + if ( count( $raw_intervals ) > $max_intervals_per_day ) { | |
| 172 | + return new WP_Error( | |
| 173 | + 'wpbc_working_time_interval_limit', | |
| 174 | + /* translators: %d: Maximum intervals per weekday. */ | |
| 175 | + sprintf( __( 'Add no more than %d Working Time intervals to one weekday.', 'booking' ), $max_intervals_per_day ), | |
| 176 | + array( 'day_number' => $day_number ) | |
| 177 | + ); | |
| 178 | + } | |
| 179 | + | |
| 180 | + $normalized_intervals = array(); | |
| 181 | + foreach ( $raw_intervals as $raw_interval ) { | |
| 182 | + if ( ! is_array( $raw_interval ) || array_diff( array_keys( $raw_interval ), array( 'start_second', 'end_second' ) ) ) { | |
| 183 | + return new WP_Error( | |
| 184 | + 'wpbc_working_time_interval_invalid', | |
| 185 | + __( 'Enter a valid start and end time for every Working Time interval.', 'booking' ), | |
| 186 | + array( 'day_number' => $day_number ) | |
| 187 | + ); | |
| 188 | + } | |
| 189 | + | |
| 190 | + $raw_start_second = isset( $raw_interval['start_second'] ) ? $raw_interval['start_second'] : null; | |
| 191 | + $raw_end_second = isset( $raw_interval['end_second'] ) ? $raw_interval['end_second'] : null; | |
| 192 | + if ( | |
| 193 | + ! is_scalar( $raw_start_second ) | |
| 194 | + || ! is_scalar( $raw_end_second ) | |
| 195 | + || ! preg_match( '/^\d+$/', (string) $raw_start_second ) | |
| 196 | + || ! preg_match( '/^\d+$/', (string) $raw_end_second ) | |
| 197 | + ) { | |
| 198 | + return new WP_Error( | |
| 199 | + 'wpbc_working_time_interval_invalid', | |
| 200 | + __( 'Enter a valid start and end time for every Working Time interval.', 'booking' ), | |
| 201 | + array( 'day_number' => $day_number ) | |
| 202 | + ); | |
| 203 | + } | |
| 204 | + | |
| 205 | + $start_second = absint( $raw_start_second ); | |
| 206 | + $end_second = absint( $raw_end_second ); | |
| 207 | + if ( | |
| 208 | + $start_second >= $end_second | |
| 209 | + || DAY_IN_SECONDS < $end_second | |
| 210 | + || 0 !== $start_second % MINUTE_IN_SECONDS | |
| 211 | + || 0 !== $end_second % MINUTE_IN_SECONDS | |
| 212 | + ) { | |
| 213 | + return new WP_Error( | |
| 214 | + 'wpbc_working_time_interval_invalid', | |
| 215 | + __( 'Every Working Time interval must end after it starts and stay within one day.', 'booking' ), | |
| 216 | + array( 'day_number' => $day_number ) | |
| 217 | + ); | |
| 218 | + } | |
| 219 | + | |
| 220 | + $normalized_intervals[] = array( | |
| 221 | + 'start_second' => $start_second, | |
| 222 | + 'end_second' => $end_second, | |
| 223 | + ); | |
| 224 | + } | |
| 225 | + | |
| 226 | + usort( | |
| 227 | + $normalized_intervals, | |
| 228 | + function ( $first_interval, $second_interval ) { | |
| 229 | + if ( $first_interval['start_second'] === $second_interval['start_second'] ) { | |
| 230 | + return $first_interval['end_second'] - $second_interval['end_second']; | |
| 231 | + } | |
| 232 | + | |
| 233 | + return $first_interval['start_second'] - $second_interval['start_second']; | |
| 234 | + } | |
| 235 | + ); | |
| 236 | + | |
| 237 | + $previous_end = -1; | |
| 238 | + foreach ( $normalized_intervals as $normalized_interval ) { | |
| 239 | + if ( $normalized_interval['start_second'] < $previous_end ) { | |
| 240 | + return new WP_Error( | |
| 241 | + 'wpbc_working_time_interval_overlap', | |
| 242 | + __( 'Working Time intervals on the same weekday cannot overlap or repeat.', 'booking' ), | |
| 243 | + array( 'day_number' => $day_number ) | |
| 244 | + ); | |
| 245 | + } | |
| 246 | + $previous_end = $normalized_interval['end_second']; | |
| 247 | + } | |
| 248 | + | |
| 249 | + $normalized_weekdays[ $day_number ] = $normalized_intervals; | |
| 250 | + } | |
| 251 | + | |
| 252 | + return $normalized_weekdays; | |
| 253 | +} | |
| 137 | 254 | |
| 138 | 255 | /** |
| 139 | 256 | * Get stored Working Time settings. |
| 140 | 257 | * |