| Server IP : 188.114.96.3 / Your IP : 216.73.216.143 Web Server : Apache System : Linux sv058.home.net.pl 6.8.2 husak_dirty_pages_fix #1 SMP PREEMPT_DYNAMIC Tue Apr 2 22:06:51 CEST 2024 x86_64 User : ( 3194769) PHP Version : 8.5.12 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /polana/admin/ |
Upload File : |
<?php
declare(strict_types=1);
require_once dirname(__DIR__).'/includes/functions.php';
$user=require_blog_manager();
ensure_blog_schema();
const P11_CAMPAIGN='polana_blog40_20261017';
function p11_campaign_rows(): array {
return db()->query("SELECT c.ordinal,c.post_id,c.reviewed_at,p.title,p.status,p.publish_at,p.slug
FROM polana_blog_campaign_items c JOIN blog_posts p ON p.id=c.post_id
WHERE c.campaign_key='".P11_CAMPAIGN."' ORDER BY c.ordinal")->fetchAll(PDO::FETCH_ASSOC);
}
$error='';$notice='';
try {$rows=p11_campaign_rows();}
catch(Throwable $e){$rows=[];$error='Nie znaleziono kampanii V10. Najpierw wgraj importer V10 i utwórz wpisy.';}
if($_SERVER['REQUEST_METHOD']==='POST'){
verify_csrf();
try {
if(($_POST['action']??'')!=='random_schedule')throw new RuntimeException('Nieznana operacja');
if(($_POST['editorial_confirm']??'')!=='yes')throw new RuntimeException('Potwierdź, że wpisy przejrzał redaktor.');
$startDate=(string)($_POST['start_date']??'');
$tz=new DateTimeZone('Europe/Warsaw');
$start=DateTimeImmutable::createFromFormat('!Y-m-d',$startDate,$tz);
if(!$start||$start->format('Y-m-d')!==$startDate)throw new RuntimeException('Nieprawidłowa data.');
if($start < new DateTimeImmutable('today',$tz))throw new RuntimeException('Start nie może być w przeszłości.');
$min=max(1,min(14,(int)($_POST['min_days']??2)));
$max=max($min,min(21,(int)($_POST['max_days']??5)));
$includeScheduled=(($_POST['include_scheduled']??'')==='1');
$db=db();$db->beginTransaction();
try {
$q=$db->prepare("SELECT c.post_id,c.ordinal,c.reviewed_at,p.status,p.publish_at
FROM polana_blog_campaign_items c JOIN blog_posts p ON p.id=c.post_id
WHERE c.campaign_key=? ORDER BY c.ordinal FOR UPDATE");
$q->execute([P11_CAMPAIGN]);$campaign=$q->fetchAll(PDO::FETCH_ASSOC);
if(!$campaign)throw new RuntimeException('Brak wpisów kampanii V10.');
$future=[];$omitted=0;
foreach($campaign as $item){
if($item['status']==='published'){$omitted++;continue;}
if($item['status']==='scheduled'&&!$includeScheduled){$omitted++;continue;}
if(!in_array($item['status'],['draft','scheduled'],true)){$omitted++;continue;}
$future[]=$item;
}
if(!$future)throw new RuntimeException('Brak szkiców do zaplanowania. Opublikowanych wpisów nie ruszamy.');
// Editorial approval is explicit, stored only for this campaign, never for arbitrary blog posts.
$review=$db->prepare('UPDATE polana_blog_campaign_items SET reviewed_at=COALESCE(reviewed_at,NOW()) WHERE campaign_key=? AND post_id=?');
$update=$db->prepare("UPDATE blog_posts SET status='scheduled',publish_at=?,updated_by_user_id=?,updated_at=NOW() WHERE id=? AND status IN ('draft','scheduled')");
$now=new DateTimeImmutable('now',$tz);
$cursor=$start;
$first=true;$count=0;
foreach($future as $item){
if(!$first)$cursor=$cursor->modify('+'.random_int($min,$max).' days');
$first=false;
$hour=random_int(9,18);$minute=random_int(0,59);
$when=$cursor->setTime($hour,$minute,0);
if($when <= $now) $when=$now->modify('+'.random_int(2,10).' hours');
$update->execute([$when->format('Y-m-d H:i:s'),(int)$user['id'],(int)$item['post_id']]);
$count += $update->rowCount();
$review->execute([P11_CAMPAIGN,(int)$item['post_id']]);
}
$db->commit();
admin_log('v11_blog_random','Losowy harmonogram: '.$count.' wpisów, odstęp '.$min.'–'.$max.' dni');
$_SESSION['v11_schedule_notice']='Zaplanowano '.$count.' wpisów. Pominięto '.$omitted.' opublikowanych lub zachowanych terminów.';
header('Location: polana-v11-harmonogram.php',true,303);exit;
}catch(Throwable $e){$db->rollBack();throw $e;}
}catch(Throwable $e){$error=$e->getMessage();}
}
$rows=$rows ?: [];
if(isset($_SESSION['v11_schedule_notice'])){$notice=(string)$_SESSION['v11_schedule_notice'];unset($_SESSION['v11_schedule_notice']);}
$counts=['draft'=>0,'scheduled'=>0,'published'=>0];foreach($rows as $row)if(isset($counts[$row['status']]))$counts[$row['status']]++;
render_admin_header('Blog — losowy harmonogram V11');
?>
<style>.v11-shell{max-width:1180px;margin:22px auto;padding:16px}.v11-card{background:#fff;border:1px solid #ddd;padding:20px;border-radius:14px;margin:16px 0}.v11-form{display:flex;gap:16px;align-items:end;flex-wrap:wrap}.v11-form label{display:flex;flex-direction:column;gap:7px}.v11-form input{padding:10px}.v11-table{border-collapse:collapse;width:100%;font-size:14px}.v11-table td,.v11-table th{padding:9px;border-bottom:1px solid #ddd;text-align:left}.v11-scroll{overflow-x:auto}</style>
<main class="v11-shell">
<h1>Blog SEO — losowe publikacje</h1>
<p><b><?= (int)$counts['published'] ?></b> opublikowanych · <b><?= (int)$counts['scheduled'] ?></b> zaplanowanych · <b><?= (int)$counts['draft'] ?></b> szkiców. Harmonogram działa w obecnym PHP/MariaDB, bez crona.</p>
<?php if($error): ?><div class="v11-card" style="border-color:#b93;color:#972"><?= e($error) ?></div><?php endif; ?>
<?php if($notice): ?><div class="v11-card" style="border-color:#528d44"><?= e($notice) ?></div><?php endif; ?>
<section class="v11-card">
<h2>Rozłóż wpisy losowo</h2>
<p>Domyślnie nie zmieniamy już zaplanowanych ani opublikowanych. Jeśli wcześniej ustawiłeś sztywne dni w V10, zaznacz „Przeplanuj przyszłe”. Terminy ustawiamy losowo w godz. 9:00–18:59 czasu Krakowa.</p>
<form method="post" class="v11-form">
<?= csrf_field() ?><input type="hidden" name="action" value="random_schedule">
<label>Data startu<input type="date" name="start_date" value="<?= e((new DateTimeImmutable('tomorrow',new DateTimeZone('Europe/Warsaw')))->format('Y-m-d')) ?>" required></label>
<label>Odstęp min. (dni)<input type="number" name="min_days" min="1" max="14" value="2"></label>
<label>Odstęp maks. (dni)<input type="number" name="max_days" min="1" max="21" value="5"></label>
<label style="flex-direction:row;align-items:center"><input type="checkbox" name="include_scheduled" value="1"> Przeplanuj także przyszłe wpisy z V10</label>
<label style="flex-direction:row;align-items:center;width:100%"><input type="checkbox" name="editorial_confirm" value="yes" required> Potwierdzam, że przejrzałem treści i akceptuję harmonogram publikacji.</label>
<button class="btn btn-primary" type="submit" onclick="return confirm('Zapisać losowe daty publikacji dla pozostałych wpisów kampanii?')">Zapisz losowy harmonogram</button>
</form>
</section>
<section class="v11-card"><h2>Wpisy kampanii</h2><div class="v11-scroll"><table class="v11-table"><thead><tr><th>Nr</th><th>Tytuł</th><th>Status</th><th>Data publikacji</th></tr></thead><tbody>
<?php foreach($rows as $r): ?><tr><td><?= (int)$r['ordinal'] ?></td><td><a href="/admin/blog-edit.php?id=<?= (int)$r['post_id'] ?>"><?= e((string)$r['title']) ?></a></td><td><?= e((string)$r['status']) ?></td><td><?= e((string)($r['publish_at']??'—')) ?></td></tr><?php endforeach; ?></tbody></table></div></section>
<p><a href="/admin/polana-blog-40.php">← Kampania V10</a> · <a href="/admin/v9-review.php">Tłumaczenia EN/DE/ES</a></p>
</main>
<?php render_admin_footer(); ?>