/
home
/
altere25
/
.trash
/
wp-to-sanity-companion.1
/
/home/altere25/.trash/wp-to-sanity-companion.1
mkdir
upload
Name
Size
Mode
Actions
assets/
-
0755
rm
includes/
-
0755
rm
templates/
-
0755
rm
assets\css\admin.css
7611
0644
edit
dl
rm
assets\js\admin.js
8534
0644
edit
dl
rm
assets\js\claude-connect.js
6502
0644
edit
dl
rm
includes\class-ad-admin.php
26374
0644
edit
dl
rm
includes\class-ad-ai-client.php
8153
0644
edit
dl
rm
includes\class-ad-auth.php
9189
0644
edit
dl
rm
includes\class-ad-cli.php
8669
0644
edit
dl
rm
includes\class-ad-extractor.php
12210
0644
edit
dl
rm
includes\class-ad-mcp.php
6429
0644
edit
dl
rm
includes\class-ad-plugin-core.php
7947
0644
edit
dl
rm
includes\class-ad-progress.php
8022
0644
edit
dl
rm
includes\class-ad-rest.php
31008
0644
edit
dl
rm
includes\class-ad-uninstall.php
1924
0644
edit
dl
rm
includes\class-ad-webhook.php
14854
0644
edit
dl
rm
templates\admin\claude-connect.php
5152
0644
edit
dl
rm
templates\admin\dashboard.php
10457
0644
edit
dl
rm
templates\admin\export.php
16570
0644
edit
dl
rm
templates\admin\log.php
10849
0644
edit
dl
rm
templates\admin\onboarding-v2.php
3827
0644
edit
dl
rm
templates\admin\onboarding.php
10714
0644
edit
dl
rm
templates\admin\progress.php
2443
0644
edit
dl
rm
templates\admin\schema-preview.php
5213
0644
edit
dl
rm
uninstall.php
365
0644
edit
dl
rm
wp-to-sanity-companion.php
4467
0644
edit
dl
rm
Edit:
/home/altere25/.trash/wp-to-sanity-companion.1/includes\class-ad-webhook.php
(14854B)
<?php /** * Altered Digital Migration — Webhook + payload builder * * Builds the webhook payload (full post data + embedded resources) and * fires it at the Worker. Also hosts the page-builder-aware data * extraction helpers (delegates builder detection to AD_Extractor). * * @package AlteredDigitalMigration */ if (!defined('ABSPATH')) { exit; } class AD_Webhook { /** @var AD_Plugin_Core */ private $core; /** @var AD_Extractor */ private $extractor; public function __construct(AD_Plugin_Core $core, AD_Extractor $extractor) { $this->core = $core; $this->extractor = $extractor; } public function register_hooks(): void { // Guard against duplicate hook registration (some sites double-fire // transition_post_status). Priority 99 keeps us after most plugins. if (!has_action('transition_post_status', [$this, 'trigger_status_webhook'])) { add_action('transition_post_status', [$this, 'trigger_status_webhook'], 99, 3); } if (!has_action('wp_trash_post', [$this, 'trigger_trash_webhook'])) { add_action('wp_trash_post', [$this, 'trigger_trash_webhook']); } } // ----------------------------------------------------------------- // Triggers // ----------------------------------------------------------------- /** * Fire on post status change. Skips unconfigured post types and * (optionally) drafts. Sends the full post payload to the Worker. * * @param string $old_status * @param string $new_status * @param WP_Post $post */ public function trigger_status_webhook($old_status, $new_status, $post): void { $settings = $this->core->settings(); if (!in_array($post->post_type, $settings['post_types'] ?? ['post', 'page'], true)) { return; } if (!$settings['auto_sync'] && $new_status !== 'publish') { return; } if (!$settings['include_drafts'] && in_array($new_status, ['draft', 'pending'], true)) { return; } $webhook_url = $settings['webhook_url']; if (empty($webhook_url)) { return; } $payload = $this->build_payload($post, $old_status, $new_status); $this->send($webhook_url, $payload, $settings['webhook_secret']); } public function trigger_trash_webhook($post_id): void { $webhook_url = $this->core->setting('webhook_url'); if (empty($webhook_url)) { return; } $payload = [ 'action' => 'delete', 'id' => (int) $post_id, 'type' => get_post_type($post_id) ?: 'post', ]; $this->send($webhook_url, $payload, $this->core->setting('webhook_secret')); } /** * Fire the webhook for an explicit action (used by Quick/Bulk Export). */ public function trigger_export(WP_Post $post): void { $webhook_url = $this->core->setting('webhook_url'); if (empty($webhook_url)) { return; } $payload = $this->build_payload($post, '', 'publish'); $payload['action'] = 'publish'; $this->send($webhook_url, $payload, $this->core->setting('webhook_secret')); } // ----------------------------------------------------------------- // Payload builder // ----------------------------------------------------------------- /** * Build the WPWebhookPayload shape the Worker expects: * action, id, type, url, modified, slug, title, status, * data { ...post, _ad_builder_hint, _ad_builder_data }, * _embedded { author, wp:featuredmedia, wp:term }. */ public function build_payload(WP_Post $post, string $old_status, string $new_status): array { $export_data = $this->get_post_export_data($post); $embedded = $export_data['_embedded'] ?? []; unset($export_data['_embedded']); // Attach builder hint + raw builder data so the Worker can dispatch // to the right parser without re-detecting. $builder = $this->extractor->detect_builder_for_post($post); if ($builder['builder'] !== 'gutenberg') { $export_data['_ad_builder_hint'] = $builder['builder']; $export_data['_ad_builder_data'] = $builder['raw']; } return [ 'action' => $this->action_from_status_change($old_status, $new_status), 'id' => (int) $post->ID, 'type' => $post->post_type, 'url' => get_permalink($post->ID), 'modified' => $post->post_modified_gmt, 'slug' => $post->post_name, 'title' => $post->post_title, 'status' => $post->post_status, 'data' => $export_data, '_embedded' => $embedded, ]; } private function action_from_status_change(string $old, string $new): string { if ($new === 'trash') return 'delete'; if ($old === 'publish' && $new !== 'publish') return 'unpublish'; if ($new === 'publish' && $old !== 'publish') return 'publish'; return 'update'; } private function send(string $url, array $payload, string $secret): void { $body = wp_json_encode($payload); $signature = hash_hmac('sha256', $body, $secret); wp_remote_post($url, [ 'body' => $body, 'headers' => [ 'Content-Type' => 'application/json', 'X-WP-Webhook-Signature' => $signature, ], 'timeout' => 15, ]); } // ----------------------------------------------------------------- // Post export data (full WP REST-shape payload for one post) // ----------------------------------------------------------------- public function get_post_export_data(WP_Post $post): array { $thumbnail_id = (int) get_post_thumbnail_id($post->ID); $featured_media = $thumbnail_id ? $this->get_media_data($thumbnail_id) : null; $categories = wp_get_post_categories($post->ID, ['fields' => 'ids']); $tags = wp_get_post_tags($post->ID, ['fields' => 'ids']); $author = get_userdata((int) $post->post_author); $data = [ 'id' => (int) $post->ID, 'date' => $post->post_date_gmt, 'date_gmt' => $post->post_date_gmt, 'modified' => $post->post_modified_gmt, 'modified_gmt' => $post->post_modified_gmt, 'slug' => $post->post_name, 'status' => $post->post_status, 'type' => $post->post_type, 'link' => get_permalink($post->ID), 'title' => ['rendered' => $post->post_title], 'content' => [ 'rendered' => apply_filters('the_content', $post->post_content), 'protected' => !empty($post->post_password), ], 'excerpt' => [ 'rendered' => apply_filters('the_excerpt', $post->post_excerpt), 'protected' => false, ], 'author' => (int) $post->post_author, 'featured_media' => $thumbnail_id, 'comment_status' => $post->comment_status, 'ping_status' => $post->ping_status, 'sticky' => is_sticky($post->ID), 'template' => get_page_template_slug($post->ID), 'format' => get_post_format($post->ID) ?: 'standard', 'meta' => $this->filter_meta(get_post_meta($post->ID)), 'categories' => array_map('intval', $categories), 'tags' => array_map('intval', $tags), ]; if (function_exists('acf') && function_exists('get_fields')) { $acf = get_fields($post->ID); if ($acf) { $data['acf'] = $this->normalize_acf_fields($acf); } } if (class_exists('WPSEO_Meta')) { $yoast = WPSEO_Meta::get_all($post->ID); if (!empty($yoast)) { $data['yoast_head_json'] = $yoast; } } // wp:term is a FLAT array filtered by taxonomy on the Worker side. $data['_embedded'] = [ 'author' => $author ? [$this->get_author_data($author)] : null, 'wp:featuredmedia' => $featured_media ? [$featured_media] : null, 'wp:term' => array_merge( $this->get_terms_data('category', $categories), $this->get_terms_data('post_tag', $tags) ), ]; return $data; } private function normalize_acf_fields(array $fields): array { $normalized = []; foreach ($fields as $key => $value) { if (is_array($value) && isset($value['ID'], $value['url'])) { $normalized[$key] = $this->get_media_data((int) $value['ID']); } elseif (is_array($value) && wp_is_numeric_array($value)) { $normalized[$key] = array_map(function ($row) { return is_array($row) ? $this->normalize_acf_fields($row) : $row; }, $value); } elseif (is_array($value) && isset($value[0]) && is_object($value[0])) { $normalized[$key] = array_map(function ($p) { return [ 'ID' => (int) $p->ID, 'id' => (int) $p->ID, 'title' => $p->post_title, 'type' => $p->post_type, 'link' => get_permalink($p->ID), ]; }, $value); } else { $normalized[$key] = $value; } } return $normalized; } public function get_media_data(int $attachment_id): ?array { $attachment = get_post($attachment_id); if (!$attachment) { return null; } $meta = wp_get_attachment_metadata($attachment_id); $sizes = []; if (is_array($meta) && isset($meta['sizes'])) { foreach ($meta['sizes'] as $name => $size) { $sizes[$name] = [ 'file' => $size['file'], 'width' => (int) $size['width'], 'height' => (int) $size['height'], 'mime_type' => $attachment->post_mime_type, 'source_url' => wp_get_attachment_image_url($attachment_id, $name), ]; } } return [ 'id' => $attachment_id, 'date' => $attachment->post_date_gmt, 'slug' => $attachment->post_name, 'type' => 'attachment', 'link' => wp_get_attachment_url($attachment_id), 'title' => ['rendered' => $attachment->post_title], 'author' => (int) $attachment->post_author, 'caption' => ['rendered' => $attachment->post_excerpt], 'description' => ['rendered' => $attachment->post_content], 'media_type' => wp_attachment_is_image($attachment_id) ? 'image' : 'file', 'mime_type' => $attachment->post_mime_type, 'media_details' => [ 'width' => (int) ($meta['width'] ?? 0), 'height' => (int) ($meta['height'] ?? 0), 'file' => $meta['file'] ?? '', 'sizes' => $sizes, 'image_meta' => $meta['image_meta'] ?? [], ], 'source_url' => wp_get_attachment_url($attachment_id), 'alt_text' => get_post_meta($attachment_id, '_wp_attachment_image_alt', true), ]; } private function get_author_data(WP_User $user): array { return [ 'id' => (int) $user->ID, 'name' => $user->display_name, 'url' => $user->user_url, 'description' => $user->description, 'link' => get_author_posts_url($user->ID), 'slug' => $user->user_nicename, 'avatar_urls' => [ '24' => get_avatar_url($user->ID, ['size' => 24]), '48' => get_avatar_url($user->ID, ['size' => 48]), '96' => get_avatar_url($user->ID, ['size' => 96]), ], ]; } private function get_terms_data(string $taxonomy, array $term_ids): array { if (empty($term_ids)) { return []; } $terms = get_terms([ 'taxonomy' => $taxonomy, 'include' => array_map('intval', $term_ids), 'hide_empty' => false, ]); if (is_wp_error($terms) || empty($terms)) { return []; } return array_map(function ($term) { return [ 'id' => (int) $term->term_id, 'link' => get_term_link($term), 'name' => $term->name, 'slug' => $term->slug, 'taxonomy' => $term->taxonomy, 'parent' => (int) $term->parent, 'count' => (int) $term->count, 'description' => $term->description, ]; }, $terms); } /** * Filter sensitive meta keys before sending to the Worker. * * get_post_meta() returns ALL meta including plugin API keys, tokens, * and other secrets. We strip keys matching common sensitive patterns * before including in the webhook payload. */ private function filter_meta(array $meta): array { $deny_patterns = [ '/_wp_/', // WP internal meta '/^_elementor_/', // Elementor internal (sent separately as builder data) '/^_fl_builder_/', // Beaver internal '/^_et_pb_/', // Divi internal '/api[_-]?key/i', '/secret/i', '/password/i', '/token/i', '/_wpseo_/i', // Yoast internal '/_yoast_/i', '/stripe/i', '/paypal/i', '/aws/i', '/smtp/i', '/mailchimp/i', '/private_key/i', ]; $filtered = []; foreach ($meta as $key => $value) { $skip = false; foreach ($deny_patterns as $pattern) { if (preg_match($pattern, $key)) { $skip = true; break; } } if (!$skip) { $filtered[$key] = $value; } } return $filtered; } }
Save
cmd:
run