├── .github
└── ISSUE_TEMPLATE
│ └── bug_report.yaml
├── .gitignore
├── README.md
├── composer.json
├── config
└── sitemapamic.php
├── resources
└── views
│ ├── index.blade.php
│ └── sitemap.blade.php
├── routes
└── web.php
└── src
├── Commands
├── ClearSitemapamicCacheCommand.php
└── ListSitemapamicCacheKeysCommand.php
├── Facades
└── Sitemapamic.php
├── Http
└── Controllers
│ └── SitemapamicController.php
├── Listeners
├── ClearSitemapamicCache.php
└── ScheduledCacheInvalidated.php
├── Models
└── SitemapamicUrl.php
├── ServiceProvider.php
├── Support
└── Sitemapamic.php
└── UpdateScripts
└── v2_0_1
└── MoveConfigFile.php
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | name: Bug report
2 | description: Fill out a bug report to help us improve.
3 | body:
4 | - type: textarea
5 | attributes:
6 | label: Bug description
7 | description: Please provide a detatiled description of what happened - and if needed, screenshots can help too.
8 | validations:
9 | required: true
10 | - type: textarea
11 | attributes:
12 | label: Steps to reproduce
13 | description: Provide clear, concise steps to reproduce the issue. A step-by-step list is a great start.
14 | validations:
15 | required: true
16 | - type: textarea
17 | attributes:
18 | label: Environment and versions
19 | description: |
20 | Details about specific vesions for Statamic, any addons and your PHP version.
21 | (The easiest thing to do is paste the output of the `php please support:details` command.)
22 | render: yaml # the format of the command is close to yaml and gets highlighted nicely
23 | validations:
24 | required: true
25 | - type: textarea
26 | attributes:
27 | label: Additional details
28 | description: Anything else you would like to add?
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | /node_modules
4 | vendor
5 | composer.lock
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sitemapamic
2 |
3 |
4 |
5 | 
6 | [](https://packagist.org/packages/mitydigital/sitemapamic/stats)
7 |
8 | ---
9 |
10 |
11 |
12 | > Sitemapamic is a XML sitemap generator for Statamic
13 |
14 | Sitemapamic creates a sitemap.xml file for your Statamic site, and includes:
15 |
16 | - automatic route registration for `sitemap.xml`
17 | - automatic updates as your content changes
18 | - support for entries and taxonomies
19 | - caching for performance (with a set time, or until content changes)
20 | - support for multi-site on the same or different domains
21 | - support for splitting large sites in to multiple smaller sitemaps
22 | - console commands for manual cache clearing
23 |
24 | ---
25 |
26 | Sitemapamic requires:
27 |
28 | - Statamic 5
29 | - PHP 8.2+
30 |
31 | ## Documentation
32 |
33 | See the [documentation](https://docs.mity.com.au/sitemapamic) for detailed installation, configuration and usage
34 | instructions.
35 |
36 | ---
37 |
38 | ## Support
39 |
40 | We love to share work like this, and help the community. However it does take time, effort and work.
41 |
42 | The best thing you can do is [log an issue](../../issues).
43 |
44 | Please try to be detailed when logging an issue, including a clear description of the problem, steps to reproduce the
45 | issue, and any steps you may have tried or taken to overcome the issue too. This is an awesome first step to helping us
46 | help you. So be awesome - it'll feel fantastic.
47 |
48 | ---
49 |
50 | ## Credits
51 |
52 | - [Marty Friedel](https://github.com/martyf)
53 | - [Jack Sleight](https://github.com/jacksleight) for adding multiple sitemap support
54 | - [Wuif](https://github.com/wuifdesign) for computed configuration support
55 |
56 | ## License
57 |
58 | This addon is licensed under the MIT license.
59 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mitydigital/sitemapamic",
3 | "description": "An XML sitemap generator for Statamic 3, 4 and 5 that includes all collections and related taxonomy pages.",
4 | "type": "statamic-addon",
5 | "keywords": [
6 | "statamic",
7 | "sitemap"
8 | ],
9 | "autoload": {
10 | "psr-4": {
11 | "MityDigital\\Sitemapamic\\": "src"
12 | }
13 | },
14 | "license": "MIT",
15 | "authors": [
16 | {
17 | "name": "Marty Friedel"
18 | }
19 | ],
20 | "require": {
21 | "php": "^8.2",
22 | "statamic/cms": "^5.0"
23 | },
24 | "extra": {
25 | "statamic": {
26 | "name": "Sitemapamic",
27 | "description": "Adds an XML sitemap to a Statamic site."
28 | },
29 | "laravel": {
30 | "providers": [
31 | "MityDigital\\Sitemapamic\\ServiceProvider"
32 | ]
33 | }
34 | },
35 | "config": {
36 | "allow-plugins": {
37 | "pixelfear/composer-dist-plugin": true
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/config/sitemapamic.php:
--------------------------------------------------------------------------------
1 | 'single',
17 |
18 |
19 | /*
20 | |--------------------------------------------------------------------------
21 | | Cache Key
22 | |--------------------------------------------------------------------------
23 | |
24 | | The key used to store the output. Will be cached forever until EventSaved or TermSaved is fired.
25 | |
26 | */
27 |
28 | 'cache' => 'sitemapamic',
29 |
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Cache Duration
34 | |--------------------------------------------------------------------------
35 | |
36 | | The number of seconds for how long the Sitemapamic Cache be held for.
37 | |
38 | | Can be an integer or DateInterval - the same options that Laravel's Cache accepts.
39 | |
40 | | Or set to 'forever' to remember forever (don't worry, it will get cleared when an Entry,
41 | | Term, Taxonomy or Collection is saved or deleted.
42 | |
43 | */
44 |
45 | 'ttl' => 'forever',
46 |
47 |
48 | /*
49 | |--------------------------------------------------------------------------
50 | | Defaults
51 | |--------------------------------------------------------------------------
52 | |
53 | | Sets defaults for different collections.
54 | |
55 | | The key is the collection handle, and the array includes default configurations.
56 | | Set "include" to true to either include or exclude without explicitly setting per article
57 | | Frequency and Priority are standard for an XML sitemap
58 | |
59 | | 'includeTaxonomies' enables (or disables) whether taxonomy URLs will be generated, if used,
60 | | for the collection. Only applies to Collections that actually use Taxonomies.
61 | |
62 | */
63 |
64 | 'defaults' => [
65 | /*'blog' => [
66 | 'include' => true,
67 | 'frequency' => 'weekly',
68 | 'priority' => '0.7'
69 | ],*/
70 |
71 | 'pages' => [
72 | 'include' => true,
73 | 'frequency' => 'yearly',
74 | 'priority' => '0.5',
75 | 'includeTaxonomies' => true,
76 | ]
77 | ],
78 |
79 |
80 | /*
81 | |--------------------------------------------------------------------------
82 | | Globals
83 | |--------------------------------------------------------------------------
84 | |
85 | | Sets global behaviour for items like taxonomies. Currently that's all that is supported.
86 | |
87 | | The 'globals.taxonomies' key expects an array of Taxonomy handles, each with an optional
88 | | priority and frequency, just like the Defaults section. This means your Taxonomy blueprint
89 | | can also take advantage of Term-specific 'meta_change_frequency' and 'meta_priority' fields,
90 | | or fall back to these defaults when not set (or present).
91 | |
92 | | If you don't want the Taxonomy included in the sitemap, simply exclude it from the array.
93 | |
94 | */
95 | 'globals' => [
96 | 'taxonomies' => [
97 | /*'tags' => [
98 | 'frequency' => 'yearly',
99 | 'priority' => '0.5',
100 | ],
101 |
102 | 'categories' => []*/
103 | ]
104 | ],
105 |
106 |
107 | /*
108 | |--------------------------------------------------------------------------
109 | | Field mappings
110 | |--------------------------------------------------------------------------
111 | |
112 | | Allows you to map your blueprint fields with what Sitemapamic is expecting for controlling
113 | | the change frequency, inclusion and priority.
114 | |
115 | | The key is the purpose (i.e. don't change this) and the value is the field handle in your blueprints.
116 | |
117 | */
118 | 'mappings' => [
119 | 'include' => 'meta_include_in_xml_sitemap',
120 | 'change_frequency' => 'meta_change_frequency',
121 | 'priority' => 'meta_priority',
122 | ]
123 | ];
124 |
--------------------------------------------------------------------------------
/resources/views/index.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @foreach ($submaps as $submap)
3 |
4 | {{ ("{$domain}/sitemap_{$submap}.xml") }}
5 |
6 | @endforeach
7 |
--------------------------------------------------------------------------------
/resources/views/sitemap.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @foreach ($entries as $entry)
3 |
4 | {{ $entry->loc }}
5 | {{ $entry->lastmod }}
6 | @if ($entry->changefreq){{ $entry->changefreq }}@endif
7 |
8 | @if ($entry->priority){{ number_format($entry->priority, 1) }}@endif
9 |
10 |
11 | @endforeach
12 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | pluck('url')
7 | ->map(fn($url) => \Statamic\Facades\URL::makeRelative($url))
8 | ->unique()
9 | ->each(fn($site) => Route::prefix($site)->group(function () {
10 | // add the standard sitemap.xml
11 | Route::get('sitemap.xml', [SitemapamicController::class, 'show']);
12 |
13 | // add the submap xml if multiple mode is enabled
14 | if (config('sitemapamic.mode', 'single') === 'multiple') {
15 | Route::get('sitemap_{submap}.xml', [SitemapamicController::class, 'show']);
16 | }
17 | }));
18 |
--------------------------------------------------------------------------------
/src/Commands/ClearSitemapamicCacheCommand.php:
--------------------------------------------------------------------------------
1 | argument('sitemaps');
37 |
38 | if (count($sitemaps) == 0)
39 | {
40 | // default cache clearing behaviour
41 | // clear it all
42 | if (Sitemapamic::clearCache()) {
43 | $this->info('Snip snip and whoosh, it\'s all gone.');
44 | }
45 | else {
46 | $this->error('Uh oh... Sitemapamic could not clear the entire cache.');
47 | }
48 | }
49 | else {
50 | // make a neat array
51 | $keys = [];
52 | foreach ($sitemaps as $key) {
53 | if (!in_array('"'.$key.'"', $keys)) {
54 | $keys[] = '"'.$key.'"';
55 | }
56 | }
57 |
58 | // make it prettier
59 | // Arr::join came in Laravel 9 - so do it manually for L8 support
60 | if (count($keys) > 1) {
61 | $lastKey = array_pop($keys);
62 | $keys = implode(', ', $keys).' and '.$lastKey;
63 | } else {
64 | $keys = end($keys);
65 | }
66 |
67 | // clear specific sitemaps only
68 | if (Sitemapamic::clearCache($sitemaps)) {
69 | $this->info('Snip snip and whoosh, sitemaps for '.$keys.' are gone.');
70 | } else {
71 | $this->error('Sitemaps for '.$keys.' could not be cleared.');
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Commands/ListSitemapamicCacheKeysCommand.php:
--------------------------------------------------------------------------------
1 | table(['Key'], collect(Sitemapamic::getCacheKeys())->map(fn($key) => ['key' => $key]));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Facades/Sitemapamic.php:
--------------------------------------------------------------------------------
1 | map(fn($loader) => $loader())
42 | ->flatten(1);
43 | return view('mitydigital/sitemapamic::sitemap', [
44 | 'entries' => $entries
45 | ])->render();
46 | };
47 | } elseif ($mode === 'multiple') {
48 | if ($request->submap) {
49 | if (!$loaders->has($request->submap)) {
50 | abort(404);
51 | }
52 | $key .= '.'.$request->submap;
53 |
54 | $generator = function () use ($loaders, $request) {
55 | $entries = $loaders->get($request->submap)();
56 | return view('mitydigital/sitemapamic::sitemap', [
57 | 'entries' => $entries
58 | ])->render();
59 | };
60 | } else {
61 | $generator = function () use ($loaders) {
62 | // return the view with submaps defined
63 | return view('mitydigital/sitemapamic::index', [
64 | 'domain' => rtrim(URL::makeAbsolute(Site::current()->url()), '/\\'),
65 | 'submaps' => $loaders->keys()
66 | ])->render();
67 | };
68 | }
69 | }
70 |
71 | // if the ttl is strictly 'forever', do just that
72 | if ($ttl == 'forever') {
73 | $xml = Cache::rememberForever($key, $generator);
74 | } else {
75 | $xml = Cache::remember($key, $ttl, $generator);
76 | }
77 |
78 | // add the XML header
79 | $xml = ''.$xml;
80 |
81 | return response($xml, 200, ['Content-Type' => 'application/xml']);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Listeners/ClearSitemapamicCache.php:
--------------------------------------------------------------------------------
1 | __DIR__.'/../routes/web.php',
30 | ];
31 |
32 | protected $listen = [
33 | CollectionDeleted::class => [
34 | ClearSitemapamicCache::class,
35 | ],
36 | CollectionSaved::class => [
37 | ClearSitemapamicCache::class,
38 | ],
39 | EntryDeleted::class => [
40 | ClearSitemapamicCache::class,
41 | ],
42 | EntrySaved::class => [
43 | ClearSitemapamicCache::class,
44 | ],
45 | TaxonomyDeleted::class => [
46 | ClearSitemapamicCache::class,
47 | ],
48 | TaxonomySaved::class => [
49 | ClearSitemapamicCache::class,
50 | ],
51 | TermDeleted::class => [
52 | ClearSitemapamicCache::class,
53 | ],
54 | TermSaved::class => [
55 | ClearSitemapamicCache::class,
56 | ],
57 | \MityDigital\StatamicScheduledCacheInvalidator\Events\ScheduledCacheInvalidated::class => [
58 | ScheduledCacheInvalidated::class
59 | ]
60 | ];
61 |
62 | protected $updateScripts = [
63 | // v2.0.1
64 | \MityDigital\Sitemapamic\UpdateScripts\v2_0_1\MoveConfigFile::class
65 | ];
66 |
67 | public function bootAddon()
68 | {
69 | $this->publishes([
70 | __DIR__.'/../resources/views' => resource_path('views/vendor/mitydigital/sitemapamic'),
71 | ], 'sitemapamic-views');
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Support/Sitemapamic.php:
--------------------------------------------------------------------------------
1 | dynamicRoutes[] = $routesClosure;
29 | }
30 |
31 | /**
32 | * Clears the Sitemapamic cache.
33 | *
34 | * Accepts an array of keys when 'mode' is set to 'multiple'. This will allow individual sitemaps to be cleared.
35 | *
36 | * Passing nothing, or an empty array, will clear the entire Sitemapamic cache.
37 | *
38 | * @param array $keys An array of keys, only for "multiple" configuration.
39 | * @return bool
40 | */
41 | public function clearCache(array $keys = []): bool
42 | {
43 | if (count($keys) === 0) {
44 | // clear everything
45 | foreach ($this->getCacheKeys() as $key) {
46 | Cache::forget($key);
47 | }
48 | } else {
49 | // only clear what was requested
50 | $siteKey = $this->getCacheKey();
51 | foreach ($keys as $key) {
52 | if (starts_with($key, $siteKey.'.')) {
53 | // we already have the cache key, so exclude it
54 | Cache::forget($key);
55 | } else {
56 | // add the cache key
57 | Cache::forget($this->getCacheKey().'.'.$key);
58 | }
59 | }
60 | }
61 |
62 | return true;
63 | }
64 |
65 | /**
66 | * Get the cache keys used by the current Sitemapamic configuration.
67 | *
68 | * @return array
69 | */
70 | public function getCacheKeys()
71 | {
72 | $key = $this->getCacheKey();
73 | $mode = config('sitemapamic.mode', 'single');
74 |
75 | $keys = [];
76 | $sites = collect(Site::all()->keys());
77 |
78 | if ($mode === 'single') {
79 | if ($sites->count() > 1) {
80 | // return the single statamic key for each site
81 | return $sites
82 | ->map(fn($site) => $key.'.'.$site)
83 | ->toArray();
84 | } else {
85 | // just return the key
86 | return [$key.'.'.Site::default()];
87 | }
88 | } elseif ($mode === 'multiple') {
89 | // get the loaders
90 | $loaders = $this->getLoaders();
91 |
92 | // get the keys relevant to the setup
93 | if ($sites->count() > 1) {
94 | return $this->getLoaders()
95 | ->keys()
96 | ->map(fn($loader) => $key.'.'.$loader)
97 | ->prepend($key)
98 | ->map(fn($key) => $sites
99 | ->map(fn($site) => $key.'.'.$site))
100 | ->flatten()
101 | ->toArray();
102 | } else {
103 | return $this->getLoaders()
104 | ->keys()
105 | ->map(fn($loader) => $key.'.'.$loader)
106 | ->prepend($key)
107 | ->toArray();
108 | }
109 | }
110 |
111 | return [];
112 | }
113 |
114 | /**
115 | * Returns the root Sitemapamic cache key.
116 | *
117 | * @return string|mixed
118 | */
119 | public function getCacheKey()
120 | {
121 | return config('sitemapamic.cache', 'sitemapamic');
122 | }
123 |
124 | /**
125 | * Get all of the content loaders used by the current configuration.
126 | *
127 | * Includes entries, collection terms, global taxonomies and dynamic routes.
128 | *
129 | * @return \Illuminate\Support\Collection
130 | */
131 | public function getLoaders()
132 | {
133 | $loaders = collect()
134 | ->merge($this->loadEntries())
135 | ->merge($this->loadCollectionTerms())
136 | ->merge($this->loadGlobalTaxonomies());
137 |
138 | // if we have any dynamic routes, let's load those too
139 | if ($this->hasDynamicRoutes()) {
140 | $loaders = $loaders->merge($this->loadDynamicRoutes());
141 | }
142 |
143 | return $loaders;
144 | }
145 |
146 | /**
147 | * Gets all published entries for all configured collections.
148 | *
149 | * Returns a collection of \MityDigital\Sitemapamic\Models\SitemapamicUrl
150 | *
151 | * @return \Illuminate\Support\Collection
152 | */
153 | protected function loadEntries(): \Illuminate\Support\Collection
154 | {
155 | return collect(config('sitemapamic.defaults'))->mapWithKeys(function ($properties, $handle) {
156 | return [
157 | $handle => function () use ($properties, $handle) {
158 | return Collection::findByHandle($handle)->queryEntries()->get()->filter(function (
159 | \Statamic\Entries\Entry $entry
160 | ) {
161 | // same site? if site is different, remove
162 | if ($entry->site() != Site::current()) {
163 | return false;
164 | }
165 |
166 | // is the entry published?
167 | if (!$entry->published()) {
168 | return false;
169 | }
170 |
171 | // are we an external redirect?
172 | // note the "dirty" trick to make the redirect a string (v4/5 has the ArrayableLink)
173 | if($entry->blueprint()->handle() === 'link' && isset($entry->redirect) && URL::isExternal('' . $entry->redirect)) {
174 | return false;
175 | }
176 |
177 | // if future listings are private or unlisted, do not include
178 | if ($entry->collection()->futureDateBehavior() == 'private' || $entry->collection()->futureDateBehavior() == 'unlisted') {
179 | if ($entry->date() > now()) {
180 | return false;
181 | }
182 | }
183 |
184 | // if past listings are private or unlisted, do not include
185 | if ($entry->collection()->pastDateBehavior() == 'private' || $entry->collection()->pastDateBehavior() == 'unlisted') {
186 | if ($entry->date() < now()) {
187 | return false;
188 | }
189 | }
190 |
191 | // include_xml_sitemap is one of null (when not set, so default to true), then either false or true
192 | $includeKey = config('sitemapamic.mappings.include', 'meta_include_in_xml_sitemap');
193 | $includeInSitemap = $entry->get($includeKey) ?? $entry->getComputed($includeKey);
194 | if ($includeInSitemap === null || $includeInSitemap == 'default') {
195 | // get the default config, or return true by default
196 | return config('sitemapamic.defaults.'.$entry->collection()->handle().'.include', true);
197 | } elseif ($includeInSitemap == "false" || $includeInSitemap === false) {
198 | // explicitly set to "false" or boolean false, so exclude
199 | return false;
200 | }
201 |
202 | // yep, keep it
203 | return true;
204 | })->map(function ($entry) {
205 |
206 | $changeFreqKey = config('sitemapamic.mappings.change_frequency', 'meta_change_frequency');
207 | $changeFreq = $entry->get($changeFreqKey) ?? $entry->getComputed($changeFreqKey);
208 | if ($changeFreq == 'default') {
209 | // clear back to use default
210 | $changeFreq = null;
211 | }
212 |
213 | $priorityKey = config('sitemapamic.mappings.priority', 'meta_priority');
214 |
215 | // return the entry as a Sitemapamic URL
216 | return new SitemapamicUrl(
217 | URL::makeAbsolute($entry->url()),
218 | Carbon::parse($entry->get('updated_at'))->toW3cString(),
219 | $changeFreq ?? config('sitemapamic.defaults.'.$entry->collection()->handle().'.frequency',
220 | false),
221 | $entry->get($priorityKey) ?? $entry->getComputed($priorityKey) ?? config('sitemapamic.defaults.'.$entry->collection()->handle().'.priority',
222 | false)
223 | );
224 | })->toArray();
225 | }
226 | ];
227 | });
228 | }
229 |
230 | /**
231 | * Gets the Taxonomy pages for the collections where they are used.
232 | *
233 | * lastmod will be set to the Term's updated_at time, or the latest entry's
234 | * updated_at time, whichever is more recent.
235 | *
236 | * Returns a collection of \MityDigital\Sitemapamic\Models\SitemapamicUrl
237 | *
238 | * @return \Illuminate\Support\Collection
239 | */
240 | protected function loadCollectionTerms(): \Illuminate\Support\Collection
241 | {
242 | // get the current site key based on the url
243 | $site = Site::default();
244 | foreach (Site::all() as $key => $props) {
245 | if ($props->url() == url('/')) {
246 | $site = $props;
247 | break;
248 | }
249 | }
250 |
251 | return collect(config('sitemapamic.defaults'))->map(function ($properties, $handle) use ($site) {
252 |
253 | // if there is a property called includeTaxonomies, and its false (or the collection is disabled) then exclude it
254 | // this has been added for backwards compatibility
255 | if (isset($properties['includeTaxonomies']) && (!$properties['includeTaxonomies'] || !$properties['include'])) {
256 | return false;
257 | }
258 |
259 | $collection = Collection::findByHandle($handle);
260 |
261 | return $collection->taxonomies()->map->collection($collection)->mapWithKeys(function ($taxonomy) use (
262 | $properties,
263 | $handle,
264 | $site
265 | ) {
266 | return [
267 | $handle.'_'.$taxonomy->handle => function () use ($taxonomy, $site) {
268 |
269 | return $taxonomy->queryTerms()->get()->filter(function ($term) use ($site) {
270 | if (!$term->published()) {
271 | return false;
272 | }
273 |
274 | // site is not configured, so exclude
275 | if (!$term->collection()->sites()->contains($site->handle())) {
276 | return false;
277 | }
278 |
279 | // include_xml_sitemap is one of null (when not set, so default to true), then either false or true
280 | $includeInSitemap = $term->get(config('sitemapamic.mappings.include', 'meta_include_in_xml_sitemap'));
281 | if ($includeInSitemap === null) {
282 | // get the default config, or return true by default
283 | return config('sitemapamic.defaults.'.$term->collection()->handle().'.include', true);
284 | } elseif ($includeInSitemap === false) {
285 | // explicitly set to false, so exclude
286 | return false;
287 | }
288 |
289 | return true; // this far, accept it
290 | })->map(function ($term) use ($site) {
291 | // get the term mod date
292 | $lastMod = $term->get('updated_at');
293 |
294 | // get entries
295 | $termEntries = $term->queryEntries()->orderBy('updated_at', 'desc');
296 |
297 | // if this term has entries, get the greater of the two updated_at timestamps
298 | if ($termEntries->count() > 0) {
299 | // get the last modified entry
300 | $entryLastMod = $termEntries->first()->get('updated_at');
301 |
302 | // entry date is after the term's mod date
303 | if ($entryLastMod > $lastMod) {
304 | $lastMod = $entryLastMod;
305 | }
306 | }
307 |
308 | $changeFreq = $term->get(config('sitemapamic.mappings.change_frequency', 'meta_change_frequency'));
309 | if ($changeFreq == 'default') {
310 | // clear back to use default
311 | $changeFreq = null;
312 | }
313 |
314 |
315 | // get the site URL, or the app URL if its "/"
316 | //$siteUrl = config('statamic.sites.sites.'.$term->locale().'.url');
317 | $siteUrl = $site->url();
318 | if ($siteUrl == '/') {
319 | $siteUrl = config('app.url');
320 | }
321 |
322 | return new SitemapamicUrl(
323 | $siteUrl.$term->url(),
324 | Carbon::parse($lastMod)->toW3cString(),
325 | $changeFreq ?? config('sitemapamic.defaults.'.$term->collection()->handle().'.frequency',
326 | false),
327 | $term->get(config('sitemapamic.mappings.priority', 'meta_priority')) ?? config('sitemapamic.defaults.'.$term->collection()->handle().'.priority',
328 | false)
329 | );
330 | });
331 | }
332 | ];
333 | });
334 | })->flatMap(fn($items) => $items);
335 | }
336 |
337 | protected function loadGlobalTaxonomies(): \Illuminate\Support\Collection
338 | {
339 | // are we configured to load the global taxonomies?
340 | // if so, what?
341 | $taxonomies = config('sitemapamic.globals.taxonomies', []);
342 |
343 | if (empty($taxonomies)) {
344 | // return an empty collection - either set to false, or not set yet
345 | return collect();
346 | }
347 |
348 | // get the current site key based on the url
349 | $site = Site::default();
350 | foreach (Site::all() as $key => $props) {
351 | if ($props->url() == url('/')) {
352 | $site = $props;
353 | break;
354 | }
355 | }
356 |
357 | return collect($taxonomies)->mapWithKeys(function ($properties, $handle) use ($site) {
358 | return [
359 | $handle => function () use ($handle, $site) {
360 |
361 | // get the taxonomy repository
362 | $taxonomy = Taxonomy::find($handle);
363 |
364 | // if the taxonomy isn't configured for the site, get out
365 | if (!$taxonomy->sites()->contains($site)) {
366 | return null;
367 | }
368 |
369 | // does a view exist for this taxonomy?
370 | // if not, it will 404, so let's not do any more
371 | if (!view()->exists($handle.'/show')) {
372 | return null;
373 | }
374 |
375 | // get the terms
376 | return Term::whereTaxonomy($handle)
377 | ->filter(function ($term) {
378 | // should we include this term?
379 | // include_xml_sitemap is one of null (when not set, so default to true), then either false or true
380 | $includeInSitemap = $term->get(config('sitemapamic.mappings.include', 'meta_include_in_xml_sitemap'));
381 | if ($includeInSitemap === "false" || $includeInSitemap === false) {
382 | // explicitly set to "false" or boolean false, so exclude
383 | return false;
384 | }
385 |
386 | // there is no meta field for the term, so include it
387 | // Why? Because if we made it this far, the Taxonomy is part of the global config, so
388 | // we want to include it. So just include it.
389 | return true;
390 | })
391 | ->map(function (\Statamic\Taxonomies\LocalizedTerm $term) use ($site) {
392 | // get the term mod date
393 | $lastMod = $term->get('updated_at');
394 |
395 | // get entries
396 | $termEntries = $term->queryEntries()->orderBy('updated_at', 'desc');
397 |
398 | // if this term has entries, get the greater of the two updated_at timestamps
399 | if ($termEntries->count() > 0) {
400 | // get the last modified entry
401 | $entryLastMod = $termEntries->first()->get('updated_at');
402 |
403 | // entry date is after the term's mod date
404 | if ($entryLastMod > $lastMod) {
405 | $lastMod = $entryLastMod;
406 | }
407 | }
408 |
409 | $changeFreq = $term->get(config('sitemapamic.mappings.change_frequency', 'meta_change_frequency'));
410 | if ($changeFreq == 'default') {
411 | // clear back to use default
412 | $changeFreq = null;
413 | }
414 |
415 | // get the site URL, or the app URL if its "/"
416 | //$siteUrl = config('statamic.sites.sites.'.$term->locale().'.url');
417 | $siteUrl = $site->url();
418 | if ($siteUrl == '/') {
419 | $siteUrl = config('app.url');
420 | }
421 |
422 | return new SitemapamicUrl(
423 | $siteUrl.$term->url(),
424 | Carbon::parse($lastMod)->toW3cString(),
425 | $changeFreq ??
426 | config('sitemapamic.globals.taxonomies.'.$term->taxonomy()->handle().'.frequency',
427 | false),
428 | $term->get(config('sitemapamic.mappings.priority', 'meta_priority')) ?? config('sitemapamic.globals.taxonomies.'.$term->taxonomy()->handle().'.priority',
429 | false)
430 | );
431 | });
432 | }
433 | ];
434 | });
435 | }
436 |
437 | public function hasDynamicRoutes(): bool
438 | {
439 | return (bool) count($this->dynamicRoutes);
440 | }
441 |
442 | protected function loadDynamicRoutes(): \Illuminate\Support\Collection
443 | {
444 | // get the dynamic routes, if any are set, and only return them if they are a SitemapamicUrl
445 | return collect([
446 | 'dynamic' => function () {
447 | return collect($this->getDynamicRoutes())
448 | ->flatMap(function ($closure) {
449 | return $closure();
450 | })
451 | ->filter(fn($route) => get_class($route) == SitemapamicUrl::class);
452 | }
453 | ]);
454 | }
455 |
456 | /**
457 | * Returns the dynamic routes
458 | *
459 | * @return array|mixed
460 | */
461 | public function getDynamicRoutes()
462 | {
463 | return $this->dynamicRoutes;
464 | }
465 | }
466 |
--------------------------------------------------------------------------------
/src/UpdateScripts/v2_0_1/MoveConfigFile.php:
--------------------------------------------------------------------------------
1 | isUpdatingTo('2.0.1');
13 | }
14 |
15 | public function update()
16 | {
17 | // check if the config is cached
18 | if ($configurationIsCached = app()->configurationIsCached()) {
19 | Artisan::call('config:clear');
20 | }
21 |
22 | // clear Sitemapamic cache
23 | Artisan::call('statamic:sitemapamic:clear');
24 |
25 | // if the config file exists within the 'config/statamic' path, move it just to 'config'
26 | if (file_exists(config_path('statamic/sitemapamic.php'))) {
27 | if (file_exists(config_path('sitemapamic.php'))) {
28 | // cannot copy
29 | $this->console()->alert('The Sitemapamic config file could not be moved to `config/sitemapamic.php` - it already exists!');
30 | $this->console()->alert('You will need to manually make sure your `config/sitemapamic.php` file is correctly configured.');
31 | } else {
32 | // move the config file
33 | rename(config_path('statamic/sitemapamic.php'), config_path('sitemapamic.php'));
34 |
35 | // output
36 | $this->console()->info('Sitemapamic config file has been moved to `config/sitemapamic.php`!');
37 | }
38 | }
39 |
40 | // re-cache config if it was cached
41 | if ($configurationIsCached) {
42 | Artisan::call('config:cache');
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------