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

@@ -0,0 +1,30 @@
<?php
namespace AAP\Core;
defined('ABSPATH') || exit;
/**
* PSR-4 style autoloader: AAP\Foo\Bar -> src/Foo/Bar.php
*/
final class Autoloader {
private static string $base = '';
public static function register(string $base): void {
self::$base = rtrim($base, '/') . '/';
spl_autoload_register([self::class, 'load']);
}
public static function load(string $class): void {
if (strncmp($class, 'AAP\\', 4) !== 0) {
return;
}
$relative = substr($class, 4);
$path = self::$base . str_replace('\\', '/', $relative) . '.php';
if (is_readable($path)) {
require_once $path;
}
}
}
@@ -0,0 +1,30 @@
<?php
namespace AAP\Core;
defined('ABSPATH') || exit;
/**
* A module is a self-contained unit of the plugin.
*
* Lifecycle:
* register() — attach hooks that must exist before boot (rarely needed).
* boot() — attach runtime hooks; dependencies are already booted here.
*/
abstract class Module {
/** Unique module id (used by dependencies()). */
abstract public function id(): string;
/** @return string[] ids of modules that must boot first. */
public function dependencies(): array {
return [];
}
/** Called before any boot(), in dependency order. */
public function register(): void {
}
/** Called after every module was registered, in dependency order. */
public function boot(): void {
}
}
@@ -0,0 +1,97 @@
<?php
namespace AAP\Core;
defined('ABSPATH') || exit;
/**
* Central place for every option the plugin stores.
*
* IMPORTANT: existing option names are kept exactly as they were in 1.x so
* upgrading never loses configuration.
*/
final class Options {
public const DEFAULTS = [
// Feeds
'aap_feeds' => "https://feeds.bbci.co.uk/news/rss.xml\nhttps://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml",
'aap_feed_max_items' => 10,
// GNews
'aap_gnews_api_key' => '',
'aap_gnews_categories' => [],
'aap_gnews_max' => 10,
// Generation
'aap_min_posts' => 1,
'aap_max_posts' => 3,
'aap_post_status' => 'draft',
'aap_post_language' => 'pt-BR',
// Editorial instructions
'aap_relevance_description' => '',
'aap_negative_profile' => '',
'aap_rewrite_instructions' => '',
// AI provider
'aap_ai_provider' => 'nvidia',
'aap_nvidia_api_key' => '',
'aap_openai_api_key' => '',
'aap_anthropic_api_key' => '',
'aap_openrouter_api_key' => '',
'aap_custom_api_key' => '',
'aap_custom_base_url' => '',
'aap_vertex_project' => '',
'aap_vertex_location' => 'us-central1',
'aap_vertex_service_account'=> '',
'aap_model' => '',
'aap_ai_timeout' => 120,
'aap_ai_max_retries' => 3,
// Images
'aap_image_instructions' => '',
'aap_image_caption' => '',
// Scheduling
'aap_auto_interval_value' => 24,
'aap_auto_interval_unit' => 'hours',
'aap_auto_enabled' => false,
'aap_auto_user_id' => 0,
'aap_next_auto_run' => 0,
'aap_last_run' => '',
// App
'aap_app_language' => 'pt_BR',
];
public static function get(string $key, $fallback = null) {
$default = self::DEFAULTS[$key] ?? $fallback;
$value = get_option($key, $default);
return $value === null ? $default : $value;
}
public static function set(string $key, $value): bool {
return update_option($key, $value);
}
public static function bool(string $key): bool {
return (bool) self::get($key);
}
public static function int(string $key): int {
return (int) self::get($key);
}
public static function string(string $key): string {
return (string) self::get($key);
}
/** Creates missing options without touching existing ones. */
public static function install_defaults(): void {
foreach (self::DEFAULTS as $key => $value) {
if (get_option($key, null) === null) {
add_option($key, $value);
}
}
}
}
@@ -0,0 +1,88 @@
<?php
namespace AAP\Core;
defined('ABSPATH') || exit;
/**
* Resolves the module dependency tree and boots it in order.
*/
final class Registry {
/** @var array<string, Module> */
private static array $modules = [];
private static bool $booted = false;
public static function add(Module $module): void {
self::$modules[$module->id()] = $module;
}
public static function get(string $id): ?Module {
return self::$modules[$id] ?? null;
}
/** @return array<string, Module> */
public static function all(): array {
return self::$modules;
}
public static function boot(): void {
if (self::$booted) {
return;
}
self::$booted = true;
$order = self::resolve();
foreach ($order as $id) {
self::$modules[$id]->register();
}
foreach ($order as $id) {
self::$modules[$id]->boot();
}
}
/**
* Topological sort. Missing dependencies are skipped with a notice
* (a broken module must not take the whole admin down).
*
* @return string[]
*/
private static function resolve(): array {
$resolved = [];
$visiting = [];
$visit = function (string $id) use (&$visit, &$resolved, &$visiting): void {
if (isset($resolved[$id]) || isset($visiting[$id])) {
return;
}
$module = self::$modules[$id] ?? null;
if (!$module) {
return;
}
$visiting[$id] = true;
foreach ($module->dependencies() as $dependency) {
if (!isset(self::$modules[$dependency])) {
if (function_exists('error_log')) {
error_log("[Automatic AI Posts] Module \"{$id}\" requires missing module \"{$dependency}\".");
}
continue;
}
$visit($dependency);
}
unset($visiting[$id]);
$resolved[$id] = true;
};
foreach (array_keys(self::$modules) as $id) {
$visit($id);
}
return array_keys($resolved);
}
}