├── .gitignore
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ └── config.yml
└── workflows
│ └── tests.yml
├── routes
└── web.php
├── src
├── Http
│ └── Controllers
│ │ ├── SitemappableController.php.stub
│ │ ├── Controller.php
│ │ └── SitemappableController.php
├── Sitemappable.php
├── IsSitemappable.php
├── SitemappableServiceProvider.php
└── ImportCommand.php
├── tests
├── TestModel.php
├── TestCase.php
└── SitemappableTest.php
├── resources
└── views
│ └── sitemap.blade.php
├── phpunit.xml.dist
├── CHANGELOG.md
├── config
└── sitemappable.php
├── database
└── migrations
│ └── create_sitemappable_table.php.stub
├── LICENSE.md
├── .phpunit.result.cache
├── composer.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 | vendor
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [vursion]
4 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | 'array',
16 | ];
17 |
18 | public function __construct(array $attributes = [])
19 | {
20 | parent::__construct($attributes);
21 |
22 | $this->table = config('sitemappable.db_table_name');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Ask a Question
4 | url: https://github.com/vursion/laravel-sitemappable/discussions/new?category=q-a
5 | about: Ask the community for help
6 | - name: Feature Request
7 | url: https://github.com/vursion/laravel-sitemappable/discussions/new?category=ideas
8 | about: Share ideas for new features
9 | - name: Bug Report
10 | url: https://github.com/vursion/laravel-sitemappable/issues/new
11 | about: Report a reproducable bug
12 |
--------------------------------------------------------------------------------
/tests/TestModel.php:
--------------------------------------------------------------------------------
1 | 'https://www.vursion.io/nl/testen/test-slug-in-het-nederlands',
20 | 'en' => 'https://www.vursion.io/en/tests/test-slug-in-english',
21 | ];
22 | }
23 |
24 | public function shouldBeSitemappable()
25 | {
26 | return (! $this->draft && $this->published);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/resources/views/sitemap.blade.php:
--------------------------------------------------------------------------------
1 | {!! '<' . '?xml version="1.0" encoding="UTF-8"?>' !!}
2 |
3 | @foreach ($sitemappables as $sitemappable)
4 | @foreach ($sitemappable->urls as $url)
5 |
6 | {{ $url }}
7 | @if (count(array_keys($sitemappable->urls)) > 1)
8 | @foreach ($sitemappable->urls as $lang => $url)
9 |
10 | @endforeach
11 | @endif
12 | @if ($sitemappable->updated_at)
13 | {{ $sitemappable->updated_at->toIso8601String() }}
14 | @endif
15 |
16 | @endforeach
17 | @endforeach
18 |
19 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `laravel-sitemappable` will be documented in this file
4 |
5 | ## 1.9.0 - 2025-02-24
6 | - Support Laravel 12
7 |
8 | ## 1.8.0 - 2024-11-25
9 | - Support PHP 8.4
10 |
11 | ## 1.7.0 - 2024-03-13
12 | - Support Laravel 11
13 |
14 | ## 1.6.0 - 2023-11-24
15 | - Support PHP 8.3
16 |
17 | ## 1.5.0 - 2023-02-15
18 | - Support Laravel 10
19 |
20 | ## 1.4.1 - 2022-12-09
21 | - Support PHP 8.2 (exclude Laravel 6 from PHP 8.2 tests)
22 |
23 | ## 1.4.0 - 2022-12-09
24 | - Support PHP 8.2
25 |
26 | ## 1.3.0 - 2022-02-10
27 | - Support Laravel 9
28 |
29 | ## 1.2.0 - 2021-12-22
30 | - Support PHP 8.1
31 |
32 | ## 1.1.0 - 2021-10-21
33 | - Follow Google guidelines for localized versions of pages.
34 |
35 | ## 1.0.0 - 2021-05-17
36 | - initial release.
37 |
--------------------------------------------------------------------------------
/config/sitemappable.php:
--------------------------------------------------------------------------------
1 | 'sitemap',
10 |
11 | /*
12 | * The generated XML sitemap is cached to speed up performance.
13 | */
14 | 'cache' => '60 minutes',
15 |
16 | /*
17 | * The batch import will loop through this directory and search for models
18 | * that use the IsSitemappable trait.
19 | */
20 | 'model_directory' => 'app/Models',
21 |
22 | /*
23 | * If you're extending the controller, you'll need to specify the new location here.
24 | */
25 | 'controller' => Vursion\LaravelSitemappable\Http\Controllers\SitemappableController::class,
26 |
27 | ];
28 |
--------------------------------------------------------------------------------
/database/migrations/create_sitemappable_table.php.stub:
--------------------------------------------------------------------------------
1 | engine = 'InnoDB';
18 | $table->increments('id');
19 | $table->morphs('entity');
20 | $table->text('urls')->nullable();
21 | $table->timestamps();
22 | $table->softDeletes();
23 | $table->index('entity_id');
24 | $table->index('entity_type');
25 | });
26 | }
27 |
28 | /**
29 | * Reverse the migrations.
30 | *
31 | * @return void
32 | */
33 | public function down()
34 | {
35 | Schema::dropIfExists(config('sitemappable.db_table_name'));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Http/Controllers/SitemappableController.php:
--------------------------------------------------------------------------------
1 | otherRoutes())->map(function ($route) {
14 | return new Sitemappable([
15 | 'urls' => $route,
16 | ]);
17 | });
18 |
19 | $sitemappables = Sitemappable::get()->concat($otherRoutes)->filter(function ($sitemappable) {
20 | return (is_array($sitemappable->urls) && count($sitemappable->urls) > 0);
21 | });
22 |
23 | return view('sitemappable::sitemap', compact('sitemappables'))->render();
24 | });
25 |
26 | return response(preg_replace('/>(\s)+<', $content), '200')->header('Content-Type', 'text/xml');
27 | }
28 |
29 | protected function otherRoutes()
30 | {
31 | return [];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020-2023 vursion
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/.phpunit.result.cache:
--------------------------------------------------------------------------------
1 | C:37:"PHPUnit\Runner\DefaultTestResultCache":1378:{a:2:{s:7:"defects";a:6:{s:79:"Vursion\LaravelSitemappable\Tests\SitemappableTest::test_it_can_be_instantiated";i:4;s:102:"Vursion\LaravelSitemappable\Tests\SitemappableTest::test_it_will_handle_a_should_be_sitemappable_model";i:4;s:107:"Vursion\LaravelSitemappable\Tests\SitemappableTest::test_it_will_discard_a_should_not_be_sitemappable_model";i:4;s:112:"Vursion\LaravelSitemappable\Tests\SitemappableTest::test_it_will_delete_a_no_longer_should_be_sitemappable_model";i:4;s:87:"Vursion\LaravelSitemappable\Tests\SitemappableTest::test_it_can_generate_an_xml_sitemap";i:4;s:93:"Vursion\LaravelSitemappable\Tests\SitemappableTest::test_it_can_batch_import_existing_records";i:3;}s:5:"times";a:6:{s:79:"Vursion\LaravelSitemappable\Tests\SitemappableTest::test_it_can_be_instantiated";d:0.366;s:102:"Vursion\LaravelSitemappable\Tests\SitemappableTest::test_it_will_handle_a_should_be_sitemappable_model";d:0.039;s:107:"Vursion\LaravelSitemappable\Tests\SitemappableTest::test_it_will_discard_a_should_not_be_sitemappable_model";d:0.011;s:112:"Vursion\LaravelSitemappable\Tests\SitemappableTest::test_it_will_delete_a_no_longer_should_be_sitemappable_model";d:0.013;s:87:"Vursion\LaravelSitemappable\Tests\SitemappableTest::test_it_can_generate_an_xml_sitemap";d:0.128;s:93:"Vursion\LaravelSitemappable\Tests\SitemappableTest::test_it_can_batch_import_existing_records";d:0.022;}}}
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | setUpDatabase();
20 | $this->setUpTestModel();
21 |
22 | config()->set('sitemappable.cache', '0 minutes');
23 | config()->set('app.locale', 'nl');
24 |
25 | $this->mock = $this->createPartialMock(ImportCommand::class, ['fetchCandidates']);
26 |
27 | $this->mock->method('fetchCandidates')
28 | ->willReturn(collect(['Vursion\LaravelSitemappable\Tests\TestModel']));
29 | }
30 |
31 | protected function getPackageProviders($app)
32 | {
33 | return [SitemappableServiceProvider::class];
34 | }
35 |
36 | protected function setUpDatabase()
37 | {
38 | include_once __DIR__ . '/../database/migrations/create_sitemappable_table.php.stub';
39 |
40 | (new \CreateSitemappableTable())->up();
41 | }
42 |
43 | protected function setUpTestModel()
44 | {
45 | Schema::create('test_models', function (Blueprint $table) {
46 | $table->increments('id');
47 | $table->boolean('draft')->default(true);
48 | $table->boolean('published')->default(false);
49 | $table->timestamps();
50 | });
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vursion/laravel-sitemappable",
3 | "description": "laravel-sitemappable",
4 | "keywords": [
5 | "vursion",
6 | "laravel",
7 | "sitemap"
8 | ],
9 | "homepage": "https://github.com/vursion/laravel-sitemappable",
10 | "license": "MIT",
11 | "type": "library",
12 | "authors": [
13 | {
14 | "name": "Jochen Sengier",
15 | "email": "support@vursion.io",
16 | "role": "Developer"
17 | }
18 | ],
19 | "require": {
20 | "php": "^7.1 || ^7.2 || ^7.3 || ^7.4 || ^8.0 || ^8.1 || ^8.2 || ^8.3 || ^8.4"
21 | },
22 | "require-dev": {
23 | "orchestra/testbench": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0",
24 | "phpunit/phpunit": "^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0"
25 | },
26 | "autoload": {
27 | "psr-4": {
28 | "Vursion\\LaravelSitemappable\\": "src"
29 | }
30 | },
31 | "autoload-dev": {
32 | "psr-4": {
33 | "Vursion\\LaravelSitemappable\\Tests\\": "tests"
34 | }
35 | },
36 | "scripts": {
37 | "test": "vendor/bin/phpunit"
38 | },
39 | "config": {
40 | "sort-packages": true,
41 | "allow-plugins": {
42 | "kylekatarnls/update-helper": true
43 | }
44 | },
45 | "extra": {
46 | "laravel": {
47 | "providers": [
48 | "Vursion\\LaravelSitemappable\\SitemappableServiceProvider"
49 | ]
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/IsSitemappable.php:
--------------------------------------------------------------------------------
1 | shouldBeSitemappable()) {
13 | static::addModel($model);
14 | } else {
15 | static::deleteModel($model, true);
16 | }
17 | });
18 |
19 | static::deleted(function ($model) {
20 | static::deleteModel($model, true);
21 | });
22 | }
23 |
24 | protected static function addModel($model)
25 | {
26 | $sitemap = Sitemappable::withTrashed()->firstOrCreate([
27 | 'entity_id' => $model->id,
28 | 'entity_type' => get_class($model),
29 | ]);
30 | $sitemap->restore();
31 | $sitemap->urls = $model->toSitemappableArray();
32 | $sitemap->save();
33 | }
34 |
35 | protected static function deleteModel($model, $forceDelete = false)
36 | {
37 | $sitemap = Sitemappable::where('entity_type', get_class($model))
38 | ->where('entity_id', $model->id)
39 | ->withTrashed();
40 |
41 | if ($sitemap) {
42 | if ($forceDelete) {
43 | $sitemap->forceDelete();
44 | } else {
45 | $sitemap->delete();
46 | }
47 | }
48 | }
49 |
50 | /**
51 | * Determine if the model should be sitemappable.
52 | *
53 | * @return bool
54 | */
55 | public function shouldBeSitemappable()
56 | {
57 | return true;
58 | }
59 |
60 | /**
61 | * Returns an array with the (localized) URLs.
62 | *
63 | * @return array
64 | */
65 | public function toSitemappableArray()
66 | {
67 | return [];
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/SitemappableServiceProvider.php:
--------------------------------------------------------------------------------
1 | loadViewsFrom(__DIR__.'/../resources/views', 'sitemappable');
15 | $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
16 |
17 | if ($this->app->runningInConsole()) {
18 | $this->publishes([
19 | __DIR__ . '/../config/sitemappable.php' => config_path('sitemappable.php'),
20 | ], 'config');
21 |
22 | $this->publishes([
23 | __DIR__ . '/../database/migrations/create_sitemappable_table.php.stub' => $this->getMigrationFileName('create_sitemappable_table.php'),
24 | ], 'migrations');
25 |
26 | $this->publishes([
27 | __DIR__ . '/../src/Http/Controllers/SitemappableController.php.stub' => app_path('Http/Controllers/SitemappableController.php'),
28 | ], 'controllers');
29 | }
30 | }
31 |
32 | public function register()
33 | {
34 | $this->mergeConfigFrom(__DIR__ . '/../config/sitemappable.php', 'sitemappable');
35 |
36 | $this->commands([
37 | ImportCommand::class,
38 | ]);
39 | }
40 |
41 | protected function getMigrationFileName($migrationFileName)
42 | {
43 | return Collection::make(database_path('migrations/*'))
44 | ->flatMap(function ($path) use ($migrationFileName) {
45 | return $this->app->make(Filesystem::class)->glob($path . '*_' . $migrationFileName);
46 | })->push(database_path('migrations/' . date('Y_m_d_His') . '_' . $migrationFileName))->first();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/ImportCommand.php:
--------------------------------------------------------------------------------
1 | truncate();
22 |
23 | $this->process();
24 |
25 | $this->table(['Model', 'Result'], $this->results);
26 | }
27 |
28 | public function process()
29 | {
30 | $this->results = $this->fetchCandidates()->filter(function ($class) {
31 | return class_exists($class);
32 | })->map(function ($class) {
33 | $reflection = new ReflectionClass($class);
34 |
35 | if (! $reflection->isAbstract() && $reflection->isSubclassOf(\Illuminate\Database\Eloquent\Model::class)){
36 | if (in_array('Vursion\LaravelSitemappable\IsSitemappable', $reflection->getTraitNames())) {
37 | return [
38 | '' . $class . '',
39 | '' . $this->import($class) . ''
40 | ];
41 | }
42 |
43 | return [
44 | '' . $class . '',
45 | 'Sitemappable trait not found',
46 | ];
47 | }
48 | })->filter()->toArray();
49 | }
50 |
51 | protected function fetchCandidates()
52 | {
53 | return collect(File::allFiles(base_path(config('sitemappable.model_directory'))))->filter(function ($file) {
54 | return ($file->getExtension() === 'php');
55 | })->map(function ($file) {
56 | return str_replace([base_path(), '/', '\app\\', '.php'], ['', '\\', 'App\\', ''], $file->getRealPath());
57 | });
58 | }
59 |
60 | protected function import($class)
61 | {
62 | $records = $class::get()->each(function ($model) use ($class) {
63 | if ($model->shouldBeSitemappable()) {
64 | Sitemappable::create([
65 | 'entity_id' => $model->id,
66 | 'entity_type' => $class,
67 | 'urls' => $model->toSitemappableArray(),
68 | ]);
69 | }
70 | });
71 |
72 | return trans_choice('{0} 0 records processed|{1} 1 record processed|[2,*] :records records processed', $records->count(), ['records' => $records->count()]);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/SitemappableTest.php:
--------------------------------------------------------------------------------
1 | app->getProvider(SitemappableServiceProvider::class));
18 | }
19 |
20 | public function test_it_will_handle_a_should_be_sitemappable_model()
21 | {
22 | $testModel = TestModel::create([
23 | 'draft' => false,
24 | 'published' => true,
25 | ]);
26 |
27 | $this->assertDatabaseHas('sitemap', [
28 | 'entity_id' => $testModel->id,
29 | 'entity_type' => get_class($testModel),
30 | ]);
31 |
32 | $testModel->delete();
33 | $this->assertDatabaseMissing($testModel->getTable(), ['id' => $testModel->id]);
34 |
35 | $this->assertDatabaseMissing('sitemap', [
36 | 'entity_id' => $testModel->id,
37 | 'entity_type' => get_class($testModel),
38 | ]);
39 | }
40 |
41 | public function test_it_will_discard_a_should_not_be_sitemappable_model()
42 | {
43 | $testModel = TestModel::create([
44 | 'draft' => false,
45 | 'published' => false,
46 | ]);
47 |
48 | $this->assertDatabaseMissing('sitemap', [
49 | 'entity_id' => $testModel->id,
50 | 'entity_type' => get_class($testModel),
51 | ]);
52 | }
53 |
54 | public function test_it_will_delete_a_no_longer_should_be_sitemappable_model()
55 | {
56 | $testModel = TestModel::create([
57 | 'draft' => false,
58 | 'published' => true,
59 | ]);
60 |
61 | $this->assertDatabaseHas('sitemap', [
62 | 'entity_id' => $testModel->id,
63 | 'entity_type' => get_class($testModel),
64 | ]);
65 |
66 | $testModel->published = false;
67 | $testModel->save();
68 |
69 | $this->assertDatabaseMissing('sitemap', [
70 | 'entity_id' => $testModel->id,
71 | 'entity_type' => get_class($testModel),
72 | ]);
73 | }
74 |
75 | public function test_it_can_generate_an_xml_sitemap()
76 | {
77 | $testModel = TestModel::create([
78 | 'draft' => false,
79 | 'published' => true,
80 | ]);
81 |
82 | $response = $this->get('sitemap.xml');
83 |
84 | $response->assertStatus(200);
85 | $this->assertContains('text/xml', explode(';', $response->headers->get('Content-Type')));
86 |
87 | $expected = preg_replace('/>(\s)+<', '
88 |
89 |
90 | https://www.vursion.io/nl/testen/test-slug-in-het-nederlands
91 |
92 |
93 | ' . $testModel->updated_at->toIso8601String() . '
94 |
95 |
96 | https://www.vursion.io/en/tests/test-slug-in-english
97 |
98 |
99 | ' . $testModel->updated_at->toIso8601String() . '
100 |
101 | ');
102 |
103 | $this->assertXmlStringEqualsXmlString($expected, $response->getContent());
104 | }
105 |
106 | public function test_it_can_batch_import_existing_records()
107 | {
108 | $testModel = TestModel::create([
109 | 'draft' => false,
110 | 'published' => true,
111 | ]);
112 |
113 | $skipTestModel = TestModel::create([
114 | 'draft' => false,
115 | 'published' => false,
116 | ]);
117 |
118 | DB::table(config('sitemappable.db_table_name'))->truncate();
119 |
120 | $this->mock->process();
121 |
122 | $this->assertDatabaseHas('sitemap', [
123 | 'entity_id' => $testModel->id,
124 | 'entity_type' => get_class($testModel),
125 | ]);
126 |
127 | $this->assertDatabaseMissing('sitemap', [
128 | 'entity_id' => $skipTestModel->id,
129 | 'entity_type' => get_class($skipTestModel),
130 | ]);
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 |
3 | on:
4 | push:
5 | pull_request:
6 | schedule:
7 | - cron: '0 8 * * *'
8 |
9 | jobs:
10 | php-tests:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | php: [7.1, 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4]
16 | laravel: [5.6.*, 5.7.*, 5.8.*, 6.*, 7.*, 8.*, 9.*, 10.*, 11.*, 12.*]
17 | version: [prefer-stable]
18 | os: [ubuntu-latest]
19 | include:
20 | - laravel: 5.6.*
21 | testbench: 3.6.*
22 | - laravel: 5.7.*
23 | testbench: 3.7.*
24 | - laravel: 5.8.*
25 | testbench: 3.8.*
26 | - laravel: 6.*
27 | testbench: 4.*
28 | - laravel: 7.*
29 | testbench: 5.*
30 | - laravel: 8.*
31 | testbench: 6.*
32 | - laravel: 9.*
33 | testbench: 7.*
34 | - laravel: 10.*
35 | testbench: 8.*
36 | - laravel: 11.*
37 | testbench: 9.*
38 | - laravel: 12.*
39 | testbench: 10.*
40 | exclude:
41 | - laravel: 5.6.*
42 | php: 8.0
43 | - laravel: 5.6.*
44 | php: 8.1
45 | - laravel: 5.6.*
46 | php: 8.2
47 | - laravel: 5.6.*
48 | php: 8.3
49 | - laravel: 5.6.*
50 | php: 8.4
51 | - laravel: 5.7.*
52 | php: 8.0
53 | - laravel: 5.7.*
54 | php: 8.1
55 | - laravel: 5.7.*
56 | php: 8.2
57 | - laravel: 5.7.*
58 | php: 8.3
59 | - laravel: 5.7.*
60 | php: 8.4
61 | - laravel: 5.8.*
62 | php: 8.0
63 | - laravel: 5.8.*
64 | php: 8.1
65 | - laravel: 5.8.*
66 | php: 8.2
67 | - laravel: 5.8.*
68 | php: 8.3
69 | - laravel: 5.8.*
70 | php: 8.4
71 | - laravel: 6.*
72 | php: 7.1
73 | - laravel: 6.*
74 | php: 8.1
75 | - laravel: 6.*
76 | php: 8.2
77 | - laravel: 6.*
78 | php: 8.3
79 | - laravel: 6.*
80 | php: 8.4
81 | - laravel: 7.*
82 | php: 7.1
83 | - laravel: 7.*
84 | php: 8.1
85 | - laravel: 7.*
86 | php: 8.2
87 | - laravel: 7.*
88 | php: 8.3
89 | - laravel: 7.*
90 | php: 8.4
91 | - laravel: 8.*
92 | php: 7.1
93 | - laravel: 8.*
94 | php: 7.2
95 | - laravel: 9.*
96 | php: 7.1
97 | - laravel: 9.*
98 | php: 7.2
99 | - laravel: 9.*
100 | php: 7.3
101 | - laravel: 9.*
102 | php: 7.4
103 | - laravel: 10.*
104 | php: 7.1
105 | - laravel: 10.*
106 | php: 7.2
107 | - laravel: 10.*
108 | php: 7.3
109 | - laravel: 10.*
110 | php: 7.4
111 | - laravel: 10.*
112 | php: 8.0
113 | - laravel: 11.*
114 | php: 7.1
115 | - laravel: 11.*
116 | php: 7.2
117 | - laravel: 11.*
118 | php: 7.3
119 | - laravel: 11.*
120 | php: 7.4
121 | - laravel: 11.*
122 | php: 8.0
123 | - laravel: 11.*
124 | php: 8.1
125 | - laravel: 12.*
126 | php: 7.1
127 | - laravel: 12.*
128 | php: 7.2
129 | - laravel: 12.*
130 | php: 7.3
131 | - laravel: 12.*
132 | php: 7.4
133 | - laravel: 12.*
134 | php: 8.0
135 | - laravel: 12.*
136 | php: 8.1
137 |
138 | name: PHP ${{ matrix.php }} - LARAVEL ${{ matrix.laravel }} - ${{ matrix.version }} - ${{ matrix.os }}
139 |
140 | steps:
141 | - name: Checkout code
142 | uses: actions/checkout@v2
143 |
144 | - name: Setup PHP
145 | uses: shivammathur/setup-php@v2
146 | with:
147 | php-version: ${{ matrix.php }}
148 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
149 | coverage: none
150 | tools: composer:v2
151 |
152 | - name: Install dependencies
153 | run: |
154 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
155 | composer update --${{ matrix.version }} --prefer-dist --no-interaction
156 |
157 | - name: Execute tests
158 | run: vendor/bin/phpunit
159 |
160 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Sitemappable
2 |
3 | [](https://packagist.org/packages/vursion/laravel-sitemappable)
4 | 
5 | [](https://packagist.org/packages/vursion/laravel-sitemappable)
6 |
7 | ## Installation
8 |
9 | You can install the package via composer:
10 |
11 | ```bash
12 | composer require vursion/laravel-sitemappable
13 | ```
14 |
15 | ***No need to register the service provider if you're using Laravel >= 5.5.
16 | The package will automatically register itself.***
17 | Once the package is installed, you can register the service provider in `config/app.php` in the providers array:
18 | ```php
19 | 'providers' => [
20 | ...
21 | Vursion\LaravelSitemappable\SitemappableServiceProvider::class
22 | ],
23 | ```
24 |
25 | You need to publish the migration with:
26 | ```bash
27 | php artisan vendor:publish --provider="Vursion\LaravelSitemappable\SitemappableServiceProvider" --tag=migrations
28 | ```
29 |
30 | You should publish the `config/sitemappable.php` config file with:
31 | ```bash
32 | php artisan vendor:publish --provider="Vursion\LaravelSitemappable\SitemappableServiceProvider" --tag=config
33 | ```
34 |
35 | This is the content of the published config file:
36 |
37 | ```php
38 | return [
39 |
40 | /*
41 | * This is the name of the table that will be created by the migration and
42 | * used by the Sitemappable model shipped with this package.
43 | */
44 | 'db_table_name' => 'sitemap',
45 |
46 | /*
47 | * The generated XML sitemap is cached to speed up performance.
48 | */
49 | 'cache' => '60 minutes',
50 |
51 | /*
52 | * The batch import will loop through this directory and search for models
53 | * that use the IsSitemappable trait.
54 | */
55 | 'model_directory' => 'app/Models',
56 |
57 | /*
58 | * If you're extending the controller, you'll need to specify the new location here.
59 | */
60 | 'controller' => Vursion\LaravelSitemappable\Http\Controllers\SitemappableController::class,
61 |
62 | ];
63 | ```
64 |
65 | ## Making a model sitemappable
66 |
67 | The required steps to make a model sitemappable are:
68 | - Add the `Vursion\LaravelSitemappable\IsSitemappable` trait.
69 | - Define a public method `toSitemappableArray` that returns an array with the (localized) URL(s).
70 | - Optionally define the conditions when a model should be sitemappable in a public method `shouldBeSitemappable`.
71 |
72 | Here's an example of a model:
73 |
74 | ```php
75 | use Illuminate\Database\Eloquent\Model;
76 | use Vursion\LaravelSitemappable\IsSitemappable;
77 |
78 | class YourModel extends Model
79 | {
80 | use IsSitemappable;
81 |
82 | public function toSitemappableArray()
83 | {
84 | return [];
85 | }
86 |
87 | public function shouldBeSitemappable()
88 | {
89 | return true;
90 | }
91 | }
92 | ```
93 |
94 | ### toSitemappableArray
95 |
96 | You need to return an array with (localized) URL(s) of your model.
97 |
98 | ```php
99 | public function toSitemappableArray()
100 | {
101 | return [
102 | 'nl' => 'https://www.vursion.io/nl/testen/test-slug-in-het-nederlands',
103 | 'en' => 'https://www.vursion.io/en/tests/test-slug-in-english',
104 | ];
105 | }
106 | ```
107 |
108 | This is an example of a model that uses [ARCANDEDEV\Localization](https://github.com/ARCANEDEV/Localization)
109 | for localized routes in combination with [spatie\laravel-translatable](https://github.com/spatie/laravel-translatable)
110 | for making Eloquent models translatable.
111 |
112 | ```php
113 | public function toSitemappableArray()
114 | {
115 | return collect(localization()->getSupportedLocalesKeys())->mapWithKeys(function ($key) {
116 | return [$key => localization()->getUrlFromRouteName($key, 'routes.your-route-name', ['slug' => $this->getTranslationWithoutFallback('slug', $key)])];
117 | });
118 | }
119 | ```
120 | ### shouldBeSitemappable (conditionally sitemappable model instances)
121 |
122 | Sometimes you may need to only make a model sitemappable under certain conditions.
123 | For example, imagine you have a `App\Models\Posts\Post` model.
124 | You may only want to allow "non-draft" and "published" posts to be sitemappable.
125 | To accomplish this, you may define a `shouldBeSitemappable` method on your model:
126 |
127 | ```php
128 | public function shouldBeSitemappable()
129 | {
130 | return (! $this->draft && $this->published);
131 | }
132 | ```
133 |
134 | ## Rebuild the sitemap from scratch
135 |
136 | If you are installing Laravel Sitemappable into an existing project, you may already have database records you need to import into your sitemap.
137 | Laravel Sitemappable provides a `sitemappable:import` Artisan command that you may use to import all of your existing records into your sitemap:
138 |
139 | ```bash
140 | php artisan sitemappable:import
141 | ```
142 |
143 | ## Adding non-model associated routes
144 |
145 | It's very likely your project will have routes that are not associated with a model.
146 | You can add these URLs by extending the controller and returning them via the `otherRoutes` method.
147 |
148 | To publish the controller to `app/Http/Controllers/SitemappableController.php` run:
149 |
150 | ```bash
151 | php artisan vendor:publish --provider="Vursion\LaravelSitemappable\SitemappableServiceProvider" --tag=controllers
152 | ```
153 |
154 | Don't forget to change the location of the controller in the `config/sitemappable.php` config file:
155 |
156 | ```php
157 | return [
158 |
159 | ...
160 |
161 | /*
162 | * If you're extending the controller, you'll need to specify the new location here.
163 | */
164 | 'controller' => App\Http\Controllers\SitemappableController::class,
165 |
166 | ...
167 |
168 | ];
169 | ```
170 |
171 | Just make sure you return an array of arrays with key/value pairs like the example below:
172 |
173 | ```php
174 | public function otherRoutes()
175 | {
176 | return [
177 | [
178 | 'nl' => 'https://www.vursion.io/nl/contacteer-ons',
179 | 'en' => 'https://www.vursion.io/en/contact-us',
180 | ],
181 | ...
182 | ];
183 | }
184 | ```
185 |
186 | ## Changelog
187 |
188 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
189 |
190 | ## Security
191 |
192 | If you discover any security related issues, please email jochen@celcius.be instead of using the issue tracker.
193 |
194 | ## Credits
195 |
196 | - [Jochen Sengier](https://github.com/celcius-jochen)
197 |
198 | ## License
199 |
200 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
201 |
--------------------------------------------------------------------------------