Add sitemap v5.2.0 and social-meta-tags v0.3.0 plugins
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,216 @@
|
|||||||
|
# v5.2.0
|
||||||
|
## 04/10/26
|
||||||
|
|
||||||
|
1. [](#improved)
|
||||||
|
* Bumped minimum requirements to PHP 7.4 and Grav 1.7 [#119](https://github.com/getgrav/grav-plugin-sitemap/pull/119)
|
||||||
|
* Switched `getgrav.org` references to HTTPS [#117](https://github.com/getgrav/grav-plugin-sitemap/pull/117)
|
||||||
|
* Fixed typo in README.md [#118](https://github.com/getgrav/grav-plugin-sitemap/pull/118)
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Fixed HTML pattern character class escaping for `v` flag compatibility in admin blueprint [#120](https://github.com/getgrav/grav-plugin-sitemap/pull/120)
|
||||||
|
* Cast page `timestamp` to integer in sitemap data to avoid type issues with non-int date values
|
||||||
|
|
||||||
|
# v5.1.0
|
||||||
|
## 06/17/24
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Added page-level `lastmod` options [#113](https://github.com/getgrav/grav-plugin-sitemap/pull/113)
|
||||||
|
1. [](#improved)
|
||||||
|
* Updated README.md with page override options.
|
||||||
|
|
||||||
|
# v5.0.0
|
||||||
|
## 10/05/2023
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* New capability to support "standalone" Sitemap News pages
|
||||||
|
* New XSL format for Sitemap News
|
||||||
|
* Added a toggle for XSL transform support to be disabled
|
||||||
|
1. [](#improved)
|
||||||
|
* Improved blueprint and created sections
|
||||||
|
* Hide `x-default` alternate links when `include_default_lang` is enabled
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Fixed an issue with translated links were not being handled properly and not showing all alternate languages
|
||||||
|
|
||||||
|
# v4.0.0
|
||||||
|
## 09/22/2023
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Added support for Google News tags [#93](https://github.com/getgrav/grav-plugin-sitemap/pull/93)
|
||||||
|
* Enhanced Google News to allow restriction to paths and configurable max-age in days
|
||||||
|
1. [](#improved)
|
||||||
|
* Added YAML syntax highlighting in README.md [#101](https://github.com/getgrav/grav-plugin-sitemap/pull/101)
|
||||||
|
|
||||||
|
# v3.0.2
|
||||||
|
## 06/14/2022
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Added new `route:` field to JSON format sitemap
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Fixed an issue with `x-default` entry not working with non-string based language code
|
||||||
|
|
||||||
|
# v3.0.1
|
||||||
|
## 02/23/2021
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Added ability to disable multi-lang completely to replicate prior functionality [#96](https://github.com/getgrav/grav-plugin-sitemap/pull/96)
|
||||||
|
* Added support for new optional `html_support` option that allows you to render the sitemap as an HTML page in your site when you access the sitemap URL with no extension or `.html`. Can be customized and extended in your theme as needed.
|
||||||
|
|
||||||
|
# v3.0.0
|
||||||
|
## 01/30/2021
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Added complete multi-language support utilizing [Google Search recommended SEO best-practices](https://developers.google.com/search/docs/advanced/crawling/localized-versions?hl=en&visit_id=637468720624267418-280936473&rd=2) for bi-directional linking to translated pages.
|
||||||
|
* Added support fo `x-default` hreflang entries.
|
||||||
|
* Added support for new `sitemap.json` custom format that is useful for other plugins to understand the multi-language structure of the site
|
||||||
|
* Added support for sitemap images per [Google guidelines](https://developers.google.com/search/docs/advanced/sitemaps/image-sitemaps) [#81](https://github.com/getgrav/grav-plugin-sitemap/pull/81)
|
||||||
|
|
||||||
|
# v2.0.2
|
||||||
|
## 12/02/2020
|
||||||
|
|
||||||
|
1. [](#improved)
|
||||||
|
* Improved readme/blueprints with regex information [#78](https://github.com/getgrav/grav-plugin-sitemap/pull/78)
|
||||||
|
|
||||||
|
# v2.0.1
|
||||||
|
## 07/01/2020
|
||||||
|
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Fixed a case issue with `SitemapEntry` PHP class
|
||||||
|
|
||||||
|
# v2.0.0
|
||||||
|
## 07/01/2020
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Added a new `Ignore External URLs` option that defaults to `true`
|
||||||
|
* Added a new `Ignore Protected Pages` option that defaults to `true` [#62](https://github.com/getgrav/grav-plugin-sitemap/issues/62)
|
||||||
|
* Added a new `onSitemapProcessed()` event to allow for dynamic manipulation of the sitemap
|
||||||
|
1. [](#improved)
|
||||||
|
* Improved `SitemapEntry` to allow setting via constructor
|
||||||
|
* Added `changefreq` and `priority` to manually and dynamically added entries
|
||||||
|
* Use composer for autoloading
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Force a fallback to `en` to ensure you can't get `null/false` language [#74](https://github.com/getgrav/grav-plugin-sitemap/issues/74)
|
||||||
|
|
||||||
|
# v1.9.5
|
||||||
|
## 04/27/2020
|
||||||
|
|
||||||
|
1. [](#improved)
|
||||||
|
* Add admin toggle for `ignore` [#68](https://github.com/getgrav/grav-plugin-sitemap/pull/68)
|
||||||
|
* Omit empty `<lastmod>` [#70](https://github.com/getgrav/grav-plugin-sitemap/pull/70)
|
||||||
|
* Added Chinese language [#73](https://github.com/getgrav/grav-plugin-sitemap/pull/73)
|
||||||
|
* Added German language [#66](https://github.com/getgrav/grav-plugin-sitemap/pull/66)
|
||||||
|
|
||||||
|
# v1.9.4
|
||||||
|
## 10/19/2019
|
||||||
|
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Fixed a regression issue that caused sitemap not to work 'unless' you had an existing `sitemap` page [#65](https://github.com/getgrav/grav-plugin-sitemap/issues/65)
|
||||||
|
|
||||||
|
# v1.9.3
|
||||||
|
## 10/18/2019
|
||||||
|
|
||||||
|
1. [](#improved)
|
||||||
|
* Support existing `sitemap` HTML page to be used in place of XSL version
|
||||||
|
* Remove `/` from end of home URLS [#58](https://github.com/getgrav/grav-plugin-sitemap/pull/58)
|
||||||
|
* Include translated pages only [#57](https://github.com/getgrav/grav-plugin-sitemap/pull/57)
|
||||||
|
* Make sure `modular` pages are not included in configuration [#56](https://github.com/getgrav/grav-plugin-sitemap/pull/56)
|
||||||
|
|
||||||
|
# v1.9.2
|
||||||
|
## 05/09/2019
|
||||||
|
|
||||||
|
1. [](#improved)
|
||||||
|
* Enhanced HTML layout with XSL transformation [#24](https://github.com/getgrav/grav-plugin-sitemap/pull/24)
|
||||||
|
* Global toggles for change frequency and priority [#52](https://github.com/getgrav/grav-plugin-sitemap/pull/52)
|
||||||
|
* Added a meta name="robots" content="noindex" into the header for seo [#50](https://github.com/getgrav/grav-plugin-sitemap/pull/50)
|
||||||
|
* Added `ru` and `uk` translations [#61](https://github.com/getgrav/grav-plugin-sitemap/pull/61)
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Only add published translations to the sitemap [#43](https://github.com/getgrav/grav-plugin-sitemap/issues/43)
|
||||||
|
|
||||||
|
# v1.9.1
|
||||||
|
## 04/21/2017
|
||||||
|
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Add a namespace xhtml for a international sitemap [#40](https://github.com/getgrav/grav-plugin-sitemap/pull/40)
|
||||||
|
|
||||||
|
# v1.9.0
|
||||||
|
## 04/19/2017
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Added wildcard ignores [#34](https://github.com/getgrav/grav-plugin-sitemap/pull/34)
|
||||||
|
* Added ability to add external URLs to sitemap [#35](https://github.com/getgrav/grav-plugin-sitemap/pull/35)
|
||||||
|
* Added page-level ignores [#37](https://github.com/getgrav/grav-plugin-sitemap/pull/37)
|
||||||
|
* Added multilanguage support [#36](https://github.com/getgrav/grav-plugin-sitemap/pull/36)
|
||||||
|
|
||||||
|
# v1.8.0
|
||||||
|
## 03/14/2017
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Added `changefreq` and `priority` [#28](https://github.com/getgrav/grav-plugin-sitemap/pull/28)
|
||||||
|
1. [](#improved)
|
||||||
|
* Use `$page->canonical()` rather than `$page->permalink()` [#28](https://github.com/getgrav/grav-plugin-sitemap/pull/28)
|
||||||
|
|
||||||
|
# v1.7.0
|
||||||
|
## 10/19/2016
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Use new Grav feature to force output to be XML even when not passed `.xml` in URL
|
||||||
|
|
||||||
|
# v1.6.2
|
||||||
|
## 07/14/2016
|
||||||
|
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Fix sitemap XLS in multilanguage
|
||||||
|
|
||||||
|
# v1.6.1
|
||||||
|
## 05/30/2016
|
||||||
|
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Priority should be `float` in blueprints
|
||||||
|
|
||||||
|
# v1.6.0
|
||||||
|
## 04/29/2016
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Added compatibility with Grav Admin 1.1
|
||||||
|
1. [](#improved)
|
||||||
|
* Use some common translated strings in the blueprint
|
||||||
|
|
||||||
|
# v1.5.0
|
||||||
|
## 01/06/2016
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Added a default XSL file for the sitemap
|
||||||
|
1. [](#improved)
|
||||||
|
* Added a note to the README on how to only allow the link to the .xml sitemap
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Fixed saving the `priority` option when adding it to a page through the Admin Plugin
|
||||||
|
|
||||||
|
# v1.4.2
|
||||||
|
## 11/11/2015
|
||||||
|
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Escape the `loc` so it's properly parsed
|
||||||
|
|
||||||
|
# v1.4.1
|
||||||
|
## 10/07/2015
|
||||||
|
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Avoid duplication of sitemap items
|
||||||
|
|
||||||
|
# v1.4.0
|
||||||
|
## 08/25/2015
|
||||||
|
|
||||||
|
1. [](#improved)
|
||||||
|
* Added blueprints for Grav Admin plugin
|
||||||
|
1. [](#bugfix)
|
||||||
|
* Don't show unpublished pages in sitemap
|
||||||
|
|
||||||
|
# v1.3.0
|
||||||
|
## 02/25/2015
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* Added `ignores` list to allow certain routes to be left out of sitemap
|
||||||
|
|
||||||
|
# v1.2.0
|
||||||
|
## 11/30/2014
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* ChangeLog started...
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Grav
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
# Grav Sitemap Plugin
|
||||||
|
|
||||||
|
`Sitemap` is a [Grav](https://github.com/getgrav/grav) Plugin that generates a [map of your pages](https://en.wikipedia.org/wiki/Site_map) in `XML` format that is easily understandable and indexable by Search engines.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
Installing the Sitemap plugin can be done in one of two ways. Our GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file.
|
||||||
|
|
||||||
|
## GPM Installation (Preferred)
|
||||||
|
|
||||||
|
The simplest way to install this plugin is via the [Grav Package Manager (GPM)](https://learn.getgrav.org/advanced/grav-gpm) through your system's Terminal (also called the command line). From the root of your Grav install type:
|
||||||
|
|
||||||
|
bin/gpm install sitemap
|
||||||
|
|
||||||
|
This will install the Sitemap plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/sitemap`.
|
||||||
|
|
||||||
|
## Manual Installation
|
||||||
|
|
||||||
|
To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `sitemap`. You can find these files either on [GitHub](https://github.com/getgrav/grav-plugin-sitemap) or via [GetGrav.org](https://getgrav.org/downloads/plugins#extras).
|
||||||
|
|
||||||
|
You should now have all the plugin files under
|
||||||
|
|
||||||
|
/your/site/grav/user/plugins/sitemap
|
||||||
|
|
||||||
|
>> NOTE: This plugin is a modular component for Grav which requires [Grav](https://github.com/getgrav/grav), the [Error](https://github.com/getgrav/grav-plugin-error) and [Problems](https://github.com/getgrav/grav-plugin-problems) plugins, and a theme to be installed in order to operate.
|
||||||
|
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
The `sitemap` plugin works out of the box. You can just go directly to `http://yoursite.com/sitemap` and you will see the generated `XML`.
|
||||||
|
|
||||||
|
## Config Defaults
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
enabled: true
|
||||||
|
route: '/sitemap'
|
||||||
|
ignore_external: true
|
||||||
|
ignore_protected: true
|
||||||
|
ignore_redirect: true
|
||||||
|
ignores:
|
||||||
|
- /blog/blog-post-to-ignore
|
||||||
|
- /ignore-this-route
|
||||||
|
- /ignore-children-of-this-route/.*
|
||||||
|
include_news_tags: false
|
||||||
|
standalone_sitemap_news: false
|
||||||
|
sitemap_news_path: '/sitemap-news.xml'
|
||||||
|
news_max_age_days: 2
|
||||||
|
news_enabled_paths:
|
||||||
|
- /blog
|
||||||
|
whitelist:
|
||||||
|
html_support: false
|
||||||
|
urlset: 'http://www.sitemaps.org/schemas/sitemap/0.9'
|
||||||
|
urlnewsset: 'http://www.google.com/schemas/sitemap-news/0.9'
|
||||||
|
short_date_format: true
|
||||||
|
include_changefreq: true
|
||||||
|
changefreq: daily
|
||||||
|
include_priority: true
|
||||||
|
priority: !!float 1
|
||||||
|
additions:
|
||||||
|
-
|
||||||
|
location: /something-special
|
||||||
|
lastmod: '2020-04-16'
|
||||||
|
changefreq: hourly
|
||||||
|
priority: 0.3
|
||||||
|
-
|
||||||
|
location: /something-else
|
||||||
|
lastmod: '2020-04-17'
|
||||||
|
changefreq: weekly
|
||||||
|
priority: 0.2
|
||||||
|
```
|
||||||
|
|
||||||
|
You can ignore your own pages by providing a list of routes to ignore. You can also use a page's Frontmatter to signal that the sitemap should ignore it:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sitemap:
|
||||||
|
ignore: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overrides
|
||||||
|
|
||||||
|
You can override several elements of the sitemap entry for the page in the page's header. For example, as well as `ignore` mentioned above, these are available:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sitemap:
|
||||||
|
lastmod: # e.g. '2024-04-17'
|
||||||
|
changefreq: # always| hourly | daily: | weekly | monthly | yearly | never
|
||||||
|
priority: # 0.1 -> 1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multi-Language Support
|
||||||
|
|
||||||
|
The latest Sitemap `v3.0` includes all new multi-language support utilizing the latest [Google Search SEO Recommendations](https://developers.google.com/search/docs/advanced/crawling/localized-versions?hl=en&visit_id=637468720624267418-280936473&rd=2) which creates bi-directional `hreflang` entries for each language available.
|
||||||
|
|
||||||
|
This is handled automatically based on your Grav multi-language System configuration.
|
||||||
|
|
||||||
|
### News Support
|
||||||
|
|
||||||
|
New in version 4.0 of the plugin is support for Google's [**News Sitemap Extension**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/news-sitemap) that uses a specific tags under a `<news:news></news:news>` tag to provide Google News specific data. When enabled, the news extensions will be enabled when an item is in one of the configured news paths (`/` by default, so all), and if the published date is not older than the configured `max age` (default of 2 per Googles recommendations).
|
||||||
|
|
||||||
|
The output of the news tags is controlled by an overridable `sitemap-extensions/news.html.twig` template.
|
||||||
|
|
||||||
|
The default behavior when **Include News Tags** is enabled, is to include the news tags directly in the primary `sitemap.xml` file. However, if you enabled the **Standalone News URLs** option, news tags will not be added to the primary `sitemap.xml`, rather, they will be available in standalone paths that contain only the pages in the designated news paths.
|
||||||
|
|
||||||
|
For example, the default behavior is to enable `/blog` as a news path. If this path exists, you have content in subfolders of this page, and that content is less than the defined "News Max Age" (2 days recommended by Google), then that sitemap-news-specific sitemap would be available via:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://yoursite.com/blog/sitemap-news.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
You can change the "News Path" to be something other than `sitemap-news.xml` if you wish.
|
||||||
|
|
||||||
|
|
||||||
|
## Images
|
||||||
|
|
||||||
|
You can add images to the sitemap by adding an entry in the page's Frontmatter.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sitemap:
|
||||||
|
images:
|
||||||
|
your_image:
|
||||||
|
loc: your-image.png
|
||||||
|
caption: A caption for the image
|
||||||
|
geoloc: Amsterdam, The Netherlands
|
||||||
|
title: The title of your image
|
||||||
|
license: A URL to the license of the image.
|
||||||
|
```
|
||||||
|
|
||||||
|
For more info on images in sitemaps see [Google image sitemaps](https://support.google.com/webmasters/answer/178636?hl=en).
|
||||||
|
|
||||||
|
## Only allow access to the .xml file
|
||||||
|
|
||||||
|
If you want your sitemap to only be accessible via `sitemap.xml` for example, set the route to `/sitemap` and add this to your `.htaccess` file:
|
||||||
|
|
||||||
|
`Redirect 301 /sitemap /sitemap.xml`
|
||||||
|
|
||||||
|
## HTML Support
|
||||||
|
|
||||||
|
As of Sitemap version `3.0.1` you can enable `html_support` in the configuration and then when you go to `/sitemap` or `/sitemap.html` you will view an HTML version of the sitemap per the `templates/sitemap.html.twig` template.
|
||||||
|
|
||||||
|
You can copy and extend this Twig template in your theme to customize it for your needs.
|
||||||
|
|
||||||
|
## Manually add pages to the sitemap
|
||||||
|
|
||||||
|
You can manually add URLs to the sitemap using the Admin settings, or by adding entries to your `sitemap.yaml` with this format:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
additions:
|
||||||
|
-
|
||||||
|
location: /something-special
|
||||||
|
lastmod: '2020-04-16'
|
||||||
|
changefreq: hourly
|
||||||
|
priority: 0.3
|
||||||
|
```
|
||||||
|
Note that Regex support is available: Just append `.*` to a path to ignore all of it's children.
|
||||||
|
|
||||||
|
## Dynamically adding pages to the sitemap
|
||||||
|
|
||||||
|
If you have some dynamic content being added to your site via another plugin, or perhaps a 3rd party API, you can now add them dynamically to the sitemap with a simple event:
|
||||||
|
|
||||||
|
Make sure you are subscribed to the `onSitemapProcessed` event then add simply add your entry to the sitemap like this:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function onSitemapProcessed(\RocketTheme\Toolbox\Event\Event $e)
|
||||||
|
{
|
||||||
|
$sitemap = $e['sitemap'];
|
||||||
|
$location = \Grav\Common\Utils::url('/foo-location', true);
|
||||||
|
$sitemap['/foo'] = new \Grav\Plugin\Sitemap\SitemapEntry($location, '2020-07-02', 'weekly', '2.0');
|
||||||
|
$e['sitemap'] = $sitemap;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The use `Utils::url()` method allow us to easily create the correct full URL by passing it a route plus the optional `true` parameter.
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
name: Sitemap
|
||||||
|
type: plugin
|
||||||
|
slug: sitemap
|
||||||
|
version: 5.2.0
|
||||||
|
description: "Provide automatically generated **XML sitemaps** with this very useful, as a simple to configure Grav plugin."
|
||||||
|
icon: map-marker
|
||||||
|
author:
|
||||||
|
name: Team Grav
|
||||||
|
email: devs@getgrav.org
|
||||||
|
url: https://getgrav.org
|
||||||
|
homepage: https://github.com/getgrav/grav-plugin-sitemap
|
||||||
|
keywords: sitemap, plugin, xml, map, index
|
||||||
|
bugs: https://github.com/getgrav/grav-plugin-sitemap/issues
|
||||||
|
license: MIT
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- { name: grav, version: '>=1.7.0' }
|
||||||
|
|
||||||
|
form:
|
||||||
|
validation: strict
|
||||||
|
fields:
|
||||||
|
enabled:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGIN_ADMIN.PLUGIN_STATUS
|
||||||
|
highlight: 0
|
||||||
|
default: 0
|
||||||
|
options:
|
||||||
|
1: PLUGIN_ADMIN.ENABLED
|
||||||
|
0: PLUGIN_ADMIN.DISABLED
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
|
||||||
|
route:
|
||||||
|
type: text
|
||||||
|
size: medium
|
||||||
|
label: PLUGIN_SITEMAP.ROUTE
|
||||||
|
placeholder: /sitemap
|
||||||
|
validate:
|
||||||
|
pattern: "/([a-z_-]+/?)+"
|
||||||
|
|
||||||
|
multilang_enabled:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGIN_SITEMAP.MULTILANG_ENABLED
|
||||||
|
help: PLUGIN_SITEMAP.MULTILANG_ENABLED_HELP
|
||||||
|
highlight: 1
|
||||||
|
default: 1
|
||||||
|
options:
|
||||||
|
1: PLUGIN_ADMIN.ENABLED
|
||||||
|
0: PLUGIN_ADMIN.DISABLED
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
|
||||||
|
ignore_external:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGIN_SITEMAP.IGNORE_EXTERNAL
|
||||||
|
help: PLUGIN_SITEMAP.IGNORE_EXTERNAL_HELP
|
||||||
|
highlight: 1
|
||||||
|
default: 1
|
||||||
|
options:
|
||||||
|
1: PLUGIN_ADMIN.ENABLED
|
||||||
|
0: PLUGIN_ADMIN.DISABLED
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
|
||||||
|
ignore_protected:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGIN_SITEMAP.IGNORE_PROTECTED
|
||||||
|
help: PLUGIN_SITEMAP.IGNORE_PROTECTED_HELP
|
||||||
|
highlight: 1
|
||||||
|
default: 1
|
||||||
|
options:
|
||||||
|
1: PLUGIN_ADMIN.ENABLED
|
||||||
|
0: PLUGIN_ADMIN.DISABLED
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
|
||||||
|
ignore_redirect:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGIN_SITEMAP.IGNORE_REDIRECT
|
||||||
|
help: PLUGIN_SITEMAP.IGNORE_REDIRECT_HELP
|
||||||
|
highlight: 1
|
||||||
|
default: 1
|
||||||
|
options:
|
||||||
|
1: PLUGIN_ADMIN.ENABLED
|
||||||
|
0: PLUGIN_ADMIN.DISABLED
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
|
||||||
|
xsl_transform:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGIN_SITEMAP.XSL_TRANSFORM
|
||||||
|
help: PLUGIN_SITEMAP.XSL_TRANSFORM_HELP
|
||||||
|
highlight: 1
|
||||||
|
default: 1
|
||||||
|
options:
|
||||||
|
1: PLUGIN_ADMIN.ENABLED
|
||||||
|
0: PLUGIN_ADMIN.DISABLED
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
|
||||||
|
html_support:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGIN_SITEMAP.HTML_SUPPORT
|
||||||
|
help: PLUGIN_SITEMAP.HTML_SUPPORT_HELP
|
||||||
|
highlight: 0
|
||||||
|
default: 0
|
||||||
|
options:
|
||||||
|
1: PLUGIN_ADMIN.ENABLED
|
||||||
|
0: PLUGIN_ADMIN.DISABLED
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
|
||||||
|
ignores:
|
||||||
|
type: array
|
||||||
|
label: PLUGIN_SITEMAP.IGNORES
|
||||||
|
help: PLUGIN_SITEMAP.IGNORES_HELP
|
||||||
|
value_only: true
|
||||||
|
placeholder_value: '/ignore-this-route'
|
||||||
|
|
||||||
|
news_section:
|
||||||
|
type: section
|
||||||
|
title: Sitemap News
|
||||||
|
underline: true
|
||||||
|
|
||||||
|
fields:
|
||||||
|
|
||||||
|
include_news_tags:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGIN_SITEMAP.INCLUDE_NEWS_TAGS
|
||||||
|
help: PLUGIN_SITEMAP.INCLUDE_NEWS_TAGS_HELP
|
||||||
|
highlight: 1
|
||||||
|
default: 0
|
||||||
|
options:
|
||||||
|
1: PLUGIN_ADMIN.ENABLED
|
||||||
|
0: PLUGIN_ADMIN.DISABLED
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
|
||||||
|
standalone_sitemap_news:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGIN_SITEMAP.STANDALONE_SITEMAP_NEWS
|
||||||
|
help: PLUGIN_SITEMAP.STANDALONE_SITEMAP_NEWS_HELP
|
||||||
|
highlight: 1
|
||||||
|
default: 0
|
||||||
|
options:
|
||||||
|
1: PLUGIN_ADMIN.ENABLED
|
||||||
|
0: PLUGIN_ADMIN.DISABLED
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
|
||||||
|
sitemap_news_path:
|
||||||
|
type: text
|
||||||
|
size: medium
|
||||||
|
label: PLUGIN_SITEMAP.SITEMAP_NEWS_PATH
|
||||||
|
help: PLUGIN_SITEMAP.SITEMAP_NEWS_PATH_HELP
|
||||||
|
|
||||||
|
news_max_age_days:
|
||||||
|
type: number
|
||||||
|
default: 2
|
||||||
|
size: x-small
|
||||||
|
label: PLUGIN_SITEMAP.NEWS_MAX_AGE_DAYS
|
||||||
|
append: Days
|
||||||
|
validate:
|
||||||
|
type: int
|
||||||
|
|
||||||
|
news_enabled_paths:
|
||||||
|
type: array
|
||||||
|
label: PLUGIN_SITEMAP.NEWS_TAG_PATHS
|
||||||
|
value_only: true
|
||||||
|
placeholder_value: '/news'
|
||||||
|
|
||||||
|
data_section:
|
||||||
|
type: section
|
||||||
|
title: Sitemap Data
|
||||||
|
underline: true
|
||||||
|
fields:
|
||||||
|
|
||||||
|
date_type:
|
||||||
|
type: select
|
||||||
|
label: PLUGIN_SITEMAP.DATE_TYPE
|
||||||
|
default: page_date
|
||||||
|
size: medium
|
||||||
|
options:
|
||||||
|
page_date: PLUGIN_SITEMAP.DATE_TYPE_PAGE_DATE
|
||||||
|
last_modified: PLUGIN_SITEMAP.DATE_TYPE_LAST_MODIFIED
|
||||||
|
|
||||||
|
include_changefreq:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGIN_SITEMAP.INCLUDE_CHANGEFREQ
|
||||||
|
help: PLUGIN_SITEMAP.INCLUDE_CHANGEFREQ_HELP
|
||||||
|
highlight: 1
|
||||||
|
default: 0
|
||||||
|
options:
|
||||||
|
1: PLUGIN_ADMIN.ENABLED
|
||||||
|
0: PLUGIN_ADMIN.DISABLED
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
|
||||||
|
changefreq:
|
||||||
|
type: select
|
||||||
|
size: medium
|
||||||
|
label: PLUGIN_SITEMAP.CHANGEFREQ
|
||||||
|
default: ''
|
||||||
|
options:
|
||||||
|
'': PLUGIN_SITEMAP.CHANGEFREQ_DEFAULT
|
||||||
|
always: PLUGIN_SITEMAP.CHANGEFREQ_ALWAYS
|
||||||
|
hourly: PLUGIN_SITEMAP.CHANGEFREQ_HOURLY
|
||||||
|
daily: PLUGIN_SITEMAP.CHANGEFREQ_DAILY
|
||||||
|
weekly: PLUGIN_SITEMAP.CHANGEFREQ_WEEKLY
|
||||||
|
monthly: PLUGIN_SITEMAP.CHANGEFREQ_MONTHLY
|
||||||
|
yearly: PLUGIN_SITEMAP.CHANGEFREQ_YEARLY
|
||||||
|
never: PLUGIN_SITEMAP.CHANGEFREQ_NEVER
|
||||||
|
|
||||||
|
include_priority:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGIN_SITEMAP.INCLUDE_PRIORITY
|
||||||
|
help: PLUGIN_SITEMAP.INCLUDE_PRIORITY_HELP
|
||||||
|
highlight: 1
|
||||||
|
default: 0
|
||||||
|
options:
|
||||||
|
1: PLUGIN_ADMIN.ENABLED
|
||||||
|
0: PLUGIN_ADMIN.DISABLED
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
|
||||||
|
priority:
|
||||||
|
type: select
|
||||||
|
label: PLUGIN_SITEMAP.PRIORITY
|
||||||
|
size: small
|
||||||
|
default: ''
|
||||||
|
options:
|
||||||
|
'': PLUGIN_SITEMAP.PRIORITY_USE_GLOBAL
|
||||||
|
'0.1': 0.1
|
||||||
|
'0.2': 0.2
|
||||||
|
'0.3': 0.3
|
||||||
|
'0.4': 0.4
|
||||||
|
'0.5': 0.5
|
||||||
|
'0.6': 0.6
|
||||||
|
'0.7': 0.7
|
||||||
|
'0.8': 0.8
|
||||||
|
'0.9': 0.9
|
||||||
|
'1.0': 1.0
|
||||||
|
validate:
|
||||||
|
type: float
|
||||||
|
|
||||||
|
advanced_section:
|
||||||
|
type: section
|
||||||
|
title: Advanced Features
|
||||||
|
underline: true
|
||||||
|
fields:
|
||||||
|
|
||||||
|
additions:
|
||||||
|
type: list
|
||||||
|
label: PLUGIN_SITEMAP.ADDITIONS
|
||||||
|
help: PLUGIN_SITEMAP.ADDITIONS_HELP
|
||||||
|
|
||||||
|
fields:
|
||||||
|
.location:
|
||||||
|
type: text
|
||||||
|
label: PLUGIN_SITEMAP.LOCATION
|
||||||
|
placeholder: "/not-a-grav-url"
|
||||||
|
.lastmod:
|
||||||
|
type: text
|
||||||
|
label: PLUGIN_SITEMAP.LASTMOD
|
||||||
|
placeholder: "2017-04-06"
|
||||||
|
.changefreq:
|
||||||
|
type: select
|
||||||
|
label: PLUGIN_SITEMAP.CHANGEFREQ
|
||||||
|
default: ''
|
||||||
|
options:
|
||||||
|
'': PLUGIN_SITEMAP.CHANGEFREQ_DEFAULT
|
||||||
|
always: PLUGIN_SITEMAP.CHANGEFREQ_ALWAYS
|
||||||
|
hourly: PLUGIN_SITEMAP.CHANGEFREQ_HOURLY
|
||||||
|
daily: PLUGIN_SITEMAP.CHANGEFREQ_DAILY
|
||||||
|
weekly: PLUGIN_SITEMAP.CHANGEFREQ_WEEKLY
|
||||||
|
monthly: PLUGIN_SITEMAP.CHANGEFREQ_MONTHLY
|
||||||
|
yearly: PLUGIN_SITEMAP.CHANGEFREQ_YEARLY
|
||||||
|
never: PLUGIN_SITEMAP.CHANGEFREQ_NEVER
|
||||||
|
.priority:
|
||||||
|
type: select
|
||||||
|
label: PLUGIN_SITEMAP.PRIORITY
|
||||||
|
default: ''
|
||||||
|
options:
|
||||||
|
'': PLUGIN_SITEMAP.PRIORITY_USE_GLOBAL
|
||||||
|
'0.1': 0.1
|
||||||
|
'0.2': 0.2
|
||||||
|
'0.3': 0.3
|
||||||
|
'0.4': 0.4
|
||||||
|
'0.5': 0.5
|
||||||
|
'0.6': 0.6
|
||||||
|
'0.7': 0.7
|
||||||
|
'0.8': 0.8
|
||||||
|
'0.9': 0.9
|
||||||
|
'1.0': 1.0
|
||||||
|
validate:
|
||||||
|
type: float
|
||||||
|
|
||||||
|
urlset:
|
||||||
|
type: text
|
||||||
|
default: 'http://www.sitemaps.org/schemas/sitemap/0.9'
|
||||||
|
label: PLUGIN_SITEMAP.URLSET
|
||||||
|
help: PLUGIN_SITEMAP.URLSET_HELP
|
||||||
|
|
||||||
|
urlimageset:
|
||||||
|
type: text
|
||||||
|
default: 'http://www.google.com/schemas/sitemap-image/1.1'
|
||||||
|
label: PLUGIN_SITEMAP.URLIMAGESET
|
||||||
|
help: PLUGIN_SITEMAP.URLIMAGESET_HELP
|
||||||
|
|
||||||
|
urlnewsset:
|
||||||
|
type: text
|
||||||
|
default: 'http://www.google.com/schemas/sitemap-news/0.9'
|
||||||
|
label: PLUGIN_SITEMAP.URLNEWSSET
|
||||||
|
help: PLUGIN_SITEMAP.URLNEWSSET_HELP
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
form:
|
||||||
|
fields:
|
||||||
|
tabs:
|
||||||
|
fields:
|
||||||
|
options:
|
||||||
|
type: tab
|
||||||
|
|
||||||
|
fields:
|
||||||
|
|
||||||
|
sitemap:
|
||||||
|
type: section
|
||||||
|
title: PLUGIN_SITEMAP.SITEMAP
|
||||||
|
underline: true
|
||||||
|
|
||||||
|
fields:
|
||||||
|
header.sitemap.ignore:
|
||||||
|
type: toggle
|
||||||
|
toggleable: true
|
||||||
|
label: PLUGIN_SITEMAP.HEADER_IGNORE
|
||||||
|
highlight: 0
|
||||||
|
options:
|
||||||
|
1: PLUGIN_ADMIN.YES
|
||||||
|
0: PLUGIN_ADMIN.NO
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
required: false
|
||||||
|
|
||||||
|
header.sitemap.lastmod:
|
||||||
|
type: datetime
|
||||||
|
label: PLUGIN_SITEMAP.HEADER_LASTMOD
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
header.sitemap.changefreq:
|
||||||
|
type: select
|
||||||
|
label: PLUGIN_SITEMAP.HEADER_CHANGEFREQ
|
||||||
|
default: ''
|
||||||
|
options:
|
||||||
|
'': PLUGIN_SITEMAP.CHANGEFREQ_DEFAULT
|
||||||
|
always: PLUGIN_SITEMAP.CHANGEFREQ_ALWAYS
|
||||||
|
hourly: PLUGIN_SITEMAP.CHANGEFREQ_HOURLY
|
||||||
|
daily: PLUGIN_SITEMAP.CHANGEFREQ_DAILY
|
||||||
|
weekly: PLUGIN_SITEMAP.CHANGEFREQ_WEEKLY
|
||||||
|
monthly: PLUGIN_SITEMAP.CHANGEFREQ_MONTHLY
|
||||||
|
yearly: PLUGIN_SITEMAP.CHANGEFREQ_YEARLY
|
||||||
|
never: PLUGIN_SITEMAP.CHANGEFREQ_NEVER
|
||||||
|
|
||||||
|
header.sitemap.priority:
|
||||||
|
type: select
|
||||||
|
label: PLUGIN_SITEMAP.HEADER_PRIORITY
|
||||||
|
default: ''
|
||||||
|
options:
|
||||||
|
'': PLUGIN_SITEMAP.PRIORITY_USE_GLOBAL
|
||||||
|
'0.1': 0.1
|
||||||
|
'0.2': 0.2
|
||||||
|
'0.3': 0.3
|
||||||
|
'0.4': 0.4
|
||||||
|
'0.5': 0.5
|
||||||
|
'0.6': 0.6
|
||||||
|
'0.7': 0.7
|
||||||
|
'0.8': 0.8
|
||||||
|
'0.9': 0.9
|
||||||
|
'1.0': 1.0
|
||||||
|
validate:
|
||||||
|
type: float
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
<?php
|
||||||
|
namespace Grav\Plugin\Sitemap;
|
||||||
|
|
||||||
|
class SitemapEntry
|
||||||
|
{
|
||||||
|
public $title;
|
||||||
|
public $route;
|
||||||
|
public $lang;
|
||||||
|
public $translated = false;
|
||||||
|
public $location;
|
||||||
|
public $lastmod;
|
||||||
|
public $changefreq;
|
||||||
|
public $priority;
|
||||||
|
public $images;
|
||||||
|
public $hreflangs = [];
|
||||||
|
|
||||||
|
public int $timestamp;
|
||||||
|
public string $rawroute;
|
||||||
|
public string $longdate;
|
||||||
|
public string $shortdate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SitemapEntry constructor.
|
||||||
|
*
|
||||||
|
* @param null $location
|
||||||
|
* @param null $lastmod
|
||||||
|
* @param null $changefreq
|
||||||
|
* @param null $priority
|
||||||
|
* @param null $images
|
||||||
|
*/
|
||||||
|
public function __construct($location = null, $lastmod = null, $changefreq = null, $priority = null, $images = null)
|
||||||
|
{
|
||||||
|
$this->location = $location;
|
||||||
|
$this->lastmod = $lastmod;
|
||||||
|
$this->changefreq = $changefreq;
|
||||||
|
$this->priority = $priority;
|
||||||
|
$this->images = $images;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $data
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function setData(array $data): SitemapEntry
|
||||||
|
{
|
||||||
|
foreach($data as $property => $value)
|
||||||
|
{
|
||||||
|
if (property_exists($this, $property)) {
|
||||||
|
$this->{$property} = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getLang()
|
||||||
|
{
|
||||||
|
return $this->lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $lang
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function setLang($lang)
|
||||||
|
{
|
||||||
|
$this->lang = $lang;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getTitle()
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $title
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function setTitle($title): SitemapEntry
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getRoute()
|
||||||
|
{
|
||||||
|
return $this->route;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $route
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function setRoute($route): SitemapEntry
|
||||||
|
{
|
||||||
|
$this->route = $route;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getBaseLang()
|
||||||
|
{
|
||||||
|
return $this->base_lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $base_lang
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function setBaseLang($base_lang): SitemapEntry
|
||||||
|
{
|
||||||
|
$this->base_lang = $base_lang;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isTranslated(): bool
|
||||||
|
{
|
||||||
|
return $this->translated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $translated
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function setTranslated(bool $translated): SitemapEntry
|
||||||
|
{
|
||||||
|
$this->translated = $translated;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function getLocation()
|
||||||
|
{
|
||||||
|
return $this->location;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null $location
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function setLocation($location): SitemapEntry
|
||||||
|
{
|
||||||
|
$this->location = $location;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function getLastmod()
|
||||||
|
{
|
||||||
|
return $this->lastmod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null $lastmod
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function setLastmod($lastmod): SitemapEntry
|
||||||
|
{
|
||||||
|
$this->lastmod = $lastmod;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function getChangefreq()
|
||||||
|
{
|
||||||
|
return $this->changefreq;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null $changefreq
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function setChangefreq($changefreq): SitemapEntry
|
||||||
|
{
|
||||||
|
$this->changefreq = $changefreq;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function getPriority()
|
||||||
|
{
|
||||||
|
return $this->priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null $priority
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function setPriority($priority): SitemapEntry
|
||||||
|
{
|
||||||
|
$this->priority = $priority;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function getImages()
|
||||||
|
{
|
||||||
|
return $this->images;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null $images
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function setImages($images)
|
||||||
|
{
|
||||||
|
$this->images = $images;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getHreflangs(): array
|
||||||
|
{
|
||||||
|
return $this->hreflangs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $hreflang
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function addHreflangs(array $hreflang): SitemapEntry
|
||||||
|
{
|
||||||
|
$this->hreflangs[] = $hreflang;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $hreflangs
|
||||||
|
* @return SitemapEntry
|
||||||
|
*/
|
||||||
|
public function setHreflangs(array $hreflangs): SitemapEntry
|
||||||
|
{
|
||||||
|
$this->hreflangs = $hreflangs;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTimestamp(): int
|
||||||
|
{
|
||||||
|
return $this->timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTimestamp(int $timestamp): void
|
||||||
|
{
|
||||||
|
$this->timestamp = $timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRawroute(): string
|
||||||
|
{
|
||||||
|
return $this->rawroute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRawroute(string $rawroute): void
|
||||||
|
{
|
||||||
|
$this->rawroute = $rawroute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLongdate(): string
|
||||||
|
{
|
||||||
|
return $this->longdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLongdate(string $longdate): void
|
||||||
|
{
|
||||||
|
$this->longdate = $longdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getShortdate(): string
|
||||||
|
{
|
||||||
|
return $this->shortdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setShortdate(string $shortdate): void
|
||||||
|
{
|
||||||
|
$this->shortdate = $shortdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "trilbymedia/sitemap",
|
||||||
|
"type": "grav-sitemap",
|
||||||
|
"description": "Provide automatically generated **XML sitemaps** with this very useful, but simple to configure, Grav plugin.",
|
||||||
|
"keywords": ["plugin"],
|
||||||
|
"homepage": "https://github.com/getgrav/grav-plugin-sitemap",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Team Grav",
|
||||||
|
"email": "devs@getgrav.org",
|
||||||
|
"homepage": "https://getgrav.org",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.4"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Grav\\Plugin\\Sitemap\\": "classes/"
|
||||||
|
},
|
||||||
|
"classmap": ["sitemap.php"]
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"platform": {
|
||||||
|
"php": "7.4.33"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+23
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "7b3a8451f3be62bce1c1dd5736e649ea",
|
||||||
|
"packages": [],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": {
|
||||||
|
"php": ">=7.1.3"
|
||||||
|
},
|
||||||
|
"platform-dev": [],
|
||||||
|
"platform-overrides": {
|
||||||
|
"php": "7.1.3"
|
||||||
|
},
|
||||||
|
"plugin-api-version": "2.0.0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"project":"grav-plugin-sitemap",
|
||||||
|
"platforms":{
|
||||||
|
"grav":{
|
||||||
|
"nodes":{
|
||||||
|
"plugin":[
|
||||||
|
{
|
||||||
|
"source":"/",
|
||||||
|
"destination":"/user/plugins/sitemap"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
en:
|
||||||
|
PLUGIN_SITEMAP:
|
||||||
|
SITEMAP: 'Sitemap'
|
||||||
|
HEADER_IGNORE: 'Sitemap ignore page'
|
||||||
|
HEADER_LASTMOD: 'Sitemap Last Modified'
|
||||||
|
HEADER_CHANGEFREQ: 'Sitemap Change Frequency'
|
||||||
|
HEADER_PRIORITY: 'Sitemap priority'
|
||||||
|
CHANGEFREQ: 'Global - Sitemap Change Frequency'
|
||||||
|
CHANGEFREQ_DEFAULT: 'Use Global (daily)'
|
||||||
|
CHANGEFREQ_ALWAYS: 'Always'
|
||||||
|
CHANGEFREQ_HOURLY: 'Hourly'
|
||||||
|
CHANGEFREQ_DAILY: 'Daily'
|
||||||
|
CHANGEFREQ_WEEKLY: 'Weekly'
|
||||||
|
CHANGEFREQ_MONTHLY: 'Monthly'
|
||||||
|
CHANGEFREQ_YEARLY: 'Yearly'
|
||||||
|
CHANGEFREQ_NEVER: 'Never'
|
||||||
|
PRIORITY: 'Global - Sitemap Priority'
|
||||||
|
PRIORITY_USE_GLOBAL: 'Use Global (1)'
|
||||||
|
ROUTE: 'Route to Sitemap'
|
||||||
|
IGNORES: 'Ignore URLs'
|
||||||
|
IGNORES_HELP: 'URLs to ignore (You can ignore all children of a path by using regex and appending ".*" to the ignore path)'
|
||||||
|
ADDITIONS: 'Additional URLs'
|
||||||
|
ADDITIONS_HELP: 'Add external URLs to the sitemap'
|
||||||
|
LOCATION: 'The URL location'
|
||||||
|
LASTMOD: 'Last modification e.g. 2017-04-06'
|
||||||
|
IGNORE_EXTERNAL: 'Ignore External URLs'
|
||||||
|
IGNORE_EXTERNAL_HELP: 'By default Sitemap hides pages that have an `external` URL'
|
||||||
|
IGNORE_PROTECTED: 'Ignore Protected Pages'
|
||||||
|
IGNORE_PROTECTED_HELP: 'Ignore pages that custom "access" set to protect them via a login'
|
||||||
|
IGNORE_REDIRECT: 'Ignore Redirect Pages'
|
||||||
|
IGNORE_REDIRECT_HELP: 'Ignores pages that have a custom "redirect" entry in the header'
|
||||||
|
URLSET: 'URLSet'
|
||||||
|
URLSET_HELP: 'The URLSet XML Namespace'
|
||||||
|
URLIMAGESET: 'URLImageSet'
|
||||||
|
URLIMAGESET_HELP: 'The URLImageSet XML Namespace,'
|
||||||
|
URLNEWSSET: 'URLNewsSet'
|
||||||
|
URLNEWSSET_HELP: 'The URLNewsSet XML Namespace'
|
||||||
|
INCLUDE_NEWS_TAGS: 'Include News Tags'
|
||||||
|
NEWS_MAX_AGE_DAYS: 'News Max Age (Days)'
|
||||||
|
NEWS_TAG_PATHS: 'News Enabled Paths'
|
||||||
|
MULTILANG_ENABLED: 'Multi-Lang Features'
|
||||||
|
MULTILANG_ENABLED_HELP: 'Enables support for multilanguage features'
|
||||||
|
INCLUDE_CHANGEFREQ: 'Include Change Frequency'
|
||||||
|
INCLUDE_PRIORITY: 'Include Priority'
|
||||||
|
SHORT_DATE_FORMAT: 'Short Date Format'
|
||||||
|
SHORT_DATE_FORMAT_HELP: 'Use Short or Long Date format'
|
||||||
|
HTML_SUPPORT: 'HTML Support'
|
||||||
|
HTML_SUPPORT_HELP: 'Use "sitemap.html.twig" if no extension or ".html" passed in the URL'
|
||||||
|
TITLE_LOCATION: 'Location'
|
||||||
|
TITLE_TITLE: 'Title'
|
||||||
|
TITLE_LASTMOD: 'Last Modified'
|
||||||
|
UNTITLED: 'Untitled'
|
||||||
|
XSL_TRANSFORM: 'XSL Transform'
|
||||||
|
XSL_TRANSFORM_HELP: 'Use XSL stylesheets to transform the XML output when viewing in a browser'
|
||||||
|
DATE_TYPE: 'Date Type'
|
||||||
|
DATE_TYPE_PAGE_DATE: 'Page Date'
|
||||||
|
DATE_TYPE_LAST_MODIFIED: 'Last Modified'
|
||||||
|
STANDALONE_SITEMAP_NEWS: 'Standalone News URLs'
|
||||||
|
STANDALONE_SITEMAP_NEWS_HELP: 'Create a standalone sitemap-news.xml file for each news-enabled path'
|
||||||
|
SITEMAP_NEWS_PATH: 'Sitemap News Path'
|
||||||
|
SITEMAP_NEWS_PATH_HELP: 'The path to the Sitemap News file'
|
||||||
|
ru:
|
||||||
|
PLUGIN_SITEMAP:
|
||||||
|
SITEMAP: 'Карта сайта'
|
||||||
|
HEADER_CHANGEFREQ: 'Частота обновления карты сайта'
|
||||||
|
HEADER_PRIORITY: 'Приоритет карты сайта'
|
||||||
|
CHANGEFREQ: 'Глобальное - частота обновления карты сайта'
|
||||||
|
CHANGEFREQ_DEFAULT: 'Использовать глобальное (ежедневно)'
|
||||||
|
CHANGEFREQ_ALWAYS: 'Всегда'
|
||||||
|
CHANGEFREQ_HOURLY: 'Ежечасно'
|
||||||
|
CHANGEFREQ_DAILY: 'Ежедневно'
|
||||||
|
CHANGEFREQ_WEEKLY: 'Еженедельно'
|
||||||
|
CHANGEFREQ_MONTHLY: 'Ежемесячно'
|
||||||
|
CHANGEFREQ_YEARLY: 'Ежегодно'
|
||||||
|
CHANGEFREQ_NEVER: 'Никогда'
|
||||||
|
PRIORITY: 'Глобальное - приоритет карты сайта'
|
||||||
|
PRIORITY_USE_GLOBAL: 'Использовать глобальное (1)'
|
||||||
|
ROUTE: 'Маршрут к карте сайта'
|
||||||
|
IGNORES: 'Игнорировать'
|
||||||
|
IGNORES_HELP: 'URL-адреса для игнорирования (Вы можете игнорировать всех дочерних элементов пути, используя регекс и добавляя ".*" к игнорируемому пути).'
|
||||||
|
ADDITIONS: 'Дополнительные URL'
|
||||||
|
ADDITIONS_HELP: 'Добавить внешние URL в карту сайта'
|
||||||
|
LOCATION: 'Расположение URL'
|
||||||
|
LASTMOD: 'Последнее изменение, например 2017-04-06'
|
||||||
|
|
||||||
|
uk:
|
||||||
|
PLUGIN_SITEMAP:
|
||||||
|
SITEMAP: 'Карта сайту'
|
||||||
|
HEADER_CHANGEFREQ: 'Частота оновлення карти сайту'
|
||||||
|
HEADER_PRIORITY: 'Пріоритет карти сайту'
|
||||||
|
CHANGEFREQ: 'Глобальне - частота оновлення карти сайту'
|
||||||
|
CHANGEFREQ_DEFAULT: 'Використовувати глобальне (щодня)'
|
||||||
|
CHANGEFREQ_ALWAYS: 'Завжди'
|
||||||
|
CHANGEFREQ_HOURLY: 'Погодинно'
|
||||||
|
CHANGEFREQ_DAILY: 'Щодня'
|
||||||
|
CHANGEFREQ_WEEKLY: 'Щотижня'
|
||||||
|
CHANGEFREQ_MONTHLY: 'Щомісячно'
|
||||||
|
CHANGEFREQ_YEARLY: 'Щорічно'
|
||||||
|
CHANGEFREQ_NEVER: 'Ніколи'
|
||||||
|
PRIORITY: 'Глобальне - пріоритет карта сайту'
|
||||||
|
PRIORITY_USE_GLOBAL: 'Використовувати глобальний (1)'
|
||||||
|
ROUTE: 'Маршрут до карти сайту'
|
||||||
|
IGNORES: 'Ігнорувати'
|
||||||
|
IGNORES_HELP: 'URL-адреси для ігнорування'
|
||||||
|
ADDITIONS: 'Додаткові URL-адреси'
|
||||||
|
ADDITIONS_HELP: 'Додати зовнішні URL-адреси до карти сайту'
|
||||||
|
LOCATION: 'Розташування URL-адреси'
|
||||||
|
LASTMOD: 'Остання модифікація, напр. 2017-04-06'
|
||||||
|
|
||||||
|
de:
|
||||||
|
PLUGIN_SITEMAP:
|
||||||
|
SITEMAP: 'Sitemap'
|
||||||
|
HEADER_CHANGEFREQ: 'Sitemap Änderungsfrequenz'
|
||||||
|
HEADER_PRIORITY: 'Sitemap Priorität'
|
||||||
|
CHANGEFREQ: 'Global - Sitemap Änderungsfrequenz'
|
||||||
|
CHANGEFREQ_DEFAULT: 'Benutze Global (Täglich)'
|
||||||
|
CHANGEFREQ_ALWAYS: 'Immer'
|
||||||
|
CHANGEFREQ_HOURLY: 'Stündlich'
|
||||||
|
CHANGEFREQ_DAILY: 'Täglich'
|
||||||
|
CHANGEFREQ_WEEKLY: 'Wöchentlich'
|
||||||
|
CHANGEFREQ_MONTHLY: 'Monatlich'
|
||||||
|
CHANGEFREQ_YEARLY: 'Jährlich'
|
||||||
|
CHANGEFREQ_NEVER: 'Nie'
|
||||||
|
PRIORITY: 'Global - Sitemap Priorität'
|
||||||
|
PRIORITY_USE_GLOBAL: 'Benutze Global (1)'
|
||||||
|
ROUTE: 'Pfad zur Sitemap'
|
||||||
|
IGNORES: 'Ignorieren'
|
||||||
|
IGNORES_HELP: 'Zu ignorierende URLs (Um alle Unterseiten einer URL zu ignorieren, können Sie Regex nutzen und ".*" an die zu ignorierende URL anhängen)'
|
||||||
|
ADDITIONS: 'Zusätzliche URLs'
|
||||||
|
ADDITIONS_HELP: 'Füge externe URLs zur Sitemap hinzu'
|
||||||
|
LOCATION: 'Seiten Pfad'
|
||||||
|
LASTMOD: 'Letzte Änderung e.g. 2017-04-06'
|
||||||
|
MULTILANG_ENABLED: 'Mehrsprachigkeit'
|
||||||
|
MULTILANG_ENABLED_HELP: 'Aktiviert Funktionen zur Mehrsprachigkeit'
|
||||||
|
|
||||||
|
zh:
|
||||||
|
PLUGIN_SITEMAP:
|
||||||
|
SITEMAP: '网站地图'
|
||||||
|
HEADER_CHANGEFREQ: '网站地图变更频率'
|
||||||
|
HEADER_PRIORITY: '网站地图优先级'
|
||||||
|
CHANGEFREQ: '全局 - 网站地图变更频率'
|
||||||
|
CHANGEFREQ_DEFAULT: '使用全局 (每日)'
|
||||||
|
CHANGEFREQ_ALWAYS: '总是'
|
||||||
|
CHANGEFREQ_HOURLY: '每小时'
|
||||||
|
CHANGEFREQ_DAILY: '每天'
|
||||||
|
CHANGEFREQ_WEEKLY: '每周'
|
||||||
|
CHANGEFREQ_MONTHLY: '每月'
|
||||||
|
CHANGEFREQ_YEARLY: '每年'
|
||||||
|
CHANGEFREQ_NEVER: '永不'
|
||||||
|
PRIORITY: '全局 - 网站地图优先级'
|
||||||
|
PRIORITY_USE_GLOBAL: '使用全局 (1)'
|
||||||
|
ROUTE: '网站地图路径'
|
||||||
|
IGNORES: '忽略'
|
||||||
|
IGNORES_HELP: '忽略的 URL (你可以通过使用regex并在忽略路径后添加".*"来忽略一个路径的所有子节点)'
|
||||||
|
ADDITIONS: '附加 URL'
|
||||||
|
ADDITIONS_HELP: '添加外部 URL 到网站地图'
|
||||||
|
LOCATION: 'URL 地址'
|
||||||
|
LASTMOD: '上次修改 例如 2017-04-06'
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Sitemap
|
||||||
|
---
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<xsl:stylesheet version="2.0"
|
||||||
|
xmlns:html="http://www.w3.org/TR/REC-html40"
|
||||||
|
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
|
||||||
|
xmlns:s="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||||
|
xmlns:n="http://www.google.com/schemas/sitemap-news/0.9"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="robots" content="noindex"/>
|
||||||
|
<title>
|
||||||
|
XML Sitemap
|
||||||
|
</title>
|
||||||
|
<style type="text/css">
|
||||||
|
@import url('//cdn.jsdelivr.net/pure/0.6.0/base-min.css');
|
||||||
|
@import url('//cdn.jsdelivr.net/pure/0.6.0/pure-min.css');
|
||||||
|
@import url('//cdn.jsdelivr.net/pure/0.6.0/grids-responsive-min.css');
|
||||||
|
@import
|
||||||
|
url('//fonts.googleapis.com/css?family=Raleway:100,300,400,700,900,100italic,300italic,400italic,700italic,900italic');
|
||||||
|
.font_smooth {
|
||||||
|
font-smooth: auto;
|
||||||
|
text-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
|
||||||
|
text-rendering: auto;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-webkit-text-size-adjust: 100%
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
font-smooth: auto;
|
||||||
|
text-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
|
||||||
|
text-rendering: auto;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
background-color: #fff
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Raleway', sans-serif;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1.8em;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-align: left;
|
||||||
|
color: #333
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 20px
|
||||||
|
}
|
||||||
|
.clear {
|
||||||
|
clear: both;
|
||||||
|
float: none
|
||||||
|
}
|
||||||
|
a,
|
||||||
|
a:link,
|
||||||
|
a:visited {
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: dotted 1px #333;
|
||||||
|
color: #333
|
||||||
|
}
|
||||||
|
h1,h2,h3,h4,h5,h6 {
|
||||||
|
font-family: Raleway;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.2em;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
color: #000
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
border: solid 1px #cbcbcb !important;
|
||||||
|
text-align: center;
|
||||||
|
background: #fff
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="pure-table pure-table-striped" border="0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="5">News Sitemap</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th width="45%">loc</th>
|
||||||
|
<th width="35%">news:title</th>
|
||||||
|
<th width="20%">news:publication_date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tfoot>
|
||||||
|
</tfoot>
|
||||||
|
<tbody>
|
||||||
|
<xsl:for-each select="s:urlset/s:url">
|
||||||
|
<xsl:sort select="n:news/n:publication_date" order="descending" data-type="text"/>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<xsl:variable name="itemURL">
|
||||||
|
<xsl:value-of select="s:loc"/>
|
||||||
|
</xsl:variable>
|
||||||
|
<a href="{$itemURL}">
|
||||||
|
<xsl:value-of select="s:loc"/>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<xsl:value-of select="n:news/n:title"/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<xsl:value-of
|
||||||
|
select="concat(substring(n:news/n:publication_date,0,11),concat(' ', substring(n:news/n:publication_date,12,5)))"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</xsl:for-each>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
<?php
|
||||||
|
namespace Grav\Plugin;
|
||||||
|
|
||||||
|
use Composer\Autoload\ClassLoader;
|
||||||
|
use Grav\Common\Cache;
|
||||||
|
use Grav\Common\Grav;
|
||||||
|
use Grav\Common\Data;
|
||||||
|
use Grav\Common\Language\Language;
|
||||||
|
use Grav\Common\Page\Interfaces\PageInterface;
|
||||||
|
use Grav\Common\Page\Page;
|
||||||
|
use Grav\Common\Plugin;
|
||||||
|
use Grav\Common\Twig\Twig;
|
||||||
|
use Grav\Common\Uri;
|
||||||
|
use Grav\Common\Page\Pages;
|
||||||
|
use Grav\Common\Utils;
|
||||||
|
use Grav\Plugin\Sitemap\SitemapEntry;
|
||||||
|
use RocketTheme\Toolbox\Event\Event;
|
||||||
|
use Twig\TwigFunction;
|
||||||
|
|
||||||
|
class SitemapPlugin extends Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $sitemap = false;
|
||||||
|
protected $route_data = [];
|
||||||
|
|
||||||
|
protected $multilang_skiplang_prefix = null;
|
||||||
|
protected $multilang_include_fallbacks = false;
|
||||||
|
protected $multilang_enabled = true;
|
||||||
|
protected $datetime_format = null;
|
||||||
|
protected $include_change_freq = true;
|
||||||
|
protected $default_change_freq = null;
|
||||||
|
protected $include_priority = true;
|
||||||
|
protected $default_priority = null;
|
||||||
|
protected $ignores = null;
|
||||||
|
protected $ignore_external = true;
|
||||||
|
protected $ignore_protected = true;
|
||||||
|
protected $ignore_redirect = true;
|
||||||
|
|
||||||
|
protected $news_route = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'onPluginsInitialized' => [
|
||||||
|
['autoload', 100000], // TODO: Remove when plugin requires Grav >=1.7
|
||||||
|
['onPluginsInitialized', 0],
|
||||||
|
],
|
||||||
|
'onBlueprintCreated' => ['onBlueprintCreated', 0]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composer autoload.
|
||||||
|
*is
|
||||||
|
* @return ClassLoader
|
||||||
|
*/
|
||||||
|
public function autoload(): ClassLoader
|
||||||
|
{
|
||||||
|
return require __DIR__ . '/vendor/autoload.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable sitemap only if url matches to the configuration.
|
||||||
|
*/
|
||||||
|
public function onPluginsInitialized()
|
||||||
|
{
|
||||||
|
if ($this->isAdmin()) {
|
||||||
|
$this->active = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Uri $uri */
|
||||||
|
$uri = $this->grav['uri'];
|
||||||
|
$route = $this->config()['route'];
|
||||||
|
$uri_route = $uri->route();
|
||||||
|
$news_page = false;
|
||||||
|
|
||||||
|
if ($this->config()['include_news_tags'] &&
|
||||||
|
$this->config()['standalone_sitemap_news'] &&
|
||||||
|
Utils::endsWith($uri->uri(), $this->config()['sitemap_news_path']) &&
|
||||||
|
in_array(dirname($uri->route()), $this->config()['news_enabled_paths'])) {
|
||||||
|
$this->news_route = dirname($uri->route());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($route === $uri->route() || !empty($this->news_route)) {
|
||||||
|
|
||||||
|
$this->enable([
|
||||||
|
'onTwigInitialized' => ['onTwigInitialized', 0],
|
||||||
|
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
|
||||||
|
'onPagesInitialized' => ['onPagesInitialized', 0],
|
||||||
|
'onPageInitialized' => ['onPageInitialized', 0],
|
||||||
|
'onTwigSiteVariables' => ['onTwigSiteVariables', 0]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate data for the sitemap.
|
||||||
|
*/
|
||||||
|
public function onPagesInitialized()
|
||||||
|
{
|
||||||
|
/** @var Cache $cache */
|
||||||
|
$cache = $this->grav['cache'];
|
||||||
|
|
||||||
|
/** @var Pages $pages */
|
||||||
|
$pages = $this->grav['pages'];
|
||||||
|
|
||||||
|
$cache_id = md5('sitemap-data-'.$pages->getPagesCacheId());
|
||||||
|
// $this->sitemap = $cache->fetch($cache_id);
|
||||||
|
|
||||||
|
if ($this->sitemap === false) {
|
||||||
|
$this->multilang_enabled = $this->config->get('plugins.sitemap.multilang_enabled');
|
||||||
|
|
||||||
|
/** @var Language $language */
|
||||||
|
$language = $this->grav['language'];
|
||||||
|
$default_lang = $language->getDefault() ?: 'en';
|
||||||
|
$active_lang = $language->getActive() ?? $default_lang;
|
||||||
|
$languages = $this->multilang_enabled && $language->enabled() ? $language->getLanguages() : [$default_lang];
|
||||||
|
$include_default_lang = $this->config->get('system.languages.include_default_lang');
|
||||||
|
|
||||||
|
$this->multilang_skiplang_prefix = $this->config->get('system.languages.include_default_lang') ? '' : $language->getDefault();
|
||||||
|
$this->multilang_include_fallbacks = $this->config->get('system.languages.pages_fallback_only') || !empty($this->config->get('system.languages.content_fallback'));
|
||||||
|
|
||||||
|
$this->datetime_format = $this->config->get('plugins.sitemap.short_date_format') ? 'Y-m-d' : 'Y-m-d\TH:i:sP';
|
||||||
|
$this->include_change_freq = $this->config->get('plugins.sitemap.include_changefreq');
|
||||||
|
$this->default_change_freq = $this->config->get('plugins.sitemap.changefreq');
|
||||||
|
$this->include_priority = $this->config->get('plugins.sitemap.include_priority');
|
||||||
|
$this->default_priority = $this->config->get('plugins.sitemap.priority');
|
||||||
|
$this->ignores = (array) $this->config->get('plugins.sitemap.ignores');
|
||||||
|
$this->ignore_external = $this->config->get('plugins.sitemap.ignore_external');
|
||||||
|
$this->ignore_protected = $this->config->get('plugins.sitemap.ignore_protected');
|
||||||
|
$this->ignore_redirect = $this->config->get('plugins.sitemap.ignore_redirect');
|
||||||
|
|
||||||
|
// Gather data for all languages
|
||||||
|
foreach ($languages as $lang) {
|
||||||
|
$language->init();
|
||||||
|
$language->setActive($lang);
|
||||||
|
$pages->reset();
|
||||||
|
$this->addRouteData($pages, $lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset back to active language
|
||||||
|
if ($language->enabled() && $language->getActive() !== $active_lang) {
|
||||||
|
$language->init();
|
||||||
|
$language->setActive($active_lang);
|
||||||
|
$pages->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build sitemap
|
||||||
|
foreach ($languages as $lang) {
|
||||||
|
foreach($this->route_data as $route => $route_data) {
|
||||||
|
if ($data = $route_data[$lang] ?? null) {
|
||||||
|
$entry = new SitemapEntry();
|
||||||
|
$entry->setData($data);
|
||||||
|
if ($language->enabled()) {
|
||||||
|
foreach ($route_data as $l => $l_data) {
|
||||||
|
$entry->addHreflangs(['hreflang' => $l, 'href' => $l_data['location']]);
|
||||||
|
if ($include_default_lang === false && $l == $default_lang) {
|
||||||
|
$entry->addHreflangs(['hreflang' => 'x-default', 'href' => $l_data['location']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->sitemap[$data['url']] = $entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$additions = (array) $this->config->get('plugins.sitemap.additions');
|
||||||
|
foreach ($additions as $addition) {
|
||||||
|
if (isset($addition['location'])) {
|
||||||
|
$location = Utils::url($addition['location'], true);
|
||||||
|
$entry = new SitemapEntry($location,$addition['lastmod'] ?? null,$addition['changefreq'] ?? null, $addition['priority'] ?? null);
|
||||||
|
$this->sitemap[$location] = $entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$cache->save($cache_id, $this->sitemap);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->grav->fireEvent('onSitemapProcessed', new Event(['sitemap' => &$this->sitemap]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPageInitialized($event)
|
||||||
|
{
|
||||||
|
$page = $event['page'] ?? null;
|
||||||
|
$route = $this->config->get('plugins.sitemap.route');
|
||||||
|
$uri = $this->grav['uri'];
|
||||||
|
$html_support = $this->config->get('plugins.sitemap.html_support', false);
|
||||||
|
$extension = $this->grav['uri']->extension() ?? ($html_support ? 'html': 'xml');
|
||||||
|
|
||||||
|
if (is_null($page) || $uri->route() === $route || !empty($this->news_route)) {
|
||||||
|
|
||||||
|
// set a dummy page
|
||||||
|
$page = new Page;
|
||||||
|
$page->init(new \SplFileInfo(__DIR__ . '/pages/sitemap.md'));
|
||||||
|
$page->templateFormat($extension);
|
||||||
|
unset($this->grav['page']);
|
||||||
|
$this->grav['page'] = $page;
|
||||||
|
$twig = $this->grav['twig'];
|
||||||
|
|
||||||
|
if (!empty($this->news_route)) {
|
||||||
|
$header = $page->header();
|
||||||
|
$header->sitemap['news_route'] = $this->news_route;
|
||||||
|
$page->header($header);
|
||||||
|
$twig->template = "sitemap-news.$extension.twig";
|
||||||
|
} else {
|
||||||
|
$twig->template = "sitemap.$extension.twig";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access plugin events in this class
|
||||||
|
public function onTwigInitialized()
|
||||||
|
{
|
||||||
|
$this->grav['twig']->twig()->addFunction(
|
||||||
|
new TwigFunction('sort_sitemap_entries_by_language', [$this, 'sortSitemapEntriesByLanguage'])
|
||||||
|
);
|
||||||
|
$this->grav['twig']->twig()->addFunction(
|
||||||
|
new TwigFunction('timestamp_within_days', [$this, 'timestampWithinDays'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add current directory to twig lookup paths.
|
||||||
|
*/
|
||||||
|
public function onTwigTemplatePaths()
|
||||||
|
{
|
||||||
|
$this->grav['twig']->twig_paths[] = __DIR__ . '/templates';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set needed variables to display the sitemap.
|
||||||
|
*/
|
||||||
|
public function onTwigSiteVariables()
|
||||||
|
{
|
||||||
|
$twig = $this->grav['twig'];
|
||||||
|
$twig->twig_vars['sitemap'] = $this->sitemap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend page blueprints with feed configuration options.
|
||||||
|
*
|
||||||
|
* @param Event $event
|
||||||
|
*/
|
||||||
|
public function onBlueprintCreated(Event $event)
|
||||||
|
{
|
||||||
|
static $inEvent = false;
|
||||||
|
|
||||||
|
/** @var Data\Blueprint $blueprint */
|
||||||
|
$blueprint = $event['blueprint'];
|
||||||
|
if (!$inEvent && $blueprint->get('form/fields/tabs', null, '/')) {
|
||||||
|
if (!in_array($blueprint->getFilename(), array_keys($this->grav['pages']->modularTypes()))) {
|
||||||
|
$inEvent = true;
|
||||||
|
$blueprints = new Data\Blueprints(__DIR__ . '/blueprints/');
|
||||||
|
$extends = $blueprints->get('sitemap');
|
||||||
|
$blueprint->extend($extends, true);
|
||||||
|
$inEvent = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sortSitemapEntriesByLanguage()
|
||||||
|
{
|
||||||
|
$entries = [];
|
||||||
|
|
||||||
|
foreach ((array) $this->sitemap as $route => $entry) {
|
||||||
|
$lang = $entry->getLang();
|
||||||
|
unset($entry->hreflangs);
|
||||||
|
unset($entry->image);
|
||||||
|
if ($lang === null) {
|
||||||
|
$lang = $this->grav['language']->getDefault() ?: 'en';
|
||||||
|
}
|
||||||
|
$entries[$lang][$route] = $entry;
|
||||||
|
}
|
||||||
|
return $entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function timestampWithinDays(int $timestamp, int $days): bool
|
||||||
|
{
|
||||||
|
$now = time();
|
||||||
|
$days_ago = $now - ($days * 24 * 60 * 60);
|
||||||
|
return $timestamp >= $days_ago;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function addRouteData($pages, $lang)
|
||||||
|
{
|
||||||
|
$routes = array_unique($pages->routes());
|
||||||
|
ksort($routes);
|
||||||
|
|
||||||
|
foreach ($routes as $route => $path) {
|
||||||
|
/** @var PageInterface $page */
|
||||||
|
$page = $pages->get($path);
|
||||||
|
|
||||||
|
$rawroute = $page->rawRoute();
|
||||||
|
$header = $page->header();
|
||||||
|
$external_url = $this->ignore_external ? isset($header->external_url) : false;
|
||||||
|
$protected_page = $this->ignore_protected ? isset($header->access) : false;
|
||||||
|
$redirect_page = $this->ignore_redirect ? isset($header->redirect) : false;
|
||||||
|
$config_ignored = preg_match(sprintf("@^(%s)$@i", implode('|', $this->ignores)), $page->route());
|
||||||
|
$page_ignored = $protected_page || $external_url || $redirect_page || (isset($header->sitemap['ignore']) ? $header->sitemap['ignore'] : false);
|
||||||
|
|
||||||
|
if ($page->routable() && $page->published() && !$config_ignored && !$page_ignored) {
|
||||||
|
$page_languages = array_keys($page->translatedLanguages());
|
||||||
|
$include_lang = $this->multilang_skiplang_prefix !== $lang;
|
||||||
|
$location = $page->canonical($include_lang);
|
||||||
|
$url = $page->url(false, $include_lang);
|
||||||
|
$lastmod = !empty($header->sitemap['lastmod']) ? strtotime($header->sitemap['lastmod']) : $page->modified();
|
||||||
|
|
||||||
|
$lang_route = [
|
||||||
|
'title' => $page->title(),
|
||||||
|
'url' => $url,
|
||||||
|
'route' => $route,
|
||||||
|
'lang' => $lang,
|
||||||
|
'translated' => in_array($lang, $page_languages),
|
||||||
|
'location' => $location,
|
||||||
|
'lastmod' => date($this->datetime_format, $lastmod),
|
||||||
|
'longdate' => date('Y-m-d\TH:i:sP', $page->date()),
|
||||||
|
'shortdate' => date('Y-m-d', $page->date()),
|
||||||
|
'timestamp' => intval($page->date()),
|
||||||
|
'rawroute' => $page->rawRoute(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->include_change_freq) {
|
||||||
|
$lang_route['changefreq'] = $header->sitemap['changefreq'] ?? $this->default_change_freq;
|
||||||
|
}
|
||||||
|
if ($this->include_priority) {
|
||||||
|
$lang_route['priority'] = $header->sitemap['priority'] ?? $this->default_priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional add image
|
||||||
|
$images = $header->sitemap['images'] ?? $this->config->get('plugins.sitemap.images') ?? [];
|
||||||
|
|
||||||
|
if (isset($images)) {
|
||||||
|
foreach ($images as $image => $values) {
|
||||||
|
if (isset($values['loc'])) {
|
||||||
|
$images[$image]['loc'] = $page->media()[$values['loc']]->url();
|
||||||
|
} else {
|
||||||
|
unset($images[$image]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$lang_route['images'] = $images;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$this->route_data[$rawroute][$lang] = $lang_route;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
<xsl:stylesheet version="1.0"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:s="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||||
|
exclude-result-prefixes="s"
|
||||||
|
>
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="robots" content="noindex" />
|
||||||
|
<title>
|
||||||
|
XML Sitemap
|
||||||
|
</title>
|
||||||
|
<style type="text/css">
|
||||||
|
@import url('//cdn.jsdelivr.net/pure/0.6.0/base-min.css');
|
||||||
|
@import url('//cdn.jsdelivr.net/pure/0.6.0/pure-min.css');
|
||||||
|
@import url('//cdn.jsdelivr.net/pure/0.6.0/grids-responsive-min.css');
|
||||||
|
@import url('//fonts.googleapis.com/css?family=Raleway:100,300,400,700,900,100italic,300italic,400italic,700italic,900italic');
|
||||||
|
.font_smooth {
|
||||||
|
font-smooth: auto;
|
||||||
|
text-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
|
||||||
|
text-rendering: auto;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-webkit-text-size-adjust: 100%
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
font-smooth: auto;
|
||||||
|
text-shadow: 0 0 1px rgba(0, 0, 0, 0.2);
|
||||||
|
text-rendering: auto;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
background-color: #fff
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Raleway', sans-serif;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1.8em;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-align: left;
|
||||||
|
color: #333
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 20px
|
||||||
|
}
|
||||||
|
.clear {
|
||||||
|
clear: both;
|
||||||
|
float: none
|
||||||
|
}
|
||||||
|
a,
|
||||||
|
a:link,
|
||||||
|
a:visited {
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: dotted 1px #333;
|
||||||
|
color: #333
|
||||||
|
}
|
||||||
|
h1,h2,h3,h4,h5,h6 {
|
||||||
|
font-family: Raleway;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.2em;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
color: #000
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
border: solid 1px #cbcbcb !important;
|
||||||
|
text-align: center;
|
||||||
|
background: #fff
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="pure-table pure-table-striped" border="0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="5">Sitemap</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Location</th>
|
||||||
|
<th>Last Modified</th>
|
||||||
|
<th>Update Frequency</th>
|
||||||
|
<th>Priority</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tfoot>
|
||||||
|
</tfoot>
|
||||||
|
<tbody>
|
||||||
|
<xsl:for-each select="s:urlset/s:url">
|
||||||
|
<xsl:sort select="s:loc" />
|
||||||
|
<tr>
|
||||||
|
<xsl:variable name="loc"><xsl:value-of select="s:loc"/></xsl:variable>
|
||||||
|
<xsl:variable name="pno"><xsl:value-of select="position()"/></xsl:variable>
|
||||||
|
<td><xsl:value-of select="$pno"/></td>
|
||||||
|
<td><a href="{$loc}"><xsl:value-of select="s:loc"/></a></td>
|
||||||
|
<td><xsl:value-of select="s:lastmod"/></td>
|
||||||
|
<td><xsl:value-of select="s:changefreq"/></td>
|
||||||
|
<td><xsl:value-of select="s:priority"/></td>
|
||||||
|
</tr>
|
||||||
|
</xsl:for-each>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
enabled: true
|
||||||
|
route: '/sitemap'
|
||||||
|
ignore_external: true
|
||||||
|
ignore_protected: true
|
||||||
|
ignore_redirect: true
|
||||||
|
include_news_tags: false
|
||||||
|
standalone_sitemap_news: false
|
||||||
|
sitemap_news_path: '/sitemap-news.xml'
|
||||||
|
news_max_age_days: 2
|
||||||
|
news_enabled_paths:
|
||||||
|
- /blog
|
||||||
|
ignores:
|
||||||
|
whitelist:
|
||||||
|
xsl_transform: true
|
||||||
|
html_support: false
|
||||||
|
urlset: 'http://www.sitemaps.org/schemas/sitemap/0.9'
|
||||||
|
urlnewsset: 'http://www.google.com/schemas/sitemap-news/0.9'
|
||||||
|
urlimageset: 'http://www.google.com/schemas/sitemap-image/1.1'
|
||||||
|
short_date_format: true
|
||||||
|
include_changefreq: true
|
||||||
|
changefreq: daily
|
||||||
|
include_priority: true
|
||||||
|
priority: !!float 1
|
||||||
|
additions: []
|
||||||
|
multilang_enabled: true
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<image:image>
|
||||||
|
{% if image.loc %}
|
||||||
|
<image:loc>{{ url(image.loc, true) }}</image:loc>
|
||||||
|
{% endif %}
|
||||||
|
{% if image.caption %}
|
||||||
|
<image:caption>{{ image.caption }}</image:caption>
|
||||||
|
{% endif %}
|
||||||
|
{% if image.geoloc %}
|
||||||
|
<image:geo_location>{{ image.geoloc }}</image:geo_location>
|
||||||
|
{% endif %}
|
||||||
|
{% if image.title %}
|
||||||
|
<image:title>{{ image.title }}</image:title>
|
||||||
|
{% endif %}
|
||||||
|
{% if image.license %}
|
||||||
|
<image:license>{{ image.license }}</image:license>
|
||||||
|
{% endif %}
|
||||||
|
</image:image>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<news:news>
|
||||||
|
<news:publication>
|
||||||
|
<news:name>{{ site.title }}</news:name>
|
||||||
|
<news:language>{{ entry.lang }}</news:language>
|
||||||
|
</news:publication>
|
||||||
|
<news:publication_date>{{ entry.longdate }}</news:publication_date>
|
||||||
|
<news:title>{{ entry.title }}</news:title>
|
||||||
|
</news:news>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{% set sitemap_config = page.header.sitemap %}
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
{% if config.plugins.sitemap.xsl_transform %}
|
||||||
|
<?xml-stylesheet type="text/xsl" href="{{ uri.rootUrl }}/user/plugins/sitemap/sitemap-news.xsl"?>
|
||||||
|
{% endif %}
|
||||||
|
<urlset
|
||||||
|
xmlns="{{ config.plugins.sitemap.urlset }}"
|
||||||
|
xmlns:xhtml="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:news="{{ config.plugins.sitemap.urlnewsset }}">
|
||||||
|
{% for entry in sitemap %}
|
||||||
|
{% if timestamp_within_days(entry.timestamp, config.plugin.sitemap.news_max_age_days|default(2)) and
|
||||||
|
entry.rawroute|starts_with(sitemap_config.news_route ~ '/') %}
|
||||||
|
<url>
|
||||||
|
<loc>{{ entry.location|e }}</loc>
|
||||||
|
{% include 'sitemap-extensions/news.xml.twig' %}
|
||||||
|
</url>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</urlset>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{% extends 'partials/base.html.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% set language = grav.language %}
|
||||||
|
{% set lang = language.enabled ? (language.active ?: language.default) : null %}
|
||||||
|
{% set count = 1 %}
|
||||||
|
|
||||||
|
<table class="sitemap">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>{{ 'PLUGIN_SITEMAP.TITLE_TITLE'|t }}</th>
|
||||||
|
<th>{{ 'PLUGIN_SITEMAP.TITLE_LOCATION'|t }}</th>
|
||||||
|
<th>{{ 'PLUGIN_SITEMAP.TITLE_LASTMOD'|t }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for entry in sitemap %}
|
||||||
|
{% if lang is null or entry.lang == lang %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ count }}</td>
|
||||||
|
<td><a href="{{ entry.location }}">{{ entry.title ?: 'PLUGIN_SITEMAP.UNTITLED'|t }}</a></td>
|
||||||
|
<td><a href="{{ entry.location }}">{{ entry.location }}</a></td>
|
||||||
|
<td>{{ entry.lastmod }}</td>
|
||||||
|
</tr>
|
||||||
|
{% set count = count + 1 %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{{ (sort_sitemap_entries_by_language()|json_encode|raw) }}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
{% if config.plugins.sitemap.xsl_transform %}
|
||||||
|
<?xml-stylesheet type="text/xsl" href="{{ uri.rootUrl }}/user/plugins/sitemap/sitemap.xsl"?>
|
||||||
|
{% endif %}
|
||||||
|
<urlset
|
||||||
|
xmlns="{{ config.plugins.sitemap.urlset }}"
|
||||||
|
xmlns:xhtml="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:image="{{ config.plugins.sitemap.urlimageset }}"
|
||||||
|
{% if config.plugins.sitemap.include_news_tags %}
|
||||||
|
xmlns:news="{{ config.plugins.sitemap.urlnewsset }}"
|
||||||
|
{% endif %}>
|
||||||
|
{% for entry in sitemap %}
|
||||||
|
<url>
|
||||||
|
<loc>{{ entry.location|e }}</loc>
|
||||||
|
{% if config.plugins.sitemap.include_news_tags and
|
||||||
|
config.plugins.sitemap.standalone_sitemap_news == false and
|
||||||
|
timestamp_within_days(entry.timestamp, config.plugin.sitemap.news_max_age_days|default(2)) and
|
||||||
|
entry.rawroute|starts_with(config.plugins.sitemap.news_enabled_paths)
|
||||||
|
%}
|
||||||
|
{% include 'sitemap-extensions/news.xml.twig' %}
|
||||||
|
{% endif %}
|
||||||
|
{% for hreflang in entry.hreflangs %}
|
||||||
|
<xhtml:link rel="alternate" hreflang="{{ hreflang.hreflang }}" href="{{ hreflang.href }}" />
|
||||||
|
{% endfor %}
|
||||||
|
{% if entry.lastmod %}
|
||||||
|
<lastmod>{{ entry.lastmod }}</lastmod>
|
||||||
|
{% endif %}
|
||||||
|
{% if entry.changefreq %}
|
||||||
|
<changefreq>{{ entry.changefreq }}</changefreq>
|
||||||
|
{% endif %}
|
||||||
|
{% if entry.priority %}
|
||||||
|
<priority>{{ entry.priority|number_format(1) }}</priority>
|
||||||
|
{% endif %}
|
||||||
|
{% for image in entry.images %}
|
||||||
|
{% include 'sitemap-extensions/image.xml.twig' %}
|
||||||
|
{% endfor %}
|
||||||
|
</url>
|
||||||
|
{% endfor %}
|
||||||
|
</urlset>
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload.php @generated by Composer
|
||||||
|
|
||||||
|
require_once __DIR__ . '/composer/autoload_real.php';
|
||||||
|
|
||||||
|
return ComposerAutoloaderInit751b26473acbe012cea5482fdb145bdc::getLoader();
|
||||||
@@ -0,0 +1,445 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Autoload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||||
|
*
|
||||||
|
* $loader = new \Composer\Autoload\ClassLoader();
|
||||||
|
*
|
||||||
|
* // register classes with namespaces
|
||||||
|
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||||
|
* $loader->add('Symfony', __DIR__.'/framework');
|
||||||
|
*
|
||||||
|
* // activate the autoloader
|
||||||
|
* $loader->register();
|
||||||
|
*
|
||||||
|
* // to enable searching the include path (eg. for PEAR packages)
|
||||||
|
* $loader->setUseIncludePath(true);
|
||||||
|
*
|
||||||
|
* In this example, if you try to use a class in the Symfony\Component
|
||||||
|
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||||
|
* the autoloader will first look for the class under the component/
|
||||||
|
* directory, and it will then fallback to the framework/ directory if not
|
||||||
|
* found before giving up.
|
||||||
|
*
|
||||||
|
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
* @see https://www.php-fig.org/psr/psr-0/
|
||||||
|
* @see https://www.php-fig.org/psr/psr-4/
|
||||||
|
*/
|
||||||
|
class ClassLoader
|
||||||
|
{
|
||||||
|
// PSR-4
|
||||||
|
private $prefixLengthsPsr4 = array();
|
||||||
|
private $prefixDirsPsr4 = array();
|
||||||
|
private $fallbackDirsPsr4 = array();
|
||||||
|
|
||||||
|
// PSR-0
|
||||||
|
private $prefixesPsr0 = array();
|
||||||
|
private $fallbackDirsPsr0 = array();
|
||||||
|
|
||||||
|
private $useIncludePath = false;
|
||||||
|
private $classMap = array();
|
||||||
|
private $classMapAuthoritative = false;
|
||||||
|
private $missingClasses = array();
|
||||||
|
private $apcuPrefix;
|
||||||
|
|
||||||
|
public function getPrefixes()
|
||||||
|
{
|
||||||
|
if (!empty($this->prefixesPsr0)) {
|
||||||
|
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrefixesPsr4()
|
||||||
|
{
|
||||||
|
return $this->prefixDirsPsr4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFallbackDirs()
|
||||||
|
{
|
||||||
|
return $this->fallbackDirsPsr0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFallbackDirsPsr4()
|
||||||
|
{
|
||||||
|
return $this->fallbackDirsPsr4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassMap()
|
||||||
|
{
|
||||||
|
return $this->classMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $classMap Class to filename map
|
||||||
|
*/
|
||||||
|
public function addClassMap(array $classMap)
|
||||||
|
{
|
||||||
|
if ($this->classMap) {
|
||||||
|
$this->classMap = array_merge($this->classMap, $classMap);
|
||||||
|
} else {
|
||||||
|
$this->classMap = $classMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a set of PSR-0 directories for a given prefix, either
|
||||||
|
* appending or prepending to the ones previously set for this prefix.
|
||||||
|
*
|
||||||
|
* @param string $prefix The prefix
|
||||||
|
* @param array|string $paths The PSR-0 root directories
|
||||||
|
* @param bool $prepend Whether to prepend the directories
|
||||||
|
*/
|
||||||
|
public function add($prefix, $paths, $prepend = false)
|
||||||
|
{
|
||||||
|
if (!$prefix) {
|
||||||
|
if ($prepend) {
|
||||||
|
$this->fallbackDirsPsr0 = array_merge(
|
||||||
|
(array) $paths,
|
||||||
|
$this->fallbackDirsPsr0
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->fallbackDirsPsr0 = array_merge(
|
||||||
|
$this->fallbackDirsPsr0,
|
||||||
|
(array) $paths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$first = $prefix[0];
|
||||||
|
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||||
|
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($prepend) {
|
||||||
|
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||||
|
(array) $paths,
|
||||||
|
$this->prefixesPsr0[$first][$prefix]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||||
|
$this->prefixesPsr0[$first][$prefix],
|
||||||
|
(array) $paths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a set of PSR-4 directories for a given namespace, either
|
||||||
|
* appending or prepending to the ones previously set for this namespace.
|
||||||
|
*
|
||||||
|
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||||
|
* @param array|string $paths The PSR-4 base directories
|
||||||
|
* @param bool $prepend Whether to prepend the directories
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function addPsr4($prefix, $paths, $prepend = false)
|
||||||
|
{
|
||||||
|
if (!$prefix) {
|
||||||
|
// Register directories for the root namespace.
|
||||||
|
if ($prepend) {
|
||||||
|
$this->fallbackDirsPsr4 = array_merge(
|
||||||
|
(array) $paths,
|
||||||
|
$this->fallbackDirsPsr4
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->fallbackDirsPsr4 = array_merge(
|
||||||
|
$this->fallbackDirsPsr4,
|
||||||
|
(array) $paths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||||
|
// Register directories for a new namespace.
|
||||||
|
$length = strlen($prefix);
|
||||||
|
if ('\\' !== $prefix[$length - 1]) {
|
||||||
|
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||||
|
}
|
||||||
|
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||||
|
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||||
|
} elseif ($prepend) {
|
||||||
|
// Prepend directories for an already registered namespace.
|
||||||
|
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||||
|
(array) $paths,
|
||||||
|
$this->prefixDirsPsr4[$prefix]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Append directories for an already registered namespace.
|
||||||
|
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||||
|
$this->prefixDirsPsr4[$prefix],
|
||||||
|
(array) $paths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a set of PSR-0 directories for a given prefix,
|
||||||
|
* replacing any others previously set for this prefix.
|
||||||
|
*
|
||||||
|
* @param string $prefix The prefix
|
||||||
|
* @param array|string $paths The PSR-0 base directories
|
||||||
|
*/
|
||||||
|
public function set($prefix, $paths)
|
||||||
|
{
|
||||||
|
if (!$prefix) {
|
||||||
|
$this->fallbackDirsPsr0 = (array) $paths;
|
||||||
|
} else {
|
||||||
|
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a set of PSR-4 directories for a given namespace,
|
||||||
|
* replacing any others previously set for this namespace.
|
||||||
|
*
|
||||||
|
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||||
|
* @param array|string $paths The PSR-4 base directories
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function setPsr4($prefix, $paths)
|
||||||
|
{
|
||||||
|
if (!$prefix) {
|
||||||
|
$this->fallbackDirsPsr4 = (array) $paths;
|
||||||
|
} else {
|
||||||
|
$length = strlen($prefix);
|
||||||
|
if ('\\' !== $prefix[$length - 1]) {
|
||||||
|
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||||
|
}
|
||||||
|
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||||
|
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns on searching the include path for class files.
|
||||||
|
*
|
||||||
|
* @param bool $useIncludePath
|
||||||
|
*/
|
||||||
|
public function setUseIncludePath($useIncludePath)
|
||||||
|
{
|
||||||
|
$this->useIncludePath = $useIncludePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used to check if the autoloader uses the include path to check
|
||||||
|
* for classes.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getUseIncludePath()
|
||||||
|
{
|
||||||
|
return $this->useIncludePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns off searching the prefix and fallback directories for classes
|
||||||
|
* that have not been registered with the class map.
|
||||||
|
*
|
||||||
|
* @param bool $classMapAuthoritative
|
||||||
|
*/
|
||||||
|
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||||
|
{
|
||||||
|
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should class lookup fail if not found in the current class map?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isClassMapAuthoritative()
|
||||||
|
{
|
||||||
|
return $this->classMapAuthoritative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||||
|
*
|
||||||
|
* @param string|null $apcuPrefix
|
||||||
|
*/
|
||||||
|
public function setApcuPrefix($apcuPrefix)
|
||||||
|
{
|
||||||
|
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getApcuPrefix()
|
||||||
|
{
|
||||||
|
return $this->apcuPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers this instance as an autoloader.
|
||||||
|
*
|
||||||
|
* @param bool $prepend Whether to prepend the autoloader or not
|
||||||
|
*/
|
||||||
|
public function register($prepend = false)
|
||||||
|
{
|
||||||
|
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters this instance as an autoloader.
|
||||||
|
*/
|
||||||
|
public function unregister()
|
||||||
|
{
|
||||||
|
spl_autoload_unregister(array($this, 'loadClass'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the given class or interface.
|
||||||
|
*
|
||||||
|
* @param string $class The name of the class
|
||||||
|
* @return bool|null True if loaded, null otherwise
|
||||||
|
*/
|
||||||
|
public function loadClass($class)
|
||||||
|
{
|
||||||
|
if ($file = $this->findFile($class)) {
|
||||||
|
includeFile($file);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the path to the file where the class is defined.
|
||||||
|
*
|
||||||
|
* @param string $class The name of the class
|
||||||
|
*
|
||||||
|
* @return string|false The path if found, false otherwise
|
||||||
|
*/
|
||||||
|
public function findFile($class)
|
||||||
|
{
|
||||||
|
// class map lookup
|
||||||
|
if (isset($this->classMap[$class])) {
|
||||||
|
return $this->classMap[$class];
|
||||||
|
}
|
||||||
|
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (null !== $this->apcuPrefix) {
|
||||||
|
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||||
|
if ($hit) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $this->findFileWithExtension($class, '.php');
|
||||||
|
|
||||||
|
// Search for Hack files if we are running on HHVM
|
||||||
|
if (false === $file && defined('HHVM_VERSION')) {
|
||||||
|
$file = $this->findFileWithExtension($class, '.hh');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $this->apcuPrefix) {
|
||||||
|
apcu_add($this->apcuPrefix.$class, $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $file) {
|
||||||
|
// Remember that this class does not exist.
|
||||||
|
$this->missingClasses[$class] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findFileWithExtension($class, $ext)
|
||||||
|
{
|
||||||
|
// PSR-4 lookup
|
||||||
|
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||||
|
|
||||||
|
$first = $class[0];
|
||||||
|
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||||
|
$subPath = $class;
|
||||||
|
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||||
|
$subPath = substr($subPath, 0, $lastPos);
|
||||||
|
$search = $subPath . '\\';
|
||||||
|
if (isset($this->prefixDirsPsr4[$search])) {
|
||||||
|
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||||
|
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||||
|
if (file_exists($file = $dir . $pathEnd)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSR-4 fallback dirs
|
||||||
|
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||||
|
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSR-0 lookup
|
||||||
|
if (false !== $pos = strrpos($class, '\\')) {
|
||||||
|
// namespaced class name
|
||||||
|
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||||
|
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||||
|
} else {
|
||||||
|
// PEAR-like class name
|
||||||
|
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->prefixesPsr0[$first])) {
|
||||||
|
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||||
|
if (0 === strpos($class, $prefix)) {
|
||||||
|
foreach ($dirs as $dir) {
|
||||||
|
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSR-0 fallback dirs
|
||||||
|
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||||
|
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSR-0 include paths.
|
||||||
|
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope isolated include.
|
||||||
|
*
|
||||||
|
* Prevents access to $this/self from included files.
|
||||||
|
*/
|
||||||
|
function includeFile($file)
|
||||||
|
{
|
||||||
|
include $file;
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace Composer;
|
||||||
|
|
||||||
|
use Composer\Semver\VersionParser;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class InstalledVersions
|
||||||
|
{
|
||||||
|
private static $installed = array (
|
||||||
|
'root' =>
|
||||||
|
array (
|
||||||
|
'pretty_version' => 'dev-develop',
|
||||||
|
'version' => 'dev-develop',
|
||||||
|
'aliases' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'reference' => '47c51ab15542055b42183b14a29ed0b206e7a0f7',
|
||||||
|
'name' => 'trilbymedia/sitemap',
|
||||||
|
),
|
||||||
|
'versions' =>
|
||||||
|
array (
|
||||||
|
'trilbymedia/sitemap' =>
|
||||||
|
array (
|
||||||
|
'pretty_version' => 'dev-develop',
|
||||||
|
'version' => 'dev-develop',
|
||||||
|
'aliases' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'reference' => '47c51ab15542055b42183b14a29ed0b206e7a0f7',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getInstalledPackages()
|
||||||
|
{
|
||||||
|
return array_keys(self::$installed['versions']);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function isInstalled($packageName)
|
||||||
|
{
|
||||||
|
return isset(self::$installed['versions'][$packageName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||||
|
{
|
||||||
|
$constraint = $parser->parseConstraints($constraint);
|
||||||
|
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||||
|
|
||||||
|
return $provided->matches($constraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getVersionRanges($packageName)
|
||||||
|
{
|
||||||
|
if (!isset(self::$installed['versions'][$packageName])) {
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ranges = array();
|
||||||
|
if (isset(self::$installed['versions'][$packageName]['pretty_version'])) {
|
||||||
|
$ranges[] = self::$installed['versions'][$packageName]['pretty_version'];
|
||||||
|
}
|
||||||
|
if (array_key_exists('aliases', self::$installed['versions'][$packageName])) {
|
||||||
|
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['aliases']);
|
||||||
|
}
|
||||||
|
if (array_key_exists('replaced', self::$installed['versions'][$packageName])) {
|
||||||
|
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['replaced']);
|
||||||
|
}
|
||||||
|
if (array_key_exists('provided', self::$installed['versions'][$packageName])) {
|
||||||
|
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['provided']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(' || ', $ranges);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getVersion($packageName)
|
||||||
|
{
|
||||||
|
if (!isset(self::$installed['versions'][$packageName])) {
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset(self::$installed['versions'][$packageName]['version'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$installed['versions'][$packageName]['version'];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getPrettyVersion($packageName)
|
||||||
|
{
|
||||||
|
if (!isset(self::$installed['versions'][$packageName])) {
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset(self::$installed['versions'][$packageName]['pretty_version'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$installed['versions'][$packageName]['pretty_version'];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getReference($packageName)
|
||||||
|
{
|
||||||
|
if (!isset(self::$installed['versions'][$packageName])) {
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset(self::$installed['versions'][$packageName]['reference'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$installed['versions'][$packageName]['reference'];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getRootPackage()
|
||||||
|
{
|
||||||
|
return self::$installed['root'];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getRawData()
|
||||||
|
{
|
||||||
|
return self::$installed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function reload($data)
|
||||||
|
{
|
||||||
|
self::$installed = $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_classmap.php @generated by Composer
|
||||||
|
|
||||||
|
$vendorDir = dirname(dirname(__FILE__));
|
||||||
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||||
|
'Grav\\Plugin\\SitemapPlugin' => $baseDir . '/sitemap.php',
|
||||||
|
);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_namespaces.php @generated by Composer
|
||||||
|
|
||||||
|
$vendorDir = dirname(dirname(__FILE__));
|
||||||
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
);
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_psr4.php @generated by Composer
|
||||||
|
|
||||||
|
$vendorDir = dirname(dirname(__FILE__));
|
||||||
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'Grav\\Plugin\\Sitemap\\' => array($baseDir . '/classes'),
|
||||||
|
);
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_real.php @generated by Composer
|
||||||
|
|
||||||
|
class ComposerAutoloaderInit751b26473acbe012cea5482fdb145bdc
|
||||||
|
{
|
||||||
|
private static $loader;
|
||||||
|
|
||||||
|
public static function loadClassLoader($class)
|
||||||
|
{
|
||||||
|
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||||
|
require __DIR__ . '/ClassLoader.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Composer\Autoload\ClassLoader
|
||||||
|
*/
|
||||||
|
public static function getLoader()
|
||||||
|
{
|
||||||
|
if (null !== self::$loader) {
|
||||||
|
return self::$loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
require __DIR__ . '/platform_check.php';
|
||||||
|
|
||||||
|
spl_autoload_register(array('ComposerAutoloaderInit751b26473acbe012cea5482fdb145bdc', 'loadClassLoader'), true, true);
|
||||||
|
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||||
|
spl_autoload_unregister(array('ComposerAutoloaderInit751b26473acbe012cea5482fdb145bdc', 'loadClassLoader'));
|
||||||
|
|
||||||
|
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||||
|
if ($useStaticLoader) {
|
||||||
|
require __DIR__ . '/autoload_static.php';
|
||||||
|
|
||||||
|
call_user_func(\Composer\Autoload\ComposerStaticInit751b26473acbe012cea5482fdb145bdc::getInitializer($loader));
|
||||||
|
} else {
|
||||||
|
$map = require __DIR__ . '/autoload_namespaces.php';
|
||||||
|
foreach ($map as $namespace => $path) {
|
||||||
|
$loader->set($namespace, $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$map = require __DIR__ . '/autoload_psr4.php';
|
||||||
|
foreach ($map as $namespace => $path) {
|
||||||
|
$loader->setPsr4($namespace, $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||||
|
if ($classMap) {
|
||||||
|
$loader->addClassMap($classMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$loader->register(true);
|
||||||
|
|
||||||
|
return $loader;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_static.php @generated by Composer
|
||||||
|
|
||||||
|
namespace Composer\Autoload;
|
||||||
|
|
||||||
|
class ComposerStaticInit751b26473acbe012cea5482fdb145bdc
|
||||||
|
{
|
||||||
|
public static $prefixLengthsPsr4 = array (
|
||||||
|
'G' =>
|
||||||
|
array (
|
||||||
|
'Grav\\Plugin\\Sitemap\\' => 20,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
public static $prefixDirsPsr4 = array (
|
||||||
|
'Grav\\Plugin\\Sitemap\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/../..' . '/classes',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
public static $classMap = array (
|
||||||
|
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||||
|
'Grav\\Plugin\\SitemapPlugin' => __DIR__ . '/../..' . '/sitemap.php',
|
||||||
|
);
|
||||||
|
|
||||||
|
public static function getInitializer(ClassLoader $loader)
|
||||||
|
{
|
||||||
|
return \Closure::bind(function () use ($loader) {
|
||||||
|
$loader->prefixLengthsPsr4 = ComposerStaticInit751b26473acbe012cea5482fdb145bdc::$prefixLengthsPsr4;
|
||||||
|
$loader->prefixDirsPsr4 = ComposerStaticInit751b26473acbe012cea5482fdb145bdc::$prefixDirsPsr4;
|
||||||
|
$loader->classMap = ComposerStaticInit751b26473acbe012cea5482fdb145bdc::$classMap;
|
||||||
|
|
||||||
|
}, null, ClassLoader::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"packages": [],
|
||||||
|
"dev": true,
|
||||||
|
"dev-package-names": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php return array (
|
||||||
|
'root' =>
|
||||||
|
array (
|
||||||
|
'pretty_version' => 'dev-develop',
|
||||||
|
'version' => 'dev-develop',
|
||||||
|
'aliases' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'reference' => '47c51ab15542055b42183b14a29ed0b206e7a0f7',
|
||||||
|
'name' => 'trilbymedia/sitemap',
|
||||||
|
),
|
||||||
|
'versions' =>
|
||||||
|
array (
|
||||||
|
'trilbymedia/sitemap' =>
|
||||||
|
array (
|
||||||
|
'pretty_version' => 'dev-develop',
|
||||||
|
'version' => 'dev-develop',
|
||||||
|
'aliases' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'reference' => '47c51ab15542055b42183b14a29ed0b206e7a0f7',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// platform_check.php @generated by Composer
|
||||||
|
|
||||||
|
$issues = array();
|
||||||
|
|
||||||
|
if (!(PHP_VERSION_ID >= 70103)) {
|
||||||
|
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.3". You are running ' . PHP_VERSION . '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($issues) {
|
||||||
|
if (!headers_sent()) {
|
||||||
|
header('HTTP/1.1 500 Internal Server Error');
|
||||||
|
}
|
||||||
|
if (!ini_get('display_errors')) {
|
||||||
|
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||||
|
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
||||||
|
} elseif (!headers_sent()) {
|
||||||
|
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trigger_error(
|
||||||
|
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||||
|
E_USER_ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# v0.3.0
|
||||||
|
## 03/16/2026
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* JSON-LD / Schema.org structured data support (`WebSite`, `Article`, `WebPage`) injected automatically before `</head>`
|
||||||
|
* Per-page overrides via `social_meta` frontmatter block (title, description, type)
|
||||||
|
* `og:locale` support — detects active Grav language and maps to locale format (e.g. `en_US`)
|
||||||
|
* `og:image:width`, `og:image:height`, `og:image:alt`, `og:image:type` for richer image previews
|
||||||
|
* `article:published_time` and `article:modified_time` tags for article pages
|
||||||
|
* `og:type` is now dynamic: `website` for home page, `article` for all other pages
|
||||||
|
2. [](#improved)
|
||||||
|
* Fixed `og:sitename` typo — corrected to `og:site_name` per Open Graph spec
|
||||||
|
* `og:site_name` now uses `site.title` from Grav config instead of `$page->value('name')`
|
||||||
|
* Description and image resolution now traverses modular page children when parent has no content
|
||||||
|
* Added whitespace collapsing to `sanitizeMarkdowns()`
|
||||||
|
* Increased description truncation limit from 140 to 280 characters
|
||||||
|
|
||||||
|
# v0.2.1
|
||||||
|
## (unreleased)
|
||||||
|
|
||||||
|
1. [](#improved)
|
||||||
|
* Minor fixes
|
||||||
|
|
||||||
|
# v0.2.0
|
||||||
|
## 03/06/2017
|
||||||
|
|
||||||
|
1. [](#improved)
|
||||||
|
* Remove mandatory dependencies to AboutMe plugin for twitter:site integration
|
||||||
|
* Use summary for meta-tag description instead content for description
|
||||||
|
|
||||||
|
# v0.1.0
|
||||||
|
## 09/28/2016
|
||||||
|
|
||||||
|
1. [](#new)
|
||||||
|
* ChangeLog started...
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
|
||||||
|
A Grav CMS plugin that automatically injects Open Graph (Facebook) and Twitter Card meta tags into every page. No template changes are required — the plugin hooks into `onPageInitialized` and adds metadata programmatically.
|
||||||
|
|
||||||
|
## How the plugin works
|
||||||
|
|
||||||
|
The entry point is `social-meta-tags.php`. The plugin subscribes to `onPageInitialized` and calls two private methods:
|
||||||
|
|
||||||
|
- `getTwitterCardMetatags($meta)` — builds `twitter:*` tags; respects existing tags via `!isset()` checks, so page-level metadata overrides the plugin.
|
||||||
|
- `getFacebookMetatags($meta)` — builds `og:*` tags; always overwrites (no `!isset()` guard).
|
||||||
|
|
||||||
|
Both methods delegate to helpers for content resolution:
|
||||||
|
|
||||||
|
- `getPageDescription($page)` — returns `$page->summary()`, falling back recursively through `$page->children()` if empty (handles modular pages).
|
||||||
|
- `getPageImage($page)` — returns the first image from `$page->media()->images()`, falling back recursively through children.
|
||||||
|
- `sanitizeMarkdowns($text)` — strips Markdown syntax and truncates to 280 characters.
|
||||||
|
|
||||||
|
## Configuration files
|
||||||
|
|
||||||
|
- `social-meta-tags.yaml` — default config (Twitter enabled, Facebook disabled, empty `appid`).
|
||||||
|
- `blueprints.yaml` — Admin panel UI form definition. Field keys map directly to config paths (e.g. `social_pages.pages.twitter.enabled`).
|
||||||
|
- `languages.yaml` — i18n strings for the Admin panel UI (en, es, fr, de).
|
||||||
|
|
||||||
|
## Installation in a Grav site
|
||||||
|
|
||||||
|
Clone into the Grav plugins folder:
|
||||||
|
```
|
||||||
|
git clone https://github.com/tucho235/grav-plugin-social-meta-tags user/plugins/social-meta-tags
|
||||||
|
```
|
||||||
|
|
||||||
|
There is no build step, no composer, no npm. PHP files are loaded directly by Grav.
|
||||||
|
|
||||||
|
## Optional integrations
|
||||||
|
|
||||||
|
- **AboutMe plugin** (`grav-plugin-about-me` ≥ 1.1.4): when `twitter.aboutme: true`, the Twitter username is pulled from the AboutMe plugin config instead of `twitter.username`.
|
||||||
|
- **Facebook App ID**: required to claim admin rights on the Open Graph Facebook Page; configured via `social_pages.pages.facebook.appid`.
|
||||||
|
|
||||||
|
## Key config paths
|
||||||
|
|
||||||
|
| Config key | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `plugins.social-meta-tags.enabled` | Master on/off |
|
||||||
|
| `plugins.social-meta-tags.social_pages.pages.twitter.enabled` | Twitter Cards on/off |
|
||||||
|
| `plugins.social-meta-tags.social_pages.pages.twitter.type` | `summary` or `summary_large_image` |
|
||||||
|
| `plugins.social-meta-tags.social_pages.pages.twitter.aboutme` | Use AboutMe plugin for username |
|
||||||
|
| `plugins.social-meta-tags.social_pages.pages.twitter.username` | Fallback Twitter handle |
|
||||||
|
| `plugins.social-meta-tags.social_pages.pages.facebook.enabled` | Open Graph on/off |
|
||||||
|
| `plugins.social-meta-tags.social_pages.pages.facebook.appid` | Facebook App ID |
|
||||||
|
| `site.title` | Used as `og:site_name` value |
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Victor Rosset
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# Social Meta Tags Plugin
|
||||||
|
|
||||||
|
The **Social Meta Tags** Plugin is for [Grav CMS](http://github.com/getgrav/grav).
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Automatically injects social meta tags and structured data into every page — no template changes required. Supports Open Graph (used by Facebook, WhatsApp, Telegram, Discord, Slack, LinkedIn and others), X/Twitter Cards, and JSON-LD structured data for Google rich results.
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **[Open Graph](http://ogp.me/)** — full support including `og:image` dimensions, `og:locale`, `og:type`, and `article:published_time` / `article:modified_time`.
|
||||||
|
- **[X/Twitter Cards](https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards)** — Summary or Summary with Large Image.
|
||||||
|
- **[JSON-LD / Schema.org](https://schema.org/)** — structured data injected as `<script type="application/ld+json">` for Google rich results. Generates `WebSite`, `Article`, or `WebPage` depending on the page.
|
||||||
|
- **Modular pages** — automatically traverses child modules to find a description and image when the parent page has none.
|
||||||
|
- **Per-page overrides** — any page can override title, description, and type via frontmatter.
|
||||||
|
- **[AboutMe plugin](https://github.com/Birssan/grav-plugin-about-me)** integration for Twitter username (min version 1.1.4).
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Via Grav Admin
|
||||||
|
|
||||||
|
Search for **Social Meta Tags** in the Admin panel under Plugins and install it directly.
|
||||||
|
|
||||||
|
### Via GPM
|
||||||
|
|
||||||
|
```
|
||||||
|
bin/gpm install social-meta-tags
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/tucho235/grav-plugin-social-meta-tags user/plugins/social-meta-tags
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Enable the plugin from the Admin panel or in `user/config/plugins/social-meta-tags.yaml`. No template changes are needed.
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Per-page overrides
|
||||||
|
|
||||||
|
Any page can override the values used for social tags by adding a `social_meta` block to its frontmatter:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
title: My Page
|
||||||
|
social_meta:
|
||||||
|
title: "Custom title for social sharing"
|
||||||
|
description: "Custom description for social sharing"
|
||||||
|
type: "website"
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
Valid values for `type`: `article`, `website`, `webPage` (controls both `og:type` and the JSON-LD `@type`). The default is `website` for the home page and `article` for all other pages.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Twitter / X account
|
||||||
|
|
||||||
|
Social Meta Tags can pull the Twitter username from the [AboutMe plugin](https://github.com/Birssan/grav-plugin-about-me) (min version 1.1.4). Enable the option in the plugin settings and configure your profile in AboutMe.
|
||||||
|
|
||||||
|
Alternatively, set your username directly in the plugin settings without using AboutMe.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Facebook / Open Graph
|
||||||
|
|
||||||
|
To claim admin rights on your Open Graph Facebook page you need a Facebook App ID. Generate one at the [Facebook Developers page](https://developers.facebook.com/apps) and set it in the plugin configuration. Without it, Open Graph will still work but you will lose admin rights on the Facebook page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### JSON-LD
|
||||||
|
|
||||||
|
Enabled by default. Injects a `<script type="application/ld+json">` block before `</head>` on every page. The structured data type is chosen automatically:
|
||||||
|
|
||||||
|
| Page | JSON-LD `@type` |
|
||||||
|
|---|---|
|
||||||
|
| Home page | `WebSite` |
|
||||||
|
| Regular pages | `Article` (includes `datePublished`, `dateModified`, `publisher`) |
|
||||||
|
| Custom via frontmatter `social_meta.type` | As specified |
|
||||||
|
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
If you think any implementation is not the best, feel free to submit ideas and pull requests. All comments and suggestions are welcome.
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
name: Social Meta Tags
|
||||||
|
version: 0.3.0
|
||||||
|
description: Facebook Open Graph and Twitter Cards Meta Tags
|
||||||
|
icon: code
|
||||||
|
author:
|
||||||
|
name: Victor Rosset
|
||||||
|
email: victorrosset@gmail.com
|
||||||
|
homepage: https://github.com/tucho235/grav-plugin-social-meta-tags
|
||||||
|
demo: https://github.com/tucho235/grav-plugin-social-meta-tags
|
||||||
|
keywords: grav, plugin, facebook, open graph, twitter cards, google
|
||||||
|
bugs: https://github.com/tucho235/grav-plugin-social-meta-tags/issues
|
||||||
|
docs: https://github.com/tucho235/grav-plugin-social-meta-tags/blob/develop/README.md
|
||||||
|
license: MIT
|
||||||
|
|
||||||
|
form:
|
||||||
|
validation: strict
|
||||||
|
fields:
|
||||||
|
social_pages_types:
|
||||||
|
type: tabs
|
||||||
|
active: 1
|
||||||
|
title: Pages
|
||||||
|
fields:
|
||||||
|
plugin:
|
||||||
|
type: tab
|
||||||
|
title: Plugin
|
||||||
|
fields:
|
||||||
|
enabled:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGINS.SOCIAL_META_TAGS.PLUGIN_STATUS
|
||||||
|
highlight: 1
|
||||||
|
default: 0
|
||||||
|
options:
|
||||||
|
1: Enabled
|
||||||
|
0: Disabled
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
twitter:
|
||||||
|
type: tab
|
||||||
|
title: PLUGINS.SOCIAL_META_TAGS.TWITTER.NAME
|
||||||
|
fields:
|
||||||
|
social_pages.pages.twitter.enabled:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGINS.SOCIAL_META_TAGS.TWITTER.ENABLED
|
||||||
|
highlight: 1
|
||||||
|
default: 0
|
||||||
|
options:
|
||||||
|
1: PLUGINS.SOCIAL_META_TAGS.YES
|
||||||
|
0: PLUGINS.SOCIAL_META_TAGS.NO
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
social_pages.pages.twitter.type:
|
||||||
|
type: select
|
||||||
|
size: medium
|
||||||
|
label: PLUGINS.SOCIAL_META_TAGS.TWITTER.TYPE
|
||||||
|
help: PLUGINS.SOCIAL_META_TAGS.TWITTER.TYPE_HELP
|
||||||
|
default: summary_large_image
|
||||||
|
options:
|
||||||
|
summary_large_image: PLUGINS.SOCIAL_META_TAGS.TWITTER.TYPE_CARDS.LARGE
|
||||||
|
summary: PLUGINS.SOCIAL_META_TAGS.TWITTER.TYPE_CARDS.SUMMARY
|
||||||
|
social_pages.pages.twitter.aboutme:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGINS.SOCIAL_META_TAGS.TWITTER.ABOUTME
|
||||||
|
highlight: 1
|
||||||
|
default: 0
|
||||||
|
options:
|
||||||
|
1: PLUGINS.SOCIAL_META_TAGS.YES
|
||||||
|
0: PLUGINS.SOCIAL_META_TAGS.NO
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
social_pages.pages.twitter.username:
|
||||||
|
type: text
|
||||||
|
label: PLUGINS.SOCIAL_META_TAGS.TWITTER.USERNAME
|
||||||
|
help: PLUGINS.SOCIAL_META_TAGS.TWITTER.USERNAME_HELP
|
||||||
|
size: small
|
||||||
|
placeholder: '@'
|
||||||
|
facebook:
|
||||||
|
type: tab
|
||||||
|
title: PLUGINS.SOCIAL_META_TAGS.FACEBOOK.NAME
|
||||||
|
fields:
|
||||||
|
social_pages.pages.facebook.enabled:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGINS.SOCIAL_META_TAGS.FACEBOOK.ENABLED
|
||||||
|
highlight: 1
|
||||||
|
default: 0
|
||||||
|
options:
|
||||||
|
1: PLUGINS.SOCIAL_META_TAGS.YES
|
||||||
|
0: PLUGINS.SOCIAL_META_TAGS.NO
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
|
social_pages.pages.facebook.appid:
|
||||||
|
type: text
|
||||||
|
label: PLUGINS.SOCIAL_META_TAGS.FACEBOOK.APPID
|
||||||
|
help: PLUGINS.SOCIAL_META_TAGS.FACEBOOK.APPID_HELP
|
||||||
|
size: small
|
||||||
|
jsonld:
|
||||||
|
type: tab
|
||||||
|
title: PLUGINS.SOCIAL_META_TAGS.JSONLD.NAME
|
||||||
|
fields:
|
||||||
|
jsonld.enabled:
|
||||||
|
type: toggle
|
||||||
|
label: PLUGINS.SOCIAL_META_TAGS.JSONLD.ENABLED
|
||||||
|
highlight: 1
|
||||||
|
default: 1
|
||||||
|
options:
|
||||||
|
1: PLUGINS.SOCIAL_META_TAGS.YES
|
||||||
|
0: PLUGINS.SOCIAL_META_TAGS.NO
|
||||||
|
validate:
|
||||||
|
type: bool
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
# English
|
||||||
|
en:
|
||||||
|
PLUGINS:
|
||||||
|
SOCIAL_META_TAGS:
|
||||||
|
PLUGIN_NAME: 'Social Meta Tags'
|
||||||
|
PLUGIN_STATUS: 'Plugin status'
|
||||||
|
PLUGIN_ACTIVE: 'Active'
|
||||||
|
PLUGIN_INACTIVE: 'Inactive'
|
||||||
|
PLUGIN_ACTIVE_HELP: 'This option is used to (de-)activate this plugin on a per-page basis.'
|
||||||
|
YES: 'Yes'
|
||||||
|
NO: 'No'
|
||||||
|
|
||||||
|
TWITTER:
|
||||||
|
NAME: 'Twitter Cards'
|
||||||
|
ENABLED: 'Twitter Cards active'
|
||||||
|
HELP: 'Share with Twitter Cards'
|
||||||
|
TYPE: 'Card Type'
|
||||||
|
TYPE_HELP: 'Summary Card with Large Image or Short Summary'
|
||||||
|
TYPE_CARDS:
|
||||||
|
LARGE: 'Summary Card with Large Image'
|
||||||
|
SUMMARY: 'Summary Card'
|
||||||
|
ABOUTME: 'Use AboutMe Plugin'
|
||||||
|
USERNAME: 'Username'
|
||||||
|
USERNAME_HELP: 'If you not use AboutMe plugin, please define your twitter username (@username)'
|
||||||
|
|
||||||
|
FACEBOOK:
|
||||||
|
NAME: 'Facebook Open Graph'
|
||||||
|
ENABLED: 'Facebook Open Graph active'
|
||||||
|
HELP: 'Share on Facebook'
|
||||||
|
APPID: 'App ID'
|
||||||
|
APPID_HELP: 'The App ID you get from the Facebook Developers page (https://developers.facebook.com/apps).'
|
||||||
|
|
||||||
|
JSONLD:
|
||||||
|
NAME: 'JSON-LD / Schema.org'
|
||||||
|
ENABLED: 'JSON-LD structured data active'
|
||||||
|
HELP: 'Inject Schema.org structured data for Google rich results'
|
||||||
|
|
||||||
|
# Español
|
||||||
|
es:
|
||||||
|
PLUGINS:
|
||||||
|
SOCIAL_META_TAGS:
|
||||||
|
PLUGIN_NAME: 'Social Meta Tags'
|
||||||
|
PLUGIN_STATUS: 'Plugin status'
|
||||||
|
PLUGIN_ACTIVE: 'Activo'
|
||||||
|
PLUGIN_INACTIVE: 'Inactivo'
|
||||||
|
PLUGIN_ACTIVE_HELP: 'Esta opción es usara para activar/desactivar el plugin'
|
||||||
|
YES: 'Si'
|
||||||
|
NO: 'No'
|
||||||
|
|
||||||
|
TWITTER:
|
||||||
|
NAME: 'Twitter Cards'
|
||||||
|
ENABLED: 'Twitter Cards activo'
|
||||||
|
HELP: 'Comparte en Twitter'
|
||||||
|
TYPE: 'Tipo de Twitter Card'
|
||||||
|
TYPE_HELP: 'Tarjetas de Resumen con o sin imagen grande'
|
||||||
|
TYPE_CARDS:
|
||||||
|
LARGE: 'Tarjetas de Resumen con imagen grande'
|
||||||
|
SUMMARY: 'Tarjetas de Resumen'
|
||||||
|
ABOUTME: 'Utilizar el plugin aboutMe'
|
||||||
|
USERNAME: 'Nombre del usuario'
|
||||||
|
USERNAME_HELP: 'Si no se utiliza aboutMe plugin, por favor, defina el nombre de usuario de Twitter'
|
||||||
|
|
||||||
|
FACEBOOK:
|
||||||
|
NAME: 'Facebook Open Graph'
|
||||||
|
ENABLED: 'Facebook Open Graph activo'
|
||||||
|
HELP: 'Comparte en Facebook'
|
||||||
|
APPID: 'App ID'
|
||||||
|
APPID_HELP: 'El ID de la aplicación que obtiene de la página de desarrolladores de Facebook (https://developers.facebook.com/apps).'
|
||||||
|
|
||||||
|
JSONLD:
|
||||||
|
NAME: 'JSON-LD / Schema.org'
|
||||||
|
ENABLED: 'Datos estructurados JSON-LD activo'
|
||||||
|
HELP: 'Inyecta datos estructurados Schema.org para resultados enriquecidos de Google'
|
||||||
|
|
||||||
|
# French
|
||||||
|
fr:
|
||||||
|
PLUGINS:
|
||||||
|
SOCIAL_META_TAGS:
|
||||||
|
PLUGIN_NAME: 'Social Meta Tags'
|
||||||
|
PLUGIN_STATUS: 'Statut du plugin'
|
||||||
|
PLUGIN_ACTIVE: 'Actif'
|
||||||
|
PLUGIN_INACTIVE: 'Inactif'
|
||||||
|
PLUGIN_ACTIVE_HELP: 'Cette option permet d’activer ou non le plugin sur une base par page.'
|
||||||
|
YES: 'Oui'
|
||||||
|
NO: 'Non'
|
||||||
|
|
||||||
|
TWITTER:
|
||||||
|
NAME: 'Cartes Twitter'
|
||||||
|
ENABLED: 'Cartes Twitter activées'
|
||||||
|
HELP: 'Partager avec les cartes Twitter'
|
||||||
|
TYPE: 'Type de carte'
|
||||||
|
TYPE_HELP: 'Carte de résumé avec grande image ou résumé court'
|
||||||
|
TYPE_CARDS:
|
||||||
|
LARGE: 'Carte de résumé avec grande image'
|
||||||
|
SUMMARY: 'Carte de résumé'
|
||||||
|
ABOUTME: 'Utiliser le plugin AboutMe'
|
||||||
|
USERNAME: 'Nom d''utilisateur'
|
||||||
|
USERNAME_HELP: 'Si le plugin AboutMe n''est pas utilisé, veuillez définir le nom d''utilisateur Twitter SVP'
|
||||||
|
|
||||||
|
FACEBOOK:
|
||||||
|
NAME: ‘Facebook Open Graph’
|
||||||
|
ENABLED: ‘Facebook Open Graph activé’
|
||||||
|
HELP: ‘Partager sur Facebook’
|
||||||
|
APPID: ‘ID d’application’
|
||||||
|
APPID_HELP: ‘L’ID d’application pour se connecter à la page Développeurs Facebook (https://developers.facebook.com/apps).’
|
||||||
|
|
||||||
|
JSONLD:
|
||||||
|
NAME: ‘JSON-LD / Schema.org’
|
||||||
|
ENABLED: ‘Données structurées JSON-LD activées’
|
||||||
|
HELP: ‘Injecter des données structurées Schema.org pour les résultats enrichis Google’
|
||||||
|
|
||||||
|
# German
|
||||||
|
de:
|
||||||
|
PLUGINS:
|
||||||
|
SOCIAL_META_TAGS:
|
||||||
|
PLUGIN_NAME: 'Social Meta Tags'
|
||||||
|
PLUGIN_STATUS: 'Plugin Status'
|
||||||
|
PLUGIN_ACTIVE: 'Aktiviert'
|
||||||
|
PLUGIN_INACTIVE: 'Deaktiviert'
|
||||||
|
PLUGIN_ACTIVE_HELP: 'Diese Option dient dem seitenbasierten Aktivieren/Deaktivieren des Plugins.'
|
||||||
|
YES: 'Ja'
|
||||||
|
NO: 'Nein'
|
||||||
|
|
||||||
|
TWITTER:
|
||||||
|
NAME: 'Twitter Cards'
|
||||||
|
ENABLED: 'Twitter Cards aktiviert'
|
||||||
|
HELP: 'Teile mit Twitter Cards'
|
||||||
|
TYPE: 'Karten-Typ'
|
||||||
|
TYPE_HELP: 'Karte mit einer Zusammenfassung und großem Bild oder nur einer Zusammenfassung'
|
||||||
|
TYPE_CARDS:
|
||||||
|
LARGE: 'Karte mit einer Zusammenfassung und großem Bild'
|
||||||
|
SUMMARY: 'Karte mit einer Zusammenfassung'
|
||||||
|
ABOUTME: 'Verwenden Sie das Plugin AboutMe'
|
||||||
|
USERNAME: 'Benutzername'
|
||||||
|
USERNAME_HELP: 'Wenn AboutMe Plugin nicht verwendet wird, setzen Sie bitte die Twitter-Benutzernamen bitte'
|
||||||
|
|
||||||
|
FACEBOOK:
|
||||||
|
NAME: 'Facebook Open Graph'
|
||||||
|
ENABLED: 'Facebook Open Graph aktiviert'
|
||||||
|
HELP: 'Teile auf Facebook'
|
||||||
|
APPID: 'App ID'
|
||||||
|
APPID_HELP: 'Dies ist die ID-Nummer, die auf der Facebook Entwickler-Website erhältlich ist (https://developers.facebook.com/apps).'
|
||||||
|
|
||||||
|
JSONLD:
|
||||||
|
NAME: 'JSON-LD / Schema.org'
|
||||||
|
ENABLED: 'JSON-LD strukturierte Daten aktiviert'
|
||||||
|
HELP: 'Schema.org strukturierte Daten für Google Rich Results einfügen'
|
||||||
@@ -0,0 +1,361 @@
|
|||||||
|
<?php
|
||||||
|
namespace Grav\Plugin;
|
||||||
|
|
||||||
|
use Grav\Common\Plugin;
|
||||||
|
use RocketTheme\Toolbox\Event\Event;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SocialMetaTagsPlugin
|
||||||
|
* @package Grav\Plugin
|
||||||
|
*/
|
||||||
|
class SocialMetaTagsPlugin extends Plugin
|
||||||
|
{
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'onPluginsInitialized' => ['onPluginsInitialized', 0]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPluginsInitialized()
|
||||||
|
{
|
||||||
|
if (!$this->isAdmin() and $this->config->get('plugins.social-meta-tags.enabled')) {
|
||||||
|
$this->enable([
|
||||||
|
'onPageInitialized' => ['onPageInitialized', 0],
|
||||||
|
'onOutputGenerated' => ['onOutputGenerated', 0],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPageInitialized(Event $e)
|
||||||
|
{
|
||||||
|
$page = $this->grav['page'];
|
||||||
|
$meta = $page->metadata(null);
|
||||||
|
$meta = $this->getTwitterCardMetatags($meta);
|
||||||
|
$meta = $this->getFacebookMetatags($meta);
|
||||||
|
$page->metadata($meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onOutputGenerated()
|
||||||
|
{
|
||||||
|
if (!$this->grav['config']->get('plugins.social-meta-tags.jsonld.enabled')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$page = $this->grav['page'];
|
||||||
|
$jsonLd = $this->buildJsonLd($page);
|
||||||
|
if ($jsonLd) {
|
||||||
|
$script = "\n" . '<script type="application/ld+json">' . $jsonLd . '</script>';
|
||||||
|
$this->grav->output = str_replace('</head>', $script . "\n</head>", $this->grav->output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getTwitterCardMetatags($meta)
|
||||||
|
{
|
||||||
|
if (!$this->grav['config']->get('plugins.social-meta-tags.social_pages.pages.twitter.enabled')) {
|
||||||
|
return $meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = $this->grav['page'];
|
||||||
|
|
||||||
|
if (!isset($meta['twitter:card'])) {
|
||||||
|
$meta['twitter:card']['name'] = 'twitter:card';
|
||||||
|
$meta['twitter:card']['property'] = 'twitter:card';
|
||||||
|
$meta['twitter:card']['content'] = $this->grav['config']->get('plugins.social-meta-tags.social_pages.pages.twitter.type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($meta['twitter:title'])) {
|
||||||
|
$meta['twitter:title']['name'] = 'twitter:title';
|
||||||
|
$meta['twitter:title']['property'] = 'twitter:title';
|
||||||
|
$meta['twitter:title']['content'] = $this->sanitizeMarkdowns(
|
||||||
|
$this->getHeaderValue($page, 'title') ?? $page->title()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($meta['twitter:description'])) {
|
||||||
|
$desc = $this->getHeaderValue($page, 'description') ?? $this->getPageDescription($page);
|
||||||
|
$meta['twitter:description']['name'] = 'twitter:description';
|
||||||
|
$meta['twitter:description']['property'] = 'twitter:description';
|
||||||
|
$meta['twitter:description']['content'] = $this->sanitizeMarkdowns(strip_tags($desc));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($meta['twitter:image'])) {
|
||||||
|
$image = $this->getPageImage($page);
|
||||||
|
if ($image !== null) {
|
||||||
|
$meta['twitter:image']['name'] = 'twitter:image';
|
||||||
|
$meta['twitter:image']['property'] = 'twitter:image';
|
||||||
|
$meta['twitter:image']['content'] = $this->grav['uri']->base() . $image->url();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($meta['twitter:site'])) {
|
||||||
|
if ($this->grav['config']->get('plugins.social-meta-tags.social_pages.pages.twitter.aboutme')) {
|
||||||
|
if ($this->grav['config']->get('plugins.aboutme.social_pages.enabled')
|
||||||
|
and $this->grav['config']->get('plugins.aboutme.social_pages.pages.twitter.url')) {
|
||||||
|
$user = preg_replace('((http|https)://twitter.com/)', '@', $this->grav['config']->get('plugins.aboutme.social_pages.pages.twitter.url'));
|
||||||
|
} else {
|
||||||
|
$user = '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$user = '@' . $this->grav['config']->get('plugins.social-meta-tags.social_pages.pages.twitter.username');
|
||||||
|
}
|
||||||
|
$meta['twitter:site']['name'] = 'twitter:site';
|
||||||
|
$meta['twitter:site']['property'] = 'twitter:site';
|
||||||
|
$meta['twitter:site']['content'] = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFacebookMetatags($meta)
|
||||||
|
{
|
||||||
|
if (!$this->grav['config']->get('plugins.social-meta-tags.social_pages.pages.facebook.enabled')) {
|
||||||
|
return $meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = $this->grav['page'];
|
||||||
|
$type = $this->getPageType($page);
|
||||||
|
$image = $this->getPageImage($page);
|
||||||
|
|
||||||
|
$meta['og:site_name']['name'] = 'og:site_name';
|
||||||
|
$meta['og:site_name']['property'] = 'og:site_name';
|
||||||
|
$meta['og:site_name']['content'] = $this->grav['config']->get('site.title');
|
||||||
|
|
||||||
|
$meta['og:title']['name'] = 'og:title';
|
||||||
|
$meta['og:title']['property'] = 'og:title';
|
||||||
|
$meta['og:title']['content'] = $this->sanitizeMarkdowns(
|
||||||
|
$this->getHeaderValue($page, 'title') ?? $page->title()
|
||||||
|
);
|
||||||
|
|
||||||
|
$meta['og:description']['name'] = 'og:description';
|
||||||
|
$meta['og:description']['property'] = 'og:description';
|
||||||
|
$meta['og:description']['content'] = $this->sanitizeMarkdowns(strip_tags(
|
||||||
|
$this->getHeaderValue($page, 'description') ?? $this->getPageDescription($page)
|
||||||
|
));
|
||||||
|
|
||||||
|
$meta['og:type']['name'] = 'og:type';
|
||||||
|
$meta['og:type']['property'] = 'og:type';
|
||||||
|
$meta['og:type']['content'] = $type;
|
||||||
|
|
||||||
|
$meta['og:url']['name'] = 'og:url';
|
||||||
|
$meta['og:url']['property'] = 'og:url';
|
||||||
|
$meta['og:url']['content'] = $this->grav['uri']->url(true);
|
||||||
|
|
||||||
|
$locale = $this->getPageLocale();
|
||||||
|
if ($locale) {
|
||||||
|
$meta['og:locale']['name'] = 'og:locale';
|
||||||
|
$meta['og:locale']['property'] = 'og:locale';
|
||||||
|
$meta['og:locale']['content'] = $locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($image !== null) {
|
||||||
|
$meta['og:image']['name'] = 'og:image';
|
||||||
|
$meta['og:image']['property'] = 'og:image';
|
||||||
|
$meta['og:image']['content'] = $this->grav['uri']->base() . $image->url();
|
||||||
|
|
||||||
|
$width = $image->get('width');
|
||||||
|
$height = $image->get('height');
|
||||||
|
|
||||||
|
if ($width) {
|
||||||
|
$meta['og:image:width']['name'] = 'og:image:width';
|
||||||
|
$meta['og:image:width']['property'] = 'og:image:width';
|
||||||
|
$meta['og:image:width']['content'] = $width;
|
||||||
|
}
|
||||||
|
if ($height) {
|
||||||
|
$meta['og:image:height']['name'] = 'og:image:height';
|
||||||
|
$meta['og:image:height']['property'] = 'og:image:height';
|
||||||
|
$meta['og:image:height']['content'] = $height;
|
||||||
|
}
|
||||||
|
|
||||||
|
$meta['og:image:alt']['name'] = 'og:image:alt';
|
||||||
|
$meta['og:image:alt']['property'] = 'og:image:alt';
|
||||||
|
$meta['og:image:alt']['content'] = $page->title();
|
||||||
|
|
||||||
|
$ext = strtolower(pathinfo($image->url(), PATHINFO_EXTENSION));
|
||||||
|
$mimeMap = [
|
||||||
|
'jpg' => 'image/jpeg',
|
||||||
|
'jpeg' => 'image/jpeg',
|
||||||
|
'png' => 'image/png',
|
||||||
|
'gif' => 'image/gif',
|
||||||
|
'webp' => 'image/webp',
|
||||||
|
];
|
||||||
|
if (isset($mimeMap[$ext])) {
|
||||||
|
$meta['og:image:type']['name'] = 'og:image:type';
|
||||||
|
$meta['og:image:type']['property'] = 'og:image:type';
|
||||||
|
$meta['og:image:type']['content'] = $mimeMap[$ext];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type === 'article') {
|
||||||
|
if ($page->date()) {
|
||||||
|
$meta['article:published_time']['name'] = 'article:published_time';
|
||||||
|
$meta['article:published_time']['property'] = 'article:published_time';
|
||||||
|
$meta['article:published_time']['content'] = date('c', $page->date());
|
||||||
|
}
|
||||||
|
if ($page->modified()) {
|
||||||
|
$meta['article:modified_time']['name'] = 'article:modified_time';
|
||||||
|
$meta['article:modified_time']['property'] = 'article:modified_time';
|
||||||
|
$meta['article:modified_time']['content'] = date('c', $page->modified());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$meta['fb:app_id']['name'] = 'fb:app_id';
|
||||||
|
$meta['fb:app_id']['property'] = 'fb:app_id';
|
||||||
|
$meta['fb:app_id']['content'] = $this->grav['config']->get('plugins.social-meta-tags.social_pages.pages.facebook.appid');
|
||||||
|
|
||||||
|
return $meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildJsonLd($page)
|
||||||
|
{
|
||||||
|
$type = $this->getPageType($page);
|
||||||
|
$title = $this->getHeaderValue($page, 'title') ?? $page->title();
|
||||||
|
$desc = strip_tags($this->getHeaderValue($page, 'description') ?? $this->getPageDescription($page));
|
||||||
|
$url = $this->grav['uri']->url(true);
|
||||||
|
$siteTitle = $this->grav['config']->get('site.title');
|
||||||
|
$image = $this->getPageImage($page);
|
||||||
|
$imageUrl = $image ? $this->grav['uri']->base() . $image->url() : null;
|
||||||
|
|
||||||
|
if ($type === 'website') {
|
||||||
|
$data = [
|
||||||
|
'@context' => 'https://schema.org',
|
||||||
|
'@type' => 'WebSite',
|
||||||
|
'name' => $siteTitle,
|
||||||
|
'url' => $url,
|
||||||
|
];
|
||||||
|
} elseif ($type === 'article') {
|
||||||
|
$data = [
|
||||||
|
'@context' => 'https://schema.org',
|
||||||
|
'@type' => 'Article',
|
||||||
|
'headline' => $title,
|
||||||
|
'url' => $url,
|
||||||
|
'publisher' => [
|
||||||
|
'@type' => 'Organization',
|
||||||
|
'name' => $siteTitle,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
if ($desc) $data['description'] = $desc;
|
||||||
|
if ($imageUrl) $data['image'] = $imageUrl;
|
||||||
|
if ($page->date()) $data['datePublished'] = date('c', $page->date());
|
||||||
|
if ($page->modified()) $data['dateModified'] = date('c', $page->modified());
|
||||||
|
} else {
|
||||||
|
$data = [
|
||||||
|
'@context' => 'https://schema.org',
|
||||||
|
'@type' => 'WebPage',
|
||||||
|
'name' => $title,
|
||||||
|
'url' => $url,
|
||||||
|
];
|
||||||
|
if ($desc) $data['description'] = $desc;
|
||||||
|
if ($imageUrl) $data['image'] = $imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPageType($page)
|
||||||
|
{
|
||||||
|
$headerType = $this->getHeaderValue($page, 'type');
|
||||||
|
if ($headerType) {
|
||||||
|
return $headerType;
|
||||||
|
}
|
||||||
|
return $page->home() ? 'website' : 'article';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPageLocale()
|
||||||
|
{
|
||||||
|
$lang = $this->grav['language']->getActive() ?? $this->grav['language']->getDefault();
|
||||||
|
if (!$lang) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$localeMap = [
|
||||||
|
'en' => 'en_US', 'es' => 'es_ES', 'fr' => 'fr_FR', 'de' => 'de_DE',
|
||||||
|
'it' => 'it_IT', 'pt' => 'pt_PT', 'nl' => 'nl_NL', 'ru' => 'ru_RU',
|
||||||
|
'ja' => 'ja_JP', 'zh' => 'zh_CN', 'ar' => 'ar_AE', 'ko' => 'ko_KR',
|
||||||
|
'sv' => 'sv_SE', 'pl' => 'pl_PL', 'tr' => 'tr_TR',
|
||||||
|
];
|
||||||
|
return $localeMap[$lang] ?? strtolower($lang) . '_' . strtoupper($lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getHeaderValue($page, $key)
|
||||||
|
{
|
||||||
|
$header = $page->header();
|
||||||
|
$socialMeta = $header->social_meta ?? null;
|
||||||
|
if ($socialMeta === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is_array($socialMeta) && isset($socialMeta[$key])) {
|
||||||
|
return $socialMeta[$key];
|
||||||
|
}
|
||||||
|
if (is_object($socialMeta) && isset($socialMeta->$key)) {
|
||||||
|
return $socialMeta->$key;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPageImage($page)
|
||||||
|
{
|
||||||
|
if (!empty($page->value('media.image'))) {
|
||||||
|
$images = $page->media()->images();
|
||||||
|
return array_shift($images);
|
||||||
|
}
|
||||||
|
foreach ($page->children() as $child) {
|
||||||
|
$image = $this->getPageImage($child);
|
||||||
|
if ($image !== null) {
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPageDescription($page)
|
||||||
|
{
|
||||||
|
$summary = $page->summary();
|
||||||
|
if (!$this->stringIsEmpty($summary)) {
|
||||||
|
return $summary;
|
||||||
|
}
|
||||||
|
foreach ($page->children() as $child) {
|
||||||
|
$description = $this->getPageDescription($child);
|
||||||
|
if (!$this->stringIsEmpty($description)) {
|
||||||
|
return $description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function stringIsEmpty($string)
|
||||||
|
{
|
||||||
|
return strlen(trim($string)) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sanitizeMarkdowns($text)
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'/(#+)(.*)/' => '\2', // headers
|
||||||
|
'/(<|<)!--\n((.*|\n)*)\n--(>|\>)/' => '', // comments
|
||||||
|
'/(\*|-|_){3}/' => '', // hr
|
||||||
|
'/!\[([^\[]+)\]\(([^\)]+)\)/' => '', // images
|
||||||
|
'/\[([^\[]+)\]\(([^\)]+)\)/' => '\1', // links
|
||||||
|
'/(\*\*|__)(.*?)\1/' => '\2', // bold
|
||||||
|
'/(\*|_)(.*?)\1/' => '\2', // emphasis
|
||||||
|
'/\~\~(.*?)\~\~/' => '\1', // del
|
||||||
|
'/\:\"(.*?)\"\:/' => '\1', // quote
|
||||||
|
'/```(.*)\n((.*|\n)+)\n```/' => '\2', // fence code
|
||||||
|
'/`(.*?)`/' => '\1', // inline code
|
||||||
|
'/(\*|\+|-)(.*)/' => '\2', // ul lists
|
||||||
|
'/\n[0-9]+\.(.*)/' => '\2', // ol lists
|
||||||
|
'/(>|\>)+(.*)/' => '\2', // blockquotes
|
||||||
|
'/\s+/' => ' ', // collapse whitespace
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($rules as $regex => $replacement) {
|
||||||
|
if (is_callable($replacement)) {
|
||||||
|
$text = preg_replace_callback($regex, $replacement, $text);
|
||||||
|
} else {
|
||||||
|
$text = preg_replace($regex, $replacement, $text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return substr(htmlspecialchars($text, ENT_QUOTES, 'UTF-8'), 0, 280);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
enabled: true
|
||||||
|
jsonld:
|
||||||
|
enabled: true
|
||||||
|
social_pages:
|
||||||
|
pages:
|
||||||
|
twitter:
|
||||||
|
enabled: true
|
||||||
|
type: summary
|
||||||
|
aboutme: false
|
||||||
|
username: ''
|
||||||
|
facebook:
|
||||||
|
enabled: false
|
||||||
|
appid: '1234567890'
|
||||||
Reference in New Issue
Block a user