add_action('rest_api_init', function () { register_rest_route('myaio/v1', '/sitemap-update', [ 'methods' => 'POST', 'callback' => 'myaio_sac_update_sitemap_api', 'permission_callback' => 'myaio_sac_rest_permission_check', ]); }); function myaio_sac_update_sitemap_api(WP_REST_Request $request) { try { $sitemap_url = esc_url_raw((string) $request->get_param('sitemap_url')); $url = esc_url_raw((string) $request->get_param('url')); $priority = trim((string) $request->get_param('priority')); $changefreq = trim((string) $request->get_param('changefreq')); $action = strtolower(trim((string) $request->get_param('action'))); if (!$sitemap_url || !$url || !in_array($action, ['add', 'remove'], true)) { return new WP_REST_Response([ 'success' => false, 'message' => 'Required params: sitemap_url, url, action(add/remove).', ], 400); } if ($priority === '') { $priority = '0.5'; } if ($changefreq === '') { $changefreq = 'monthly'; } if (!preg_match('/^(0(\.\d+)?|1(\.0)?)$/', $priority)) { return new WP_REST_Response([ 'success' => false, 'message' => 'Priority must be between 0.0 and 1.0.', ], 400); } $allowed_freq = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never']; if (!in_array($changefreq, $allowed_freq, true)) { return new WP_REST_Response([ 'success' => false, 'message' => 'Invalid changefreq.', ], 400); } $file_path = myaio_sac_sitemap_url_to_local_path($sitemap_url); if (!$file_path) { return new WP_REST_Response([ 'success' => false, 'message' => 'Only local sitemap URLs from this website can be updated.', ], 400); } if (!file_exists($file_path)) { return new WP_REST_Response([ 'success' => false, 'message' => 'Sitemap file does not exist.', 'file_path' => $file_path, ], 404); } if (!is_writable($file_path)) { return new WP_REST_Response([ 'success' => false, 'message' => 'Sitemap file is not writable.', 'file_path' => $file_path, ], 403); } $xml_string = file_get_contents($file_path); if (!$xml_string) { return new WP_REST_Response([ 'success' => false, 'message' => 'Unable to read sitemap file.', ], 500); } libxml_use_internal_errors(true); $xml = simplexml_load_string($xml_string); libxml_clear_errors(); if (!$xml) { return new WP_REST_Response([ 'success' => false, 'message' => 'Invalid XML sitemap.', ], 500); } $type = myaio_sac_dashboard_parse_sitemap_type($xml); if ($type !== 'urlset') { return new WP_REST_Response([ 'success' => false, 'message' => 'Only URL sitemap files can be updated. Sitemap index files cannot directly contain page URLs.', ], 400); } $result = myaio_sac_update_urlset_xml($xml, $url, $priority, $changefreq, $action); $dom = dom_import_simplexml($xml)->ownerDocument; $dom->formatOutput = true; $saved = file_put_contents($file_path, $dom->saveXML()); if ($saved === false) { return new WP_REST_Response([ 'success' => false, 'message' => 'Failed to save sitemap file.', ], 500); } return rest_ensure_response([ 'success' => true, 'action' => $action, 'message' => $result['message'], 'sitemap_url' => $sitemap_url, 'url' => $url, 'priority' => $priority, 'changefreq' => $changefreq, 'lastmod' => gmdate('c'), ]); } catch (Throwable $e) { return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage(), ], 500); } } function myaio_sac_update_urlset_xml(SimpleXMLElement $xml, $url, $priority, $changefreq, $action) { $target_norm = myaio_sac_dashboard_normalize_url($url); $nodes = $xml->xpath('/*[local-name()="urlset"]/*[local-name()="url"]'); foreach ((array) $nodes as $node) { $loc_nodes = $node->xpath('./*[local-name()="loc"]'); $loc = $loc_nodes ? trim((string) $loc_nodes[0]) : ''; if (myaio_sac_dashboard_normalize_url($loc) !== $target_norm) { continue; } if ($action === 'remove') { $dom_node = dom_import_simplexml($node); $dom_node->parentNode->removeChild($dom_node); return [ 'message' => 'URL removed from sitemap.', ]; } myaio_sac_set_or_update_xml_child($node, 'lastmod', gmdate('c')); myaio_sac_set_or_update_xml_child($node, 'changefreq', $changefreq); myaio_sac_set_or_update_xml_child($node, 'priority', $priority); return [ 'message' => 'URL already existed. Sitemap entry updated.', ]; } if ($action === 'remove') { return [ 'message' => 'URL was not found in sitemap. Nothing removed.', ]; } $new = $xml->addChild('url'); $new->addChild('loc', esc_url_raw($url)); $new->addChild('lastmod', gmdate('c')); $new->addChild('changefreq', $changefreq); $new->addChild('priority', $priority); return [ 'message' => 'URL added to sitemap.', ]; } function myaio_sac_set_or_update_xml_child(SimpleXMLElement $node, $child_name, $value) { $children = $node->xpath('./*[local-name()="' . $child_name . '"]'); if ($children && isset($children[0])) { $children[0][0] = $value; return; } $node->addChild($child_name, $value); } function myaio_sac_sitemap_url_to_local_path($sitemap_url) { $site_url = home_url('/'); $site_parts = wp_parse_url($site_url); $sitemap_parts = wp_parse_url($sitemap_url); if (empty($site_parts['host']) || empty($sitemap_parts['host'])) { return false; } if (strtolower($site_parts['host']) !== strtolower($sitemap_parts['host'])) { return false; } $path = $sitemap_parts['path'] ?? ''; if ($path === '') { return false; } $path = ltrim($path, '/'); if (strpos($path, '..') !== false) { return false; } if (!preg_match('/sitemap.*\.xml$/i', basename($path))) { return false; } return trailingslashit(ABSPATH) . $path; }