# Napojení webu na Adneto

Návod je psaný tak, aby podle něj web napojil AI programovací agent (nebo vývojář) bez další pomoci. Postupujte shora dolů.

Adneto doručuje reklamu z backendu. Váš server si stáhne malý manifest ve formátu JSON, uloží ho do cache a reklamu vypíše jako součást vlastního HTML. Prohlížeč návštěvníka se s Adneto nespojí: odkazy, obrázky i měření jdou přes vaši doménu. Žádný JavaScript třetí strany, žádné cookies, žádné identifikátory návštěvníků.

## 1. Co potřebujete

| Položka | Kde ji vzít |
|---|---|
| Token webu | Adneto → Weby → váš web. Zobrazí se jednou při založení webu; když ho ztratíte, vygenerujte nový. |
| Klíče ploch | Adneto → Weby → váš web → Plochy. Klíč vypadá jako `sidebar`. |
| Soubor klienta | `https://adneto.com/download/client` (uloží se jako `adneto.php`) |
| PHP 8.1+ | Bez povinných rozšíření. cURL se použije, když je k dispozici. |
| Zapisovatelný adresář | Pro cache. **Nesmí** být dostupný z webu. |

Token je tajemství: držte ho v proměnné prostředí nebo v konfiguračním souboru mimo veřejnou složku (například `private/config.php`, který vrací pole), nikdy ve veřejném repozitáři. Příklady níže ho čtou přes `getenv('SLOTS_TOKEN')`; použijte to, čím váš web tajemství už spravuje.

### Volby nastavení

`Adneto::configure([...])` přijímá:

| Volba | Výchozí | Význam |
|---|---|---|
| `token` | — | Token webu. Prázdný = klient nedělá nic. |
| `cache_dir` | — | Zapisovatelný adresář mimo veřejnou složku. |
| `endpoint` | `/go.php` | Adresa na vašem webu, která volá `Adneto::handle()`. |
| `api` | `https://adneto.com/api/v1` | Základ API. Měňte jen při testování proti jiné instalaci Adneto. |
| `timeout` | `3` | Sekundy na jeden požadavek do Adneto. |
| `params` | `['click' => 'r', 'view' => 'v', 'image' => 'm']` | Názvy parametrů adresy pro prokliky. |
| `fallback_url` | `/` | Kam vede proklik, jehož reklama už v manifestu není. |

`Adneto::refreshNow()` stáhne manifest hned a při úspěchu vrátí `true`. Běžné stránky ji nepotřebují; hodí se pro testy a pro cron, pokud chcete obnovu plánovat.

## 2. Pravidla, která musí platit

1. **Neutrální názvy.** Adresa, která obsluhuje prokliky, její parametry a třída nebo id každého obalového prvku, který přidáte, nesmí obsahovat slova `ad`, `ads`, `banner`, `sponsor`, `promo` ani `adneto` (jako celé slovo nebo část oddělenou `-` či `_`, například `ad-box` nebo `side_banner`; `padding` nebo `loading` nevadí). Blokovače reklam takové prvky skrývají. Použijte názvy jako `/go.php`, `/l`, `tip-box`.
2. **Vrácené HTML neupravujte.** Vypište ho tak, jak je. Neescapujte ho a neobalujte odkazem.
3. **Výstup adresy pro prokliky necachujte.** Stránky s plochami cachovat můžete jako obvykle.
4. **Chyba nesmí rozbít stránku.** `Adneto::render()` při jakémkoli problému vrátí prázdný řetězec; nepřidávejte kolem něj kód, který vyhazuje výjimky.

## 3. Čisté PHP

Rozložení adresářů (veřejná složka je `public/`):

```
private/adneto.php        ← klient, mimo veřejnou složku
private/cache/            ← zapisovatelná, mimo veřejnou složku
public/go.php             ← adresa pro prokliky (libovolný neutrální název)
public/_slots.php         ← společné nastavení, vkládá se tam, kde je potřeba
```

`public/_slots.php`:

```php
<?php
require __DIR__.'/../private/adneto.php';

Adneto::configure([
    'token'     => getenv('SLOTS_TOKEN'),          // token webu
    'cache_dir' => __DIR__.'/../private/cache',
    'endpoint'  => '/go.php',
]);
```

`public/go.php`:

```php
<?php
require __DIR__.'/_slots.php';
Adneto::handle();
```

V šabloně, kde má plocha být:

```php
<?php
require_once __DIR__.'/_slots.php';
$tip = Adneto::render('sidebar');
?>
<?php if ($tip !== ''): ?>
  <aside class="tip-box"><?= $tip ?></aside>
<?php endif; ?>
```

Obal se vypíše jen tehdy, když má plocha obsah, takže prázdná plocha nezanechá prázdný rámeček.

Když je `_slots.php` dostupný přes URL, jen nastaví klienta a nic nevypíše, takže to nevadí. Můžete ho dát i mimo veřejnou složku.

## 4. Laravel

1. Klienta uložte jako `app/Support/adneto.php` (nebo kamkoli mimo `public/`).
2. Do `.env` a `config/services.php` přidejte:

```
SLOTS_TOKEN=token-vaseho-webu
```

```php
'slots' => ['token' => env('SLOTS_TOKEN')],
```

3. Klienta nastavte jednou, v `AppServiceProvider::boot()`:

```php
require_once app_path('Support/adneto.php');

\Adneto::configure([
    'token'     => (string) config('services.slots.token'),
    'cache_dir' => storage_path('app/slots'),
    'endpoint'  => '/l',
]);
```

4. Do `routes/web.php` přidejte routu pro prokliky. Nesmí zakládat session ani nastavovat cookies, proto ji vyjměte ze skupiny middleware `web`:

```php
Route::get('/l', function () {
    \Adneto::handle();
    exit;
})->withoutMiddleware('web');
```

5. V šabloně Blade (pozor na neescapovaný výpis):

```blade
@php($tip = \Adneto::render('sidebar'))
@if ($tip !== '')
    <aside class="tip-box">{!! $tip !!}</aside>
@endif
```

S prázdným tokenem klient nedělá nic, takže lokální vývoj a testy fungují bez nastavení.

## 5. Jak se to chová

- **První požadavek:** manifest ještě není, plocha je prázdná. Manifest se stáhne po odeslání odpovědi; plocha se objeví při dalším požadavku.
- **Obnova:** každých 5–15 minut ji spustí požadavek na stránku a proběhne až po dokončení výstupu stránky. Obnovuje vždy jen jeden požadavek. Pod PHP-FPM na ni návštěvník nečeká vůbec. Pod mod_php nebo vestavěným serverem se stránka odešle jako první, ale ten jeden požadavek zůstane otevřený, dokud obnova neskončí (běžně hluboko pod sekundu, při výpadku nejvýš asi 8 sekund); ostatních požadavků se to netýká.
- **Výpadek:** když je Adneto nedostupné, používá se poslední stažený manifest. Bez manifestu je plocha prázdná a stránka funguje.
- **Prázdná plocha:** `render()` vrátí `''`. Pokud má váš obal okraj nebo odsazení, při prázdném výsledku ho skryjte.
- **Obrázky** se stahují do cache a servírují z vaší domény přes adresu pro prokliky.
- **Měření:** klient počítá vykreslení na serveru, načtení měřicího bodu a prokliky do souboru v cache a při obnově posílá do Adneto hodinové součty. IP adresy ani cookies se neukládají.
- **Roboti se nepočítají.** Požadavky, jejichž user-agent vypadá jako robot nebo nástroj z příkazové řádky (včetně výchozího user-agenta `curl` a `wget` a prázdného user-agenta), reklamu, obrázek i přesměrování dostanou, ale nezaznamenají se. Při zkoušení měření přes curl posílejte user-agent prohlížeče: `curl -A "Mozilla/5.0 Firefox/130.0" …`.
- **Proklik na reklamu, která už v manifestu není** (například z nacachované stránky), přesměruje na `fallback_url`, ve výchozím nastavení na úvodní stránku.

## 6. Kontrolní seznam

Proveďte tyto kontroly a u každé uveďte výsledek. U všech požadavků posílejte user-agent prohlížeče (viz „Roboti se nepočítají").

1. Adresář cache obsahuje `manifest.json` po dvou načteních stránky pár sekund po sobě.
2. Zdrojový kód stránky obsahuje HTML plochy a každé `href` a `src` v něm míří na vaši doménu.
3. V HTML plochy a ve vašem obalu neobsahuje žádná hodnota `class`, `id`, `href` ani `src` slova `adneto`, `ads`, `banner`, `sponsor` nebo `promo` a žádná neobsahuje `ad` jako slovo nebo část oddělenou `-` či `_`. Kontrolujte jen hodnoty atributů; text jako `padding` ve stylech nebo hash obrázku se nepočítá.
4. Otevření odkazu z plochy přesměruje (HTTP 302) na adresu zadavatele.
5. Odpověď adresy pro prokliky nemá hlavičku `Set-Cookie`.
6. Zkouška výpadku: nastavte `'api' => 'https://invalid.invalid/api/v1'`, cache nechte a vynuťte obnovu jedním spuštěním `Adneto::refreshNow()` z dočasného skriptu, který načte vaše nastavení. Musí vrátit `false`. Pak načtěte stránku: plocha se dál vykreslí z cache. Nastavení vraťte a skript smažte.
7. Zkouška prázdné cache: nasměrujte `cache_dir` na prázdný adresář, nechte neplatné `api` a načtěte stránku dvakrát. Stránka se načte, plocha je prázdná a v chybovém logu PHP (nebo v logu vašeho frameworku) nepřibyl žádný záznam. Obě nastavení vraťte.
8. Do 15 minut ukáže detail webu v Adneto „Naposledy připojen" a po několika zobrazeních s user-agentem prohlížeče i přijaté statistiky.

## 7. Jiné technologie: API

Když web není v PHP, naprogramujte stejné chování proti API. Základní adresa: `https://adneto.com/api/v1`. Každý požadavek autentizujte hlavičkou `Authorization: Bearer <token webu>`.

### GET /manifest

Stahujte nejvýš jednou za 5 minut; řiďte se hodnotou `ttl` (sekundy). Posílejte `If-None-Match` s posledním `ETag`; `304` znamená beze změny. Při `401` je token špatně nebo je web pozastavený: nechte si poslední manifest a zkuste to později.

```json
{
  "v": 1,
  "ttl": 600,
  "slots": {
    "sidebar": {
      "schedule": [
        {
          "from": 1794351600,
          "until": 1794610800,
          "items": [
            {
              "id": 12,
              "campaign": 5,
              "html": "<span class=\"x1a2b3c4\"><a href=\"{{click}}\">…</a><img src=\"{{pixel}}\" alt=\"\"></span>",
              "url": "https://zadavatel.example/stranka",
              "images": { "<sha256>": "https://cdn.example/…/<sha256>.webp" }
            }
          ]
        }
      ]
    }
  }
}
```

Vykreslení plochy:

1. Vyberte úsek rozvrhu, pro který platí `from <= teď < until` (unixové sekundy). Bez úseku nebo bez položek je plocha prázdná.
2. Vyberte náhodně jednu položku.
3. V `html` nahraďte `{{click}}` adresou na vaší doméně, která přesměruje na `url`, `{{pixel}}` adresou na vaší doméně, která vrátí GIF 1×1 s `Cache-Control: no-store`, a každé `{{img:<sha256>}}` adresou na vaší doméně, která servíruje obrázek z cache. Vkládané adresy escapujte pro HTML.
4. Každý obrázek stáhněte jednou, ověřte, že SHA-256 obsahu odpovídá klíči, a servírujte ho s dlouhou dobou cache.
5. Položku, jejíž `html` obsahuje `<script`, atribut `on…=` nebo `javascript:`, zahoďte.
6. Adresa prokliku musí určit plochu, kampaň a kreativu a `url` dohledat v manifestu. Nikdy nepřesměrovávejte na adresu převzatou z požadavku.

### POST /stats

Počty posílejte při obnově manifestu. Řádek je jedna plocha, kampaň, kreativa a hodina.

```json
{
  "batch": "32 malých hexadecimálních znaků, pro každou dávku jiných",
  "rows": [
    { "hour": 1794391200, "slot": "sidebar", "campaign": 5, "creative": 12, "renders": 120, "views": 100, "clicks": 4 }
  ]
}
```

- `hour` je unixový čas začátku hodiny (UTC).
- `renders` = vykreslení na serveru, `views` = načtení měřicího bodu, `clicks` = přesměrování.
- `2xx` znamená přijato; opakovaná `batch` se potvrdí a nezapočítá dvakrát, takže po chybě sítě pošlete stejnou dávku znovu. `422` znamená vadnou dávku: zahoďte ji.
- Požadavky robotů nepočítejte. Neukládejte ani neposílejte nic, co identifikuje návštěvníka.
