| @@ -93,9 +93,8 @@ | ||
| 93 | 93 | /** |
| 94 | 94 | * This is for when user select report as monthly, we will have the day number, instead of text. |
| 95 | 95 | * |
| 96 | 96 | * @var int |
| 97 | - * @sanitize_text_field | |
| 98 | 97 | * @defender_property |
| 99 | 98 | */ |
| 100 | 99 | public int $day_n = 1; |
| 101 | 100 | |
| @@ -250,14 +249,14 @@ | ||
| 250 | 249 | $est->setTimestamp( $this->last_sent ); |
| 251 | 250 | } |
| 252 | 251 | |
| 253 | 252 | // Est should be set as the last send. Create now timestamp. |
| 254 | - $now = new DateTime( 'now', wp_timezone() ); | |
| 255 | - $interval = DateInterval::createFromDateString( (string) $est->getOffset() . 'seconds' ); | |
| 256 | - $time_parts = array_pad( explode( ':', (string) $this->time ), 2, 0 ); | |
| 257 | - [ $hour, $min ] = $time_parts; | |
| 258 | - $hour = (int) $hour; | |
| 259 | - $min = (int) $min; | |
| 253 | + $now = new DateTime( 'now', wp_timezone() ); | |
| 254 | + $interval = DateInterval::createFromDateString( (string) $est->getOffset() . 'seconds' ); | |
| 255 | + $time_parts = array_pad( explode( ':', (string) $this->time ), 2, 0 ); | |
| 256 | + [ $hour, $min ] = $time_parts; | |
| 257 | + $hour = (int) $hour; | |
| 258 | + $min = (int) $min; | |
| 260 | 259 | switch ( $this->frequency ) { |
| 261 | 260 | case 'daily': |
| 262 | 261 | // Set the time. |
| 263 | 262 | $est->add( $interval ); |
| @@ -268,9 +267,9 @@ | ||
| 268 | 267 | $est->setTime( $hour, $min, 0 ); |
| 269 | 268 | } |
| 270 | 269 | break; |
| 271 | 270 | case 'weekly': |
| 272 | - if ( empty( $this->day ) ) { | |
| 271 | + if ( '' === $this->day || null === $this->day ) { | |
| 273 | 272 | break; |
| 274 | 273 | } |
| 275 | 274 | $est->modify( 'this ' . $this->day ); |
| 276 | 275 | $est->add( $interval ); |
| @@ -282,16 +281,18 @@ | ||
| 282 | 281 | break; |
| 283 | 282 | case 'monthly': |
| 284 | 283 | // We will need to check if the date is passed today, if not, use this, if yes, then queue for next month. |
| 285 | 284 | $est->setDate( (int) $est->format( 'Y' ), (int) $est->format( 'm' ), 1 ); |
| 286 | - if ( 31 === $this->day_n ) { | |
| 287 | - $this->day_n = (int) $est->format( 't' ); | |
| 288 | - } | |
| 289 | - $est->add( new DateInterval( 'P' . ( $this->day_n - 1 ) . 'D' ) ); | |
| 285 | + // Clamp day_n to a valid day in the current month. | |
| 286 | + $day_n = max( 1, min( $this->day_n, (int) $est->format( 't' ) ) ); | |
| 287 | + $est->add( new DateInterval( 'P' . ( $day_n - 1 ) . 'D' ) ); | |
| 290 | 288 | $est->setTime( $hour, $min, 0 ); |
| 291 | 289 | while ( $est->getTimestamp() < $now->getTimestamp() ) { |
| 292 | - // Already over, move to next month. | |
| 293 | - $est->modify( 'next month' ); | |
| 290 | + // Already over, move to first day of next month. | |
| 291 | + $est->modify( 'first day of next month' ); | |
| 292 | + // Re-clamp for the new month's length. | |
| 293 | + $day_n = max( 1, min( $this->day_n, (int) $est->format( 't' ) ) ); | |
| 294 | + $est->add( new DateInterval( 'P' . ( $day_n - 1 ) . 'D' ) ); | |
| 294 | 295 | $est->setTime( $hour, $min, 0 ); |
| 295 | 296 | } |
| 296 | 297 | break; |
| 297 | 298 | } |
| @@ -445,8 +446,31 @@ | ||
| 445 | 446 | return $data; |
| 446 | 447 | } |
| 447 | 448 | |
| 448 | 449 | /** |
| 450 | + * Prepare data for persistence. | |
| 451 | + * | |
| 452 | + * Recipients are indexed by email in memory (see after_load()), so re-index them back to | |
| 453 | + * sequential arrays before storing. Otherwise they are JSON-encoded as objects, which on | |
| 454 | + * downgrade breaks Defender 5 with "all_subscribers.slice is not a function". | |
| 455 | + * | |
| 456 | + * @param array $data The data array to import values from. | |
| 457 | + * | |
| 458 | + * @return array | |
| 459 | + */ | |
| 460 | + protected function prepare_data( $data = array() ): array { | |
| 461 | + $data = parent::prepare_data( $data ); | |
| 462 | + | |
| 463 | + foreach ( array( 'in_house_recipients', 'out_house_recipients', 'all_subscribers' ) as $key ) { | |
| 464 | + if ( isset( $data[ $key ] ) && is_array( $data[ $key ] ) ) { | |
| 465 | + $data[ $key ] = array_values( $data[ $key ] ); | |
| 466 | + } | |
| 467 | + } | |
| 468 | + | |
| 469 | + return $data; | |
| 470 | + } | |
| 471 | + | |
| 472 | + /** | |
| 449 | 473 | * Overrided method to manipulate user details dynamically. |
| 450 | 474 | */ |
| 451 | 475 | protected function after_load(): void { |
| 452 | 476 | $in_house_recipients = array(); |
| @@ -481,9 +505,9 @@ | ||
| 481 | 505 | private function get_email_indexed_recipients( array $recipients ): array { |
| 482 | 506 | $email_indexed_recipients = array(); |
| 483 | 507 | |
| 484 | 508 | foreach ( $recipients as $recipient ) { |
| 485 | - if ( empty( $recipient['email'] ) ) { | |
| 509 | + if ( ! isset( $recipient['email'] ) ) { | |
| 486 | 510 | continue; |
| 487 | 511 | } |
| 488 | 512 | |
| 489 | 513 | $email = trim( $recipient['email'] ); |