['Content-Type' => 'application/json', 'X-GI-Token' => GI_API_TOKEN], 'body' => wp_json_encode(['urls' => array_values($urls), 'type' => $type]), // WordPress still waits for the reply even with blocking=false: the short timeout is the real cap. 'timeout' => 2, 'blocking' => false, ]); } function gi_wp_is_job($post): bool { return $post instanceof WP_Post && in_array($post->post_type, GI_WP_JOB_TYPES, true); } /** Public URL even once the job is no longer published (get_permalink would return ?p=ID). */ function gi_wp_url(WP_Post $post): string { $public = clone $post; $public->post_status = 'publish'; $public->post_name = preg_replace('/__trashed$/', '', $public->post_name); return get_permalink($public); } /** Expiry fires both the status change and WP Job Manager's own hook: send each URL once per request. */ function gi_wp_once(WP_Post $post, string $type): void { static $sent = []; $url = gi_wp_url($post); if (isset($sent[$type . $url])) return; $sent[$type . $url] = true; gi_wp_send([$url], $type); } // Published (new or re-published) -> URL_UPDATED; leaving 'publish' (expired, draft, trash) -> URL_DELETED. add_action('transition_post_status', function ($new, $old, $post) { if (!gi_wp_is_job($post)) return; if ($new === 'publish') gi_wp_once($post, 'updated'); elseif ($old === 'publish') gi_wp_once($post, 'deleted'); }, 10, 3); // WP Job Manager marks expired jobs with its own status; also covers the cron that expires them. add_action('job_manager_job_expired', function ($post_id) { $post = get_post($post_id); if (gi_wp_is_job($post)) gi_wp_once($post, 'deleted'); }); // Hard delete of a published job (bypassing trash). add_action('before_delete_post', function ($post_id) { $post = get_post($post_id); if (gi_wp_is_job($post) && $post->post_status === 'publish') gi_wp_once($post, 'deleted'); });