¿Cómo generar/actualizar un sitemap XML sin plugins?
Me gusta programar todo manualmente en mis sitios WordPress, sin usar ningún plugin. ¿Hay alguna manera de generar o actualizar mi sitemap cada vez que publico/actualizo una entrada en uno de mis blogs de Multisite, sin usar plugins?

El siguiente código funciona inmediatamente. Tu mapa del sitio aparecerá en: https://your-website-name.com/sitemap.xml
Cada vez que crees o actualices una página, entrada o tipo de contenido personalizado, se mostrará. Asegúrate de añadir el nombre de tu tipo de contenido personalizado:
add_action( 'publish_post', 'ow_create_sitemap' );
add_action( 'publish_page', 'ow_create_sitemap' );
add_action( 'save_post', 'ow_create_sitemap' );
function ow_create_sitemap() {
$postsForSitemap = get_posts(array(
'numberposts' => -1,
'orderby' => 'modified',
// 'custom_post' debe ser reemplazado con tu propio Tipo de Contenido Personalizado (uno o varios)
'post_type' => array( 'post', 'page', 'custom_post' ),
'order' => 'DESC'
));
$sitemap = '<?xml version="1.0" encoding="UTF-8"?>';
$sitemap .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">';
foreach( $postsForSitemap as $post ) {
setup_postdata( $post );
$postdate = explode( " ", $post->post_modified );
$sitemap .= '<url>'.
'<loc>' . get_permalink( $post->ID ) . '</loc>' .
'<lastmod>' . $postdate[0] . '</lastmod>' .
'<changefreq>monthly</changefreq>' .
'</url>';
}
$sitemap .= '</urlset>';
$fp = fopen( ABSPATH . 'sitemap.xml', 'w' );
fwrite( $fp, $sitemap );
fclose( $fp );
}

No estoy seguro si esto funciona en multisitio, pero en una instalación única de WordPress funciona perfectamente para mí.
Cuando creas o actualizas cualquier entrada o página, generará un archivo sitemap.xml y actualizará los enlaces (URLs) con los más recientes primero (última modificación).
Copia y pega el siguiente código en el archivo functions.php de tu tema activo:
/* Función para crear el archivo sitemap.xml en el directorio raíz del sitio */
// add_action("publish_post", "eg_create_sitemap");
// add_action("publish_page", "eg_create_sitemap");
add_action( "save_post", "eg_create_sitemap" );
function eg_create_sitemap() {
$postsForSitemap = get_posts( array(
'numberposts' => -1,
'orderby' => 'modified',
'post_type' => array( 'post', 'page' ),
'order' => 'DESC'
) );
$sitemap = '<?xml version="1.0" encoding="UTF-8"?>';
$sitemap .= "\n" . '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
foreach( $postsForSitemap as $post ) {
setup_postdata( $post );
$postdate = explode( " ", $post->post_modified );
$sitemap .= "\t" . '<url>' . "\n" .
"\t\t" . '<loc>' . get_permalink( $post->ID ) . '</loc>' .
"\n\t\t" . '<lastmod>' . $postdate[0] . '</lastmod>' .
"\n\t\t" . '<changefreq>monthly</changefreq>' .
"\n\t" . '</url>' . "\n";
}
$sitemap .= '</urlset>';
$fp = fopen( ABSPATH . "sitemap.xml", 'w' );
fwrite( $fp, $sitemap );
fclose( $fp );
}

No creo que eso funcione en multisitio: está escribiendo sitemap.xml en la misma ubicación de archivo en el servidor, por lo que solo habría 1 archivo sitemap.xml real que se sobrescribiría cada vez que algún blog haga un cambio...

Consultar todas las publicaciones del sitio en cada guardado es una forma segura de colapsar un sitio que tiene una cantidad no trivial de contenido. Además, hará que guardar nuevas publicaciones sea cada vez más lento con cada publicación publicada

@MarkKaplun - ¿Qué recomendarías como alternativa a consultar todas las publicaciones?

Antes de utilizar el código proporcionado en la respuesta de w3uiguru, tuve que hacer algunas mejoras que siguen el estándar aceptado para archivos XML. El código está a continuación:
/* función para crear el archivo sitemap.xml en el directorio raíz del sitio */
// add_action("publish_post", "eg_create_sitemap");
// add_action("publish_page", "eg_create_sitemap");
add_action( "save_post", "eg_create_sitemap" );
function eg_create_sitemap() {
if ( str_replace( '-', '', get_option( 'gmt_offset' ) ) < 10 ) {
$tempo = '-0' . str_replace( '-', '', get_option( 'gmt_offset' ) );
} else {
$tempo = get_option( 'gmt_offset' );
}
if( strlen( $tempo ) == 3 ) { $tempo = $tempo . ':00'; }
$postsForSitemap = get_posts( array(
'numberposts' => -1,
'orderby' => 'modified',
'post_type' => array( 'post', 'page' ),
'order' => 'DESC'
) );
$sitemap .= '<?xml version="1.0" encoding="UTF-8"?>' . '<?xml-stylesheet type="text/xsl" href="' .
esc_url( home_url( '/' ) ) . 'sitemap.xsl"?>';
$sitemap .= "\n" . '<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
$sitemap .= "\t" . '<url>' . "\n" .
"\t\t" . '<loc>' . esc_url( home_url( '/' ) ) . '</loc>' .
"\n\t\t" . '<lastmod>' . date( "Y-m-d\TH:i:s", current_time( 'timestamp', 0 ) ) . $tempo . '</lastmod>' .
"\n\t\t" . '<changefreq>daily</changefreq>' .
"\n\t\t" . '<priority>1.0</priority>' .
"\n\t" . '</url>' . "\n";
foreach( $postsForSitemap as $post ) {
setup_postdata( $post);
$postdate = explode( " ", $post->post_modified );
$sitemap .= "\t" . '<url>' . "\n" .
"\t\t" . '<loc>' . get_permalink( $post->ID ) . '</loc>' .
"\n\t\t" . '<lastmod>' . $postdate[0] . 'T' . $postdate[1] . $tempo . '</lastmod>' .
"\n\t\t" . '<changefreq>Weekly</changefreq>' .
"\n\t\t" . '<priority>0.5</priority>' .
"\n\t" . '</url>' . "\n";
}
$sitemap .= '</urlset>';
$fp = fopen( ABSPATH . "sitemap.xml", 'w' );
fwrite( $fp, $sitemap );
fclose( $fp );
}

¿Podrías explicar tu código y presentar una [edición] para reformatearlo y mejorar su legibilidad? Gracias

@locutor-antonio-cezar @gabriel Necesitan cambiar Weekly
por weekly
debido a Error 1840: Element '{http://www.sitemaps.org/schemas/sitemap/0.9}changefreq': [facet 'enumeration'] The value 'Weekly' is not an element of the set {'always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'}
.

WordPress incluye una función de mapa del sitio XML desde la versión 5.5 que puedes personalizar.
Consulta esta entrada de blog para obtener más detalles y algunos ejemplos rápidos. Echa un vistazo aquí para ver una lista de hooks que puedes utilizar para personalizar el mapa del sitio predeterminado.

Modifiqué un poco el código de @locutor-antonio-cezar ya que estaba buscando un caso de uso muy específico. Necesitaba un sitemap especialmente escrito para Google News. ¿Qué tiene de diferente? Todo el marcado sigue las reglas. En mi caso específico, limité el número de publicaciones a 20. Además, las publicaciones con más de 2 días desaparecen. Tal vez alguien necesite esto:
/* función para crear el archivo sitemap.xml en el directorio raíz del sitio */
// add_action("publish_post", "eg_create_sitemap");
// add_action("publish_page", "eg_create_sitemap");
add_action( "save_post", "eg_create_sitemap" );
function eg_create_sitemap() {
if ( str_replace( '-', '', get_option( 'gmt_offset' ) ) < 10 ) {
$tempo = '-0' . str_replace( '-', '', get_option( 'gmt_offset' ) );
} else {
$tempo = get_option( 'gmt_offset' );
}
if( strlen( $tempo ) == 3 ) { $tempo = $tempo . ':00'; }
$postsForSitemap = get_posts( array(
'numberposts' => 20,
'orderby' => 'modified',
'post_type' => array( 'post', 'page' ),
'order' => 'DESC',
'date_query' => array(
'after' => date('Y-m-d', strtotime('-2 days'))
)
) );
$sitemap .= '<?xml version="1.0" encoding="UTF-8"?>';
$sitemap .= "\n" . '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">' . "\n";
foreach( $postsForSitemap as $post ) {
setup_postdata( $post);
$postdate = explode( " ", $post->post_modified );
$sitemap .= "\t" . "<url>" . "\n";
$sitemap .= "\t\t" . "<loc>" . get_permalink( $post->ID ) . '</loc>';
$sitemap .= "\t\t" . '<news:news>' . "\n";
$sitemap .= "\t\t\t" . '<news:publication>' . "\n";
$sitemap .= "\t\t\t\t" . '<news:name><![CDATA[ TU SITIO ]]></news:name>' . "\n";
$sitemap .= "\t\t\t\t" . '<news:language>TU IDIOMA</news:language>' . "\n";
$sitemap .= "\t\t\t" . '</news:publication>' . "\n";
$sitemap .= "\t\t\t<news:publication_date>" . $postdate[0] . 'T' . $postdate[1] . $tempo . "</news:publication_date>\n";
$sitemap .= "\t\t\t" . '<news:title><![CDATA[' . get_the_title( $post) . ']]></news:title>' . "\n";
$sitemap .= "\t\t" . '</news:news>' . "\n";
$sitemap .= "\t" . '</url>' . "\n";
}
$sitemap .= '</urlset>';
$fp = fopen( ABSPATH . "sitemap_news.xml", 'w' );
fwrite( $fp, $sitemap );
fclose( $fp );
}
