prefix . 'chat4u_changes'; } public static function activate(): void { global $wpdb; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $table = self::table(); dbDelta("CREATE TABLE {$table} ( sequence bigint unsigned NOT NULL AUTO_INCREMENT, object_id bigint unsigned NOT NULL, post_type varchar(32) NOT NULL, locale varchar(32) NOT NULL, removed tinyint NOT NULL DEFAULT 0, happened_at datetime NOT NULL, PRIMARY KEY (sequence), KEY happened_at (happened_at) ) " . $wpdb->get_charset_collate() . ';'); if (!wp_next_scheduled('chat4u_cleanup')) { wp_schedule_event(time() + 60, 'hourly', 'chat4u_cleanup'); } $state = Chat4U_Protocol::connection(); if (($state['state'] ?? '') === 'connected') { update_option('chat4u_pending_control', 'reactivated', false); update_option('chat4u_control_delivery', wp_generate_uuid4(), false); wp_schedule_single_event(time() + 2, 'chat4u_deliver'); } } public static function head(): int { global $wpdb; return (int) $wpdb->get_var($wpdb->prepare('SELECT COALESCE(MAX(sequence),0) FROM %i', self::table())); } public static function changed(int $id): void { $post = get_post($id); if (!$post || wp_is_post_revision($id) || !in_array($post->post_type, Chat4U_Content::types(), true)) { return; } self::append($id, $post->post_type, Chat4U_Content::locale($id), !Chat4U_Content::public_post($post)); } public static function metadata($meta_id, int $id): void { self::changed($id); } public static function render_changed(): void { foreach (Chat4U_Content::languages() as $language) { self::append(0, 'home', $language['code'], false); } } public static function deleted(int $id, $post): void { if ($post instanceof WP_Post && in_array($post->post_type, Chat4U_Content::types(), true)) { self::append($id, $post->post_type, Chat4U_Content::locale($id), true); } } public static function commerce(int $id, bool $removed): void { self::append($id, 'woo_product', Chat4U_Content::locale($id), $removed); } private static function append(int $id, string $type, string $locale, bool $removed): void { global $wpdb; $wpdb->insert(self::table(), ['object_id' => $id, 'post_type' => $type, 'locale' => $locale, 'removed' => (int) $removed, 'happened_at' => gmdate('Y-m-d H:i:s')], ['%d', '%s', '%s', '%d', '%s']); if (!wp_next_scheduled('chat4u_deliver')) { wp_schedule_single_event(time() + 2, 'chat4u_deliver'); } } public static function page(int $after, int $limit = 25): array { global $wpdb; $table = self::table(); $floor = (int) $wpdb->get_var($wpdb->prepare('SELECT COALESCE(MIN(sequence),0) FROM %i', $table)); $head = self::head(); if ($after > $head || ($floor && $after < $floor - 1)) { return ['cursor_expired' => true, 'head' => $head, 'high_water' => Chat4U_Content::high_water()]; } $limit = min(100, max(1, $limit)); $rows = $wpdb->get_results($wpdb->prepare('SELECT * FROM %i WHERE sequence > %d ORDER BY sequence LIMIT %d', $table, $after, $limit + 1)); $items = []; foreach (array_slice($rows, 0, $limit) as $row) { if ($row->post_type === 'woo_product') { $value = ['type' => 'woo_product', 'id' => (int) $row->object_id, 'locale' => $row->locale, 'removed' => (bool) $row->removed, 'version' => Chat4U_Commerce_Catalog::family_version((int) $row->object_id)]; $items[] = $value; continue; } $value = $row->removed ? ['removed' => true, 'id' => (int) $row->object_id, 'type' => $row->post_type, 'locale' => $row->locale] : Chat4U_Content::record((int) $row->object_id, $row->post_type, $row->locale); $value = Chat4U_Protocol::discovery_record($value); if ($items && !Chat4U_Protocol::fits(['items' => [...$items, $value], 'cursor' => (int) $row->sequence, 'head' => $head, 'complete' => false])) { break; } $items[] = $value; $after = (int) $row->sequence; } return ['items' => $items, 'cursor' => $after, 'head' => $head, 'complete' => count($items) === count($rows)]; } public static function deliver(): void { $state = Chat4U_Protocol::connection(); if (($state['state'] ?? '') !== 'connected') { return; } $control = get_option('chat4u_pending_control', ''); if ($control === 'reactivated') { $result = Chat4U_Protocol::remote('events', ['installation_id' => $state['scope'], 'delivery_id' => get_option('chat4u_control_delivery'), 'topic' => $control], $state); if (is_wp_error($result)) { wp_schedule_single_event(time() + 60, 'chat4u_deliver'); return; } delete_option('chat4u_pending_control'); delete_option('chat4u_control_delivery'); } $delivery = get_option('chat4u_pending_delivery', []); if (!$delivery) { $after = (int) get_option('chat4u_delivered_cursor', 0); $page = self::page($after, 100); if (!empty($page['cursor_expired'])) { $page = ['head' => self::head(), 'cursor' => self::head(), 'items' => [], 'complete' => true]; } $removed = []; foreach ($page['items'] as $item) { if (!empty($item['removed']) && !empty(Chat4U_Content::record((int) $item['id'], $item['type'], $item['locale'])['removed'])) { $removed[] = $item['type'] . ':' . $item['id'] . ':' . $item['locale']; } } $delivery = ['delivery_id' => wp_generate_uuid4(), 'head' => $page['head'], 'removed' => $removed, 'cursor' => $page['cursor'], 'complete' => $page['complete']]; update_option('chat4u_pending_delivery', $delivery, false); } $result = Chat4U_Protocol::remote('events', ['installation_id' => $state['scope'], 'topic' => 'changes', 'head' => $delivery['head'], 'removed' => $delivery['removed'], 'delivery_id' => $delivery['delivery_id']], $state); if (is_wp_error($result)) { $attempts = min(6, (int) get_option('chat4u_delivery_attempts', 0) + 1); update_option('chat4u_delivery_attempts', $attempts, false); wp_schedule_single_event(time() + min(900, 15 * (2 ** $attempts)), 'chat4u_deliver'); return; } update_option('chat4u_delivery_attempts', 0, false); update_option('chat4u_delivered_cursor', $delivery['cursor'], false); delete_option('chat4u_pending_delivery'); if (!$delivery['complete']) { wp_schedule_single_event(time() + 2, 'chat4u_deliver'); } } public static function cleanup(): void { global $wpdb; $table = self::table(); $wpdb->query($wpdb->prepare('DELETE FROM %i WHERE happened_at < %s LIMIT 10000', $table, gmdate('Y-m-d H:i:s', time() - 7 * DAY_IN_SECONDS))); $cutoff = (int) $wpdb->get_var($wpdb->prepare('SELECT sequence FROM %i ORDER BY sequence DESC LIMIT 1 OFFSET 99999', $table)); if ($cutoff) { $wpdb->query($wpdb->prepare('DELETE FROM %i WHERE sequence < %d LIMIT 10000', $table, $cutoff)); } $pattern = $wpdb->esc_like('chat4u_req_') . '%'; $wpdb->query($wpdb->prepare('DELETE FROM %i WHERE option_name LIKE %s AND CAST(option_value AS UNSIGNED) < %d LIMIT 10000', $wpdb->options, $pattern, time())); } public static function deactivate(): void { $state = Chat4U_Protocol::connection(); if (($state['state'] ?? '') === 'connected') { Chat4U_Protocol::remote('events', ['installation_id' => $state['scope'], 'delivery_id' => wp_generate_uuid4(), 'topic' => 'deactivated'], $state); } wp_clear_scheduled_hook('chat4u_deliver'); wp_clear_scheduled_hook('chat4u_cleanup'); } }