| 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/includes/ |
Upload File : |
<?php
declare(strict_types=1);
/** POLANA V19.3 — automatyczne i kontrolowane planowanie postów. */
function p193_schedule_settings(): array {
db()->exec("CREATE TABLE IF NOT EXISTS polana_blog_scheduler_v193 (
singleton TINYINT UNSIGNED NOT NULL PRIMARY KEY,
active TINYINT(1) NOT NULL DEFAULT 1,
weekdays VARCHAR(20) NOT NULL DEFAULT '1,3,5',
hour TINYINT UNSIGNED NOT NULL DEFAULT 10,
minute TINYINT UNSIGNED NOT NULL DEFAULT 0,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
db()->exec("INSERT IGNORE INTO polana_blog_scheduler_v193 (singleton,active,weekdays,hour,minute) VALUES (1,1,'1,3,5',10,0)");
$row=db()->query('SELECT active,weekdays,hour,minute FROM polana_blog_scheduler_v193 WHERE singleton=1')->fetch(PDO::FETCH_ASSOC);
if (!is_array($row)) throw new RuntimeException('Brak ustawień harmonogramu.');
return $row;
}
function p193_schedule_update(bool $active, array $days, int $hour, int $minute): void {
p193_schedule_settings();
$days=array_values(array_unique(array_map('intval',$days)));
sort($days);
if (!$days || count(array_filter($days,static fn($d)=>$d<1||$d>7))) throw new RuntimeException('Wybierz co najmniej jeden dzień tygodnia.');
if ($hour<0 || $hour>23 || $minute<0 || $minute>59) throw new RuntimeException('Niepoprawna godzina.');
db()->prepare('UPDATE polana_blog_scheduler_v193 SET active=?,weekdays=?,hour=?,minute=? WHERE singleton=1')
->execute([$active?1:0,implode(',',$days),$hour,$minute]);
}
function p193_with_lock(callable $callback) {
$db=db();
$lock=$db->query("SELECT GET_LOCK('polana_blog_schedule_v193',4)")->fetchColumn();
if ((int)$lock!==1) throw new RuntimeException('Harmonogram jest aktualnie zajęty. Ponów próbę.');
try{return $callback();}
finally{try{$db->query("SELECT RELEASE_LOCK('polana_blog_schedule_v193')");}catch(Throwable $ignored){}}
}
function p193_schedule_date(PDO $db, array $settings): string {
$tz=new DateTimeZone('Europe/Warsaw');
$days=array_map('intval',explode(',',(string)$settings['weekdays']));
$cursor=(new DateTimeImmutable('tomorrow 00:00:00',$tz));
// MAX 2 years of slots: no date collisions and never overwrite existing commitments.
$exists=$db->prepare("SELECT 1 FROM blog_posts WHERE status IN ('scheduled','published') AND publish_at>=? AND publish_at<? LIMIT 1");
for($i=0;$i<740;$i++){
$candidate=$cursor->modify('+'.$i.' days');
if(!in_array((int)$candidate->format('N'),$days,true))continue;
$date=$candidate->setTime((int)$settings['hour'],(int)$settings['minute']);
$dayStart=$candidate->format('Y-m-d 00:00:00');
$dayEnd=$candidate->modify('+1 day')->format('Y-m-d 00:00:00');
$exists->execute([$dayStart,$dayEnd]);
if(!$exists->fetchColumn())return $date->format('Y-m-d H:i:s');
}
throw new RuntimeException('Brak wolnego terminu publikacji w ciągu 2 lat.');
}
function p193_schedule_one_locked(int $id,int $actor,array $settings): ?string {
$db=db();
$q=$db->prepare('SELECT status,publish_at FROM blog_posts WHERE id=? LIMIT 1');
$q->execute([$id]);$post=$q->fetch(PDO::FETCH_ASSOC);
if(!$post || $post['status']!=='draft' || !empty($post['publish_at']))return null;
$dt=p193_schedule_date($db,$settings);
$upd=$db->prepare("UPDATE blog_posts SET status='scheduled',publish_at=?,updated_by_user_id=?,updated_at=NOW() WHERE id=? AND status='draft' AND publish_at IS NULL");
$upd->execute([$dt,$actor,$id]);
return $upd->rowCount()===1?$dt:null;
}
/** Schedules a saved, explicitly auto-selected new article. */
function p193_schedule_on_save(int $id,int $actor): ?string {
$config=p193_schedule_settings();
if(!(int)$config['active'])return null;
return p193_with_lock(static fn()=>p193_schedule_one_locked($id,$actor,$config));
}
/** For campaign: only articles confirmed as reviewed; no published/scheduled posts affected. */
function p193_schedule_reviewed_campaign(array $ids,int $actor): array {
$config=p193_schedule_settings();
if(!(int)$config['active'])return ['count'=>0,'paused'=>true,'dates'=>[]];
$ids=array_values(array_unique(array_filter(array_map('intval',$ids),static fn($n)=>$n>0)));
if(!$ids)return ['count'=>0,'dates'=>[]];
if(count($ids)>100)throw new RuntimeException('Za dużo wpisów w jednej partii.');
return p193_with_lock(static function()use($ids,$actor,$config){
$db=db();$dates=[];
$q=$db->prepare("SELECT c.post_id FROM polana_blog_campaign_items c JOIN blog_posts p ON p.id=c.post_id
WHERE c.campaign_key='polana_blog40_20261017' AND c.reviewed_at IS NOT NULL
AND p.status='draft' AND p.publish_at IS NULL AND c.post_id=?");
foreach($ids as $id){
$q->execute([$id]);
if(!$q->fetchColumn())continue;
$when=p193_schedule_one_locked($id,$actor,$config);
if($when!==null)$dates[$id]=$when;
}
return ['count'=>count($dates),'dates'=>$dates];
});
}
/** For existing manual drafts: explicit admin/editor approval through dedicated UI only. */
function p193_schedule_manual_drafts(array $ids,int $actor): array {
$config=p193_schedule_settings();
if(!(int)$config['active'])throw new RuntimeException('Automatyczny harmonogram jest wyłączony.');
$ids=array_values(array_unique(array_filter(array_map('intval',$ids),static fn($n)=>$n>0)));
if(count($ids)>100)throw new RuntimeException('Maksymalnie 100 wpisów.');
return p193_with_lock(static function()use($ids,$actor,$config){
$db=db();$dates=[];
foreach($ids as $id){$when=p193_schedule_one_locked($id,$actor,$config);if($when!==null)$dates[$id]=$when;}
return ['count'=>count($dates),'dates'=>$dates];
});
}