├── .gitignore ├── phpspec.yml ├── src ├── Interfaces │ ├── Generatable.php │ └── Renderable.php ├── Traits │ ├── Renderer.php │ └── Common.php ├── Sitemap.php ├── Helpers │ └── Matchers.php ├── SitemapIndex.php ├── UrlSet.php └── Url.php ├── .travis.yml ├── composer.json ├── spec ├── SitemapSpec.php ├── SitemapIndexSpec.php ├── UrlSetSpec.php └── UrlSpec.php ├── license.md └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .php_cs.cache 3 | .idea 4 | composer.lock -------------------------------------------------------------------------------- /phpspec.yml: -------------------------------------------------------------------------------- 1 | suites: 2 | bard_suite: 3 | namespace: Laravelista\Bard 4 | psr4_prefix: Laravelista\Bard 5 | src_path: src 6 | spec_prefix: spec -------------------------------------------------------------------------------- /src/Interfaces/Generatable.php: -------------------------------------------------------------------------------- 1 | generate(), Response::HTTP_OK, [ 15 | 'Content-Type' => 'text/xml' 16 | ]); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravelista/bard", 3 | "description": "Laravel friendly Bard that writes sitemap poetry in xml.", 4 | "keywords": [ 5 | "sitemap", 6 | "google", 7 | "xml", 8 | "laravel" 9 | ], 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Mario Basic", 14 | "email": "mario.basic@outlook.com" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=5.4", 19 | "sabre/xml": "^2.1", 20 | "symfony/http-foundation": "~2.0|~3.0|~4.0", 21 | "nesbot/carbon": "~1.0" 22 | }, 23 | "require-dev": { 24 | "phpspec/phpspec": "~2.1" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Laravelista\\Bard\\": "src" 29 | } 30 | }, 31 | "minimum-stability": "stable" 32 | } 33 | -------------------------------------------------------------------------------- /src/Sitemap.php: -------------------------------------------------------------------------------- 1 | setLocation($location); 21 | } 22 | 23 | /** 24 | * @param Writer $writer 25 | * @return void 26 | */ 27 | public function xmlSerialize(Writer $writer) 28 | { 29 | $writer->write([ 30 | 'loc' => $this->location, 31 | ]); 32 | 33 | $this->add($writer, ['lastmod']); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spec/SitemapSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('http://acme.me/sitemap.xml'); 14 | } 15 | 16 | public function it_is_initializable() 17 | { 18 | $this->shouldHaveType('Laravelista\Bard\Sitemap'); 19 | } 20 | 21 | public function it_sets_location() 22 | { 23 | $this->setLocation('http://acme.me/sitemap.xml')->shouldReturn($this); 24 | } 25 | 26 | public function it_sets_last_modification() 27 | { 28 | $this->setLastModification(new DateTime("2014-12-22"))->shouldReturn($this); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Helpers/Matchers.php: -------------------------------------------------------------------------------- 1 | function ($result) { 19 | if (! simplexml_load_string($result)) { 20 | return false; 21 | } 22 | 23 | return true; 24 | } 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2018 Mario Bašić 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/SitemapIndexSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(new Writer); 16 | } 17 | 18 | public function it_is_initializable() 19 | { 20 | $this->shouldHaveType('Laravelista\Bard\SitemapIndex'); 21 | } 22 | 23 | public function it_adds_sitemap_to_sitemap_index() 24 | { 25 | $this->addSitemap('http://acme.me/sitemap.xml')->shouldHaveType('Laravelista\Bard\Sitemap'); 26 | } 27 | 28 | public function it_generates_sitemap_index_xml_string() 29 | { 30 | $this->addSitemap('http://acme.me/sitemap.xml', Carbon::now()); 31 | 32 | $this->generate()->shouldBeValidXml(); 33 | } 34 | 35 | public function it_renders_sitemap_index_in_xml_response() 36 | { 37 | $this->addSitemap('http://acme.me/sitemap.xml', Carbon::now()); 38 | 39 | //var_dump($this->render()->getWrappedObject()->getContent()); 40 | 41 | $this->render()->shouldHaveType('Symfony\Component\HttpFoundation\Response'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /spec/UrlSetSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(new Writer); 15 | } 16 | 17 | public function it_is_initializable() 18 | { 19 | $this->shouldHaveType('Laravelista\Bard\UrlSet'); 20 | } 21 | 22 | public function it_adds_url_to_urlset() 23 | { 24 | $this->addUrl('http://acme.me')->shouldHaveType('Laravelista\Bard\Url'); 25 | } 26 | 27 | public function it_generates_sitemap_xml_string() 28 | { 29 | $this->addUrl('http://acme.me', 1.0, 'monthly', null, [['hreflang' => 'en', 'href' => "http://acme.me/en"]]); 30 | 31 | $this->generate()->shouldBeValidXml(); 32 | } 33 | 34 | public function it_renders_sitemap_in_xml_response() 35 | { 36 | $this->addUrl('http://acme.me', 1.0, 'monthly', null, [['hreflang' => 'en', 'href' => "http://acme.me/en"]]); 37 | 38 | //var_dump($this->render()->getWrappedObject()->getContent()); 39 | 40 | $this->render()->shouldHaveType('Symfony\Component\HttpFoundation\Response'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/SitemapIndex.php: -------------------------------------------------------------------------------- 1 | writer = $writer; 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function generate() 30 | { 31 | $this->writer->openMemory(); 32 | 33 | $this->writer->startDocument("1.0", 'UTF-8'); 34 | 35 | $this->writer->namespaceMap = [ 36 | 'http://www.sitemaps.org/schemas/sitemap/0.9' => '' 37 | ]; 38 | 39 | $this->writer->startElement('sitemapindex'); 40 | 41 | foreach ($this->sitemaps as $sitemap) { 42 | $this->writer->write([ 43 | 'sitemap' => $sitemap 44 | ]); 45 | } 46 | 47 | $this->writer->endElement(); 48 | 49 | return $this->writer->outputMemory(); 50 | } 51 | 52 | /** 53 | * @param $location 54 | * @return Sitemap 55 | */ 56 | public function addSitemap($location) 57 | { 58 | $this->sitemaps[] = $sitemap = new Sitemap($location); 59 | 60 | return $sitemap; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Traits/Common.php: -------------------------------------------------------------------------------- 1 | validateUrl($url); 23 | 24 | $this->location = $url; 25 | 26 | return $this; 27 | } 28 | 29 | 30 | /** 31 | * @param DateTime $lastModification 32 | */ 33 | public function setLastModification(DateTime $lastModification) 34 | { 35 | $this->lastmod = Carbon::instance($lastModification)->toW3cString(); 36 | 37 | return $this; 38 | } 39 | 40 | /** 41 | * Adds property from properties to url if it is not null. 42 | * 43 | * @param Writer $writer 44 | * @param array $properties 45 | */ 46 | private function add(Writer $writer, array $properties) 47 | { 48 | foreach ($properties as $property) { 49 | if (! is_null($this->$property)) { 50 | $writer->write([$property => $this->$property]); 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * @param $url 57 | * @throws ValidationException 58 | */ 59 | private function validateUrl($url) 60 | { 61 | if (! filter_var($url, FILTER_VALIDATE_URL)) { 62 | throw new Exception('Not a valid URL'); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/UrlSet.php: -------------------------------------------------------------------------------- 1 | writer = $writer; 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function generate() 30 | { 31 | $this->writer->openMemory(); 32 | 33 | $this->writer->startDocument("1.0", 'UTF-8'); 34 | 35 | $this->writer->namespaceMap = [ 36 | 'http://www.sitemaps.org/schemas/sitemap/0.9' => '', 37 | 'http://www.w3.org/1999/xhtml' => 'xhtml' 38 | ]; 39 | 40 | $this->writer->startElement('urlset'); 41 | 42 | foreach ($this->urls as $url) { 43 | $this->writer->write([ 44 | 'url' => $url 45 | ]); 46 | } 47 | 48 | $this->writer->endElement(); 49 | 50 | return $this->writer->outputMemory(); 51 | } 52 | 53 | /** 54 | * It adds a URL. 55 | * 56 | * @param $location 57 | * @return Url 58 | */ 59 | public function addUrl($location) 60 | { 61 | $this->urls[] = $url = new Url($location); 62 | 63 | return $url; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Bard 2 | 3 | Bard is the simplest PHP Sitemap package, just add some URLs and you are ready to go. 4 | 5 | [![Become a Patron](https://img.shields.io/badge/Becoma%20a-Patron-f96854.svg?style=for-the-badge)](https://www.patreon.com/shockmario) 6 | 7 | **Abandoned!** I suggest using [`spatie/larevel-sitemap`](https://github.com/spatie/laravel-sitemap) instead. It is a much better package with automatic sitemap generation. 8 | 9 | ## Syntax 10 | 11 | ``` 12 | use Laravelista\Bard\UrlSet; 13 | use Sabre\Xml\Writer; 14 | use Carbon\Carbon; 15 | 16 | $sitemap = new UrlSet(new Writer); 17 | 18 | $sitemap->addUrl('http://domain.com/contact') 19 | ->setPriority(0.8) 20 | ->setChangeFrequency('hourly') 21 | ->setLastModification(Carbon::now()) 22 | ->addTranslation('hr', 'http://domain.com/hr/contact'); 23 | 24 | $sitemap->render()->send(); 25 | ``` 26 | 27 | ## Start learning 28 | 29 | ### Installation 30 | 31 | Run this from your project root in command line: 32 | 33 | ``` 34 | composer require laravelista/bard 35 | ``` 36 | 37 | ### Documentation 38 | 39 | - [Usage](https://github.com/laravelista/Bard/wiki/Usage) 40 | - [Learn the API](https://github.com/laravelista/Bard/wiki/Learn-the-API) 41 | - [Laravel + Bard](https://github.com/laravelista/Bard/wiki/Laravel-and-Bard) 42 | 43 | #### Tutorials 44 | 45 | - [Bard 2.0 and Laravel](https://laravelista.com/lessons/bard-20-and-laravel) 46 | - [Creating a Sitemap with Bard 2.0](https://laravelista.com/lessons/creating-a-sitemap-with-bard-20) 47 | - [Sitemap for better SEO](https://laravelista.com/lessons/sitemap-for-better-seo) 48 | - [Create a sitemap with Laravel and Bard](https://laravelista.com/posts/create-a-sitemap-with-laravel-and-bard) 49 | 50 | ![Bard](http://news.cdn.leagueoflegends.com/public/images/pages/2015/breveal/img/Promo_Bard_Reveal_Mask.png) 51 | -------------------------------------------------------------------------------- /spec/UrlSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('http://acme.me'); 12 | } 13 | 14 | public function it_is_initializable() 15 | { 16 | $this->shouldHaveType('Laravelista\Bard\Url'); 17 | } 18 | 19 | public function it_sets_priority() 20 | { 21 | // Test all from 0.0 to 1.0 22 | for ($i = 0.0; $i <= 1; $i += 0.1) { 23 | $this->setPriority($i)->shouldReturn($this); 24 | } 25 | } 26 | 27 | public function it_cannot_set_priority() 28 | { 29 | $this->shouldThrow('Exception')->duringSetPriority(22); 30 | $this->shouldThrow('Exception')->duringSetPriority(1.1); 31 | $this->shouldThrow('Exception')->duringSetPriority(- 1); 32 | } 33 | 34 | public function it_sets_location() 35 | { 36 | $this->setLocation('http://acme.me')->shouldReturn($this); 37 | } 38 | 39 | public function it_sets_change_frequency() 40 | { 41 | // Test all valid values for change frequency field 42 | $validChangeFrequencyValues = [ 43 | "always", "hourly", "daily", "weekly", 44 | "monthly", "yearly", "never" 45 | ]; 46 | 47 | foreach ($validChangeFrequencyValues as $changeFrequency) { 48 | $this->setChangeFrequency($changeFrequency)->shouldReturn($this); 49 | } 50 | } 51 | 52 | public function it_cannot_set_change_frequency() 53 | { 54 | $this->shouldThrow('Exception')->duringSetChangeFrequency('from time to time'); 55 | } 56 | 57 | public function it_sets_last_modification() 58 | { 59 | $this->setLastModification(new DateTime("2014-12-22"))->shouldReturn($this); 60 | } 61 | 62 | public function it_adds_a_translation() 63 | { 64 | $this->addTranslation('de', "http://acme.me/de")->shouldReturn($this); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Url.php: -------------------------------------------------------------------------------- 1 | setLocation($location); 27 | } 28 | 29 | /** 30 | * 31 | * @param Writer $writer 32 | * @return void 33 | */ 34 | public function xmlSerialize(Writer $writer) 35 | { 36 | $writer->write([ 37 | 'loc' => $this->location, 38 | ]); 39 | 40 | $this->add($writer, ['priority', 'changefreq', 'lastmod']); 41 | 42 | $this->addTranslations($writer); 43 | } 44 | 45 | /** 46 | * Used for serialization. 47 | * 48 | * @param Writer $writer 49 | */ 50 | private function addTranslations(Writer $writer) 51 | { 52 | foreach ($this->translations as $translation) { 53 | $writer->write([ 54 | [ 55 | 'name' => 'xhtml:link', 56 | 'attributes' => [ 57 | 'rel' => 'alternate', 58 | 'hreflang' => $translation['hreflang'], 59 | 'href' => $translation['href'] 60 | ], 61 | 'value' => null 62 | ] 63 | ]); 64 | } 65 | } 66 | 67 | /** 68 | * It adds a translation 69 | * 70 | * addTranslation('hr', 'http://domain.com/hr/contact'); 71 | * 72 | * @param $locale 73 | * @param $url 74 | */ 75 | public function addTranslation($locale, $url) 76 | { 77 | $this->validateUrl($url); 78 | 79 | $this->translations[] = [ 80 | 'hreflang' => $locale, 81 | 'href' => $url 82 | ]; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * It sets change frequency. 89 | * 90 | * @param $changeFrequency 91 | * @throws Exception 92 | */ 93 | public function setChangeFrequency($changeFrequency) 94 | { 95 | if (! in_array($changeFrequency, [ 96 | "always", "hourly", "daily", "weekly", 97 | "monthly", "yearly", "never" 98 | ])) { 99 | throw new Exception('Change frequency not supported!'); 100 | } 101 | 102 | $this->changefreq = $changeFrequency; 103 | 104 | return $this; 105 | } 106 | 107 | /** 108 | * It sets priority. 109 | * 110 | * @param $priority 111 | * @throws Exception 112 | */ 113 | public function setPriority($priority) 114 | { 115 | if (! (is_float($priority) && $priority >= 0 && $priority <= 1)) { 116 | throw new Exception('Priority must be >=0.0 and <=1.0'); 117 | } 118 | 119 | $this->priority = number_format($priority, 1); 120 | 121 | return $this; 122 | } 123 | } 124 | --------------------------------------------------------------------------------