Files
yogibook_aury_new/public/class/NewsletterWp.php
T
2026-10-16 15:10:11 +02:00

165 lines
7.1 KiB
PHP

<?php
/**
* Collegamento al database di WordPress per The Newsletter.
*
* Usa un utente MySQL dedicato che può leggere/scrivere SOLO la tabella delle newsletter.
* Variabili nel .env di YogiBook:
* WP_DB_HOST, WP_DB_NAME, WP_DB_USER, WP_DB_PASSWORD, WP_DB_PREFIX (default wp_),
* WP_ADMIN_URL (default https://yogasoul.it/wp-admin/)
*/
class NewsletterWp
{
private static ?PDO $pdo = null;
public static function isConfigured(): bool
{
return !empty($_ENV['WP_DB_NAME']) && !empty($_ENV['WP_DB_USER']);
}
public static function adminUrl(): string
{
return rtrim($_ENV['WP_ADMIN_URL'] ?? 'https://yogasoul.it/wp-admin/', '/') . '/admin.php?page=newsletter_emails_index';
}
private static function table(): string
{
$prefix = preg_replace('/[^a-zA-Z0-9_]/', '', $_ENV['WP_DB_PREFIX'] ?? 'wp_');
return '`' . $prefix . 'newsletter_emails`';
}
private static function db(): PDO
{
if (!self::isConfigured()) {
throw new RuntimeException('Collegamento a WordPress non configurato (manca WP_DB_* nel file .env).');
}
if (self::$pdo === null) {
$host = $_ENV['WP_DB_HOST'] ?? 'localhost';
$port = '3306';
if (strpos($host, ':') !== false) {
[$host, $port] = explode(':', $host, 2);
}
try {
self::$pdo = new PDO(
"mysql:host={$host};port={$port};dbname={$_ENV['WP_DB_NAME']};charset=utf8mb4",
$_ENV['WP_DB_USER'],
$_ENV['WP_DB_PASSWORD'] ?? '',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_TIMEOUT => 5,
]
);
} catch (PDOException $e) {
error_log('NewsletterWp: connessione fallita: ' . $e->getMessage());
throw new RuntimeException('Collegamento al database di WordPress non riuscito: controlla WP_DB_* nel file .env.');
}
}
return self::$pdo;
}
private static function unserializeOptions(?string $raw): array
{
if ($raw === null || $raw === '') return [];
$o = @unserialize($raw, ['allowed_classes' => false]);
return is_array($o) ? $o : [];
}
/** Ultime newsletter del plugin (per l'import e per lo stato). */
public static function listEmails(int $limit = 30): array
{
$st = self::db()->prepare("SELECT id, subject, status, created, sent, total FROM " . self::table() . "
WHERE type = 'message' ORDER BY id DESC LIMIT " . max(1, min(100, $limit)));
$st->execute();
return $st->fetchAll();
}
/** Stato di più newsletter in una sola query: [id => status]. */
public static function statuses(array $ids): array
{
$ids = array_values(array_unique(array_filter(array_map('intval', $ids))));
if (!$ids) return [];
$ph = implode(',', array_fill(0, count($ids), '?'));
$st = self::db()->prepare("SELECT id, status FROM " . self::table() . " WHERE id IN ($ph)");
$st->execute($ids);
$out = [];
foreach ($st->fetchAll() as $r) $out[(int) $r['id']] = $r['status'];
return $out;
}
/** Legge una newsletter del plugin e la converte in blocchi YogiBook. */
public static function import(int $id): array
{
$st = self::db()->prepare("SELECT id, subject, message, options, editor FROM " . self::table() . " WHERE id = ?");
$st->execute([$id]);
$row = $st->fetch();
if (!$row) {
throw new RuntimeException('Newsletter non trovata in WordPress.');
}
$opts = self::unserializeOptions($row['options']);
return [
'subject' => (string) $row['subject'],
'preheader' => (string) ($opts['preheader'] ?? ''),
'blocks' => NewsletterRenderer::fromComposerHtml((string) $row['message']),
'settings' => NewsletterRenderer::settingsFromWpOptions($opts),
];
}
/**
* Crea o aggiorna la bozza in The Newsletter.
* Aggiorna solo se è ancora "new" (mai partita). Restituisce [id, created(bool)].
*/
public static function push(?int $wpId, string $subject, string $preheader, string $html, string $text, array $settings): array
{
$db = self::db();
$composerOpts = NewsletterRenderer::wpComposerOptions($settings);
if ($wpId) {
$st = $db->prepare("SELECT status, options FROM " . self::table() . " WHERE id = ?");
$st->execute([$wpId]);
$row = $st->fetch();
if ($row) {
if ($row['status'] !== 'new') {
throw new RuntimeException('In WordPress questa newsletter è già "' . $row['status'] . '": non la modifico. Duplica la bozza per crearne una nuova.');
}
// Mantiene tutto quello scelto in WordPress (liste, mittente, utm...)
$opts = array_merge(self::unserializeOptions($row['options']), $composerOpts, ['preheader' => $preheader]);
unset($opts['error_message']);
$up = $db->prepare("UPDATE " . self::table() . "
SET subject = ?, message = ?, message_text = ?, options = ?, editor = 2, updated = ?
WHERE id = ? AND status = 'new'");
$up->execute([$subject, $html, $text, serialize($opts), time(), $wpId]);
return [$wpId, false];
}
// Cancellata in WordPress: la ricreiamo
}
// Nuova: il pubblico parte uguale all'ultima newsletter (si controlla in WordPress)
$base = [];
$last = $db->query("SELECT options FROM " . self::table() . " WHERE type = 'message' ORDER BY id DESC LIMIT 1")->fetchColumn();
if ($last) {
$prev = self::unserializeOptions($last);
foreach (['status', 'wp_users', 'lists', 'lists_operator', 'lists_exclude', 'countries', 'regions', 'cities',
'sender_email', 'sender_name', 'text_message_mode', 'utm_source', 'utm_campaign', 'utm_medium', 'utm_term', 'utm_content'] as $k) {
if (array_key_exists($k, $prev)) $base[$k] = $prev[$k];
}
}
$opts = array_merge(['status' => 'C', 'wp_users' => '0', 'lists' => [], 'lists_operator' => 'or', 'lists_exclude' => []], $base, $composerOpts, ['preheader' => $preheader]);
$ins = $db->prepare("INSERT INTO " . self::table() . "
(language, subject, message, subject2, message2, name2, status, total, last_id, sent, track, list,
type, query, editor, sex, theme, message_text, preferences, send_on, token, options, private, version, updated)
VALUES ('', ?, ?, '', '', '', 'new', 0, 0, 0, 1, 0,
'message', '', 2, '', '', ?, '', ?, '', ?, 0, '', ?)");
$ins->execute([$subject, $html, $text, time(), serialize($opts), time()]);
return [(int) $db->lastInsertId(), true];
}
}