Skip to content

Commit

Permalink
feat: add sitemap() method 馃槏
Browse files Browse the repository at this point in the history
  • Loading branch information
BernhardBaumrock committed Mar 19, 2024
1 parent 4d26c73 commit 600bb32
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 37 deletions.
146 changes: 109 additions & 37 deletions RockFrontend.module.php
Original file line number Diff line number Diff line change
Expand Up @@ -2284,6 +2284,73 @@ public function setViewFolders(array $folders): void
$this->viewfolders = $folders;
}

/**
* Create a sitemap.xml file with a simple callback
* @return void
*/
public function sitemap($callback = null, $options = []): void
{
if (!$callback) $callback = function (Page $page) {
return $page;
};
$this->sitemapCallback = $callback;
wire()->addHookAfter("/sitemap.xml", $this, "sitemapRender");
wire()->addHookAfter("Modules::refresh", $this, "sitemapReset");
wire()->addHookAfter("Pages::saved", $this, "sitemapReset");
}

protected function sitemapRender()
{
// make sure to render the sitemap as seen by the guest user
$this->wire->user = $this->wire->users->get('guest');

// create markup
$out = "<?xml version='1.0' encoding='UTF-8'?>\n";
$out .= "<urlset xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'>\n";

// recursive function to traverse the page tree
$f = function ($items = null) use (&$f, &$out) {
if (!$items) $items = wire()->pages->get(1);
if ($items instanceof Page) $items = [$items];
foreach ($items as $p) {
/** @var Page $p */
if (!$p->viewable()) continue;
$callback = $this->sitemapCallback;
$result = $callback($p);
if ($result === false) {
// don't traverse further down the tree
return;
} elseif ($result instanceof Page) {
// if a page is returned we create a basic default markup
$modified = date("Y-m-d", $result->modified);
$out .= "<url>\n"
. "<loc>{$result->httpUrl()}</loc>\n"
. "<lastmod>$modified</lastmod>\n"
. "</url>\n";
} elseif ($result) {
// custom markup returned - add it to output
$out .= "$result\n";
}
$f($p->children("include=hidden"));
}
};
$f();
$out .= '</urlset>';

// create sitemap.xml file
$file = $this->wire->config->paths->root . "sitemap.xml";
$this->wire->files->filePutContents($file, $out);

header('Content-Type: application/xml');
return $out;
}

protected function sitemapReset(HookEvent $event): void
{
$file = $this->wire->config->paths->root . "sitemap.xml";
if (is_file($file)) wire()->files->unlink($file);
}

/**
* Get given StylesArray instance or a new one if no name is provided
*
Expand Down Expand Up @@ -2554,16 +2621,10 @@ public function getModuleConfigInputfields($inputfields)

private function configLatte(InputfieldWrapper $inputfields): void
{
$hasLatteFiles = $this->wire->files->find(
$this->wire->config->paths->templates,
['extensions' => ['latte']]
);
$fs = new InputfieldFieldset();
$fs->label = "Latte";
$fs->icon = "code";
$fs->collapsed = $hasLatteFiles
? Inputfield::collapsedNo
: Inputfield::collapsedYes;
$fs->collapsed = Inputfield::collapsedYes;
$inputfields->add($fs);

$f = new InputfieldCheckbox();
Expand Down Expand Up @@ -2600,6 +2661,7 @@ private function configLivereload(InputfieldWrapper $inputfields)
$fs = new InputfieldFieldset();
$fs->label = "LiveReload";
$fs->icon = "refresh";
$fs->collapsed = Inputfield::collapsedYes;
$inputfields->add($fs);

$f = new InputfieldMarkup();
Expand Down Expand Up @@ -2643,48 +2705,56 @@ private function configSEO($inputfields)
$fs = new InputfieldFieldset();
$fs->label = "SEO";
$fs->icon = "search";
$fs->collapsed = Inputfield::collapsedYesAjax;
$inputfields->add($fs);
$root = $this->wire->config->paths->root;
$warn = '<svg style="color:#F9A825" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0m9-3v4m0 3v.01"/></svg>';
$check = '<svg style="color:#388E3C" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0"/><path d="m9 12l2 2l4-4"/></g></svg>';

$fs->add([
'type' => 'markup',
'label' => 'robots.txt',
'value' => is_file($root . "robots.txt")
? "$check robots.txt is present"
: "$warn no robots.txt in site root",
]);
$fs->add([
'type' => 'markup',
'label' => 'sitemap.xml',
'value' => is_file($root . "sitemap.xml")
? "$check sitemap.xml was found"
: "$warn no sitemap.xml in site root",
'notes' => is_file($root . "sitemap.xml")
? ''
: 'See [docs](https://www.baumrock.com/en/processwire/modules/rockfrontend/docs/seo/).',
]);
if ($this->wire->config->ajax) {
$http = new WireHttp();

$http = new WireHttp();
try {
$markup = $http->get($this->wire->pages->get(1)->httpUrl());
$url = $http->getResponseHeaders('location') ?: '/';
$dom = rockfrontend()->dom($markup);
$ogimg = $dom->filter("meta[property='og:image']")->count() > 0;
$fs->add([
'type' => 'markup',
'label' => 'og:image',
'value' => $ogimg
? "$check og:image tag found on page $url"
: "$warn no og:image tag on page $url",
'label' => 'robots.txt',
'value' => is_file($root . "robots.txt")
? "$check robots.txt is present"
: "$warn no robots.txt in site root",
]);
} catch (\Throwable $th) {

$hasSitemap = $http->status(
$this->wire->pages->get(1)->httpUrl() . "sitemap.xml"
);
$fs->add([
'type' => 'markup',
'label' => 'og:image',
'value' => $warn . " " . $th->getMessage(),
'label' => 'sitemap.xml',
'value' => $hasSitemap
? "$check sitemap.xml was found"
: "$warn no sitemap.xml in site root",
'notes' => is_file($root . "sitemap.xml")
? ''
: 'See [docs](https://www.baumrock.com/en/processwire/modules/rockfrontend/docs/seo/).',
]);

try {
$markup = $http->get($this->wire->pages->get(1)->httpUrl());
$url = $http->getResponseHeaders('location') ?: '/';
$dom = rockfrontend()->dom($markup);
$ogimg = $dom->filter("meta[property='og:image']")->count() > 0;
$fs->add([
'type' => 'markup',
'label' => 'og:image',
'value' => $ogimg
? "$check og:image tag found on page $url"
: "$warn no og:image tag on page $url",
]);
} catch (\Throwable $th) {
$fs->add([
'type' => 'markup',
'label' => 'og:image',
'value' => $warn . " " . $th->getMessage(),
]);
}
}
}

Expand All @@ -2693,6 +2763,7 @@ private function configSettings($inputfields)
$fs = new InputfieldFieldset();
$fs->label = "Settings";
$fs->icon = "cogs";
$fs->collapsed = Inputfield::collapsedYes;
$inputfields->add($fs);

$fs->add([
Expand Down Expand Up @@ -2746,6 +2817,7 @@ private function configTools(&$inputfields)
$fs = new InputfieldFieldset();
$fs->label = "Tools";
$fs->icon = "wrench";
$fs->collapsed = Inputfield::collapsedYes;

$this->manifestConfig($fs);

Expand Down
62 changes: 62 additions & 0 deletions docs/seo/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,68 @@ RockFrontend comes with tools that help you build SEO optimized websites in no t

## sitemap.xml

RockFrontend introduces a streamlined way to generate `sitemap.xml` files for your website, enhancing your SEO capabilities with minimal effort. The `sitemap()` method simplifies the creation and maintenance of sitemaps, ensuring your site's pages are easily discoverable by search engines.

### Quick Start

To generate a sitemap, simply call the `sitemap()` method in your `/site/ready.php` file:

```php
rockfrontend()->sitemap();
```

This will do the following:

* It will create the file /sitemap.xml on demand when it is requested
* It will automatically delete this file on `Pages::saved` and on `Modules::refresh`
* It will create the following markup for every viewable page in your tree:

```xml
<url>
<loc>https://example.com/foo/bar/</loc>
<lastmod>2024-03-18</lastmod>
</url>
```

### Customisation

If you want, you can totally customise the markup of every page's entry by providing a callback to the `sitemap()` method. This is the default callback that is used by RockFrontend if no callback is provided:

```php
rockfrontend()->sitemap(function (Page $page) {
$modified = date("Y-m-d", $page->modified);
return "<url>\n"
. "<loc>{$page->httpUrl()}</loc>\n"
. "<lastmod>$modified</lastmod>\n"
. "</url>";
});
```

### Excluding pages from the sitemap

By default, all hidden pages will be included in the sitemap generated by RockFrontend. However, there might be instances where you want to exclude specific pages or even an entire branch of your page tree from appearing in the sitemap. This can be easily achieved by customizing the callback function provided to the `sitemap()` method:

```php
rockfrontend()->sitemap(function (Page $page) {
if($page->name === 'foo') {
// this will exclude the whole /foo branch from the sitemap
return false;
}

if($page->name === 'bar') {
// this will skip page /bar
// but it will still process /bar/one, /bar/two, etc
return;
}

if($page->name === 'baz') {
// returning the page object results in the default markup:
// <url><loc>...</loc><lastmod>...</lastmod></url>
return $page;
}
});
```

## Concept

The Concept of RockFrontend's SEO tools differ from many other solutions where you add many fields to every template and then have the user fill all these fields.
Expand Down

0 comments on commit 600bb32

Please sign in to comment.