├── LICENSE
├── README.md
└── sitemap
├── SitemapPlugin.php
├── controllers
└── Sitemap_SitemapController.php
├── library
├── Sitemap_ChangeFrequency.php
└── Sitemap_Priority.php
├── models
├── Sitemap_AlternateUrlModel.php
├── Sitemap_BaseModel.php
└── Sitemap_UrlModel.php
├── services
└── SitemapService.php
└── templates
└── _settings.html
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Craft Plugins
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > [!IMPORTANT]
2 | >
3 | > **This plugin is no longer maintained.**
4 | >
5 | > We recommend the [SEOmatic plugin](https://plugins.craftcms.com/seomatic) or the [SEO plugin](https://plugins.craftcms.com/seo) instead.
6 |
7 | # Craft Sitemap
8 |
9 | A simple plugin for [Craft](http://craftcms.com) that generates a [sitemap.xml](http://www.sitemaps.org/) based on enabled sections.
10 |
11 | 
12 |
13 | ## Installation
14 |
15 | 1. Copy the `sitemap/` folder into `craft/plugins/`
16 | 2. Go to Settings → Plugins and click the “Install” button next to “Sitemap”
17 |
18 | ## Usage
19 |
20 | Within the plugin settings, check the boxes in the “Enabled” column to include them in the sitemap.
21 |
22 | To view the output visit `/sitemap.xml`.
23 |
24 | ## Advanced
25 |
26 | This plugin exposes various [service methods](#service-methods), which can be used to add custom items to the sitemap through the [`renderSitemap` hook](#rendersitemap). Please read the official [‘Hooks and Events’ documentation](http://buildwithcraft.com/docs/plugins/hooks-and-events), if you’re not sure how this works.
27 |
28 | ### Hooks
29 |
30 | ##### `renderSitemap`
31 |
32 | Add a `renderSitemap` method to your plugin to add items via the various [service methods](#service-methods) listed below.
33 |
34 | Here’s an example plugin hook method with comments:
35 |
36 | ```php
37 | public function renderSitemap()
38 | {
39 | // Get an ElementCriteriaModel from the ElementsService
40 | $criteria = craft()->elements->getCriteria(ElementType::Entry);
41 |
42 | // Specify that we want entries within the ‘locations’ section
43 | $criteria->section = 'locations';
44 |
45 | // Loop through any entries that were found
46 | foreach ($criteria->find() as $locationEntry)
47 | {
48 | // Here we’re building a path using the entry slug.
49 | // This might match a custom route you’ve defined that
50 | // should be included in the sitemap.
51 | $path = 'cars-for-sale-in-' . $locationEntry->slug;
52 |
53 | // Make sure that we’re using a full URL, not just the path.
54 | $url = UrlHelper::getSiteUrl($path);
55 |
56 | // For the sake of this example, we’re setting the $lastmod
57 | // value to the most recent time the location entry was
58 | // updated. You can pass any time using the DateTime class.
59 | $lastmod = $locationEntry->dateUpdated;
60 |
61 | // Add the URL to the sitemap
62 | craft()->sitemap->addUrl($url, $lastmod, Sitemap_ChangeFrequency::Daily, 0.5);
63 | }
64 | }
65 | ```
66 |
67 | And here’s an example of the resulting element in the sitemap XML:
68 |
69 | ```xml
70 |
71 | http://example.com/cars-for-sale-in-scotland
72 | 2015-08-28T15:08:28+00:00
73 |
74 | ```
75 |
76 | ### Service Methods
77 |
78 | There’s several service methods made available to add items to the sitemap.
79 |
80 | ##### `addUrl($loc, $lastmod, [$changefreq, [$priority]])`
81 | Adds a URL to the sitemap.
82 |
83 | ```php
84 | $loc = UrlHelper::getSiteUrl('special/route');
85 | $lastmod = new DateTime('now');
86 | craft()->sitemap->addUrl($loc, $lastmod, Sitemap_ChangeFrequency::Yearly, 0.1);
87 | ```
88 |
89 | ##### `addElement(BaseElementModel $element, [$changefreq, [$priority]])`
90 | Adds an element to the sitemap.
91 |
92 | ```php
93 | $element = craft()->elements->getElementById(2);
94 | craft()->sitemap->addElement($element, Sitemap_ChangeFrequency::Daily, 1.0);
95 | ```
96 |
97 | ##### `addSection(SectionModel $section, [$changefreq, [$priority]])`
98 | Adds all entries in the section to the sitemap.
99 |
100 | ```php
101 | $section = craft()->sections->getSectionByHandle('homepage');
102 | craft()->sitemap->addSection($section, Sitemap_ChangeFrequency::Weekly, 1.0);
103 | ```
104 |
105 | ##### `addCategoryGroup(CategoryGroupModel $categoryGroup, [$changefreq, [$priority]])`
106 | Adds all categories in the group to the sitemap.
107 |
108 | ```php
109 | $group = craft()->categories->getGroupByHandle('news');
110 | craft()->sitemap->addCategoryGroup($group);
111 | ```
112 |
113 | ##### `getElementUrlForLocale(BaseElementModel $element, $locale)`
114 | Gets a element URL for the specified locale. The locale must be enabled.
115 |
116 | ```php
117 | echo $element->url;
118 | // http://example.com/en/hello-world
119 |
120 | echo craft()->sitemap->getElementUrlForLocale($element, 'fr');
121 | // http://example.com/fr/bonjour-monde
122 | ```
123 |
124 | ##### `getUrlForLocale($path, $locale)`
125 | Gets a URL for the specified locale. The locale must be enabled.
126 |
127 | ```php
128 | echo UrlHelper::getSiteUrl('foo/bar');
129 | // http://example.com/en/foo/bar
130 |
131 | echo craft()->sitemap->getUrlForLocale('foo/bar', 'fr');
132 | // http://example.com/fr/foo/bar
133 | ```
134 |
135 | #### Helper Classes
136 |
137 | ##### `Sitemap_ChangeFrequency`
138 | Enumeration of valid `changefreq` values.
139 |
140 | ```php
141 | Sitemap_ChangeFrequency::Always
142 | Sitemap_ChangeFrequency::Hourly
143 | Sitemap_ChangeFrequency::Daily
144 | Sitemap_ChangeFrequency::Weekly
145 | Sitemap_ChangeFrequency::Monthly
146 | Sitemap_ChangeFrequency::Yearly
147 | Sitemap_ChangeFrequency::Never
148 | ```
149 |
--------------------------------------------------------------------------------
/sitemap/SitemapPlugin.php:
--------------------------------------------------------------------------------
1 | array(),
46 | );
47 | }
48 |
49 | /**
50 | * {@inheritdoc} BaseSavableComponentType::getSettingsHtml()
51 | */
52 | public function getSettingsHtml()
53 | {
54 | return craft()->templates->render('sitemap/_settings', array(
55 | 'sections' => craft()->sitemap->sectionsWithUrls,
56 | 'settings' => $this->settings,
57 | ));
58 | }
59 |
60 | /**
61 | * {@inheritdoc} BaseSavableComponentType::prepSettings()
62 | */
63 | public function prepSettings($input)
64 | {
65 | // We’re rewriting every time
66 | $settings = $this->defineSettings();
67 |
68 | // Loop through valid sections
69 | foreach (craft()->sitemap->sectionsWithUrls as $section) {
70 | // Check if the section is enabled
71 | if ($input['enabled'][$section->id]) {
72 | // If it is, save the changefreq and priority values into settings
73 | $settings['sections'][$section->id] = array(
74 | 'changefreq' => $input['changefreq'][$section->id],
75 | 'priority' => $input['priority'][$section->id],
76 | );
77 | }
78 | }
79 |
80 | // Return the parsed settings ready for the database
81 | return $settings;
82 | }
83 |
84 | /**
85 | * Registers the /sitemap.xml route.
86 | *
87 | * @return array
88 | */
89 | public function registerSiteRoutes()
90 | {
91 | return array(
92 | 'sitemap.xml' => array(
93 | 'action' => 'sitemap/sitemap/output',
94 | ),
95 | );
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/sitemap/controllers/Sitemap_SitemapController.php:
--------------------------------------------------------------------------------
1 | sitemap->sitemap;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/sitemap/library/Sitemap_ChangeFrequency.php:
--------------------------------------------------------------------------------
1 | hreflang = $hreflang;
16 | $this->href = $href;
17 | }
18 |
19 | /**
20 | * {@inheritdoc} Sitemap_UrlModel::getDomElement()
21 | */
22 | public function getDomElement(\DOMDocument $document)
23 | {
24 | $element = $document->createElement('xhtml:link');
25 | $element->setAttribute('rel', 'alternate');
26 | $element->setAttribute('hreflang', $this->hreflang);
27 | $element->setAttribute('href', $this->href);
28 |
29 | return $element;
30 | }
31 |
32 | /**
33 | * {@inheritdoc} BaseModel::rules()
34 | */
35 | public function rules()
36 | {
37 | $rules = parent::rules();
38 | $rules[] = array('href', 'CUrlValidator');
39 | return $rules;
40 | }
41 |
42 | /**
43 | * {@inheritdoc} BaseModel::defineAttributes()
44 | */
45 | protected function defineAttributes()
46 | {
47 | return array(
48 | 'hreflang' => AttributeType::Locale,
49 | 'href' => AttributeType::Url,
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/sitemap/models/Sitemap_BaseModel.php:
--------------------------------------------------------------------------------
1 | config->get('devMode')) {
17 | foreach ($this->getAllErrors() as $attribute => $error) {
18 | throw new Exception(Craft::t($error));
19 | }
20 | }
21 |
22 | return $validate;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/sitemap/models/Sitemap_UrlModel.php:
--------------------------------------------------------------------------------
1 | loc = $loc;
25 | $this->lastmod = $lastmod;
26 | $this->changefreq = $changefreq;
27 | $this->priority = $priority;
28 | }
29 |
30 | /**
31 | * Add an alternate URL.
32 | *
33 | * @param string|LocaleModel $hreflang
34 | * @param string $href
35 | *
36 | * @return Sitemap_AlternateUrlModel
37 | */
38 | public function addAlternateUrl($hreflang, $href)
39 | {
40 | $alternateUrl = new Sitemap_AlternateUrlModel($hreflang, $href);
41 |
42 | if ($alternateUrl->validate()) {
43 | $this->alternateUrls[$alternateUrl->hreflang] = $alternateUrl;
44 | }
45 |
46 | return $alternateUrl;
47 | }
48 |
49 | /**
50 | * Returns an array of assigned Sitemap_AlternateUrlModel instances.
51 | *
52 | * @return array
53 | */
54 | public function getAlternateUrls()
55 | {
56 | return $this->alternateUrls;
57 | }
58 |
59 | /**
60 | * Returns true if there’s one or more alternate URLs, excluding the current locale.
61 | *
62 | * @return bool
63 | */
64 | public function hasAlternateUrls()
65 | {
66 | return count(array_filter($this->alternateUrls, function ($alternateUrl) {
67 | return $alternateUrl->hreflang != craft()->language;
68 | })) > 0;
69 | }
70 |
71 | /**
72 | * Generates the relevant DOMElement instances.
73 | *
74 | * @param \DOMDocument $document
75 | *
76 | * @return \DOMElement
77 | */
78 | public function getDomElement(\DOMDocument $document)
79 | {
80 | $url = $document->createElement('url');
81 |
82 | $loc = $document->createElement('loc', $this->loc);
83 | $url->appendChild($loc);
84 |
85 | $lastmod = $document->createElement('lastmod', $this->lastmod->w3c());
86 | $url->appendChild($lastmod);
87 |
88 | if ($this->changefreq) {
89 | $changefreq = $document->createElement('changefreq', $this->changefreq);
90 | $url->appendChild($changefreq);
91 | }
92 |
93 | if ($this->priority) {
94 | $priority = $document->createElement('priority', $this->priority);
95 | $url->appendChild($priority);
96 | }
97 |
98 | if ($this->hasAlternateUrls()) {
99 | foreach ($this->alternateUrls as $alternateUrl) {
100 | $link = $alternateUrl->getDomElement($document);
101 | $url->appendChild($link);
102 | }
103 | }
104 |
105 | return $url;
106 | }
107 |
108 | /**
109 | * {@inheritdoc} BaseModel::setAttribute()
110 | */
111 | public function setAttribute($name, $value)
112 | {
113 | if ($name == 'loc') {
114 | $this->addAlternateUrl(craft()->language, $value);
115 | }
116 |
117 | if ($name == 'lastmod') {
118 | if (!$value instanceof \DateTime) {
119 | try {
120 | $value = new DateTime($value);
121 | } catch (\Exception $e) {
122 | $message = Craft::t('“{object}->{attribute}” must be a DateTime object or a valid Unix timestamp.', array('object' => get_class($this), 'attribute' => $name));
123 | throw new Exception($message);
124 | }
125 | }
126 | if (new DateTime() < $value) {
127 | $message = Craft::t('“{object}->{attribute}” must be in the past.', array('object' => get_class($this), 'attribute' => $name));
128 | throw new Exception($message);
129 | }
130 | }
131 |
132 | return parent::setAttribute($name, $value);
133 | }
134 |
135 | /**
136 | * {@inheritdoc} BaseModel::rules()
137 | */
138 | public function rules()
139 | {
140 | $rules = parent::rules();
141 | $rules[] = array('loc', 'CUrlValidator');
142 | return $rules;
143 | }
144 |
145 | /**
146 | * {@inheritdoc} BaseModel::defineAttributes()
147 | */
148 | protected function defineAttributes()
149 | {
150 | return array(
151 | 'loc' => AttributeType::Url,
152 | 'lastmod' => AttributeType::DateTime,
153 | 'changefreq' => array(AttributeType::Enum, 'values' => Sitemap_ChangeFrequency::getConstants()),
154 | 'priority' => array(AttributeType::Enum, 'values' => Sitemap_Priority::getConstants()),
155 | );
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/sitemap/services/SitemapService.php:
--------------------------------------------------------------------------------
1 | sections->allSections, function ($section) {
32 | return $section->isHomepage() || $section->urlFormat;
33 | });
34 | }
35 |
36 | /**
37 | * Return the sitemap as a string.
38 | *
39 | * @return string
40 | */
41 | public function getSitemap()
42 | {
43 | $settings = $this->pluginSettings;
44 |
45 | // Loop through and add the sections checked in the plugin settings
46 | foreach ($this->sectionsWithUrls as $section) {
47 | if (!empty($settings['sections'][$section->id])) {
48 | $changefreq = $settings['sections'][$section->id]['changefreq'];
49 | $priority = $settings['sections'][$section->id]['priority'];
50 | $this->addSection($section, $changefreq, $priority);
51 | }
52 | }
53 |
54 | // Hook: renderSitemap
55 | craft()->plugins->call('renderSitemap');
56 |
57 | // Use DOMDocument to generate XML
58 | $document = new \DOMDocument('1.0', 'utf-8');
59 |
60 | // Format XML output when devMode is active for easier debugging
61 | if (craft()->config->get('devMode')) {
62 | $document->formatOutput = true;
63 | }
64 |
65 | // Append a urlset node
66 | $urlset = $document->createElement('urlset');
67 | $urlset->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');
68 | $urlset->setAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
69 | $document->appendChild($urlset);
70 |
71 | // Loop through and append Sitemap_UrlModel elements
72 | foreach ($this->urls as $url) {
73 | $urlElement = $url->getDomElement($document);
74 | $urlset->appendChild($urlElement);
75 | }
76 |
77 | return $document->saveXML();
78 | }
79 |
80 | /**
81 | * Adds a URL to the sitemap.
82 | *
83 | * @param string $loc
84 | * @param DateTime $lastmod
85 | * @param string $changefreq
86 | * @param string $priority
87 | *
88 | * @return Sitemap_UrlModel
89 | */
90 | public function addUrl($loc, $lastmod, $changefreq = null, $priority = null)
91 | {
92 | $url = new Sitemap_UrlModel($loc, $lastmod, $changefreq, $priority);
93 |
94 | if ($url->validate()) {
95 | $this->urls[$url->loc] = $url;
96 | }
97 |
98 | return $url;
99 | }
100 |
101 | /**
102 | * Adds an element to the sitemap.
103 | *
104 | * @param BaseElementModel $element
105 | * @param string $changefreq
106 | * @param string $priority
107 | */
108 | public function addElement(BaseElementModel $element, $changefreq = null, $priority = null)
109 | {
110 | $url = $this->addUrl($element->url, $element->dateUpdated, $changefreq, $priority);
111 |
112 | $locales = craft()->elements->getEnabledLocalesForElement($element->id);
113 | foreach ($locales as $locale) {
114 | $href = craft()->sitemap->getElementUrlForLocale($element, $locale);
115 | $url->addAlternateUrl($locale, $href);
116 | }
117 | }
118 |
119 | /**
120 | * Adds all entries in the section to the sitemap.
121 | *
122 | * @param SectionModel $section
123 | * @param string $changefreq
124 | * @param string $priority
125 | */
126 | public function addSection(SectionModel $section, $changefreq = null, $priority = null)
127 | {
128 | $criteria = craft()->elements->getCriteria(ElementType::Entry);
129 | $criteria->section = $section;
130 | foreach ($criteria->find() as $element) {
131 | $this->addElement($element, $changefreq, $priority);
132 | }
133 | }
134 |
135 | /**
136 | * Adds all categories in the group to the sitemap.
137 | *
138 | * @param CategoryGroupModel $categoryGroup
139 | * @param string $changefreq
140 | * @param string $priority
141 | */
142 | public function addCategoryGroup(CategoryGroupModel $categoryGroup, $changefreq = null, $priority = null)
143 | {
144 | $criteria = craft()->elements->getCriteria(ElementType::Category);
145 | $criteria->group = $categoryGroup;
146 |
147 | $categories = $criteria->find();
148 | foreach ($categories as $category) {
149 | $this->addElement($category, $changefreq, $priority);
150 | }
151 | }
152 |
153 | /**
154 | * Gets a element URL for the specified locale.
155 | *
156 | * @param Element $element
157 | * @param string|LocaleModel $locale
158 | *
159 | * @return string
160 | */
161 | public function getElementUrlForLocale(BaseElementModel $element, $locale)
162 | {
163 | $this->validateLocale($locale);
164 |
165 | $oldLocale = $element->locale;
166 | $oldUri = $element->uri;
167 | $element->locale = $locale;
168 | $element->uri = craft()->elements->getElementUriForLocale($element->id, $locale);
169 | $url = $element->getUrl();
170 | $element->locale = $oldLocale;
171 | $element->uri = $oldUri;
172 |
173 | return $url;
174 | }
175 |
176 | /**
177 | * Gets a URL for the specified locale.
178 | *
179 | * @param string $path
180 | * @param string|LocaleModel $locale
181 | *
182 | * @return string
183 | */
184 | public function getUrlForLocale($path, $locale)
185 | {
186 | $this->validateLocale($locale);
187 |
188 | // Get the site URL for the current locale
189 | $siteUrl = craft()->siteUrl;
190 |
191 | if (UrlHelper::isFullUrl($path)) {
192 | // Return $path if it’s a remote URL
193 | if (!stripos($path, $siteUrl)) {
194 | return $path;
195 | }
196 |
197 | // Remove the current locale siteUrl
198 | $path = str_replace($siteUrl, '', $path);
199 | }
200 |
201 | // Get the site URL for the specified locale
202 | $localizedSiteUrl = craft()->config->getLocalized('siteUrl', $locale);
203 |
204 | // Trim slahes
205 | $localizedSiteUrl = rtrim($localizedSiteUrl, '/');
206 | $path = trim($path, '/');
207 |
208 | return UrlHelper::getUrl($localizedSiteUrl.'/'.$path);
209 | }
210 |
211 | /**
212 | * Ensures that the requested locale is valid.
213 | *
214 | * @param string|LocaleModel $locale
215 | */
216 | protected function validateLocale($locale)
217 | {
218 | if (!in_array($locale, craft()->i18n->siteLocales)) {
219 | throw new Exception(Craft::t('“{locale}” is not a valid site locale.', array('locale' => $locale)));
220 | }
221 | }
222 |
223 | /**
224 | * Gets the plugin settings.
225 | *
226 | * @return array
227 | */
228 | protected function getPluginSettings()
229 | {
230 | $plugin = craft()->plugins->getPlugin('sitemap');
231 |
232 | if (is_null($plugin)) {
233 | return array();
234 | }
235 |
236 | return $plugin->settings;
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/sitemap/templates/_settings.html:
--------------------------------------------------------------------------------
1 | {% import '_includes/forms' as forms %}
2 |
3 | {% set frequencyOptions = {
4 | 'always': 'always',
5 | 'hourly': 'hourly',
6 | 'daily': 'daily',
7 | 'weekly': 'weekly',
8 | 'monthly': 'monthly',
9 | 'yearly': 'yearly',
10 | 'never': 'never',
11 | } %}
12 |
13 | {% set priorityOptions = {
14 | '1.0': '1.0',
15 | '0.9': '0.9',
16 | '0.8': '0.8',
17 | '0.7': '0.7',
18 | '0.6': '0.6',
19 | '0.5': '0.5',
20 | '0.4': '0.4',
21 | '0.3': '0.3',
22 | '0.2': '0.2',
23 | '0.1': '0.1',
24 | '0.0': '0.0',
25 | } %}
26 |
27 |
28 |
29 | {{ 'Enabled'|t }} |
30 | {{ 'Section'|t }} |
31 | {{ 'URL Format'|t }} |
32 | {{ 'Entries'|t }} |
33 | {{ 'Change Frequency'|t }} |
34 | {{ 'Priority'|t }} |
35 |
36 |
37 | {% for section in sections %}
38 |
39 |
40 | {{- forms.checkbox({
41 | name: "enabled[#{section.id}]",
42 | value: true,
43 | checked: settings.sections[section.id] is defined
44 | }) -}}
45 | |
46 |
47 | {{- section -}}
48 | |
49 |
50 | {%- if section.isHomepage() -%}
51 |
52 | {%- else -%}
53 | {{ section.urlFormat }}
54 | {%- endif -%}
55 | |
56 |
57 | {{- craft.entries.section(section).total() -}}
58 | |
59 |
60 | {{- forms.selectField({
61 | name: "changefreq[#{section.id}]",
62 | options: frequencyOptions,
63 | value: settings.sections[section.id].changefreq|default('weekly')
64 | }) -}}
65 | |
66 |
67 | {{- forms.selectField({
68 | name: "priority[#{section.id}]",
69 | options: priorityOptions,
70 | value: settings.sections[section.id].priority|default(section.isHomepage() ? '1.0' : '0.5')
71 | }) -}}
72 | |
73 |
74 | {% endfor %}
75 |
76 |
77 |
--------------------------------------------------------------------------------