| @@ -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 | |
| @@ -163,17 +162,25 @@ | ||
| 163 | 162 | * |
| 164 | 163 | * @return array |
| 165 | 164 | */ |
| 166 | 165 | protected function get_default_user(): array { |
| 166 | + if ( ! is_user_logged_in() ) { | |
| 167 | + return array(); | |
| 168 | + } | |
| 169 | + | |
| 167 | 170 | $user_id = get_current_user_id(); |
| 168 | 171 | |
| 172 | + $email = $this->get_current_user_email( $user_id ); | |
| 173 | + | |
| 169 | 174 | return array( |
| 170 | - 'name' => $this->get_user_display( $user_id ), | |
| 171 | - 'id' => $user_id, | |
| 172 | - 'email' => $this->get_current_user_email( $user_id ), | |
| 173 | - 'role' => $this->get_current_user_role( $user_id ), | |
| 174 | - 'avatar' => get_avatar_url( $this->get_current_user_email( $user_id ) ), | |
| 175 | - 'status' => self::USER_SUBSCRIBED, | |
| 175 | + $email => array( | |
| 176 | + 'name' => $this->get_user_display( $user_id ), | |
| 177 | + 'id' => $user_id, | |
| 178 | + 'email' => $email, | |
| 179 | + 'role' => $this->get_current_user_role( $user_id ), | |
| 180 | + 'avatar' => get_avatar_url( $email ), | |
| 181 | + 'status' => self::USER_SUBSCRIBED, | |
| 182 | + ), | |
| 176 | 183 | ); |
| 177 | 184 | } |
| 178 | 185 | |
| 179 | 186 | /** |
| @@ -244,9 +251,10 @@ | ||
| 244 | 251 | |
| 245 | 252 | // Est should be set as the last send. Create now timestamp. |
| 246 | 253 | $now = new DateTime( 'now', wp_timezone() ); |
| 247 | 254 | $interval = DateInterval::createFromDateString( (string) $est->getOffset() . 'seconds' ); |
| 248 | - [ $hour, $min ] = explode( ':', $this->time ); | |
| 255 | + $time_parts = array_pad( explode( ':', (string) $this->time ), 2, 0 ); | |
| 256 | + [ $hour, $min ] = $time_parts; | |
| 249 | 257 | $hour = (int) $hour; |
| 250 | 258 | $min = (int) $min; |
| 251 | 259 | switch ( $this->frequency ) { |
| 252 | 260 | case 'daily': |
| @@ -259,8 +267,11 @@ | ||
| 259 | 267 | $est->setTime( $hour, $min, 0 ); |
| 260 | 268 | } |
| 261 | 269 | break; |
| 262 | 270 | case 'weekly': |
| 271 | + if ( '' === $this->day || null === $this->day ) { | |
| 272 | + break; | |
| 273 | + } | |
| 263 | 274 | $est->modify( 'this ' . $this->day ); |
| 264 | 275 | $est->add( $interval ); |
| 265 | 276 | $est->setTime( $hour, $min, 0 ); |
| 266 | 277 | while ( $est->getTimestamp() < $now->getTimestamp() ) { |
| @@ -270,16 +281,18 @@ | ||
| 270 | 281 | break; |
| 271 | 282 | case 'monthly': |
| 272 | 283 | // We will need to check if the date is passed today, if not, use this, if yes, then queue for next month. |
| 273 | 284 | $est->setDate( (int) $est->format( 'Y' ), (int) $est->format( 'm' ), 1 ); |
| 274 | - if ( 31 === $this->day_n ) { | |
| 275 | - $this->day_n = (int) $est->format( 't' ); | |
| 276 | - } | |
| 277 | - $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' ) ); | |
| 278 | 288 | $est->setTime( $hour, $min, 0 ); |
| 279 | 289 | while ( $est->getTimestamp() < $now->getTimestamp() ) { |
| 280 | - // Already over, move to next month. | |
| 281 | - $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' ) ); | |
| 282 | 295 | $est->setTime( $hour, $min, 0 ); |
| 283 | 296 | } |
| 284 | 297 | break; |
| 285 | 298 | } |
| @@ -307,9 +320,9 @@ | ||
| 307 | 320 | */ |
| 308 | 321 | public function check_active_status(): bool { |
| 309 | 322 | // Exception after migrating Scheduled scanning to Scan settings. |
| 310 | 323 | if ( Malware_Report::SLUG === $this->slug |
| 311 | - && true === ( new \WP_Defender\Model\Setting\Scan() )->scheduled_scanning | |
| 324 | + && ( new \WP_Defender\Model\Setting\Scan() )->is_enabled_scheduled_scanning() | |
| 312 | 325 | ) { |
| 313 | 326 | return true; |
| 314 | 327 | } |
| 315 | 328 | |
| @@ -370,16 +383,17 @@ | ||
| 370 | 383 | if ( 'notification' === $this->type ) { |
| 371 | 384 | return $for_hub ? false : esc_html__( 'Never', 'defender-security' ); |
| 372 | 385 | } |
| 373 | 386 | |
| 387 | + $est_timestamp = ! is_int( $this->est_timestamp ) ? (int) $this->est_timestamp : $this->est_timestamp; | |
| 374 | 388 | if ( $for_hub ) { |
| 375 | 389 | return $this->check_active_status() |
| 376 | - ? $this->persistent_hub_datetime_format( $this->est_timestamp ) | |
| 390 | + ? $this->persistent_hub_datetime_format( $est_timestamp ) | |
| 377 | 391 | : false; |
| 378 | 392 | } elseif ( $this->check_active_status() ) { |
| 379 | 393 | $format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' ); |
| 380 | 394 | $date = new DateTime( 'now', wp_timezone() ); |
| 381 | - $date->setTimestamp( (int) $this->est_timestamp ); | |
| 395 | + $date->setTimestamp( $est_timestamp ); | |
| 382 | 396 | |
| 383 | 397 | return $date->format( $format ); |
| 384 | 398 | } else { |
| 385 | 399 | return esc_html__( 'Never', 'defender-security' ); |
| @@ -432,8 +446,31 @@ | ||
| 432 | 446 | return $data; |
| 433 | 447 | } |
| 434 | 448 | |
| 435 | 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 | + /** | |
| 436 | 473 | * Overrided method to manipulate user details dynamically. |
| 437 | 474 | */ |
| 438 | 475 | protected function after_load(): void { |
| 439 | 476 | $in_house_recipients = array(); |
| @@ -442,18 +479,42 @@ | ||
| 442 | 479 | $id = $recipient['id']; |
| 443 | 480 | $user_data = get_userdata( $id ); |
| 444 | 481 | |
| 445 | 482 | if ( $user_data instanceof WP_User ) { |
| 446 | - $in_house_recipients[] = array( | |
| 483 | + $in_house_recipients[ $user_data->user_email ] = array( | |
| 447 | 484 | 'name' => $user_data->display_name, |
| 448 | 485 | 'id' => $user_data->ID, |
| 449 | 486 | 'email' => $user_data->user_email, |
| 450 | 487 | 'role' => $this->get_first_user_role( $user_data ), |
| 451 | 488 | 'avatar' => get_avatar_url( $id ), |
| 452 | - 'status' => $recipient['status'], | |
| 489 | + 'status' => $recipient['status'] ?? '', | |
| 453 | 490 | ); |
| 454 | 491 | } |
| 455 | 492 | } |
| 456 | 493 | |
| 457 | - $this->in_house_recipients = $in_house_recipients; | |
| 494 | + $this->in_house_recipients = $in_house_recipients; | |
| 495 | + $this->out_house_recipients = $this->get_email_indexed_recipients( $this->out_house_recipients ); | |
| 496 | + } | |
| 497 | + | |
| 498 | + /** | |
| 499 | + * Indexes recipients by email. | |
| 500 | + * | |
| 501 | + * @param array $recipients Recipients list. | |
| 502 | + * | |
| 503 | + * @return array | |
| 504 | + */ | |
| 505 | + private function get_email_indexed_recipients( array $recipients ): array { | |
| 506 | + $email_indexed_recipients = array(); | |
| 507 | + | |
| 508 | + foreach ( $recipients as $recipient ) { | |
| 509 | + if ( ! isset( $recipient['email'] ) ) { | |
| 510 | + continue; | |
| 511 | + } | |
| 512 | + | |
| 513 | + $email = trim( $recipient['email'] ); | |
| 514 | + $recipient['email'] = $email; | |
| 515 | + $email_indexed_recipients[ $email ] = $recipient; | |
| 516 | + } | |
| 517 | + | |
| 518 | + return $email_indexed_recipients; | |
| 458 | 519 | } |
| 459 | 520 | } |