Skip to content

Commit

Permalink
feat: add NativeRenderer, to allow usage of this package outside of L…
Browse files Browse the repository at this point in the history
…aravel. Fix the composer requirements.
  • Loading branch information
mauriziofonte committed Mar 23, 2023
1 parent 3001799 commit 15efcaf
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 50 deletions.
76 changes: 46 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
# Generate sitemaps with Laravel, compatible with Images Sitemap and News Sitemap
# Generate sitemaps with Laravel (or for a Non-Laravel project), compatible with Images Sitemap and News Sitemap

[This package has been forked from spatie/laravel-sitemap](https://github.com/spatie/laravel-sitemap), to remove support for `SitemapGenerator`, remove installation requirement for PHP 8, and add support for **Images Sitemaps** and **News Sitemaps**.

Additionally, this package adds support for installation **outside a Laravel Project**. See **Using this package outside Laravel** section.

This package can generate a valid sitemap by writing your own custom logic for the sitemap structure, via the API provided by this package.

This package requires **PHP 7.4** and **Laravel 8**.
> Heads up! This package requires _Laravel 9_ or _Laravel 10_
> For **PHP 7.4 and Laravel 8 compatibility** refer to __v1.1.*__
[![Latest Stable Version](https://poser.pugx.org/mfonte/laravel-sitemap/v/stable)](https://packagist.org/packages/mfonte/laravel-sitemap)
[![Total Downloads](https://poser.pugx.org/mfonte/laravel-sitemap/downloads)](https://packagist.org/packages/mfonte/laravel-sitemap)
[![Coverage Status](https://scrutinizer-ci.com/g/mauriziofonte/laravel-sitemap/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/mauriziofonte/laravel-sitemap/)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/mauriziofonte/laravel-sitemap/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/mauriziofonte/laravel-sitemap/)

## Installation

For Laravel 9 or 10, or for a non-Laravel-based project running on **PHP >= 8.0**

`composer require mfonte/laravel-sitemap`

For Laravel 8, or for a non-Laravel-based project running on **PHP >= 7.4 && < 8.0**

`composer require mfonte/laravel-sitemap "^1.1"`

## Creating sitemaps

You can only create your sitemap manually:
Expand All @@ -21,7 +34,6 @@ use Mfonte\Sitemap\Sitemap;
use Mfonte\Sitemap\Tags\Url;

Sitemap::create()

->add(
Url::create('/home')
->setLastModificationDate(Carbon::yesterday())
Expand All @@ -30,9 +42,7 @@ Sitemap::create()
->addImage('/path/to/image', 'A wonderful Caption')
->addNews('A long story short', 'en', Carbon::yesterday(), 'Sitemaps are this great!')
)

->add(...)

->writeToFile($path);
```

Expand Down Expand Up @@ -77,6 +87,7 @@ class Post extends Model implements Sitemapable
```

Now you can add a single post model to the sitemap or even a whole collection.

```php
use Mfonte\Sitemap\Sitemap;

Expand All @@ -87,33 +98,10 @@ Sitemap::create()

This way you can add all your pages super fast without the need to crawl them all.

## Installation

First, install the package via composer:

``` bash
composer require mfonte/laravel-sitemap
```

The package will automatically register itself.

## Usage
### Manually creating a sitemap

You can also create a sitemap fully manual:
## Creating a sitemap index

```php
use Carbon\Carbon;

Sitemap::create()
->add('/page1')
->add('/page2')
->add(Url::create('/page3')->setLastModificationDate(Carbon::create('2016', '1', '1')))
->writeToFile($sitemapPath);
```

### Creating a sitemap index
You can create a sitemap index:

```php
use Mfonte\Sitemap\SitemapIndex;

Expand Down Expand Up @@ -152,6 +140,34 @@ the generated sitemap index will look similar to this:
</sitemapindex>
```

## Using this package outside Laravel

The same instructions above apply, except for:

1. You **can not** use `Contracts\Sitemapable` to extend a _Model_ (you're not on Laravel, aren't you?)
2. You **have to** use the `Sitemap::render()`, `Sitemap::writeToFile()`, `SitemapIndex::render()` and `SitemapIndex::writeToFile()` via providing the extra boolean flag `$nativeRenderer = true`
3. You **can not** use `Sitemap::writeToDisk()`, `Sitemap::toResponse()`, `SitemapIndex::writeToDisk()` and `SitemapIndex::toResponse()`

So, for example, a basic approach may be:

```php
use Carbon\Carbon;
use Mfonte\Sitemap\Sitemap;
use Mfonte\Sitemap\Tags\Url;

$sitemapStream = Sitemap::create()
->add(
Url::create('/home')
->setLastModificationDate(Carbon::yesterday())
->setChangeFrequency(Url::CHANGE_FREQUENCY_YEARLY)
->setPriority(0.1)
->addImage('/path/to/image', 'A wonderful Caption')
->addNews('A long story short', 'en', Carbon::yesterday(), 'Sitemaps are this great!')
)
->add(...)
->render(true); // note the "true" on the render() method.
```

## Changelog

Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
}
],
"require": {
"php": ">=7.4",
"illuminate/support": "^8.0",
"illuminate/contracts": "^8.0",
"nesbot/carbon": "^2.0",
"spatie/laravel-package-tools": "^1.5"
},
Expand Down
191 changes: 191 additions & 0 deletions src/Renderer/NativeRenderer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<?php

namespace Mfonte\Sitemap\Renderer;

use \DateTime;

use Mfonte\Sitemap\Tags\Sitemap;
use Mfonte\Sitemap\Tags\Url;

class NativeRenderer
{
/**
* Params Array. Should be injected into current instance with a compact() call, for example: compact('tags', 'hasImages', 'hasNews')
*
* @var array
*/
private array $params;

public static function instance(array $params) : self
{
return new self($params);
}

public function __construct(array $params)
{
$this->params = $params;
}

/**
* Renders the sitemap or sitemap index
*
* @param string $type - sitemap or sitemapIndex
*
* @return string
*/
public function render(string $type) : string
{
try {
switch($type) {
case 'sitemap':
$xml = $this->sitemapTemplate();

break;
case 'sitemapIndex':
$xml = $this->sitemapIndexTemplate();

break;
default:
throw new \Exception('Invalid Render Type', 999);
}
} catch(\Exception $e) {
if ($e->getCode() === 999) {
throw new \Exception('The render type must be "sitemap" or "sitemapIndex"');
}

throw new \Exception('Error while rendering the xml: ' . $e->getMessage());
}

if (! class_exists('\DOMDocument')) {
return $xml;
}

$dom = new \DOMDocument();
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml, LIBXML_NONET | LIBXML_NOWARNING | LIBXML_PARSEHUGE | LIBXML_NOERROR);
$out = $dom->saveXML($dom->documentElement);

if ($out === false) {
throw new \Exception('DOMDocument: Error while prettifying the xml');
}

return $out;
}

private function sitemapIndexTemplate() : string
{
$template = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$template .= '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';

foreach ($this->params['tags'] as $tag) {
/** @var Sitemap $tag */

$template .= '<sitemap>';
if (! empty($tag->url)) {
$template .= '<loc>' . url($tag->url) . '</loc>';
}

if (! empty($tag->lastModificationDate)) {
$template .= '<lastmod>' . $tag->lastModificationDate->format(DateTime::ATOM) . '</lastmod>';
}

$template .= '</sitemap>';
}

$template .= '</sitemapindex>';

return $template;
}

private function sitemapTemplate() : string
{
$template = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$template .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"';
if ($this->params['hasImages']) {
$template .= ' xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"';
}
if ($this->params['hasNews']) {
$template .= ' xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"';
}
$template .= '>';

foreach ($this->params['tags'] as $tag) {
$template .= $this->urlTemplate($tag);
}

$template .= '</urlset>';

return $template;
}

private function urlTemplate(Url $tag) : string
{
$template = '<url>';
if (! empty($tag->url)) {
$template .= '<loc>' . url($tag->url) . '</loc>';
}
if (count($tag->alternates)) {
foreach ($tag->alternates as $alternate) {
$template .= '<xhtml:link rel="alternate" hreflang="' . $alternate->locale . '" href="' . url($alternate->url) . '" />';
}
}
if (! empty($tag->lastModificationDate)) {
$template .= '<lastmod>' . $tag->lastModificationDate->format(DateTime::ATOM) . '</lastmod>';
}
if (! empty($tag->changeFrequency)) {
$template .= '<changefreq>' . $tag->changeFrequency . '</changefreq>';
}
if (! empty($tag->priority)) {
$template .= '<priority>' . number_format($tag->priority, 1) . '</priority>';
}
if (count($tag->images)) {
foreach ($tag->images as $image) {
if (! empty($image->url)) {
$template .= '<image:image>';
$template .= '<image:loc>' . url($image->url) . '</image:loc>';
if (! empty($image->caption)) {
$template .= '<image:caption>' . $image->caption . '</image:caption>';
}
if (! empty($image->geo_location)) {
$template .= '<image:geo_location>' . $image->geo_location . '</image:geo_location>';
}
if (! empty($image->title)) {
$template .= '<image:title>' . $image->title . '</image:title>';
}
if (! empty($image->license)) {
$template .= '<image:license>' . $image->license . '</image:license>';
}
$template .= '</image:image>';
}
}
}
if (count($tag->news)) {
foreach ($tag->news as $new) {
$template .= '<news:news>';
if (! empty($new->publication_date)) {
$template .= '<news:publication_date>' . $new->publication_date->format('Y-m-d') . '</news:publication_date>';
}
if (! empty($new->title)) {
$template .= '<news:title>' . $new->title . '</news:title>';
}
if (! empty($new->name) || ! empty($new->language)) {
$template .= '<news:publication>';
if (! empty($new->name)) {
$template .= '<news:name>' . $new->name . '</news:name>';
}

if (! empty($new->language)) {
$template .= '<news:language>' . $new->language . '</news:language>';
}
$template .= '</news:publication>';
}
$template .= '</news:news>';
}
}

$template .= '</url>';

return $template;
}
}
Loading

0 comments on commit 15efcaf

Please sign in to comment.