diff --git a/classes/suggested-tasks/class-task.php b/classes/suggested-tasks/class-task.php index c85db2fa6..b8f896e30 100644 --- a/classes/suggested-tasks/class-task.php +++ b/classes/suggested-tasks/class-task.php @@ -29,7 +29,7 @@ * @property int|null $target_term_id The target term ID for the task * @property string|null $target_taxonomy The target taxonomy for the task * @property string|null $target_term_name The target term name for the task - * @property string|null $date The task date in YW format (year-week) + * @property string|null $date The task date in oW format (ISO year + ISO week) */ class Task { /** diff --git a/classes/suggested-tasks/class-tasks-manager.php b/classes/suggested-tasks/class-tasks-manager.php index 880e5223e..64b2aefb5 100644 --- a/classes/suggested-tasks/class-tasks-manager.php +++ b/classes/suggested-tasks/class-tasks-manager.php @@ -315,7 +315,7 @@ public function cleanup_pending_tasks() { $task_provider = $this->get_task_provider( $task->get_provider_id() ); // Should we delete the task? Delete tasks which don't have a task provider or repetitive tasks which were created in the previous week. - if ( ! $task_provider || ( $task_provider->is_repetitive() && ( ! $task->date || \gmdate( 'YW' ) !== (string) $task->date ) ) ) { + if ( ! $task_provider || ( $task_provider->is_repetitive() && ( ! $task->date || \gmdate( 'oW' ) !== (string) $task->date ) ) ) { \progress_planner()->get_suggested_tasks_db()->delete_recommendation( $task->ID ); } } @@ -346,7 +346,7 @@ public function handle_task_unsnooze( $new_status, $old_status, $post ) { $task_provider = $this->get_task_provider( $task->get_provider_id() ); // Delete tasks which don't have a task provider or repetitive tasks which were created in the previous week. - if ( ! $task_provider || ( $task_provider->is_repetitive() && ( ! $task->date || \gmdate( 'YW' ) !== (string) $task->date ) ) ) { + if ( ! $task_provider || ( $task_provider->is_repetitive() && ( ! $task->date || \gmdate( 'oW' ) !== (string) $task->date ) ) ) { \progress_planner()->get_suggested_tasks_db()->delete_recommendation( $task->ID ); } diff --git a/classes/suggested-tasks/providers/class-content-create.php b/classes/suggested-tasks/providers/class-content-create.php index 4f038b0f6..94a72abbc 100644 --- a/classes/suggested-tasks/providers/class-content-create.php +++ b/classes/suggested-tasks/providers/class-content-create.php @@ -109,7 +109,7 @@ public function should_add_task() { } // Add tasks if there are no posts published this week. - return \gmdate( 'YW' ) !== \gmdate( 'YW', \strtotime( $last_published_post_data['post_date'] ) ); + return \gmdate( 'oW' ) !== \gmdate( 'oW', \strtotime( $last_published_post_data['post_date'] ) ); } /** diff --git a/classes/suggested-tasks/providers/class-content-review.php b/classes/suggested-tasks/providers/class-content-review.php index f0898f586..da23e6052 100644 --- a/classes/suggested-tasks/providers/class-content-review.php +++ b/classes/suggested-tasks/providers/class-content-review.php @@ -289,7 +289,7 @@ public function get_tasks_to_inject() { 'provider_id' => $this->get_provider_id(), 'target_post_id' => $task_data['target_post_id'], 'target_post_type' => $task_data['target_post_type'], - 'date' => \gmdate( 'YW' ), + 'date' => \gmdate( 'oW' ), 'post_title' => $this->get_title_with_data( $task_data ), 'url' => $this->get_url_with_data( $task_data ), 'url_target' => $this->get_url_target(), diff --git a/classes/suggested-tasks/providers/class-tasks.php b/classes/suggested-tasks/providers/class-tasks.php index eed1b097c..d074ed83a 100644 --- a/classes/suggested-tasks/providers/class-tasks.php +++ b/classes/suggested-tasks/providers/class-tasks.php @@ -295,7 +295,9 @@ public function get_task_id( $task_data = [] ) { $parts[] = $task_data['target_taxonomy'] ?? false; // If the task is repetitive, add the date as the last part (format: YYYYWW, e.g., 202542 for week 42 of 2025). // This creates a new task instance each week for repetitive tasks. - $parts[] = $this->is_repetitive() ? \gmdate( 'YW' ) : false; + // Note: We use 'oW' format (ISO year + ISO week) instead of 'YW' to handle year boundaries correctly. + // For example, Dec 29, 2025 is ISO week 01 of 2026, so 'oW' returns '202601' while 'YW' would incorrectly return '202501'. + $parts[] = $this->is_repetitive() ? \gmdate( 'oW' ) : false; // Remove empty parts to keep IDs clean. $parts = \array_filter( $parts ); @@ -447,7 +449,7 @@ public function is_task_relevant() { * - Returns the task object if completed, false otherwise * * Repetitive tasks: - * - Must be completed within the same week they were created (using YW format: year + week number) + * - Must be completed within the same week they were created (using oW format: ISO year + ISO week number) * - For example, a task created in week 42 of 2025 must be completed in 2025W42 * - This prevents tasks from previous weeks being marked as complete * - Allows child classes to add completion data (e.g., post_id for "create post" tasks) @@ -483,8 +485,8 @@ public function evaluate_task( $task_id ) { $task->provider && $task->provider->slug === $this->get_provider_id() && \DateTime::createFromFormat( 'Y-m-d H:i:s', $task->post_date ) && - // Check if the task was created in the current week (YW format: e.g., 202542 = week 42 of 2025). - \gmdate( 'YW' ) === \gmdate( 'YW', \DateTime::createFromFormat( 'Y-m-d H:i:s', $task->post_date )->getTimestamp() ) && // @phpstan-ignore-line + // Check if the task was created in the current week (oW format: ISO year + ISO week, e.g., 202542 = week 42 of 2025). + \gmdate( 'oW' ) === \gmdate( 'oW', \DateTime::createFromFormat( 'Y-m-d H:i:s', $task->post_date )->getTimestamp() ) && // @phpstan-ignore-line $this->is_task_completed( \progress_planner()->get_suggested_tasks()->get_task_id_from_slug( $task->post_name ) ) ) { // Allow adding more data, for example in case of 'create-post' tasks we are adding the post_id. @@ -599,7 +601,7 @@ public function get_task_details( $task_data = [] ) { 'parent' => $this->get_parent(), 'priority' => $this->get_priority(), 'points' => $this->get_points(), - 'date' => \gmdate( 'YW' ), + 'date' => \gmdate( 'oW' ), 'url' => $this->get_url_with_data( $task_data ), 'url_target' => $this->get_url_target(), 'link_setting' => $this->get_link_setting(), diff --git a/classes/suggested-tasks/providers/traits/class-dismissable-task.php b/classes/suggested-tasks/providers/traits/class-dismissable-task.php index 15a07f63f..5c068538d 100644 --- a/classes/suggested-tasks/providers/traits/class-dismissable-task.php +++ b/classes/suggested-tasks/providers/traits/class-dismissable-task.php @@ -87,7 +87,7 @@ public function handle_task_dismissal( $post_id ) { // Store the task dismissal data. $dismissal_data = [ - 'date' => \gmdate( 'YW' ), + 'date' => \gmdate( 'oW' ), 'timestamp' => \time(), ]; @@ -157,7 +157,7 @@ protected function is_task_dismissed( $task_data ) { $dismissal_data = $dismissed_tasks[ $provider_key ][ $task_identifier ]; // If the task was dismissed in the current week, don't show it again. - if ( $dismissal_data['date'] === \gmdate( 'YW' ) ) { + if ( $dismissal_data['date'] === \gmdate( 'oW' ) ) { return true; } diff --git a/tests/phpunit/test-class-suggested-tasks.php b/tests/phpunit/test-class-suggested-tasks.php index a13eef01d..fec2a15d4 100644 --- a/tests/phpunit/test-class-suggested-tasks.php +++ b/tests/phpunit/test-class-suggested-tasks.php @@ -21,30 +21,30 @@ public function test_task_cleanup() { // Tasks that should not be removed. $tasks_to_keep = [ [ - 'post_title' => 'review-post-14-' . \gmdate( 'YW' ), - 'task_id' => 'review-post-14-' . \gmdate( 'YW' ), - 'date' => \gmdate( 'YW' ), + 'post_title' => 'review-post-14-' . \gmdate( 'oW' ), + 'task_id' => 'review-post-14-' . \gmdate( 'oW' ), + 'date' => \gmdate( 'oW' ), 'category' => 'content-update', 'provider_id' => 'review-post', ], [ - 'post_title' => 'create-post-' . \gmdate( 'YW' ), - 'task_id' => 'create-post-' . \gmdate( 'YW' ), - 'date' => \gmdate( 'YW' ), + 'post_title' => 'create-post-' . \gmdate( 'oW' ), + 'task_id' => 'create-post-' . \gmdate( 'oW' ), + 'date' => \gmdate( 'oW' ), 'category' => 'content-new', 'provider_id' => 'create-post', ], [ - 'post_title' => 'update-core-' . \gmdate( 'YW' ), - 'task_id' => 'update-core-' . \gmdate( 'YW' ), - 'date' => \gmdate( 'YW' ), + 'post_title' => 'update-core-' . \gmdate( 'oW' ), + 'task_id' => 'update-core-' . \gmdate( 'oW' ), + 'date' => \gmdate( 'oW' ), 'category' => 'maintenance', 'provider_id' => 'update-core', ], [ - 'post_title' => 'core-siteicon-' . \gmdate( 'YW' ), - 'task_id' => 'core-siteicon-' . \gmdate( 'YW' ), - 'date' => \gmdate( 'YW' ), + 'post_title' => 'core-siteicon-' . \gmdate( 'oW' ), + 'task_id' => 'core-siteicon-' . \gmdate( 'oW' ), + 'date' => \gmdate( 'oW' ), 'provider_id' => 'core-siteicon', 'category' => 'configuration', ],