PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.25
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.25
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
metasync / views / metasync-xml-sitemap.php

metasync-xml-sitemap.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.25, at views/metasync-xml-sitemap.php

1,163 lines 75.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The XML Sitemap admin page view
4 *
5 * @link https://searchatlas.com
6 * @since 1.0.0
7 *
8 * @package Metasync
9 * @subpackage Metasync/views
10 */
11
12 if (!defined('ABSPATH')) {
13 exit; // Exit if accessed directly
14 }
15
16 require_once dirname(__DIR__) . '/includes/sitemap-taxonomy-picker.php';
17 ?>
18
19 <?php $this->render_layout_open('XML Sitemap', 'xml_sitemap', 'Manage your XML sitemap settings and status.'); ?>
20
21 <div class="metasync-sitemap-tabs">
22 <ul class="metasync-tab-nav">
23 <li>
24 <a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-xml-sitemap&tab=general')); ?>"
25 data-tab="general">
26 <?php esc_html_e('General', 'metasync'); ?>
27 </a>
28 </li>
29 <li>
30 <a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-xml-sitemap&tab=news')); ?>"
31 data-tab="news">
32 <?php esc_html_e('News Sitemap', 'metasync'); ?>
33 </a>
34 </li>
35 <li>
36 <a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-xml-sitemap&tab=video')); ?>"
37 data-tab="video">
38 <?php esc_html_e('Video Sitemap', 'metasync'); ?>
39 </a>
40 </li>
41 </ul>
42 </div>
43
44 <!-- General Tab -->
45 <div id="metasync-sitemap-general" class="metasync-sitemap-tab-content">
46
47 <div class="metasync-sitemap-outer">
48 <div class="metasync-sitemap-container">
49
50 <!-- Sitemap Status Card -->
51 <div class="dashboard-card metasync-sitemap-status-card">
52 <h2>Sitemap Status</h2>
53 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">Monitor your sitemap generation and status.</p>
54
55 <?php
56 $sitemap_files = $sitemap_generator->get_sitemap_files();
57 $sitemap_count = count($sitemap_files);
58
59 // Check news/video sitemap status
60 $news_sm_enabled = !empty($news_settings['enabled']);
61 $video_sm_enabled = !empty($video_settings['enabled']);
62 $news_sm_exists = false !== get_transient('metasync_vsm_' . md5('news-sitemap.xml'))
63 || file_exists(ABSPATH . 'news-sitemap.xml');
64 $video_sm_exists = false !== get_transient('metasync_vsm_' . md5('video-sitemap.xml'))
65 || file_exists(ABSPATH . 'video-sitemap.xml');
66 $extra_sitemap_count = ($news_sm_exists ? 1 : 0) + ($video_sm_exists ? 1 : 0);
67 $total_sitemap_count = $sitemap_count + $extra_sitemap_count;
68
69 // Count URLs in news/video sitemaps for total
70 $news_url_count = 0;
71 $video_url_count = 0;
72 if ($news_sm_exists) {
73 $news_xml = get_transient('metasync_vsm_' . md5('news-sitemap.xml'));
74 if (!$news_xml && file_exists(ABSPATH . 'news-sitemap.xml')) {
75 $news_xml = file_get_contents(ABSPATH . 'news-sitemap.xml');
76 }
77 if ($news_xml) {
78 $news_url_count = substr_count($news_xml, '<url>');
79 }
80 }
81 if ($video_sm_exists) {
82 $video_xml = get_transient('metasync_vsm_' . md5('video-sitemap.xml'));
83 if (!$video_xml && file_exists(ABSPATH . 'video-sitemap.xml')) {
84 $video_xml = file_get_contents(ABSPATH . 'video-sitemap.xml');
85 }
86 if ($video_xml) {
87 $video_url_count = substr_count($video_xml, '<url>');
88 }
89 }
90 $total_url_count = $url_count + $news_url_count + $video_url_count;
91 $any_sitemap_exists = $sitemap_exists || $news_sm_exists || $video_sm_exists;
92 ?>
93
94 <div class="metasync-sitemap-status-grid">
95 <div class="metasync-sitemap-stat">
96 <div class="stat-icon"><span class="dashicons dashicons-media-default"></span></div>
97 <div class="stat-content">
98 <div class="stat-label">Status</div>
99 <div class="stat-value <?php echo $any_sitemap_exists ? 'status-active' : 'status-inactive'; ?>">
100 <?php
101 echo $any_sitemap_exists ? 'Generated' : 'Not Generated';
102 if ($sitemap_exists && $sitemap_generator->is_virtual_mode()): ?>
103 <span style="color: #2271b1; font-size: 12px; margin-left: 5px;">(Virtual)</span>
104 <?php endif; ?>
105 </div>
106 </div>
107 </div>
108
109 <div class="metasync-sitemap-stat">
110 <div class="stat-icon"><span class="dashicons dashicons-admin-links"></span></div>
111 <div class="stat-content">
112 <div class="stat-label">Total URLs</div>
113 <div class="stat-value"><?php echo number_format($total_url_count); ?></div>
114 </div>
115 </div>
116
117 <div class="metasync-sitemap-stat">
118 <div class="stat-icon"><span class="dashicons dashicons-list-view"></span></div>
119 <div class="stat-content">
120 <div class="stat-label">Sitemap Files</div>
121 <div class="stat-value"><?php echo $total_sitemap_count; ?></div>
122 </div>
123 </div>
124
125 <div class="metasync-sitemap-stat">
126 <div class="stat-icon"><span class="dashicons dashicons-update"></span></div>
127 <div class="stat-content">
128 <div class="stat-label">Auto-Update</div>
129 <div class="stat-value <?php echo $auto_update_enabled ? 'status-active' : 'status-inactive'; ?>">
130 <?php echo $auto_update_enabled ? 'Enabled' : 'Disabled'; ?>
131 </div>
132 </div>
133 </div>
134
135 <div class="metasync-sitemap-stat">
136 <div class="stat-icon"><span class="dashicons dashicons-clock"></span></div>
137 <div class="stat-content">
138 <div class="stat-label">Last Generated</div>
139 <div class="stat-value stat-value-small">
140 <?php
141 if ($last_generated) {
142 $time_diff = human_time_diff(strtotime($last_generated), current_time('timestamp'));
143 echo esc_html($time_diff) . ' ago';
144 } else {
145 echo 'Never';
146 }
147 ?>
148 </div>
149 </div>
150 </div>
151 </div>
152
153 <?php if ($sitemap_exists): ?>
154 <div class="metasync-sitemap-url-box">
155 <strong>Sitemap Index URL:</strong>
156 <a href="<?php echo esc_url($sitemap_url); ?>" target="_blank" class="metasync-sitemap-link">
157 <?php echo esc_url($sitemap_url); ?>
158 <span class="dashicons dashicons-external"></span>
159 </a>
160 <p style="margin: 10px 0 0 0; color: var(--dashboard-text-secondary); font-size: 13px;">
161 <em>Submit this URL to Google Search Console for indexing.</em>
162 </p>
163 </div>
164 <?php endif; ?>
165 </div>
166
167 <?php if (!empty($active_sitemap_plugins)): ?>
168 <!-- Info notice about other sitemap plugins -->
169 <div class="dashboard-card metasync-sitemap-info-notice">
170 <div class="metasync-info-notice-content">
171 <span class="dashicons dashicons-info" style="font-size: 20px; color: var(--dashboard-text-secondary);"></span>
172 <div>
173 <strong>Other Sitemap Plugins Detected:</strong>
174 <p style="margin: 5px 0 0 0; color: var(--dashboard-text-secondary); font-size: 14px;">
175 <?php
176 $plugin_names = array_values($active_sitemap_plugins);
177 echo esc_html(implode(', ', $plugin_names));
178 ?>
179 <?php if (count($active_sitemap_plugins) === 1): ?>
180 is active. This plugin's sitemap will be automatically disabled when you generate the sitemap below.
181 <?php else: ?>
182 are active. These plugins' sitemaps will be automatically disabled when you generate the sitemap below.
183 <?php endif; ?>
184 </p>
185 </div>
186 </div>
187 </div>
188 <?php endif; ?>
189
190 <!-- Content Settings Card -->
191 <div class="dashboard-card metasync-sitemap-content-settings-card">
192 <h2><?php esc_html_e('Content Settings', 'metasync'); ?></h2>
193 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">
194 <?php esc_html_e('Control which content types and URLs are included in the main sitemap.', 'metasync'); ?>
195 </p>
196
197 <form method="post">
198 <?php wp_nonce_field('metasync_sitemap_settings_action', 'metasync_sitemap_settings_nonce'); ?>
199
200 <table class="form-table">
201 <tr>
202 <th scope="row"><?php esc_html_e('Post Types', 'metasync'); ?></th>
203 <td>
204 <?php
205 $general_filtered_pts = metasync_get_sitemap_post_type_objects();
206 $sitemap_configured = !empty($sitemap_settings['_configured']);
207 $selected_sitemap_pts = !empty($sitemap_settings['post_types']) ? (array) $sitemap_settings['post_types'] : [];
208 $gen_pts_scrollable = count($general_filtered_pts) > 10;
209 if ($gen_pts_scrollable) : ?>
210 <input type="text" class="metasync-checkbox-search" placeholder="<?php esc_attr_e('Filter post types...', 'metasync'); ?>">
211 <?php endif; ?>
212 <div class="metasync-checkbox-list <?php echo $gen_pts_scrollable ? 'metasync-checkbox-scroll' : ''; ?>">
213 <?php foreach ($general_filtered_pts as $pt) : ?>
214 <label>
215 <input type="checkbox" name="sitemap_post_types[]" value="<?php echo esc_attr($pt->name); ?>"
216 <?php checked(!$sitemap_configured || in_array($pt->name, $selected_sitemap_pts, true)); ?> />
217 <?php echo esc_html($pt->label); ?>
218 </label>
219 <?php endforeach; ?>
220 <p class="metasync-no-results"><?php esc_html_e('No post types match your search.', 'metasync'); ?></p>
221 </div>
222 <p class="description"><?php esc_html_e('Select which post types to include. All are included by default.', 'metasync'); ?></p>
223 </td>
224 </tr>
225 <tr>
226 <th scope="row">
227 <?php esc_html_e('Categories', 'metasync'); ?>
228 <?php $gen_categories = get_categories(['hide_empty' => false]); ?>
229 <span class="metasync-checkbox-count">(<?php echo count($gen_categories); ?>)</span>
230 </th>
231 <td>
232 <?php
233 $selected_gen_cats = isset($sitemap_settings['categories']) ? (array) $sitemap_settings['categories'] : [];
234 $gen_cats_scrollable = count($gen_categories) > 10;
235 if ($gen_cats_scrollable) : ?>
236 <input type="text" class="metasync-checkbox-search" placeholder="<?php esc_attr_e('Filter categories...', 'metasync'); ?>">
237 <?php endif; ?>
238 <div class="metasync-checkbox-list <?php echo $gen_cats_scrollable ? 'metasync-checkbox-scroll' : ''; ?>">
239 <?php foreach ($gen_categories as $cat) : ?>
240 <label>
241 <input type="checkbox" name="sitemap_categories[]" value="<?php echo esc_attr($cat->term_id); ?>"
242 <?php checked(in_array($cat->term_id, $selected_gen_cats)); ?> />
243 <?php echo esc_html($cat->name); ?>
244 </label>
245 <?php endforeach; ?>
246 <p class="metasync-no-results"><?php esc_html_e('No categories match your search.', 'metasync'); ?></p>
247 </div>
248 <p class="description"><?php esc_html_e('Leave empty to include all categories.', 'metasync'); ?></p>
249 </td>
250 </tr>
251 <tr>
252 <th scope="row">
253 <?php esc_html_e('Tags', 'metasync'); ?>
254 <?php $gen_tags = get_tags(['hide_empty' => false]); ?>
255 <span class="metasync-checkbox-count">(<?php echo count($gen_tags); ?>)</span>
256 </th>
257 <td>
258 <?php
259 $selected_gen_tags = isset($sitemap_settings['tags']) ? (array) $sitemap_settings['tags'] : [];
260 if (!empty($gen_tags)) :
261 $gen_tags_scrollable = count($gen_tags) > 10;
262 if ($gen_tags_scrollable) : ?>
263 <input type="text" class="metasync-checkbox-search" placeholder="<?php esc_attr_e('Filter tags...', 'metasync'); ?>">
264 <?php endif; ?>
265 <div class="metasync-checkbox-list <?php echo $gen_tags_scrollable ? 'metasync-checkbox-scroll' : ''; ?>">
266 <?php foreach ($gen_tags as $tag) : ?>
267 <label>
268 <input type="checkbox" name="sitemap_tags[]" value="<?php echo esc_attr($tag->term_id); ?>"
269 <?php checked(in_array($tag->term_id, $selected_gen_tags)); ?> />
270 <?php echo esc_html($tag->name); ?>
271 </label>
272 <?php endforeach; ?>
273 <p class="metasync-no-results"><?php esc_html_e('No tags match your search.', 'metasync'); ?></p>
274 </div>
275 <?php else : ?>
276 <p class="description"><?php esc_html_e('No tags found.', 'metasync'); ?></p>
277 <?php endif; ?>
278 <p class="description"><?php esc_html_e('Leave empty to include all tags.', 'metasync'); ?></p>
279 </td>
280 </tr>
281 <tr>
282 <th scope="row"><?php esc_html_e('Taxonomies', 'metasync'); ?></th>
283 <td>
284 <?php
285 $all_taxonomies = get_taxonomies(['public' => true], 'objects');
286 $filtered_taxonomies = metasync_get_sitemap_taxonomy_picker_taxonomies($all_taxonomies);
287 $selected_sitemap_taxes = !empty($sitemap_settings['taxonomies']) ? (array) $sitemap_settings['taxonomies'] : [];
288 $gen_tax_scrollable = count($filtered_taxonomies) > 10;
289 if ($gen_tax_scrollable) : ?>
290 <input type="text" class="metasync-checkbox-search" placeholder="<?php esc_attr_e('Filter taxonomies...', 'metasync'); ?>">
291 <?php endif; ?>
292 <div class="metasync-checkbox-list <?php echo $gen_tax_scrollable ? 'metasync-checkbox-scroll' : ''; ?>">
293 <?php foreach ($filtered_taxonomies as $tax) : ?>
294 <label>
295 <input type="checkbox" name="sitemap_taxonomies[]" value="<?php echo esc_attr($tax->name); ?>"
296 <?php checked(!$sitemap_configured || in_array($tax->name, $selected_sitemap_taxes, true)); ?> />
297 <?php echo esc_html($tax->label); ?> <code><?php echo esc_html($tax->name); ?></code>
298 </label>
299 <?php endforeach; ?>
300 <p class="metasync-no-results"><?php esc_html_e('No taxonomies match your search.', 'metasync'); ?></p>
301 </div>
302 <p class="description"><?php esc_html_e('Select which taxonomy archive pages to include. All are included by default.', 'metasync'); ?></p>
303 </td>
304 </tr>
305 <tr>
306 <th scope="row"><?php esc_html_e('Exclude URLs', 'metasync'); ?></th>
307 <td>
308 <textarea name="sitemap_excluded_urls" rows="5" class="large-text code" placeholder="<?php esc_attr_e("https://example.com/page-to-exclude/\nhttps://example.com/another-page/", 'metasync'); ?>"><?php echo esc_textarea($sitemap_settings['excluded_urls'] ?? ''); ?></textarea>
309 <p class="description"><?php esc_html_e('Enter one URL per line. These URLs will be excluded from the main sitemap.', 'metasync'); ?></p>
310 </td>
311 </tr>
312 </table>
313
314 <?php submit_button(esc_html__('Save & Generate Sitemap', 'metasync'), 'primary', 'save_sitemap_settings'); ?>
315 </form>
316 </div>
317
318 <!-- Generate Sitemap Card -->
319 <div class="dashboard-card metasync-sitemap-actions-card">
320 <h2>Sitemap Actions</h2>
321 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">
322 Generate or manage your XML sitemap with the controls below.
323 </p>
324
325
326
327 <div class="metasync-sitemap-actions-grid">
328 <form method="post" action="" class="metasync-sitemap-action-form">
329 <?php wp_nonce_field('metasync_sitemap_action', 'metasync_sitemap_nonce'); ?>
330 <button type="submit" name="generate_sitemap" class="button button-primary button-hero metasync-sitemap-button-large">
331 <span class="dashicons dashicons-update"></span>
332 Generate Sitemaps Now
333 </button>
334 <p class="metasync-button-description">
335 <?php
336 $urls_per_sitemap = $sitemap_generator->get_urls_per_sitemap();
337 if (!empty($active_sitemap_plugins)): ?>
338 Creates sitemap files (split into <?php echo number_format($urls_per_sitemap); ?> URLs each) and disables conflicting sitemaps.
339 <?php else: ?>
340 Creates sitemap index and individual sitemap files (split into <?php echo number_format($urls_per_sitemap); ?> URLs each).
341 <?php endif; ?>
342 </p>
343 </form>
344
345 <form method="post" action="" class="metasync-sitemap-action-form">
346 <?php wp_nonce_field('metasync_sitemap_action', 'metasync_sitemap_nonce'); ?>
347 <?php if ($auto_update_enabled): ?>
348 <button type="submit" name="disable_auto_update" class="button button-secondary button-hero metasync-sitemap-button-large">
349 <span class="dashicons dashicons-no"></span>
350 Disable Auto-Update
351 </button>
352 <p class="metasync-button-description">
353 Currently enabled - sitemap updates automatically when posts change.
354 </p>
355 <?php else: ?>
356 <button type="submit" name="enable_auto_update" class="button button-secondary button-hero metasync-sitemap-button-large">
357 <span class="dashicons dashicons-yes"></span>
358 Enable Auto-Update
359 </button>
360 <p class="metasync-button-description">
361 Automatically regenerate sitemap when posts are created or updated.
362 </p>
363 <?php endif; ?>
364 </form>
365
366 <?php if ($any_sitemap_exists): ?>
367 <form method="post" action="" class="metasync-sitemap-action-form" onsubmit="return confirm('Are you sure you want to delete all sitemap files? This action cannot be undone.');">
368 <?php wp_nonce_field('metasync_sitemap_action', 'metasync_sitemap_nonce'); ?>
369 <button type="submit" name="delete_sitemap" class="button button-secondary button-hero metasync-sitemap-button-large" style="border-color: #dc3232; color: #dc3232;">
370 <span class="dashicons dashicons-trash"></span>
371 Delete All Sitemaps
372 </button>
373 <p class="metasync-button-description">
374 Permanently delete all sitemap files from your server.
375 </p>
376 </form>
377 <?php endif; ?>
378
379 <?php if (!empty($active_sitemap_plugins)): ?>
380 <form method="post" action="" class="metasync-sitemap-action-form">
381 <?php wp_nonce_field('metasync_sitemap_action', 'metasync_sitemap_nonce'); ?>
382 <button type="submit" name="enable_other_sitemaps" class="button button-secondary button-hero metasync-sitemap-button-large" style="border-color: #00a32a; color: #00a32a;">
383 <span class="dashicons dashicons-controls-repeat"></span>
384 Re-enable Other Sitemaps
385 </button>
386 <p class="metasync-button-description">
387 Re-enable sitemap generation from <?php echo esc_html(implode(', ', array_values($active_sitemap_plugins))); ?>.
388 </p>
389 </form>
390 <?php endif; ?>
391
392 <!-- Import from SEO Plugins Button (Always visible) -->
393 <div >
394 <a href="<?php echo esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '-import-external&tab=sitemap')); ?>" class="button button-secondary button-hero metasync-sitemap-button-large">
395 <span class="dashicons dashicons-download" style="margin-top:3px;font-size:15px;width:15px;height:15px;"></span> Import from SEO Plugins
396 </a>
397 <p class="metasync-button-description">
398 Import sitemap settings from other SEO plugins like Yoast, Rank Math, or AIOSEO.
399 </p>
400 </div>
401 </div>
402
403 </div>
404
405 <?php if (($sitemap_exists && !empty($sitemap_files)) || $news_sm_exists || $video_sm_exists || $news_sm_enabled || $video_sm_enabled): ?>
406 <!-- Sitemap Files List Card -->
407 <div class="dashboard-card metasync-sitemap-files-card">
408 <h2>Sitemap Files</h2>
409 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">
410 <?php if ($total_sitemap_count > 0): ?>
411 Your sitemap has <?php echo $total_sitemap_count; ?> file<?php echo $total_sitemap_count > 1 ? 's' : ''; ?> — <?php echo $sitemap_count; ?> main (up to <?php echo number_format($sitemap_generator->get_urls_per_sitemap()); ?> URLs each)<?php
412 if ($news_sm_exists || $video_sm_exists) {
413 $extras = [];
414 if ($news_sm_exists) $extras[] = 'news';
415 if ($video_sm_exists) $extras[] = 'video';
416 echo ', plus ' . implode(' &amp; ', $extras);
417 }
418 ?>.
419 <?php else: ?>
420 <?php esc_html_e('No sitemap files generated yet. Use "Generate Sitemaps Now" above to create them.', 'metasync'); ?>
421 <?php endif; ?>
422 </p>
423
424 <table class="metasync-sitemap-table">
425 <thead>
426 <tr>
427 <th><?php esc_html_e('Sitemap File', 'metasync'); ?></th>
428 <th><?php esc_html_e('URLs', 'metasync'); ?></th>
429 <th><?php esc_html_e('Last Modified', 'metasync'); ?></th>
430 <th><?php esc_html_e('Actions', 'metasync'); ?></th>
431 </tr>
432 </thead>
433 <tbody>
434 <?php if ($sitemap_exists && !empty($sitemap_files)):
435 $urls_per_file = $sitemap_generator->get_urls_per_sitemap();
436 $total_urls = $url_count;
437 foreach ($sitemap_files as $index => $sitemap_file):
438 // Calculate URLs in this file
439 $start_url = ($index * $urls_per_file) + 1;
440 $end_url = min(($index + 1) * $urls_per_file, $total_urls);
441 $urls_in_file = $end_url - $start_url + 1;
442 ?>
443 <tr>
444 <td>
445 <div class="sitemap-filename"><?php echo esc_html($sitemap_file['filename']); ?></div>
446 <div class="sitemap-url-range">
447 URLs <?php echo number_format($start_url); ?> - <?php echo number_format($end_url); ?>
448 </div>
449 </td>
450 <td><?php echo number_format($urls_in_file); ?></td>
451 <td>
452 <?php
453 if ($last_generated) {
454 echo esc_html(date('M j, Y g:i A', strtotime($last_generated)));
455 } else {
456 echo '—';
457 }
458 ?>
459 </td>
460 <td>
461 <a href="<?php echo esc_url($sitemap_file['url']); ?>" target="_blank" class="button-view">
462 <span class="dashicons dashicons-external"></span>
463 View
464 </a>
465 <?php if ($index === 0): ?>
466 <form method="post" action="" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete the General sitemap? This action cannot be undone.');">
467 <?php wp_nonce_field('metasync_sitemap_action', 'metasync_sitemap_nonce'); ?>
468 <button type="submit" name="delete_general_sitemap" class="button-delete">
469 <span class="dashicons dashicons-trash"></span>
470 <?php esc_html_e('Delete', 'metasync'); ?>
471 </button>
472 </form>
473 <?php endif; ?>
474 </td>
475 </tr>
476 <?php endforeach; endif; ?>
477
478 <?php if ($news_sm_enabled): ?>
479 <tr>
480 <td>
481 <div class="sitemap-filename">news-sitemap.xml</div>
482 <div class="sitemap-url-range"><?php esc_html_e('Google News Sitemap', 'metasync'); ?></div>
483 </td>
484 <td><?php echo $news_sm_exists ? $news_url_count : '<em style="color:var(--dashboard-text-secondary);">Not generated</em>'; ?></td>
485 <td><?php echo ($news_sm_exists && $last_generated) ? esc_html(date('M j, Y g:i A', strtotime($last_generated))) : '—'; ?></td>
486 <td>
487 <?php if ($news_sm_exists): ?>
488 <a href="<?php echo esc_url(home_url('/news-sitemap.xml')); ?>" target="_blank" class="button-view">
489 <span class="dashicons dashicons-external"></span>
490 View
491 </a>
492 <form method="post" action="" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete the News sitemap? This action cannot be undone.');">
493 <?php wp_nonce_field('metasync_sitemap_action', 'metasync_sitemap_nonce'); ?>
494 <button type="submit" name="delete_news_sitemap" class="button-delete">
495 <span class="dashicons dashicons-trash"></span>
496 <?php esc_html_e('Delete', 'metasync'); ?>
497 </button>
498 </form>
499 <?php else: ?>
500 <span style="color:var(--dashboard-text-secondary); font-style:italic;"><?php esc_html_e('Enabled — generate to create file', 'metasync'); ?></span>
501 <?php endif; ?>
502 </td>
503 </tr>
504 <?php endif; ?>
505
506 <?php if ($video_sm_enabled): ?>
507 <tr>
508 <td>
509 <div class="sitemap-filename">video-sitemap.xml</div>
510 <div class="sitemap-url-range"><?php esc_html_e('Video Sitemap', 'metasync'); ?></div>
511 </td>
512 <td><?php echo $video_sm_exists ? $video_url_count : '<em style="color:var(--dashboard-text-secondary);">Not generated</em>'; ?></td>
513 <td><?php echo ($video_sm_exists && $last_generated) ? esc_html(date('M j, Y g:i A', strtotime($last_generated))) : '—'; ?></td>
514 <td>
515 <?php if ($video_sm_exists): ?>
516 <a href="<?php echo esc_url(home_url('/video-sitemap.xml')); ?>" target="_blank" class="button-view">
517 <span class="dashicons dashicons-external"></span>
518 View
519 </a>
520 <form method="post" action="" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete the Video sitemap? This action cannot be undone.');">
521 <?php wp_nonce_field('metasync_sitemap_action', 'metasync_sitemap_nonce'); ?>
522 <button type="submit" name="delete_video_sitemap" class="button-delete">
523 <span class="dashicons dashicons-trash"></span>
524 <?php esc_html_e('Delete', 'metasync'); ?>
525 </button>
526 </form>
527 <?php else: ?>
528 <span style="color:var(--dashboard-text-secondary); font-style:italic;"><?php esc_html_e('Enabled — generate to create file', 'metasync'); ?></span>
529 <?php endif; ?>
530 </td>
531 </tr>
532 <?php endif; ?>
533 </tbody>
534 </table>
535 </div>
536
537 <!-- Sitemap Index Preview Card -->
538 <div class="dashboard-card metasync-sitemap-preview-card">
539 <h2>Sitemap Index Preview</h2>
540 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">
541 Preview the sitemap index file that references all individual sitemaps.
542 </p>
543
544 <div class="metasync-sitemap-preview-container">
545 <div class="metasync-sitemap-preview-header">
546 <span class="metasync-preview-label">sitemap_index.xml</span>
547 <a href="<?php echo esc_url($sitemap_url); ?>" target="_blank" class="button button-small">
548 <span class="dashicons dashicons-external"></span>
549 View Sitemap Index
550 </a>
551 </div>
552 <div class="metasync-sitemap-code-block">
553 <pre><?php
554 $content = $sitemap_generator->get_sitemap_content();
555 if ($content) {
556 $lines = explode("\n", $content);
557 $preview_lines = array_slice($lines, 0, 50);
558 echo esc_html(implode("\n", $preview_lines));
559 if (count($lines) > 50) {
560 echo "\n... [" . (count($lines) - 50) . " more lines]";
561 }
562 } else {
563 echo "Unable to read sitemap index content.";
564 }
565 ?></pre>
566 </div>
567 </div>
568 </div>
569 <?php endif; ?>
570
571 </div>
572 </div>
573 </div><!-- /#metasync-sitemap-general -->
574
575 <!-- News Sitemap Tab -->
576 <div id="metasync-sitemap-news" class="metasync-sitemap-tab-content">
577 <div class="metasync-sitemap-outer">
578 <div class="metasync-sitemap-container">
579
580 <?php
581 require_once plugin_dir_path(dirname(__FILE__)) . 'sitemap/class-metasync-sitemap-news.php';
582 $news_checker = new Metasync_Sitemap_News();
583 $news_conflicts = $news_checker->get_conflict_notices();
584 $news_has_content = false !== get_transient('metasync_vsm_' . md5('news-sitemap.xml'))
585 || file_exists(ABSPATH . 'news-sitemap.xml');
586 $news_conflict_name = $news_checker->get_conflict_plugin_name();
587 $mss_brand = Metasync::get_effective_plugin_name();
588
589 # Flag a generated-but-empty news sitemap. $news_url_count is already
590 # computed once near the top of this file for the status card, so reuse it
591 # rather than re-reading and re-scanning the XML on the same page load.
592 $news_empty_warning = !empty($news_settings['enabled'])
593 && empty($news_conflicts)
594 && $news_has_content
595 && $news_url_count === 0;
596 ?>
597
598 <?php if (!empty($news_conflict_name)): ?>
599 <div class="metasync-sitemap-conflict-note" style="display:flex; gap:7px; align-items:center; margin:0 0 16px; padding:8px 12px; border-radius:7px; background:rgba(245,158,11,0.08); border:1px solid rgba(245,158,11,0.28); font-size:12px; line-height:1.5; color:var(--dashboard-text-secondary,#6b7280);">
600 <span style="font-size:13px; line-height:1;">&#9888;&#65039;</span>
601 <span><strong style="color:#8a5a00;"><?php echo esc_html($news_conflict_name); ?></strong> <?php printf(esc_html__('is also active — its news sitemap settings may conflict with %s\'s.', 'metasync'), esc_html($mss_brand)); ?></span>
602 </div>
603 <?php endif; ?>
604
605 <!-- News Sitemap Status -->
606 <div class="dashboard-card" style="margin-bottom: 20px;">
607 <h2><?php esc_html_e('News Sitemap Status', 'metasync'); ?></h2>
608 <div style="display: flex; gap: 24px; align-items: center; margin-top: 16px; flex-wrap: wrap;">
609 <div style="display: flex; align-items: center; gap: 10px;">
610 <span style="font-size: 20px;">📄</span>
611 <div>
612 <div style="color: var(--dashboard-text-secondary); font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em;"><?php esc_html_e('Status', 'metasync'); ?></div>
613 <?php if (!empty($news_settings['enabled']) && $news_has_content && empty($news_conflicts)): ?>
614 <div style="color: var(--dashboard-success, #10b981); font-weight: 600;"><?php esc_html_e('Generated', 'metasync'); ?></div>
615 <?php elseif (!empty($news_settings['enabled']) && !$news_has_content && empty($news_conflicts)): ?>
616 <div style="color: var(--dashboard-warning, #f59e0b); font-weight: 600;"><?php esc_html_e('Not Generated', 'metasync'); ?></div>
617 <?php elseif (!empty($news_conflicts)): ?>
618 <div style="color: var(--dashboard-error, #ef4444); font-weight: 600;"><?php esc_html_e('Conflict Detected', 'metasync'); ?></div>
619 <?php else: ?>
620 <div style="color: var(--dashboard-text-secondary); font-weight: 600;"><?php esc_html_e('Disabled', 'metasync'); ?></div>
621 <?php endif; ?>
622 </div>
623 </div>
624 <?php if (!empty($news_settings['enabled']) && $news_has_content && empty($news_conflicts)): ?>
625 <div style="display: flex; align-items: center; gap: 10px;">
626 <span style="font-size: 20px;">🔗</span>
627 <div>
628 <div style="color: var(--dashboard-text-secondary); font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em;"><?php esc_html_e('URL', 'metasync'); ?></div>
629 <a href="<?php echo esc_url(home_url('/news-sitemap.xml')); ?>" target="_blank" style="color: var(--dashboard-accent, #3b82f6); font-weight: 500; text-decoration: none;">
630 <?php echo esc_html(home_url('/news-sitemap.xml')); ?>
631 <span class="dashicons dashicons-external" style="font-size: 14px; width: 14px; height: 14px; vertical-align: middle;"></span>
632 </a>
633 </div>
634 </div>
635 <?php endif; ?>
636 </div>
637
638 <?php if (!empty($news_settings['enabled']) && empty($news_conflicts)): ?>
639 <div style="margin-top: 20px;">
640 <form method="post" style="display: inline;">
641 <?php wp_nonce_field('metasync_news_sitemap_action', 'metasync_news_sitemap_nonce'); ?>
642 <button type="submit" name="generate_news_sitemap" class="button button-primary button-hero metasync-sitemap-button-large">
643 <span class="dashicons dashicons-update"></span>
644 <?php $news_has_content ? esc_html_e('Regenerate News Sitemap', 'metasync') : esc_html_e('Generate News Sitemap Now', 'metasync'); ?>
645 </button>
646 </form>
647 <p style="margin-top: 8px; color: var(--dashboard-text-secondary); font-size: 13px;">
648 <?php esc_html_e('Generates the news sitemap with articles from the last 2 days (max 1,000 URLs).', 'metasync'); ?>
649 </p>
650 </div>
651 <?php endif; ?>
652 </div>
653
654 <?php foreach ($news_conflicts as $notice) {
655 $notice_type = (strpos($notice, 'can coexist') !== false) ? 'notice-info' : 'notice-warning';
656 echo '<div class="notice ' . $notice_type . ' inline" style="margin-bottom: 12px; padding: 12px 16px; border-radius: 8px; background: rgba(255, 152, 0, 0.1); border-left: 4px solid var(--dashboard-warning, #f59e0b); color: var(--dashboard-text-primary, #fff);"><p style="margin: 0;">&#9888; ' . esc_html($notice) . '</p></div>';
657 } ?>
658
659 <?php if ($news_empty_warning): ?>
660 <div class="notice notice-warning inline" style="margin-bottom: 12px; padding: 12px 40px 12px 16px; border-radius: 8px; background: rgba(255, 152, 0, 0.1); border-left: 4px solid var(--dashboard-warning, #f59e0b); color: var(--dashboard-text-primary, #fff);">
661 <p style="margin: 0;">
662 &#9888; <?php esc_html_e('The last generated news sitemap contains 0 URLs. A news sitemap only includes posts published in the last 2 days, so this can be normal. If you expected entries here, regenerate it above and review the Post Types and filters below.', 'metasync'); ?>
663 </p>
664 </div>
665 <?php endif; ?>
666
667 <div class="dashboard-card">
668 <h2><?php esc_html_e('News Sitemap Settings', 'metasync'); ?></h2>
669 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">
670 <?php esc_html_e('Configure the Google News Sitemap. Only articles published within the last 2 days will be included (max 1,000 URLs).', 'metasync'); ?>
671 </p>
672
673 <form method="post">
674 <?php wp_nonce_field('metasync_news_sitemap_action', 'metasync_news_sitemap_nonce'); ?>
675
676 <table class="form-table">
677 <tr>
678 <th scope="row"><?php esc_html_e('Enable News Sitemap', 'metasync'); ?><?php Metasync::render_tooltip_icon('news_sitemap_enable_info', 'A News sitemap is a special list of your recent news articles (published in the last 48 hours) that helps Google News find and show them quickly. Only turn this on if your site publishes news-style content and is accepted into Google News.'); ?></th>
679 <td>
680 <label>
681 <input type="checkbox" name="news_enabled" value="1" <?php checked(!empty($news_settings['enabled'])); ?> />
682 <?php esc_html_e('Enable Google News Sitemap at /news-sitemap.xml', 'metasync'); ?>
683 </label>
684 </td>
685 </tr>
686 <tr>
687 <th scope="row"><?php esc_html_e('Post Types', 'metasync'); ?><?php Metasync::render_tooltip_icon('news_sitemap_post_types', 'Choose which content types are included in your Google News sitemap. Only select types that qualify as news content — including everything can hurt News eligibility.'); ?></th>
688 <td>
689 <?php
690 $filtered_pts = metasync_get_sitemap_post_type_objects();
691 $selected_post_types = isset($news_settings['post_types']) ? (array) $news_settings['post_types'] : ['post'];
692 $pts_scrollable = count($filtered_pts) > 10;
693 if ($pts_scrollable) : ?>
694 <input type="text" class="metasync-checkbox-search" placeholder="<?php esc_attr_e('Filter post types...', 'metasync'); ?>">
695 <?php endif; ?>
696 <div class="metasync-checkbox-list <?php echo $pts_scrollable ? 'metasync-checkbox-scroll' : ''; ?>">
697 <?php foreach ($filtered_pts as $pt) : ?>
698 <label>
699 <input type="checkbox" name="news_post_types[]" value="<?php echo esc_attr($pt->name); ?>"
700 <?php checked(in_array($pt->name, $selected_post_types, true)); ?> />
701 <?php echo esc_html($pt->label); ?>
702 </label>
703 <?php endforeach; ?>
704 <p class="metasync-no-results"><?php esc_html_e('No post types match your search.', 'metasync'); ?></p>
705 </div>
706 </td>
707 </tr>
708 <tr>
709 <th scope="row">
710 <?php esc_html_e('Categories', 'metasync'); ?>
711 <?php $categories = get_categories(['hide_empty' => false]); ?>
712 <span class="metasync-checkbox-count">(<?php echo count($categories); ?>)</span>
713 </th>
714 <td>
715 <?php
716 $selected_cats = isset($news_settings['categories']) ? (array) $news_settings['categories'] : [];
717 $cats_scrollable = count($categories) > 10;
718 if ($cats_scrollable) : ?>
719 <input type="text" class="metasync-checkbox-search" placeholder="<?php esc_attr_e('Filter categories...', 'metasync'); ?>">
720 <?php endif; ?>
721 <div class="metasync-checkbox-list <?php echo $cats_scrollable ? 'metasync-checkbox-scroll' : ''; ?>">
722 <?php foreach ($categories as $cat) : ?>
723 <label>
724 <input type="checkbox" name="news_categories[]" value="<?php echo esc_attr($cat->term_id); ?>"
725 <?php checked(in_array($cat->term_id, $selected_cats)); ?> />
726 <?php echo esc_html($cat->name); ?>
727 </label>
728 <?php endforeach; ?>
729 <p class="metasync-no-results"><?php esc_html_e('No categories match your search.', 'metasync'); ?></p>
730 </div>
731 <p class="description"><?php esc_html_e('Leave empty to include all categories.', 'metasync'); ?></p>
732 </td>
733 </tr>
734 <tr>
735 <th scope="row">
736 <?php esc_html_e('Tags', 'metasync'); ?>
737 <?php $tags = get_tags(['hide_empty' => false]); ?>
738 <span class="metasync-checkbox-count">(<?php echo count($tags); ?>)</span>
739 </th>
740 <td>
741 <?php
742 $selected_tags = isset($news_settings['tags']) ? (array) $news_settings['tags'] : [];
743 if (!empty($tags)) :
744 $tags_scrollable = count($tags) > 10;
745 if ($tags_scrollable) : ?>
746 <input type="text" class="metasync-checkbox-search" placeholder="<?php esc_attr_e('Filter tags...', 'metasync'); ?>">
747 <?php endif; ?>
748 <div class="metasync-checkbox-list <?php echo $tags_scrollable ? 'metasync-checkbox-scroll' : ''; ?>">
749 <?php foreach ($tags as $tag) : ?>
750 <label>
751 <input type="checkbox" name="news_tags[]" value="<?php echo esc_attr($tag->term_id); ?>"
752 <?php checked(in_array($tag->term_id, $selected_tags)); ?> />
753 <?php echo esc_html($tag->name); ?>
754 </label>
755 <?php endforeach; ?>
756 <p class="metasync-no-results"><?php esc_html_e('No tags match your search.', 'metasync'); ?></p>
757 </div>
758 <?php else : ?>
759 <p class="description"><?php esc_html_e('No tags found.', 'metasync'); ?></p>
760 <?php endif; ?>
761 <p class="description"><?php esc_html_e('Leave empty to include all tags.', 'metasync'); ?></p>
762 </td>
763 </tr>
764 <?php
765 // Generic taxonomy filters for news sitemap
766 $news_custom_taxonomies = get_taxonomies(['public' => true, '_builtin' => false], 'objects');
767 $news_saved_taxonomies = isset($news_settings['taxonomies']) ? (array) $news_settings['taxonomies'] : [];
768 if (!empty($news_custom_taxonomies)) :
769 foreach ($news_custom_taxonomies as $tax) :
770 $tax_terms = get_terms(['taxonomy' => $tax->name, 'hide_empty' => false]);
771 if (empty($tax_terms) || is_wp_error($tax_terms)) continue;
772 $selected_term_ids = isset($news_saved_taxonomies[$tax->name]) ? (array) $news_saved_taxonomies[$tax->name] : [];
773 ?>
774 <tr>
775 <th scope="row">
776 <?php echo esc_html($tax->label); ?>
777 <span class="metasync-checkbox-count">(<?php echo count($tax_terms); ?>)</span>
778 </th>
779 <td>
780 <?php
781 $tax_scrollable = count($tax_terms) > 10;
782 if ($tax_scrollable) : ?>
783 <input type="text" class="metasync-checkbox-search" placeholder="<?php echo esc_attr(sprintf(__('Filter %s...', 'metasync'), strtolower($tax->label))); ?>">
784 <?php endif; ?>
785 <div class="metasync-checkbox-list <?php echo $tax_scrollable ? 'metasync-checkbox-scroll' : ''; ?>">
786 <?php foreach ($tax_terms as $term) : ?>
787 <label>
788 <input type="checkbox" name="news_taxonomies[<?php echo esc_attr($tax->name); ?>][]" value="<?php echo esc_attr($term->term_id); ?>"
789 <?php checked(in_array($term->term_id, $selected_term_ids)); ?> />
790 <?php echo esc_html($term->name); ?>
791 </label>
792 <?php endforeach; ?>
793 <p class="metasync-no-results"><?php esc_html_e('No items match your search.', 'metasync'); ?></p>
794 </div>
795 <p class="description"><?php esc_html_e('Leave empty to include all.', 'metasync'); ?></p>
796 </td>
797 </tr>
798 <?php
799 endforeach;
800 endif;
801 ?>
802 <tr>
803 <th scope="row"><?php esc_html_e('Exclude URLs', 'metasync'); ?></th>
804 <td>
805 <textarea name="news_excluded_urls" rows="4" class="large-text code" placeholder="<?php esc_attr_e("https://example.com/post-to-exclude/\nhttps://example.com/another-post/", 'metasync'); ?>"><?php echo esc_textarea($news_settings['excluded_urls'] ?? ''); ?></textarea>
806 <p class="description"><?php esc_html_e('Enter one URL per line. These URLs will be excluded from the news sitemap.', 'metasync'); ?></p>
807 </td>
808 </tr>
809 <tr>
810 <th scope="row"><?php esc_html_e('Publication Name', 'metasync'); ?></th>
811 <td>
812 <input type="text" name="publication_name" class="regular-text"
813 value="<?php echo esc_attr(!empty($news_settings['publication_name']) ? $news_settings['publication_name'] : get_bloginfo('name')); ?>" />
814 <p class="description"><?php esc_html_e('Defaults to your site name.', 'metasync'); ?></p>
815 </td>
816 </tr>
817 <tr>
818 <th scope="row"><?php esc_html_e('Publication Language', 'metasync'); ?></th>
819 <td>
820 <input type="text" name="publication_language" class="small-text"
821 value="<?php echo esc_attr(!empty($news_settings['publication_language']) ? $news_settings['publication_language'] : substr(get_locale(), 0, 2)); ?>" />
822 <p class="description"><?php esc_html_e('ISO 639 language code (e.g. en, fr, de). Defaults to site language.', 'metasync'); ?></p>
823 </td>
824 </tr>
825 </table>
826
827 <?php submit_button(esc_html__('Save & Generate Sitemap', 'metasync'), 'primary', 'save_news_sitemap'); ?>
828 </form>
829 </div>
830 </div>
831 </div>
832 </div><!-- /#metasync-sitemap-news -->
833
834 <!-- Video Sitemap Tab -->
835 <div id="metasync-sitemap-video" class="metasync-sitemap-tab-content">
836 <div class="metasync-sitemap-outer">
837 <div class="metasync-sitemap-container">
838
839 <?php
840 require_once plugin_dir_path(dirname(__FILE__)) . 'sitemap/class-metasync-sitemap-video.php';
841 $video_checker = new Metasync_Sitemap_Video();
842 $video_conflicts = $video_checker->get_conflict_notices();
843 $video_has_content = false !== get_transient('metasync_vsm_' . md5('video-sitemap.xml'))
844 || file_exists(ABSPATH . 'video-sitemap.xml');
845 $video_conflict_name = $video_checker->get_conflict_plugin_name();
846 $mss_brand = Metasync::get_effective_plugin_name();
847
848 # Why videos were left out. Recorded by generate_video_sitemap() so the
849 # notices below can name the actual cause: a post with a video but no
850 # resolvable thumbnail is skipped (thumbnail_loc is required by Google),
851 # which is invisible without this.
852 $video_diagnostics = get_option('metasync_video_sitemap_diagnostics', []);
853 $video_skipped_no_thumb = isset($video_diagnostics['skipped_no_thumbnail'])
854 ? (int) $video_diagnostics['skipped_no_thumbnail']
855 : 0;
856 $video_skipped_ids = isset($video_diagnostics['skipped_no_thumbnail_ids'])
857 ? (array) $video_diagnostics['skipped_no_thumbnail_ids']
858 : [];
859 # Videos, not posts: a post that emits one working video and drops
860 # another is invisible to the per-post counter, and that partial case
861 # is the more common shape on a real catalog.
862 $video_skipped_videos = isset($video_diagnostics['skipped_no_thumbnail_videos'])
863 ? (int) $video_diagnostics['skipped_no_thumbnail_videos']
864 : $video_skipped_no_thumb;
865 $video_skipped_dupe = isset($video_diagnostics['skipped_duplicate_thumbnail'])
866 ? (int) $video_diagnostics['skipped_duplicate_thumbnail']
867 : 0;
868
869 # Common gate: the feature is on, nothing else owns the video sitemap,
870 # and something has actually been generated to report on.
871 $video_notices_active = !empty($video_settings['enabled'])
872 && empty($video_conflicts)
873 && $video_has_content;
874
875 # Posts whose video was dropped for want of a thumbnail. Deliberately
876 # NOT gated on an empty sitemap: the common case is a mixed site where
877 # most videos resolve and a few do not, and gating on $video_url_count
878 # === 0 would keep exactly that case silent — the silence this notice
879 # exists to end.
880 $video_nothumb_notice = $video_notices_active && $video_skipped_videos > 0;
881
882 # A generated-but-empty sitemap with nothing skipped means detection
883 # found no videos at all, which is a different problem with a different
884 # fix (post types, or a theme-injected player). $video_url_count is
885 # already computed near the top of this file for the status card.
886 $video_no_videos_notice = $video_notices_active
887 && $video_url_count === 0
888 && $video_skipped_videos === 0;
889
890 # A post-level image (featured image, OG image, first content image)
891 # may only be the thumbnail for ONE video on that post: Google treats a
892 # reused thumbnail as a sign that two videos are the same. Extra videos
893 # on such a post are left out, which needs saying even when the sitemap
894 # is otherwise healthy.
895 $video_dupe_notice = $video_notices_active && $video_skipped_dupe > 0;
896 ?>
897
898 <?php if (!empty($video_conflict_name)): ?>
899 <div class="metasync-sitemap-conflict-note" style="display:flex; gap:7px; align-items:center; margin:0 0 16px; padding:8px 12px; border-radius:7px; background:rgba(245,158,11,0.08); border:1px solid rgba(245,158,11,0.28); font-size:12px; line-height:1.5; color:var(--dashboard-text-secondary,#6b7280);">
900 <span style="font-size:13px; line-height:1;">&#9888;&#65039;</span>
901 <span><strong style="color:#8a5a00;"><?php echo esc_html($video_conflict_name); ?></strong> <?php printf(esc_html__('is also active — its video sitemap settings may conflict with %s\'s.', 'metasync'), esc_html($mss_brand)); ?></span>
902 </div>
903 <?php endif; ?>
904
905 <!-- Video Sitemap Status -->
906 <div class="dashboard-card" style="margin-bottom: 20px;">
907 <h2><?php esc_html_e('Video Sitemap Status', 'metasync'); ?></h2>
908 <div style="display: flex; gap: 24px; align-items: center; margin-top: 16px; flex-wrap: wrap;">
909 <div style="display: flex; align-items: center; gap: 10px;">
910 <span style="font-size: 20px;">📄</span>
911 <div>
912 <div style="color: var(--dashboard-text-secondary); font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em;"><?php esc_html_e('Status', 'metasync'); ?></div>
913 <?php if (!empty($video_settings['enabled']) && $video_has_content && empty($video_conflicts)): ?>
914 <div style="color: var(--dashboard-success, #10b981); font-weight: 600;"><?php esc_html_e('Generated', 'metasync'); ?></div>
915 <?php elseif (!empty($video_settings['enabled']) && !$video_has_content && empty($video_conflicts)): ?>
916 <div style="color: var(--dashboard-warning, #f59e0b); font-weight: 600;"><?php esc_html_e('Not Generated', 'metasync'); ?></div>
917 <?php elseif (!empty($video_conflicts)): ?>
918 <div style="color: var(--dashboard-error, #ef4444); font-weight: 600;"><?php esc_html_e('Conflict Detected', 'metasync'); ?></div>
919 <?php else: ?>
920 <div style="color: var(--dashboard-text-secondary); font-weight: 600;"><?php esc_html_e('Disabled', 'metasync'); ?></div>
921 <?php endif; ?>
922 </div>
923 </div>
924 <?php if (!empty($video_settings['enabled']) && $video_has_content && empty($video_conflicts)): ?>
925 <div style="display: flex; align-items: center; gap: 10px;">
926 <span style="font-size: 20px;">🔗</span>
927 <div>
928 <div style="color: var(--dashboard-text-secondary); font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em;"><?php esc_html_e('URL', 'metasync'); ?></div>
929 <a href="<?php echo esc_url(home_url('/video-sitemap.xml')); ?>" target="_blank" style="color: var(--dashboard-accent, #3b82f6); font-weight: 500; text-decoration: none;">
930 <?php echo esc_html(home_url('/video-sitemap.xml')); ?>
931 <span class="dashicons dashicons-external" style="font-size: 14px; width: 14px; height: 14px; vertical-align: middle;"></span>
932 </a>
933 </div>
934 </div>
935 <?php endif; ?>
936 </div>
937
938 <?php if (!empty($video_settings['enabled']) && empty($video_conflicts)): ?>
939 <div style="margin-top: 20px;">
940 <form method="post" style="display: inline;">
941 <?php wp_nonce_field('metasync_video_sitemap_action', 'metasync_video_sitemap_nonce'); ?>
942 <button type="submit" name="generate_video_sitemap" class="button button-primary button-hero metasync-sitemap-button-large">
943 <span class="dashicons dashicons-update"></span>
944 <?php $video_has_content ? esc_html_e('Regenerate Video Sitemap', 'metasync') : esc_html_e('Generate Video Sitemap Now', 'metasync'); ?>
945 </button>
946 </form>
947 <p style="margin-top: 8px; color: var(--dashboard-text-secondary); font-size: 13px;">
948 <?php esc_html_e('Scans all posts for embedded videos and generates the video sitemap.', 'metasync'); ?>
949 </p>
950 </div>
951 <?php endif; ?>
952 </div>
953
954 <?php foreach ($video_conflicts as $notice) {
955 $notice_type = (strpos($notice, 'can coexist') !== false) ? 'notice-info' : 'notice-warning';
956 echo '<div class="notice ' . $notice_type . ' inline" style="margin-bottom: 12px; padding: 12px 16px; border-radius: 8px; background: rgba(255, 152, 0, 0.1); border-left: 4px solid var(--dashboard-warning, #f59e0b); color: var(--dashboard-text-primary, #fff);"><p style="margin: 0;">&#9888; ' . esc_html($notice) . '</p></div>';
957 } ?>
958
959 <?php if ($video_nothumb_notice): ?>
960 <div class="notice notice-warning inline" style="margin-bottom: 12px; padding: 12px 40px 12px 16px; border-radius: 8px; background: rgba(255, 152, 0, 0.1); border-left: 4px solid var(--dashboard-warning, #f59e0b); color: var(--dashboard-text-primary, #fff);">
961 <p style="margin: 0;">
962 &#9888;
963 <?php
964 printf(
965 esc_html(
966 /* translators: %s: number of posts. */
967 _n(
968 '%s video was left out because no thumbnail could be resolved for it. Google requires a thumbnail on every video sitemap entry — set a featured image on the post, or fill Thumbnail URL in the Video Sitemap box.',
969 '%s videos were left out because no thumbnail could be resolved for them. Google requires a thumbnail on every video sitemap entry — set a featured image on those posts, or fill Thumbnail URL in the Video Sitemap box.',
970 $video_skipped_videos,
971 'metasync'
972 )
973 ),
974 esc_html(number_format_i18n($video_skipped_videos))
975 );
976 ?>
977 </p>
978 <?php if (!empty($video_skipped_ids)): ?>
979 <p style="margin: 8px 0 0;">
980 <?php esc_html_e('Affected posts:', 'metasync'); ?>
981 <?php
982 $video_skipped_links = [];
983 foreach (array_slice($video_skipped_ids, 0, 20) as $skipped_id) {
984 $skipped_id = (int) $skipped_id;
985 $skipped_title = get_the_title($skipped_id);
986 if ($skipped_title === '') {
987 /* translators: %d: post ID. */
988 $skipped_title = sprintf(esc_html__('Post %d', 'metasync'), $skipped_id);
989 }
990 // Null for a post deleted since the last generation, an
991 // unregistered post type, or a user without edit_post.
992 // esc_url(null) both emits a PHP 8.1 deprecation and
993 // renders an anchor pointing at the current page.
994 $skipped_link = get_edit_post_link($skipped_id);
995 if (empty($skipped_link)) {
996 $video_skipped_links[] = esc_html($skipped_title);
997 continue;
998 }
999 $video_skipped_links[] = '<a href="' . esc_url($skipped_link) . '" target="_blank" style="color: var(--dashboard-accent, #3b82f6);">' . esc_html($skipped_title) . '</a>';
1000 }
1001 echo wp_kses_post(implode(', ', $video_skipped_links));
1002 if ($video_skipped_videos > count($video_skipped_links)) {
1003 echo ' ' . esc_html(
1004 sprintf(
1005 /* translators: %s: number of additional posts. */
1006 __('and %s more.', 'metasync'),
1007 number_format_i18n($video_skipped_videos - count($video_skipped_links))
1008 )
1009 );
1010 }
1011 ?>
1012 </p>
1013 <?php endif; ?>
1014 </div>
1015 <?php endif; ?>
1016
1017 <?php if ($video_no_videos_notice): ?>
1018 <div class="notice notice-warning inline" style="margin-bottom: 12px; padding: 12px 40px 12px 16px; border-radius: 8px; background: rgba(255, 152, 0, 0.1); border-left: 4px solid var(--dashboard-warning, #f59e0b); color: var(--dashboard-text-primary, #fff);">
1019 <p style="margin: 0;">
1020 &#9888; <?php esc_html_e('The last generated video sitemap contains 0 URLs, because no videos were found in the selected post types. Check that the right content types are ticked under Post Types below. Note that players injected by a theme or page builder are not visible in post content — if that is your setup, point the plugin at the custom field holding the video with the Video URL Meta Keys setting below.', 'metasync'); ?>
1021 </p>
1022 </div>
1023 <?php endif; ?>
1024
1025 <?php if ($video_dupe_notice): ?>
1026 <div class="notice notice-info inline" style="margin-bottom: 12px; padding: 12px 40px 12px 16px; border-radius: 8px; background: rgba(59, 130, 246, 0.1); border-left: 4px solid var(--dashboard-accent, #3b82f6); color: var(--dashboard-text-primary, #fff);">
1027 <p style="margin: 0;">
1028 <?php
1029 printf(
1030 esc_html(
1031 /* translators: %s: number of videos. */
1032 _n(
1033 '%s video was left out because the only thumbnail available was already used by another video on the same post. Google treats a reused thumbnail as a sign that two videos are the same, so every video needs its own image. The Thumbnail URL field and thumbnail custom fields apply to the whole post, so giving each video on one post a distinct thumbnail needs the metasync_video_sitemap_thumbnail filter — or split the videos across separate posts.',
1034 '%s videos were left out because the only thumbnail available was already used by another video on the same post. Google treats a reused thumbnail as a sign that two videos are the same, so every video needs its own image. The Thumbnail URL field and thumbnail custom fields apply to the whole post, so giving each video on one post a distinct thumbnail needs the metasync_video_sitemap_thumbnail filter — or split the videos across separate posts.',
1035 $video_skipped_dupe,
1036 'metasync'
1037 )
1038 ),
1039 esc_html(number_format_i18n($video_skipped_dupe))
1040 );
1041 ?>
1042 </p>
1043 </div>
1044 <?php endif; ?>
1045
1046 <div class="dashboard-card">
1047 <h2><?php esc_html_e('Video Sitemap Settings', 'metasync'); ?></h2>
1048 <p style="color: var(--dashboard-text-secondary); margin-bottom: 20px;">
1049 <?php esc_html_e('Configure the Video Sitemap. Auto-detects YouTube, Vimeo, VideoPress, and self-hosted videos in your content.', 'metasync'); ?>
1050 </p>
1051
1052 <form method="post">
1053 <?php wp_nonce_field('metasync_video_sitemap_action', 'metasync_video_sitemap_nonce'); ?>
1054
1055 <table class="form-table">
1056 <tr>
1057 <th scope="row"><?php esc_html_e('Enable Video Sitemap', 'metasync'); ?><?php Metasync::render_tooltip_icon('video_sitemap_enable_info', 'A Video sitemap lists the videos embedded on your pages (with their title, description, thumbnail, and duration) so search engines can understand them and show them in video search results. Turn this on if your pages contain videos you want discovered.'); ?></th>
1058 <td>
1059 <label>
1060 <input type="checkbox" name="video_enabled" value="1" <?php checked(!empty($video_settings['enabled'])); ?> />
1061 <?php esc_html_e('Enable Video Sitemap at /video-sitemap.xml', 'metasync'); ?>
1062 </label>
1063 </td>
1064 </tr>
1065 <tr>
1066 <th scope="row"><?php esc_html_e('Post Types', 'metasync'); ?><?php Metasync::render_tooltip_icon('video_sitemap_post_types', 'Choose which content types are scanned for embedded videos to include in your video sitemap.'); ?></th>
1067 <td>
1068 <?php
1069 $vid_filtered_pts = metasync_get_sitemap_post_type_objects();
1070 $selected_video_pts = isset($video_settings['post_types']) ? (array) $video_settings['post_types'] : ['post', 'page'];
1071 $vid_pts_scrollable = count($vid_filtered_pts) > 10;
1072 if ($vid_pts_scrollable) : ?>
1073 <input type="text" class="metasync-checkbox-search" placeholder="<?php esc_attr_e('Filter post types...', 'metasync'); ?>">
1074 <?php endif; ?>
1075 <div class="metasync-checkbox-list <?php echo $vid_pts_scrollable ? 'metasync-checkbox-scroll' : ''; ?>">
1076 <?php foreach ($vid_filtered_pts as $pt) : ?>
1077 <label>
1078 <input type="checkbox" name="video_post_types[]" value="<?php echo esc_attr($pt->name); ?>"
1079 <?php checked(in_array($pt->name, $selected_video_pts, true)); ?> />
1080 <?php echo esc_html($pt->label); ?>
1081 </label>
1082 <?php endforeach; ?>
1083 <p class="metasync-no-results"><?php esc_html_e('No post types match your search.', 'metasync'); ?></p>
1084 </div>
1085 </td>
1086 </tr>
1087 <tr>
1088 <th scope="row"><?php esc_html_e('Auto-Detect Videos', 'metasync'); ?></th>
1089 <td>
1090 <label>
1091 <input type="checkbox" name="auto_detect" value="1" <?php checked(!empty($video_settings['auto_detect'])); ?> />
1092 <?php esc_html_e('Automatically detect embedded videos from post content', 'metasync'); ?>
1093 </label>
1094 <p class="description"><?php esc_html_e('Detects YouTube, Vimeo, Vimeo OTT (VHX), VideoPress, and self-hosted &lt;video&gt; tags in post content.', 'metasync'); ?></p>
1095 </td>
1096 </tr>
1097 <tr>
1098 <th scope="row"><?php esc_html_e('Video URL Meta Keys', 'metasync'); ?><?php Metasync::render_tooltip_icon('video_sitemap_url_meta_keys', 'For sites whose theme or page builder stores the video in a custom field (ACF, for example) instead of the post content. Enter the custom-field name(s) holding the video URL, one per line. Leave empty if your videos are embedded directly in the content.'); ?></th>
1099 <td>
1100 <textarea name="video_url_meta_keys" rows="3" class="large-text code" placeholder="<?php esc_attr_e("my_video_url\nepisode_embed_url", 'metasync'); ?>"><?php echo esc_textarea($video_settings['video_url_meta_keys'] ?? ''); ?></textarea>
1101 <p class="description"><?php esc_html_e('One custom-field name per line. Use this when the video player is added by your theme or page builder, so the video URL never appears in the post content. Checked in the order listed; the first field with a valid URL wins.', 'metasync'); ?></p>
1102 </td>
1103 </tr>
1104 <tr>
1105 <th scope="row"><?php esc_html_e('Thumbnail Meta Keys', 'metasync'); ?><?php Metasync::render_tooltip_icon('video_sitemap_thumbnail_meta_keys', 'Custom-field name(s) holding the video thumbnail image URL. Google requires a thumbnail for every video, and entries without one are left out. If empty, the plugin falls back to the post\'s Open Graph image, then its featured image, then the first image in the content.'); ?></th>
1106 <td>
1107 <textarea name="video_thumbnail_meta_keys" rows="3" class="large-text code" placeholder="<?php esc_attr_e("my_video_thumbnail\nepisode_poster", 'metasync'); ?>"><?php echo esc_textarea($video_settings['video_thumbnail_meta_keys'] ?? ''); ?></textarea>
1108 <p class="description"><?php esc_html_e('One custom-field name per line. Falls back to the post\'s Open Graph image, then the featured image, then the first image in the content. Each video needs its own thumbnail — Google treats a reused thumbnail as a sign that two videos are the same.', 'metasync'); ?></p>
1109 </td>
1110 </tr>
1111 <?php
1112 // Taxonomy filters for video sitemap
1113 $video_all_taxonomies = get_taxonomies(['public' => true], 'objects');
1114 $video_saved_taxonomies = isset($video_settings['taxonomies']) ? (array) $video_settings['taxonomies'] : [];
1115 // Show categories, tags, and custom taxonomies
1116 foreach ($video_all_taxonomies as $tax) :
1117 $tax_terms = get_terms(['taxonomy' => $tax->name, 'hide_empty' => false]);
1118 if (empty($tax_terms) || is_wp_error($tax_terms)) continue;
1119 $selected_term_ids = isset($video_saved_taxonomies[$tax->name]) ? (array) $video_saved_taxonomies[$tax->name] : [];
1120 ?>
1121 <tr>
1122 <th scope="row">
1123 <?php echo esc_html($tax->label); ?>
1124 <span class="metasync-checkbox-count">(<?php echo count($tax_terms); ?>)</span>
1125 </th>
1126 <td>
1127 <?php
1128 $vtax_scrollable = count($tax_terms) > 10;
1129 if ($vtax_scrollable) : ?>
1130 <input type="text" class="metasync-checkbox-search" placeholder="<?php echo esc_attr(sprintf(__('Filter %s...', 'metasync'), strtolower($tax->label))); ?>">
1131 <?php endif; ?>
1132 <div class="metasync-checkbox-list <?php echo $vtax_scrollable ? 'metasync-checkbox-scroll' : ''; ?>">
1133 <?php foreach ($tax_terms as $term) : ?>
1134 <label>
1135 <input type="checkbox" name="video_taxonomies[<?php echo esc_attr($tax->name); ?>][]" value="<?php echo esc_attr($term->term_id); ?>"
1136 <?php checked(in_array($term->term_id, $selected_term_ids)); ?> />
1137 <?php echo esc_html($term->name); ?>
1138 </label>
1139 <?php endforeach; ?>
1140 <p class="metasync-no-results"><?php esc_html_e('No items match your search.', 'metasync'); ?></p>
1141 </div>
1142 <p class="description"><?php esc_html_e('Leave empty to include all.', 'metasync'); ?></p>
1143 </td>
1144 </tr>
1145 <?php endforeach; ?>
1146 <tr>
1147 <th scope="row"><?php esc_html_e('Exclude URLs', 'metasync'); ?></th>
1148 <td>
1149 <textarea name="video_excluded_urls" rows="4" class="large-text code" placeholder="<?php esc_attr_e("https://example.com/post-to-exclude/\nhttps://example.com/another-post/", 'metasync'); ?>"><?php echo esc_textarea($video_settings['excluded_urls'] ?? ''); ?></textarea>
1150 <p class="description"><?php esc_html_e('Enter one URL per line. These URLs will be excluded from the video sitemap.', 'metasync'); ?></p>
1151 </td>
1152 </tr>
1153 </table>
1154
1155 <?php submit_button(esc_html__('Save & Generate Sitemap', 'metasync'), 'primary', 'save_video_sitemap'); ?>
1156 </form>
1157 </div>
1158 </div>
1159 </div>
1160 </div><!-- /#metasync-sitemap-video -->
1161
1162 <?php $this->render_layout_close(); ?>
1163