/
home
/
altere25
/
.trash
/
wp-to-sanity-migration.12
/
/home/altere25/.trash/wp-to-sanity-migration.12
mkdir
upload
Name
Size
Mode
Actions
acf-json\.gitkeep
0
0644
edit
dl
rm
assets\css\admin.css
12113
0644
edit
dl
rm
assets\js\admin.js
52894
0644
edit
dl
rm
includes\class-ad-admin.php
10358
0644
edit
dl
rm
includes\class-ad-ai-client.php
10724
0644
edit
dl
rm
includes\class-ad-auth.php
9189
0644
edit
dl
rm
includes\class-ad-cli.php
8671
0644
edit
dl
rm
includes\class-ad-extractor.php
17237
0644
edit
dl
rm
includes\class-ad-mcp.php
6429
0644
edit
dl
rm
includes\class-ad-plugin-core.php
10252
0644
edit
dl
rm
includes\class-ad-progress.php
8022
0644
edit
dl
rm
includes\class-ad-rest.php
43443
0644
edit
dl
rm
includes\class-ad-uninstall.php
1966
0644
edit
dl
rm
includes\class-ad-webhook.php
24172
0644
edit
dl
rm
languages\.gitkeep
0
0644
edit
dl
rm
templates\admin\wizard.php
29414
0644
edit
dl
rm
wp-to-sanity-migration\acf-json\.gitkeep
0
0644
edit
dl
rm
wp-to-sanity-migration\assets\
0
0644
edit
dl
rm
wp-to-sanity-migration\assets\css\admin.css
12113
0644
edit
dl
rm
wp-to-sanity-migration\assets\js\admin.js
52894
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-admin.php
10358
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-ai-client.php
10724
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-auth.php
9189
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-cli.php
8671
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-extractor.php
17237
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-mcp.php
6429
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-plugin-core.php
10252
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-progress.php
8022
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-rest.php
43443
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-uninstall.php
1966
0644
edit
dl
rm
wp-to-sanity-migration\includes\class-ad-webhook.php
24172
0644
edit
dl
rm
wp-to-sanity-migration\languages\.gitkeep
0
0644
edit
dl
rm
wp-to-sanity-migration\package.json
220
0644
edit
dl
rm
wp-to-sanity-migration\readme.txt
8174
0644
edit
dl
rm
wp-to-sanity-migration\templates\
0
0644
edit
dl
rm
wp-to-sanity-migration\templates\admin\wizard.php
29414
0644
edit
dl
rm
wp-to-sanity-migration\uninstall.php
365
0644
edit
dl
rm
wp-to-sanity-migration\wp-to-sanity-migration.php
4418
0644
edit
dl
rm
Edit:
/home/altere25/.trash/wp-to-sanity-migration.12/includes\class-ad-webhook.php
(24172B)
<?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; } } // Normalized SEO meta (Yoast OR Rank Math). The Worker transformer // maps this into the Sanity `seo` object. Kept alongside yoast_head_json // for back-compat with any consumers that still read the raw form. $seo_meta = $this->extract_seo_meta($post->ID); if (!empty($seo_meta)) { $data['seo_meta'] = $seo_meta; } // 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) { $row = [ '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, ]; // Term-level SEO meta (Yoast stores it in `wpseo_category` / // `wpseo_term` meta; Rank Math in `rank_math_title` etc.). $seo_meta = $this->extract_term_seo_meta($term); if (!empty($seo_meta)) { $row['seo_meta'] = $seo_meta; } return $row; }, $terms); } // ----------------------------------------------------------------- // SEO meta extraction (Yoast + Rank Math) — Phase C // ----------------------------------------------------------------- /** * Extract a normalized SEO meta object from Yoast or Rank Math post meta. * Yoast is preferred when both are present (Yoast is checked first). * * Returns an empty array when neither plugin is active for this post. * * @param int $post_id * @return array<string,mixed> */ private function extract_seo_meta(int $post_id): array { // Yoast (if WPSEO_Meta exists). Read post meta directly so we get // structured data even when the Yoast frontend integration is off. if (class_exists('WPSEO_Meta')) { $robots = $this->parse_yoast_robots($post_id); return [ 'title' => get_post_meta($post_id, '_yoast_wpseo_title', true) ?: null, 'description' => get_post_meta($post_id, '_yoast_wpseo_metadesc', true) ?: null, 'robots' => $robots, 'canonical' => get_post_meta($post_id, '_yoast_wpseo_canonical', true) ?: null, 'og_title' => get_post_meta($post_id, '_yoast_wpseo_opengraph-title', true) ?: null, 'og_description' => get_post_meta($post_id, '_yoast_wpseo_opengraph-description', true) ?: null, 'og_image' => $this->collect_og_image($post_id, '_yoast_wpseo_opengraph-image'), 'twitter_card' => get_post_meta($post_id, '_yoast_wpseo_twitter-card', true) ?: null, 'schema' => $this->extract_yoast_schema($post_id), ]; } // Rank Math if (class_exists('RankMath')) { $robots = $this->parse_rankmath_robots($post_id); $schema_raw = get_post_meta($post_id, 'rank_math_schema', true); $schema = is_string($schema_raw) ? json_decode($schema_raw, true) : null; return [ 'title' => get_post_meta($post_id, 'rank_math_title', true) ?: null, 'description' => get_post_meta($post_id, 'rank_math_description', true) ?: null, 'robots' => $robots, 'canonical' => get_post_meta($post_id, 'rank_math_canonical_url', true) ?: null, 'og_title' => get_post_meta($post_id, 'rank_math_facebook_title', true) ?: null, 'og_description' => get_post_meta($post_id, 'rank_math_facebook_description', true) ?: null, 'og_image' => $this->collect_og_image($post_id, 'rank_math_facebook_image'), 'twitter_card' => get_post_meta($post_id, 'rank_math_twitter_card_type', true) ?: null, 'schema' => is_array($schema) ? $schema : null, ]; } return []; } /** * Extract SEO meta for a term. Yoast stores term SEO in option rows * (`wpseo_taxonomy_meta`); Rank Math stores in term meta. Both paths * are best-effort — return [] when nothing is found. */ private function extract_term_seo_meta($term): array { $term_id = (int) $term->term_id; $taxonomy = $term->taxonomy; // Rank Math term meta (straightforward). if (class_exists('RankMath')) { $title = get_term_meta($term_id, 'rank_math_title', true); $desc = get_term_meta($term_id, 'rank_math_description', true); $canon = get_term_meta($term_id, 'rank_math_canonical_url', true); if ($title || $desc || $canon) { return [ 'title' => $title ?: null, 'description' => $desc ?: null, 'canonical' => $canon ?: null, 'robots' => $this->parse_rankmath_robots_term($term_id), ]; } } // Yoast term meta — stored in a site option keyed by taxonomy+term_id. if (class_exists('WPSEO_Meta')) { $all = get_option('wpseo_taxonomy_meta', []); $row = is_array($all) && isset($all[$taxonomy][$term_id]) ? $all[$taxonomy][$term_id] : []; if (is_array($row) && !empty($row)) { return [ 'title' => $row['wpseo_title'] ?? null, 'description' => $row['wpseo_desc'] ?? null, 'canonical' => $row['wpseo_canonical'] ?? null, 'og_title' => $row['wpseo_opengraph-title'] ?? null, 'og_description' => $row['wpseo_opengraph-description'] ?? null, ]; } } return []; } /** * Parse Yoast robots meta into a structured {index,follow} object. */ private function parse_yoast_robots(int $post_id): array { $raw = get_post_meta($post_id, '_yoast_wpseo_meta-robots-adv', true); if (!is_string($raw) || $raw === '') { $raw = get_post_meta($post_id, '_yoast_wpseo_meta-robots-noindex', true); // noindex = '1' → index:false; anything else → index:true. return ['index' => $raw !== '1', 'follow' => true]; } $parts = array_filter(array_map('trim', explode(',', $raw))); return [ 'index' => !in_array('noindex', $parts, true), 'follow' => !in_array('nofollow', $parts, true), ]; } /** * Parse Rank Math robots meta. Rank Math stores a comma-separated string * in `rank_math_robots` (e.g. "index,follow" or "noindex,follow"). */ private function parse_rankmath_robots(int $post_id): array { $raw = get_post_meta($post_id, 'rank_math_robots', true); if (!is_string($raw) || $raw === '') { return ['index' => true, 'follow' => true]; } $parts = array_filter(array_map('trim', explode(',', strtolower($raw)))); return [ 'index' => !in_array('noindex', $parts, true), 'follow' => !in_array('nofollow', $parts, true), ]; } private function parse_rankmath_robots_term(int $term_id): array { $raw = get_term_meta($term_id, 'rank_math_robots', true); if (!is_string($raw) || $raw === '') { return ['index' => true, 'follow' => true]; } $parts = array_filter(array_map('trim', explode(',', strtolower($raw)))); return [ 'index' => !in_array('noindex', $parts, true), 'follow' => !in_array('nofollow', $parts, true), ]; } /** * Collect the OG image URL from Yoast/Rank Math meta. Both plugins store * either an attachment ID or a URL string in the given meta key. We * resolve to a URL so the Worker can decide whether to upload as asset. */ private function collect_og_image(int $post_id, string $meta_key): ?string { $val = get_post_meta($post_id, $meta_key, true); if (empty($val)) { return null; } // Numeric → attachment ID. if (is_numeric($val)) { $url = wp_get_attachment_url((int) $val); return $url ?: null; } // Otherwise it's already a URL (Yoast stores URLs; Rank Math may // store a JSON-encoded array — best-effort unwrap). if (is_string($val)) { $decoded = json_decode($val, true); if (is_array($decoded) && !empty($decoded[0]) && is_string($decoded[0])) { return $decoded[0]; } if (filter_var($val, FILTER_VALIDATE_URL)) { return $val; } } return null; } /** * Extract Yoast's schema.org JSON-LD. Yoast stores it in * `_yoast_wpseo_schema` (deprecated) or generates it from the * `_yoast_wpseo_` meta set. The most reliable source is the * `_yoast_json_ld_old` meta key (when present). Best-effort: return null * if not found — the Worker will skip jsonLd when absent. */ private function extract_yoast_schema(int $post_id): ?array { // Newer Yoast versions store structured data in `_yoast_wpseo_wordproof_timestamp` // and generate schema at runtime; we read `_yoast_wpseo_schema` if present. $raw = get_post_meta($post_id, '_yoast_wpseo_schema', true); if (is_string($raw) && $raw !== '') { $decoded = json_decode($raw, true); if (is_array($decoded)) { return $decoded; } } return null; } /** * 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