Jak nejsnáze zařídit sitemap.xml generator

scientific
Člen | 93
+
0
-

Ahoj všem,

umí Nette generovat sitemaps.xml?

Našel jsem třeba toto: https://github.com/…ap-generator

ale nejde mi to nainstalovat, protože: „Could not find package icamys/php-sitemap-generator at any version matching your PHP version 5.4.16.0“

Aktualizace je u tohoto projektu komplikovaná, a nezvládl bych ji. Vyžadovala by dokonce dost programování kvůli zaniklému modulu a nedodržení MVC tvůrcem webu.

Proto bych raději využil přímo Nette, jestli by to nějak šlo?

Děkuji za informace.

Kamil Valenta
Člen | 752
+
0
-

Já mám „sitemap generátor“ jako rozšíření Routerů, protože kdo jiný lépe ví, jaké adresy na webu jsou dostupné. Snadno se to udržuje.

A SitemapPresenter si pak jen projde Routery všech modulů a požádá si o data pro sitemap.xml

stepos2
Člen | 51
+
+5
-

Sitemapu si vytvoříš v presenteru a šablonu napíšeš v latte jako kteroukoli jinou stránku.

Presenter:

class SitemapPresenter extends Nette\Application\UI\Presenter
{
	/** @var IResponse @inject */
	public $response;

	public function renderDefault()
	{
		$this->response->setContentType('text/xml');
		$this->template->urls = [
			new Url($this->link('//Homepage:default'), new DateTime(), 'always', 0.5),
		];
	}
}

Url:

class Url
{
	public $loc;
	public $lastmod;
	public $frequency;
	public $priority;

	public function __construct(string $loc, DateTime $lastmod, string $frequency, float $priority)
	{
		$this->loc = $loc;
		$this->lastmod = $lastmod;
		$this->frequency = $frequency;
		$this->priority = $priority;
	}
}

Router:

$router->addRoute('sitemap.xml', 'Sitemap:default');

templates/Sitemap/default.latte

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" >
	<url n:foreach="$urls as $url">
		<loc>{$url->loc}</loc>
		<lastmod>{$url->lastmod->format('Y-m-d')}</lastmod>
		<changefreq>{$url->frequency}</changefreq>
		<priority>{$url->priority}</priority>
	</url>
</urlset>

Editoval stepos2 (27. 11. 2020 1:10)

scientific
Člen | 93
+
0
-

To vypadá parádně a jednoduše. Díky moc.

Akorát jsem začátečním, a nejsem si úplně jistý, co s tím mám dělat.

Pouze hádám:

  • **Presenter **mám asi vytvořit jako app/presenters/SitemapPresenter.php
  • **Url **vůbec netusím kam dát, že by do app/models/SitemapManager.php
  • **Router **možná přidat jako app/router/SitemapFactory.php
  • **Template **jediné vím kam dá (možná, protože jsi to uvedl), a to do app/templates/Sitemap/default.latte

Chápu to správně?

Editoval scientific (25. 11. 2020 13:45)

stepos2
Člen | 51
+
+1
-

Presenter přidáš tam kde máš ostatní presentery. Url dáš kamkoli do složky, kterou vidí RobotLoader (obvykle složka app). Router už máš, stačí tam přidat ten kód (možná upravit podle verze Nette).

scientific
Člen | 93
+
0
-

No presentery mám v app/presenters/*Presenter.php
Napadlo mě tedy i tento presenter přidat jako app/presenters/SitemapPresenter.php

Tracy mi ale po přístupu na /sitemap.xml píše:

Nette\Application\BadRequestException #404

Cannot load presenter 'Sitemap:Xml', class 'App\SitemapModule\Presenters\XmlPresenter' was not found.

V tvém kodu se píše „class SitemapPresenter extends Presenter“, tak proč to chce existenci „App\SitemapModule\Presenters\XmlPresenter“

/vendor/nette/application/src/Application/Application.php se snaží spouštět: „$this->presenterFactory->getPresenterClass("Sitemap:Xml“);"

Tady:

121:            try {
122:                $name = $request->getPresenterName();
123:                $this->presenterFactory->getPresenterClass($name);
124:            } catch (InvalidPresenterException $e) {
125:                throw new BadRequestException($e->getMessage(), 0, $e);
126:            }

Dále mi moc nedává smysl, aby se ta sitemapa generovala s každým načtením, asi jsem nějak retro. Přijde mi rozumnější spíš zadat CRON úlohu a tu sitemapu jednou denně v noci přegenerovat. Uchovávat jako soubor sitemap.xml ve FS. Nebo? Tohle byste mi doporučili i v případě, kdybyste věděli, že web má pět milionů stránek? :-)

Presenter Sitemap:xml se to snaží načíst právě tím, že přistupuji přes /sitemap.xml, možná se k němu snažím přistupovat špatně, jak se s tím tedy prosím pěkně pracuje?

EDIT:
Tím pádem mám špatně ten router, když neroutuje na Sitemap:default, ale na Sitemap:xml…

Takhle vypadá můj router, který je tedy jen jeden:

<?php

namespace App;

use Nette,
	Nette\Application\Routers\RouteList,
	Nette\Application\Routers\Route,
	Nette\Application\Routers\SimpleRouter;


/**
 * Router factory.
 */
class RouterFactory
{

	/**
	 * @return \Nette\Application\IRouter
	 */
	public static function createRouter()
	{
		$router = new RouteList();
		$router[] = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');
		$router[] = new Route('sitemap.xml', 'Sitemap:default');
		return $router;
	}

}

EDIT #2:

Kromě toho mám tedy tento presenter app/presenters/SitemapPresenter.php:

<?php
namespace App\Presenters;

use Nette,
	App\Model;

class SitemapPresenter extends Presenter
{
	public function renderDefault()
	{
		$this->template->urls = [
			new Url($this->link('//Homepage:default'), new DateTime(), 'always', 0.5),
			...
		]
	}
}
?>

Já ho mám ale celý zakmentovaný, když ho totiž odkomentuji, přestane web fungovat. Viz screenshot na odkazu, protože nejde nahrávat přílohy: https://www.imgbank.cz/image/9lfE

Nelíbí se tomu ty tři tečky. Taky se mi nezdá, že by tam měli syntakticky patřit a projít překladačem, ale i když je odstraním, tak to začne failovat pro unexpected „}“. :-D

Editoval scientific (26. 11. 2020 23:36)

Kamil Valenta
Člen | 752
+
+1
-

Cronem bych to negeneroval, co když přibude nějaká stránka a ty budeš muset čekat, dokud netikne cron. Lépe by bylo cachovat výstup toho SitemapPresenteru, resp. té latte šablony. A když přibude stránka, invaliduješ cache.

Tři tečky samozřejmě znamenaly, že dál to bude obdobně pokračovat.
Když je odebereš, ještě si doplň ; za ] na následujícím řádku.

Stále si ale myslím, že to není šťastný způsob, bude to špatně udržitelné, u nových stránek budeš muset pamatovat na editaci SitemapPresenteru, je to nepřenositelné, nemodulární.

Ale na malém projektu ti to fungovat bude…

stepos2
Člen | 51
+
+1
-

Novou routu musíš umístit jako první (přehodíš pořadí položek $router[]).

Ten můj kód je příklad, jak udělat v Nette to stejné jako knihovna, na kterou jsi odkazoval. Stránky si musíš ručně přidat do toho pole $this->template->urls (v knihovně se to stejné dělá přes metodu addURL). Takže není ani potřeba to v tomhle stavu dělat cronem nebo cachovat, protože se tam neděje nic náročného. Samozřejmě nějaké chytřejší generování by se hodilo. Tohle je základ, na kterém to pochopíš a pak na tom můžeš stavět.

scientific
Člen | 93
+
0
-

U malého projektu ani sitemapa snad není potřeba ne.
Generování adres nějak zkusím vymyslet. Fajn by asi bylo to pole adres udělat jako nějakou globální proměnnou, a pak do v SitemapManager.php plnit z databáze. Snad si s tím nějak rporadím, protože ručně vepisovat URI do toho presenteru je dost nesmysl.

Nicméně i tak mi to nefunguje. :-( Netušíte prosím co s tím?

class URL jsem přidal na konec presenteru app/presenters/SitemapPresenter.php, je v tom ale háček, failuje t pro syntax errory:

Chybovka #1: „Parse Error syntax error, unexpected ‚string‘ (T_STRING), expecting variable (T_VARIABLE)“
Řádek: „public string $loc;“

Chybovka #2: „Parse Error syntax error, unexpected ‚DateTime‘ (T_STRING), expecting variable (T_VARIABLE)“
Řádek: „public DateTime $lastmod;“

Chybovka #3: „Fatal Error Class ‚App\Presenters\DateTime‘ not found“
Řádek: „new Url($this->link(‚//Homepage:default‘), new DateTime(), ‚always‘, 0.5),“

Ukázka celého presenteru app/presenters/SitemapPresenter.php, ve kterém jsou failující řádky:

<?php
namespace App\Presenters;

use Nette,
	App\Model;

class SitemapPresenter extends BasePresenter
{
	public function renderDefault()
	{
		$this->template->urls = [
			new Url($this->link('//Homepage:default'), new DateTime(), 'always', 0.5),
			new Url($this->link('//Sign:in'), new DateTime(), 'always', 0.5)
		];
	}
}

class Url
{
	public string $loc;
	public DateTime $lastmod;
	public string $frequency;
	public float $priority;

	public function __construct(string $loc, DateTime $lastmod, string $frequency, float $priority)
	{
		$this->loc = $loc;
		$this->lastmod = $lastmod;
		$this->frequency = $frequency;
		$this->priority = $priority;
	}
}
?>

Editoval scientific (30. 11. 2020 12:22)

David Matějka
Moderator | 6445
+
+2
-

používáš php 8 syntaxi (typy u class properties), ale máš jen php 7

Kamil Valenta
Člen | 752
+
+1
-

Ve třetím případě, buď si srovnej namespace v use, nebo ho uváděj u inicializace objektu s počátečním \.

Classa Url patří spíš někam do modelu, rozhodně ne do Presenteru.

Editoval Kamil Valenta (30. 11. 2020 13:06)

scientific
Člen | 93
+
0
-

Dal jsem to do toho presenteru, protože, jinak mi tracy failovalo pro: Class ‚App\Presenters\Url‘ not found search►

Já si říkal, už jsem to předělal a funguje mi to. :-) Šílenci, už používáte PHP8 :-D

Chápu, že by se to dalo asi nějak takhle zařídit, ale jistý si tím vůbec nejsem, asi to nechám v tom presenteru, dokud to funguje, jsem low programátor typu „matlák“:

$this->url = new \App\Model\UrlManager($this->database);

public function renderDefault()
	{
		$this->template->urls = [
			this->url($this->link('//Homepage:default'), 'always', 0.5),
			this->url($this->link('//Sign:in'), 'always', 0.5)
		];
	}

Nějak mi to funguje, tak super a díky všem, kdyby měl někdo typ, jak managerem přes SQL dotaz generovat obsah $this->template->urls, tak budu rád. :-)

David Grudl
Nette Core | 8082
+
+2
-

Já vím, že ses na to neptal, ale odpověď je „negeneruj sitemap, fakt to nepotřebuješ“.

scientific
Člen | 93
+
0
-

Můžeš nějakým argumentem podložit tvé tvrzení? Mám tě za boha, takže si nedovolím ti nijak oponovat. Jde o to, že celkově nemám moc rád, když mi někdo trvdí, že něco tak je, ale už mi neřekne proč. Budu ti tedy vděčný, když mi dáš vědět, proč tvrdíš, že sitemapu nepotřebuji.

Typicky jsem alergický na to, když mi někdo tvrdí, že koronavirem chce vláda zničit ekonomiku, ale už mi není schopný vysvětlit, proč to tvrdí. Neví, nerozumí tomu, jen to tvrdí jako prostý fakt, který když nepřijmu, tak jsem hloupý tupec. :-D

Běžné weby sitemapu asi nepotřebují, ale podle mého amatérského vnímání, není možné říct, že sitemapa je něco, co nikdo nepotřebuje. Můj web má mnoho stránek, které jsou dostupné po přihlášení. Z nichž některé se náhodně rotují na hlavní stránce i bez přihlášení. Takže bez sitemapy si myslím, že to v tomto případě nebude fungovat dobře, když ty stránky jsou dostupné až tabulky po přihlášení. Zvenku (bez přihlášení) tedy na něj odkazy nevedou.

Domnívám se tedy, že není pravda, žer sitemapu nepotřebuji. Nicméně, jak říkám mám tě za boha, a nechám se poučit od zkušenějších. I po tom, co jsem ti poslal můj web, pořád si myslíš, že sitemapu nepotřebuji? Děkuji ti moc za informaci profíka.

Editoval scientific (1. 12. 2020 10:25)

Kamil Valenta
Člen | 752
+
0
-

scientific napsal(a):

Mám tě za boha, takže si nedovolím ti nijak oponovat.

Pardon, že do toho vstupuji, ale tohle je z principu špatně a doporučuji takové postoje nemít :) Hrome, a teď musím říct proč, že? Protože mít jakékoliv idoly snadno vede k tomu, že je pak člověk napodobuje i s jejich nešvary. Lépe je se tedy jen inspirovat druhými, ale udržovat si vlastní úsudek a dávku kritického myšlení…

Takže bez sitemapy si myslím, že to v tomto případě nebude fungovat dobře, když ty stránky jsou dostupné až tabulky po přihlášení. Zvenku (bez přihlášení) tedy na něj odkazy nevedou.

A jaké URL tedy chceš v té sitemapě mít? Když nejsou bez přihlášení dostupné. Naopak vnímám jako zbytečnost dávat do sitemapy URL, která nekončí HTTP 200, protože je Google stejně zahodí.

Já si myslím, že sitemapa dobrá je. Už jen třeba na webu, kde do obsahu šťourá klient a nejde zaručit, že na každou stránku povede v reálu odkaz (co když povede a klient ho smaže?).

Pavel Kravčík
Člen | 1180
+
0
-

@scientific – Sitemap obecně slouží spíše pro roboty, než lidi. Pokud děláš sitemapu pro stránky po přihlášení, tak je to zbytečné jako tleskat na zmije. :)

Zdroje se dají najít snadno přes vyhledávač:

Google, Alza robot.txt, Seznam

Gappa
Nette Blogger | 198
+
0
-

scientific napsal(a):

Můžeš nějakým argumentem podložit tvé tvrzení?

Ve zkratce se dá říci, že sitemap.xml řeší problém, který už nějakou dobu neexistuje :)

Edit: V článku jsou zmíněné případy, kdy se hodit může, ale pro drtivou většinu webů nedává smysl.

Editoval Gappa (1. 12. 2020 11:35)

scientific
Člen | 93
+
0
-

No to přihlašování jsem do toho zamíchal a trochu vás to zmátlo. Stránka samozřejmě odpovídá a existuje i pro nepřihlášené, jen na ni nikde nevede odkaz. Resp. tyhle demo stránky rotují na hlavní stránce. Plnohodnotné stránky stránky vidí pouze registrovaný a přihlášený uživatel. Takže ty demo stránky chci samozřejmě indexovat, i přestože na ně z veřejné stránky není odkaz, přestože existují.

Bez sitemapy se tedy neobejdu.

Nyní ještě musím vymyslet SitemapManager, který bude z databáze plnit to pole „Urls“, a bude snad hotovo.

Díky všem za poskytnuté informace a názory.

David Grudl
Nette Core | 8082
+
+1
-

A není lepší aby na ně vedl odkaz? Stránku na kterou nevede žádný odkaz Google vůbec nemusí indexovat. Sitemap ho k tomu nedonutí.

Kamil Valenta
Člen | 752
+
0
-

Nedonutí, ale přesto to sám Google doporučuje.
https://developers.google.com/…search-works

A odtud
https://developers.google.com/…aps/overview
pak:
„Using a sitemap doesn't guarantee that all the items in your sitemap will be crawled and indexed, as Google processes rely on complex algorithms to schedule crawling. However, in most cases, your site will benefit from having a sitemap, and you'll never be penalized for having one.“

scientific
Člen | 93
+
0
-

Já zkrátka sitemapu potřebuji, protože nejsou odkazy a dělat je nechci. Taky používám Google nástroj, který vyloženě trvá na tom, že sitemapu chce.

Nakonec jsem to nějak zvládl vyřešit po svém. Přikládám řešení, kdyby náhodou někdo další potřeboval sitemapu a zároveň nechtěl někde v presenteru ručně vypisovat jednotlivé stránky. Web měl nějak brutálně hodně stránek, tak doporučuji sitemapu uchovávat jako fyzický soubor ve FS, generovaný jednou denně, či dle potřeby. Eventuálně s přidáním nějakého hooku/handleru pro vyvolání spuštění rutiny na přegenerování sitemapy. Ještě by se asi hodilo přes nějakou komponentu, se kterou já pracovat neumím, getovat hostname, aby nemuselo být v kodu staticky vepsané.

Myslím, že jsem to ani tentokrát snad moc nezfušoval, a mám radost, že to funguje. :-)

Template:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" >
	<url n:foreach="$data as $url">
		<loc>{$url->loc}</loc>
		<lastmod>{$url->lastmod}</lastmod>
		<changefreq>{$url->frequency}</changefreq>
		<priority>{$url->priority}</priority>
	</url>
</urlset>

Presenter:

<?php
namespace App\Presenters;

use Nette,
	App\Model;

class SitemapPresenter extends BasePresenter
{
    /** @var \App\Model\User */
    public $user;

	public function renderDefault()
	{
		$this->template->data = $this->user->listUrlSitemap();
	}
}
?>

Model:

<?php
namespace App\Model;
use Nette;

class SitemapManager
{
	/** @var Nette\Database\Context */
	private $database;

	public function __construct(Nette\Database\Context $database)
	{
		$this->database = $database;
	}

	public function listUrlSitemap()
	{
		$query = $this->database->query('
			SELECT
				CONCAT("http://domain.example/content/videos/", id) AS loc,
				NOW() AS lastmod,
				"always" AS frequency,
				"0.5" AS priority
			FROM videos
			WHERE
				state = "ready"
				AND active = 1
				AND prepare_progress = 0

			UNION

			SELECT
				CONCAT("http://domain.example/content/photos/", id),
				NOW() AS lastmod,
				"always" AS frequency,
				"0.5" AS priority
			FROM photogallery
			WHERE
				  active = 1

		')->fetchAll();

		return $query;
	}
}
?>

Router:

<?php

namespace App;

use Nette,
	Nette\Application\Routers\RouteList,
	Nette\Application\Routers\Route,
	Nette\Application\Routers\SimpleRouter;


/**
 * Router factory.
 */
class RouterFactory
{

	/**
	 * @return \Nette\Application\IRouter
	 */
	public static function createRouter()
	{
		$router = new RouteList();
		$router[] = new Route('sitemap.xml', 'Sitemap:default');
		$router[] = new Route('<presenter>/<action>[/<id>]', 'Homepage:default');
		return $router;
	}

}

Tím je to vyřešené, díky moc.

Editoval scientific (1. 12. 2020 20:59)

m.brecher
Generous Backer | 713
+
0
-

Kamil Valenta napsal(a):

Já mám „sitemap generátor“ jako rozšíření Routerů, protože kdo jiný lépe ví, jaké adresy na webu jsou dostupné. Snadno se to udržuje.

A SitemapPresenter si pak jen projde Routery všech modulů a požádá si o data pro sitemap.xml

@KamilValenta
Ahoj, to zní zajímavě, také teď řeším, jak obecně vyřešit generátor všech dostupných url na webu. Myšlenka jít na to přes Routery vypadá analyticky správně. Mohl by Jsi prosím Tě poslat nějakou ukázku těch Routerů, těch rozšíření a jak to celé pracuje, takhle bez kódu si to neumím prakticky představit.

Díky

Editoval m.brecher (3. 2. 2022 19:02)