| @@ -72,8 +72,10 @@ | ||
| 72 | 72 | $this->save_type_state($type, $s); |
| 73 | 73 | |
| 74 | 74 | // reset control flags |
| 75 | 75 | $this->save_type_control($type, $this->default_control_state()); |
| 76 | + | |
| 77 | + do_action("wpmcs_{$type}_started", $type); | |
| 76 | 78 | } |
| 77 | 79 | |
| 78 | 80 | public function pause(string $type) { |
| 79 | 81 | $c = $this->get_type_control($type); |
| @@ -113,9 +115,11 @@ | ||
| 113 | 115 | (( $s['status'] ?? 'stopped') === 'running' ) && |
| 114 | 116 | ( isset($c['time']) && ( $c['time'] > 0 ) && ( ( time() - $c['time'] ) > self::$lock_duration ) ) && |
| 115 | 117 | ($c['pause_requested'] === true || $c['stop_requested'] === true) |
| 116 | 118 | ) { |
| 117 | - if($c['stop_requested'] === true) { | |
| 119 | + $was_stop_requested = $c['stop_requested'] === true; | |
| 120 | + | |
| 121 | + if($was_stop_requested) { | |
| 118 | 122 | $s = $this->default_type_state(); |
| 119 | 123 | } else { |
| 120 | 124 | $s['status'] = 'paused'; |
| 121 | 125 | } |
| @@ -125,8 +129,15 @@ | ||
| 125 | 129 | $c['pause_requested'] = false; |
| 126 | 130 | $c['stop_requested'] = false; |
| 127 | 131 | $c['time'] = time(); |
| 128 | 132 | $this->save_type_control($type, $c); |
| 133 | + | |
| 134 | + // Stale-lock self-heal is a second cancellation path, separate from | |
| 135 | + // process_iterations()'s own — a job cancelled via a stalled lock | |
| 136 | + // (e.g. cron stopped ticking) still needs to fire the same hook. | |
| 137 | + if ($was_stop_requested) { | |
| 138 | + do_action("wpmcs_{$type}_cancelled", $type); | |
| 139 | + } | |
| 129 | 140 | } |
| 130 | 141 | |
| 131 | 142 | return $this->format_status($type, $s); |
| 132 | 143 | } |
| @@ -138,8 +149,13 @@ | ||
| 138 | 149 | } |
| 139 | 150 | return $statuses; |
| 140 | 151 | } |
| 141 | 152 | |
| 153 | + /** Job type keys registered so far via set_callback() — e.g. for an integration to hook every type's lifecycle actions. */ | |
| 154 | + public function get_registered_types() { | |
| 155 | + return array_keys($this->callback_map); | |
| 156 | + } | |
| 157 | + | |
| 142 | 158 | private function format_status(string $type, array $s) { |
| 143 | 159 | $c = $this->get_type_control($type); |
| 144 | 160 | |
| 145 | 161 | $total = $s['iterations_total'] ?? 0; |
| @@ -372,9 +388,11 @@ | ||
| 372 | 388 | $is_completed = ($latest_s['iterations_done'] ?? 0) >= ($latest_s['iterations_total'] ?? 0); |
| 373 | 389 | |
| 374 | 390 | // Update state if completed, paused or stopped |
| 375 | 391 | if ($is_completed || ($latest_c['stop_requested'] ?? false) || ($latest_c['pause_requested'] ?? false)) { |
| 376 | - $latest_s['status'] = $is_completed || ($latest_c['stop_requested'] ?? false) ? 'stopped' : 'paused'; | |
| 392 | + $was_stop_requested = $latest_c['stop_requested'] ?? false; | |
| 393 | + | |
| 394 | + $latest_s['status'] = $is_completed || $was_stop_requested ? 'stopped' : 'paused'; | |
| 377 | 395 | $latest_s['completed'] = $is_completed; |
| 378 | 396 | $this->save_type_state($type, $latest_s); |
| 379 | 397 | |
| 380 | 398 | $latest_c['pause_requested'] = false; |
| @@ -380,8 +398,14 @@ | ||
| 380 | 398 | $latest_c['pause_requested'] = false; |
| 381 | 399 | $latest_c['stop_requested'] = false; |
| 382 | 400 | $latest_c['time'] = time(); |
| 383 | 401 | $this->save_type_control($type, $latest_c); |
| 402 | + | |
| 403 | + if ($is_completed) { | |
| 404 | + do_action("wpmcs_{$type}_completed", $type); | |
| 405 | + } elseif ($was_stop_requested) { | |
| 406 | + do_action("wpmcs_{$type}_cancelled", $type); | |
| 407 | + } | |
| 384 | 408 | } |
| 385 | 409 | |
| 386 | 410 | gc_collect_cycles(); |
| 387 | 411 | } |