Skip to content

Commit

Permalink
Add possibility to add custom entries to the sitemap
Browse files Browse the repository at this point in the history
  • Loading branch information
wanze committed Mar 11, 2019
1 parent 6507f4d commit 2178ee5
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 65 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Added

* The canonical URL is now part of the `meta` group and can be customized ([#4](https://github.com/wanze/SeoMaestro/issues/4))
* Add possibility to include custom sitemap items by hooking `SeoMaestro::sitemapItems`

### Fixed

Expand Down
14 changes: 14 additions & 0 deletions SeoMaestro.module.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@ public function ___sitemapAlwaysExclude(PageArray $excludedPages)
return $excludedPages;
}

/**
* Hook to modify sitemap items.
*
* Use this hook to modify or add or modify items in the sitemap.
*
* @param \SeoMaestro\SitemapItem[] $sitemapItems
*
* @return array
*/
public function ___sitemapItems(array $sitemapItems)
{
return $sitemapItems;
}

private function shouldGenerateSitemap()
{
if (!$this->get('sitemapEnable')) {
Expand Down
36 changes: 36 additions & 0 deletions src/SitemapItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace SeoMaestro;

use ProcessWire\WireData;

class SitemapItem extends WireData
{
public function __construct()
{
parent::__construct();

$this->set('loc', '');
$this->set('lastmod', '');
$this->set('priority', 0.5);
$this->set('changefreq', 'monthly');
$this->set('alternates', []);
}

/**
* Add an alternate URL for another language.
*
* @param string $languageCode
* @param string $url
*
* @return \SeoMaestro\SitemapItem
*/
public function addAlternate($languageCode, $url)
{
$alternates = $this->get('alternates');
$alternates[$languageCode] = $url;
$this->set('alternates', $alternates);

return $this;
}
}
131 changes: 90 additions & 41 deletions src/SitemapManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace SeoMaestro;

use ProcessWire\Page;
use ProcessWire\PageArray;
use ProcessWire\TemplateFile;
use ProcessWire\WireData;
Expand All @@ -18,13 +19,7 @@ public function __construct(array $config)
{
parent::__construct();

$this->data = array_merge(
[
'baseUrl' => '',
'defaultLanguage' => 'en',
],
$config
);
$this->data = array_merge(['baseUrl' => '', 'defaultLanguage' => 'en'], $config);
}

/**
Expand All @@ -36,26 +31,37 @@ public function __construct(array $config)
*/
public function generate($sitemapPath)
{
$pages = $this->getPages();
$items = $this->buildSitemapItems();

if (!$pages->count()) {
if (!count($items)) {
return false;
}

$sitemap = $this->renderSitemap($pages);
$sitemap = $this->renderSitemap($items);

return file_put_contents($sitemapPath, $sitemap);
}

/**
* @return \ProcessWire\PageArray
* @return \SeoMaestro\SitemapItem[]
*/
protected function getPages()
private function buildSitemapItems()
{
$items = $this->buildSitemapItemsFromPages();

return $this->wire('modules')->get('SeoMaestro')
->sitemapItems($items);
}

/**
* @return \SeoMaestro\SitemapItem[]
*/
private function buildSitemapItemsFromPages()
{
$templates = $this->getTemplatesWithSeoMaestroField();

if (!count($templates)) {
return new PageArray();
return [];
}

$selector = sprintf('template=%s,template!=admin,id!=%s,include=hidden',
Expand All @@ -65,23 +71,25 @@ protected function getPages()

$pages = $this->wire('pages')->findMany($selector);

// Use the guest user while building the sitemap items, to ensure proper page view permissions.
$user = $this->wire('users')->getCurrentUser();
$guest = $this->wire('users')->getGuestUser();
$this->wire('users')->setCurrentUser($guest);

$filtered = [];
$items = [];
foreach ($pages as $page) {
$field = $templates[$page->template->name];

if (!$page->viewable($guest) || !$page->get($field)->sitemap->include) {
if (!$page->viewable() || !$page->get($field)->sitemap->include) {
continue;
}

// Set a temporary alias to the sitemap data, referenced during rendering.
$page->set('seoMaestroSitemapData', $page->get($field)->sitemap);

$filtered[] = $page;
$items = array_merge($items, $this->buildSitemapItemsFromPage($page, $page->get($field)->sitemap));
}

return (new PageArray())->import($filtered);
$this->wire('users')->setCurrentUser($user);

return $items;
}

/**
Expand All @@ -96,37 +104,19 @@ private function getExcludedPages()
$excluded = (new PageArray())
->add($page404);

// Allow to exclude additional pages by hooking SeoMaestro::sitemapAlwaysExclude().
return $this->wire('modules')->get('SeoMaestro')
->sitemapAlwaysExclude($excluded);
}

/**
* Render the sitemap with the given pages.
*
* @param \ProcessWire\PageArray $pages
*
* @return string
*/
private function renderSitemap(PageArray $pages)
private function renderSitemap(array $items)
{
$template = new TemplateFile(dirname(__DIR__) . '/templates/sitemap.xml.php');
$template->set('pages', $pages);
$template->set('baseUrl', rtrim($this->get('baseUrl'), '/'));
$template->set('defaultLanguageCode', $this->get('defaultLanguage'));
$template->set('hasLanguageSupport', $this->wire('modules')->isInstalled('LanguageSupport'));
$template->set('hasLanguageSupportPageNames', $this->wire('modules')->isInstalled('LanguageSupportPageNames'));

// Use the guest user while rendering, to ensure proper page view permissions.
$user = $this->wire('users')->getCurrentUser();
$guest = $this->wire('users')->getGuestUser();
$this->wire('users')->setCurrentUser($guest);

$sitemap = $template->render();

$this->wire('users')->setCurrentUser($user);
$template->set('items', $items);

return $sitemap;
return $template->render();
}

/**
Expand All @@ -146,4 +136,63 @@ private function getTemplatesWithSeoMaestroField()

return $templates;
}

/**
* @return \SeoMaestro\SitemapItem[]
*/
private function buildSitemapItemsFromPage(Page $page, SitemapSeoData $sitemapData)
{
$languageSupport = $this->wire('modules')->isInstalled('LanguageSupport');
$languageSupportPageNames = $this->wire('modules')->isInstalled('LanguageSupportPageNames');
$items = [];

if ($languageSupport && $languageSupportPageNames) {
foreach ($this->wire('languages') as $language) {
if (!$page->viewable($language)) {
continue;
}

$loc = $this->get('baseUrl') ? $this->get('baseUrl') . $page->localUrl($language) : $page->localHttpUrl($language);

$item = (new SitemapItem())
->set('priority', $sitemapData->priority)
->set('lastmod', $this->getLastMod($page))
->set('changefreq', $sitemapData->changeFrequency)
->set('loc', $loc);

$this->addAlternatesToSitemapItem($page, $item);
$items[] = $item;
}

return $items;
}

// Single language setups.
$item = (new SitemapItem())
->set('priority', $sitemapData->priority)
->set('lastmod', $this->getLastMod($page))
->set('changefreq', $sitemapData->changeFrequency)
->set('loc', $this->get('baseUrl') ? $this->get('baseUrl') . $page->url : $page->httpUrl);

return [$item];
}

private function addAlternatesToSitemapItem(Page $page, SitemapItem $item)
{
foreach ($this->wire('languages') as $language) {
if (!$page->viewable($language)) {
continue;
}

$code = $language->isDefault() ? $this->get('defaultLanguage') : $language->name;
$loc = $this->get('baseUrl') ? $this->get('baseUrl') . $page->localUrl($language) : $page->localHttpUrl($language);

$item->addAlternate($code, $loc);
}
}

private function getLastMod(Page $page)
{
return date('c', $page->modified);
}
}
32 changes: 10 additions & 22 deletions templates/sitemap.xml.php
Original file line number Diff line number Diff line change
@@ -1,30 +1,18 @@
<?php namespace ProcessWire; ?>
<?php /** @var $pages \ProcessWire\PageArray */ ?>
<?php /** @var $languages \ProcessWire\PageArray */ ?>
<?php /** @var $items \SeoMaestro\SitemapItem[] */ ?>
<?php echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
<?php foreach ($pages as $page): ?>
<?php if ($hasLanguageSupport && $hasLanguageSupportPageNames): ?>
<?php foreach ($languages ?: [] as $language): ?>
<?php if (!$page->viewable($language)) continue; ?>
<?php foreach ($items as $item): ?>
<url>
<loc><?= $baseUrl ? $baseUrl . $page->localUrl($language) : $page->localHttpUrl($language) ?></loc>
<lastmod><?= date('c', $page->modified) ?></lastmod>
<changefreq><?= $page->seoMaestroSitemapData->changeFrequency ?></changefreq>
<priority><?= $page->seoMaestroSitemapData->priority ?></priority>
<?php foreach ($languages as $language): ?>
<?php if (!$page->viewable($language)) continue; ?>
<xhtml:link rel="alternate" hreflang="<?= $language->isDefault() ? $defaultLanguageCode : $language->name ?>" href="<?= $baseUrl ? $baseUrl . $page->localUrl($language) : $page->localHttpUrl($language) ?>"/>
<loc><?= $item->loc ?></loc>
<?php if ($item->lastmod): ?>
<lastmod><?= $item->lastmod ?></lastmod>
<?php endif ?>
<changefreq><?= $item->changefreq ?></changefreq>
<priority><?= $item->priority ?></priority>
<?php foreach ($item->alternates as $langCode => $url): ?>
<xhtml:link rel="alternate" hreflang="<?= $langCode ?>" href="<?= $url ?>"/>
<?php endforeach ?>
</url>
<?php endforeach ?>
<?php else: ?>
<url>
<loc><?= $baseUrl ? $baseUrl . $page->url : $page->httpUrl ?></loc>
<lastmod><?= date('c', $page->modified) ?></lastmod>
<changefreq><?= $page->seoMaestroSitemapData->changeFrequency ?></changefreq>
<priority><?= $page->seoMaestroSitemapData->priority ?></priority>
</url>
<?php endif ?>
<?php endforeach ?>
</urlset>
27 changes: 25 additions & 2 deletions tests/src/SitemapManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use ProcessWire\HookEvent;
use ProcessWire\Page;
use SeoMaestro\SitemapItem;
use SeoMaestro\SitemapManager;

/**
Expand Down Expand Up @@ -182,7 +183,7 @@ public function sitemap_should_not_contain_pages_excluded_with_hook()
$this->assertSitemapContains($page2->get('name'));

// Exclude $page2
$hookId = $this->wire()->addHookAfter('SeoMaestro::sitemapAlwaysExclude', function (HookEvent $event) use ($page2) {
$hookId = $this->addHookAfter('SeoMaestro::sitemapAlwaysExclude', function (HookEvent $event) use ($page2) {
/** @var \ProcessWire\PageArray $pageArray */
$pageArray = $event->arguments(0);
$pageArray->add($page2);
Expand All @@ -191,8 +192,30 @@ public function sitemap_should_not_contain_pages_excluded_with_hook()
$this->sitemapManager->generate($this->sitemap);
$this->assertSitemapContains($page1->get('name'));
$this->assertSitemapNotContains($page2->get('name'));
}

/**
* @test
* @covers ::generate
*/
public function sitemap_should_contain_items_added_with_hook()
{
$item = (new SitemapItem())
->set('loc', '/en/my-custom-url')
->set('priority', 'custom-priority')
->set('changefreq', 'changefreq-custom')
->addAlternate('de', '/de/my-custom-url-de');

$this->addHookAfter('SeoMaestro::sitemapItems', function (HookEvent $event) use ($item) {
$event->return = array_merge($event->return, [$item]);
});

$this->sitemapManager->generate($this->sitemap);

$this->wire()->removeHook($hookId);
$this->assertSitemapContains($item->loc);
$this->assertSitemapContains($item->priority);
$this->assertSitemapContains($item->changefreq);
$this->assertSitemapContains('/de/my-custom-url-de');
}

protected function tearDown()
Expand Down

0 comments on commit 2178ee5

Please sign in to comment.