Automatic AI Posts, setup automatizado e seeder de permalinks/avatar

Plugin automatic-ai-posts (v2.0.0)
- Pipeline completo: coleta de feeds RSS/Atom, filtro por IA, reescrita,
  categoria, imagem de capa e agendamento
- Página de configuração com teste real do provedor de IA
- Editor: botão para refazer imagem de capa e metadados, textos em PT-BR
- Ajuda com botão Pix (copia-e-cola + QR) e sem nota de avaliação
- Cache-busting por mtime nos assets do admin

Tema gustavoo-portfolio
- Marca do cabeçalho reduzida a "Gustavo"
- Suporte a título próprio da marca

Setup (docker/wp-init.sh, compose.yaml)
- Plugins de terceiros (Post Views Counter, Rank Math, Site Kit)
  instalados do WordPress.org em vez de versionados
- Falha explícita ao final se algum plugin obrigatório não instalar
- WPCLI_UID/WPCLI_GID configuráveis

Seeder do tema
- Links permanentes "Dia e nome" e avatar "Iniciais" aplicados
  apenas em instalação nova, com regravação do .htaccess
This commit is contained in:
Gustavo committed 2026-11-02 13:33:30 -03:00
1 parent 5891467266
commit 0ea3651fb5
73 files changed
+8858 -39

No files matched your search

@@ -72,6 +72,7 @@ final class GSO_Seeder {
* @return void
*/
public static function maybe_seed() {
self::normalize_sidebar_widgets();
self::seed_site_configuration();
$state = self::get_state();
@@ -158,12 +159,103 @@ final class GSO_Seeder {
}
}
if ( empty( $state['title'] ) ) {
self::seed_site_title();
$state = is_array( $state ) ? $state : array();
$state['title'] = true;
update_option( self::SITE_OPTION, $state, false );
}
if ( empty( $state['widgets'] ) ) {
self::seed_theme_widgets();
$state = is_array( $state ) ? $state : array();
$state['widgets'] = true;
update_option( self::SITE_OPTION, $state, false );
}
if ( empty( $state['permalinks'] ) ) {
self::seed_permalinks();
$state = is_array( $state ) ? $state : array();
$state['permalinks'] = true;
update_option( self::SITE_OPTION, $state, false );
}
if ( empty( $state['avatars'] ) ) {
self::seed_avatars();
$state = is_array( $state ) ? $state : array();
$state['avatars'] = true;
update_option( self::SITE_OPTION, $state, false );
}
}
/**
* Use the "Day and name" permalink structure.
*
* The post URL then reads /2026/11/02/post-slug/, which keeps dates
* meaningful for a news-style blog. Only the stock "plain" structure is
* replaced, so a structure chosen by the owner is respected.
*
* @return void
*/
private static function seed_permalinks() {
if ( '' !== (string) get_option( 'permalink_structure' ) ) {
return;
}
update_option( 'permalink_structure', '/%year%/%monthnum%/%day%/%postname%/' );
// Rebuild the rewrite rules and write the Apache block into .htaccess.
// `hard = true` is required: without it the rules stay cached in the DB
// and the web server never sees the new URLs (everything would 404).
global $wp_rewrite;
if ( $wp_rewrite instanceof WP_Rewrite ) {
$wp_rewrite->set_permalink_structure( '/%year%/%monthnum%/%day%/%postname%/' );
$wp_rewrite->flush_rules( true );
}
// flush_rules() only rewrites .htaccess when it can detect mod_rewrite,
// which needs an HTTP request context. Force the block in otherwise.
if ( function_exists( 'save_mod_rewrite_rules' ) ) {
save_mod_rewrite_rules();
}
}
/**
* Fall back to the "initials" avatar.
*
* Stand-in avatars avoid broken images and third-party requests on every
* comment. Only the stock "mystery" default is replaced.
*
* @return void
*/
private static function seed_avatars() {
if ( 'mystery' === (string) get_option( 'avatar_default' ) ) {
update_option( 'avatar_default', 'initials' );
}
// Gravatar images are remote; keep them enabled but never required.
if ( '' === (string) get_option( 'show_avatars' ) ) {
update_option( 'show_avatars', '1' );
}
}
/**
* Name the site with the full professional title used on the home page.
*
* WordPress fills the browser title of the front page from `blogname`, so
* this value is what search engines and tabs display. A name changed by the
* site owner is respected: only the stock install value is replaced.
*
* @return void
*/
private static function seed_site_title() {
$title = __( 'Gustavo Desenvolvedor Full Stack | Web, Mobile, Infraestrutura & Automação', 'gustavoo-portfolio-core' );
$name = (string) get_option( 'blogname' );
if ( '' === $name || in_array( $name, array( 'Gustavo Portfolio', 'My Blog', 'Just another WordPress site', 'WordPress' ), true ) ) {
update_option( 'blogname', $title );
}
}
/**
@@ -220,6 +312,15 @@ final class GSO_Seeder {
* @return void
*/
private static function seed_theme_widgets() {
/*
* WordPress stores a snapshot of the widget layout in the `sidebars_widgets`
* theme mod. Retrieve_widgets() reapplies that snapshot on every `init`
* whenever it exists, which reverts this seeding and re-introduces blocks
* that were moved to the inactive area. The option is the source of truth
* here, so a stale snapshot is dropped before reading the layout.
*/
remove_theme_mod( 'sidebars_widgets' );
$sidebars = wp_get_sidebars_widgets();
$sidebars = is_array( $sidebars ) ? $sidebars : array();
@@ -255,6 +356,55 @@ final class GSO_Seeder {
}
update_option( 'sidebars_widgets', $sidebars, false );
// The option is authoritative from now on; discarding the stale theme
// mod snapshot stops retrieve_widgets() from overriding it on `init`.
remove_theme_mod( 'sidebars_widgets' );
}
/**
* Repair widget areas that print the same widget more than once.
*
* Earlier versions persisted duplicate widget ids, so the same block was
* rendered twice in the sidebar and footer. Runs on every boot and only
* writes when a duplicate is actually found.
*
* @return void
*/
private static function normalize_sidebar_widgets() {
/*
* Read the raw option on purpose. wp_get_sidebars_widgets() merges the
* layout through retrieve_widgets(), which already drops duplicate
* ids from the array it returns. Relying on it would hide the
* duplicated entries and the repair below would never run.
*/
$sidebars = get_option( 'sidebars_widgets' );
if ( ! is_array( $sidebars ) ) {
return;
}
$changed = false;
foreach ( $sidebars as $sidebar_id => $widgets ) {
if ( ! is_array( $widgets ) ) {
continue;
}
$unique = array_values( array_unique( $widgets ) );
if ( count( $unique ) !== count( $widgets ) ) {
$sidebars[ $sidebar_id ] = $unique;
$changed = true;
}
}
if ( ! $changed ) {
return;
}
update_option( 'sidebars_widgets', $sidebars, false );
remove_theme_mod( 'sidebars_widgets' );
}
/**