Skip to content

Commit

Permalink
Merge branch 'release/3.4.68' into v3
Browse files Browse the repository at this point in the history
  • Loading branch information
khalwat committed Dec 12, 2023
2 parents 61aa396 + e3f044f commit cc2dc54
Show file tree
Hide file tree
Showing 13 changed files with 230 additions and 43 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# SEOmatic Changelog

## 3.4.68 - 2023.12.12
### Added
* SEOmatic now requires at least Craft CMS `^3.2.0` or later
* Added a `EVENT_INCLUDE_SITEMAP_ENTRY` event to allow plugins or modules to determine whether entries should be added to the sitemap or not ([#1393](https://github.com/nystudio107/craft-seomatic/issues/1393))
* Allow the `config/seomatic.php` `siteUrlOverride` to be set to either a string, or an array of site URLs, indexed by the site handle for overriding complex headless multi-site Craft setups ([#1376](https://github.com/nystudio107/craft-seomatic/issues/1376))

### Changed
* Switch over to listening for element changes via `Element::EVENT_AFTER_PROPAGATE` events instead of `Elements::EVENT_AFTER_SAVE_ELEMENT` and have it check the `Element::$resaving` attribute instead of `Model::$scenario` = `SCENARIO_ESSENTIALS` to determine whether sitemap queue jobs should be created ([#1388](https://github.com/nystudio107/craft-seomatic/issues/1388))
* If the Site URL Override feature is used, pass along the parameters, too, when building the URL ([#950](https://github.com/nystudio107/craft-seomatic/issues/950))
* Removed the automatic Google Sitemap ping endpoint, since [Google has deprecated it and will be removing it entirely soon](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping) ([#1392](https://github.com/nystudio107/craft-seomatic/issues/1392))

## 3.4.67 - 2023.11.28
### Added
* Switch over to Vite `^5.0.0` & Node `^20.0.0` for the buildchain
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "nystudio107/craft-seomatic",
"description": "SEOmatic facilitates modern SEO best practices & implementation for Craft CMS 3. It is a turnkey SEO system that is comprehensive, powerful, and flexible.",
"type": "craft-plugin",
"version": "3.4.67",
"version": "3.4.68",
"keywords": [
"craft",
"cms",
Expand Down Expand Up @@ -33,7 +33,7 @@
"vlucas/phpdotenv": "^3.0"
},
"require": {
"craftcms/cms": "^3.1.19",
"craftcms/cms": "^3.2.0",
"nystudio107/craft-plugin-vite": "^1.0.31",
"nystudio107/craft-code-editor": "^1.0.0",
"php-science/textrank": "^1.0.3",
Expand Down
15 changes: 15 additions & 0 deletions docs/docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,21 @@ Event::on(MetaContainers::class, MetaContainers::EVENT_INVALIDATE_CONTAINER_CACH
});
```

### IncludeSitemapEntryEvent

const EVENT_INCLUDE_SITEMAP_ENTRY = 'IncludeSitemapEntryEvent';

The event that is triggered when an entry is about to be included in a sitemap.

```php
use nystudio107\seomatic\events\IncludeSitemapEntryEvent;
use nystudio107\seomatic\helpers\Sitemap;
use yii\base\Event;
Event::on(Sitemap::class, Sitemap::EVENT_INCLUDE_SITEMAP_ENTRY, function(IncludeSitemapEntryEvent $e) {
$e->include = false;
});
```

### RegisterSitemapUrlsEvent

const EVENT_REGISTER_SITEMAP_URLS = 'registerSitemapUrls';
Expand Down
13 changes: 7 additions & 6 deletions src/Seomatic.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use craft\errors\SiteNotFoundException;
use craft\events\DefineGqlTypeFieldsEvent;
use craft\events\ElementEvent;
use craft\events\ModelEvent;
use craft\events\PluginEvent;
use craft\events\RegisterCacheOptionsEvent;
use craft\events\RegisterComponentTypesEvent;
Expand Down Expand Up @@ -464,17 +465,17 @@ function (RegisterComponentTypesEvent $event) {
$event->types[] = Seomatic_MetaField::class;
}
);
// Handler: Elements::EVENT_AFTER_SAVE_ELEMENT
// Handler: Element::EVENT_AFTER_PROPAGATE
Event::on(
Elements::class,
Elements::EVENT_AFTER_SAVE_ELEMENT,
function (ElementEvent $event) {
Element::class,
Element::EVENT_AFTER_PROPAGATE,
static function (ModelEvent $event) {
Craft::debug(
'Elements::EVENT_AFTER_SAVE_ELEMENT',
'Element::EVENT_AFTER_PROPAGATE',
__METHOD__
);
/** @var $element Element */
$element = $event->element;
$element = $event->sender;
self::$plugin->metaBundles->invalidateMetaBundleByElement(
$element,
$event->isNew
Expand Down
10 changes: 7 additions & 3 deletions src/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
* @copyright Copyright (c) 2017 nystudio107
*/

use nystudio107\seomatic\base\SeoElementInterface;

/**
* @author nystudio107
* @package Seomatic
Expand Down Expand Up @@ -81,7 +79,7 @@
// If `devMode` is on, prefix the <title> with this string
'devModeTitlePrefix' => '&#x1f6a7; ',

// Prefix the Control Panel <title> with this string
// Prefix the Control Panel <title> with this string
'cpTitlePrefix' => '&#x2699; ',

// If `devMode` is on, prefix the Control Panel <title> with this string
Expand Down Expand Up @@ -141,6 +139,12 @@
// SEOmatic uses the Craft `siteUrl` to generate the external URLs. If you
// are using it in a non-standard environment, such as a headless GraphQL or
// ElementAPI server, you can override what it uses for the `siteUrl` below.
// This can be either a simple string, or an array of strings indexed by the site
// handle, for multi-site setups. e.g.:
// 'siteUrlOverride' => [
// 'default' => 'http://example.com/',
// 'spanish' => 'http://example.com/es/',
// ],
'siteUrlOverride' => '',

// The duration of the SEOmatic meta cache in seconds. Null means always cached until explicitly broken
Expand Down
42 changes: 42 additions & 0 deletions src/events/IncludeSitemapEntryEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php
/**
* SEOmatic plugin for Craft CMS 3.x
*
* A turnkey SEO implementation for Craft CMS that is comprehensive, powerful,
* and flexible
*
* @link https://nystudio107.com
* @copyright Copyright (c) 2017 nystudio107
*/

namespace nystudio107\seomatic\events;

use craft\base\ElementInterface;
use nystudio107\seomatic\models\MetaBundle;
use yii\base\Event;

/**
* @author nystudio107
* @package Seomatic
* @since 3.4.68
*/
class IncludeSitemapEntryEvent extends Event
{
// Properties
// =========================================================================

/**
* @var ElementInterface The Craft element corresponding to the sitemap entry
*/
public $element;

/**
* @var MetaBundle The SEOmatic MetaBundle corresponding to the sitemap entry
*/
public $metaBundle;

/**
* @var bool Whether to include the sitemap entry.
*/
public $include;
}
6 changes: 5 additions & 1 deletion src/helpers/DynamicMeta.php
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,11 @@ public static function addMetaJsonLdBreadCrumbs(int $siteId = null)
Craft::error($e->getMessage(), __METHOD__);
}
if (!empty(Seomatic::$settings->siteUrlOverride)) {
$siteUrl = Seomatic::$settings->siteUrlOverride;
try {
$siteUrl = UrlHelper::getSiteUrlOverrideSetting($siteId);
} catch (\Throwable $e) {
// That's okay
}
}
$siteUrl = $siteUrl ?: '/';
/** @var $crumbs BreadcrumbList */
Expand Down
34 changes: 33 additions & 1 deletion src/helpers/Sitemap.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
use craft\models\SiteGroup;
use nystudio107\fastcgicachebust\FastcgiCacheBust;
use nystudio107\seomatic\base\SeoElementInterface;
use nystudio107\seomatic\events\IncludeSitemapEntryEvent;
use nystudio107\seomatic\fields\SeoSettings;
use nystudio107\seomatic\helpers\Field as FieldHelper;
use nystudio107\seomatic\jobs\GenerateSitemap;
use nystudio107\seomatic\models\MetaBundle;
use nystudio107\seomatic\models\SitemapTemplate;
use nystudio107\seomatic\Seomatic;
use yii\base\Event;
use yii\base\Exception;
use yii\caching\TagDependency;
use yii\helpers\Html;
Expand All @@ -30,6 +32,29 @@
*/
class Sitemap
{
/**
* @event IncludeSitemapEntryEvent The event that is triggered when an entry is
* about to be included in a sitemap
*
* ---
* ```php
* use nystudio107\seomatic\events\IncludeSitemapEntryEvent;
* use nystudio107\seomatic\helpers\Sitemap;
* use yii\base\Event;
* Event::on(Sitemap::class, Sitemap::EVENT_INCLUDE_SITEMAP_ENTRY, function(IncludeSitemapEntryEvent $e) {
* $e->include = false;
* });
* ```
*/
const EVENT_INCLUDE_SITEMAP_ENTRY = 'includeSitemapEntry';

/**
* Generate a sitemap with the passed in $params
*
* @param array $params
* @return void
* @throws \craft\errors\SiteNotFoundException
*/
public static function generateSitemap(array $params)
{
/** @var \craft\queue\Queue $queue */
Expand Down Expand Up @@ -173,8 +198,15 @@ public static function generateSitemap(array $params)
if (Seomatic::$craft34) {
$enabled = $element->getEnabledForSite($metaBundle->sourceSiteId);
}
$enabled = $enabled && $path !== null && $metaBundle->metaSitemapVars->sitemapUrls && $robotsEnabled;
$event = new IncludeSitemapEntryEvent([
'include' => $enabled,
'element' => $element,
'metaBundle' => $metaBundle,
]);
Event::trigger(self::class, self::EVENT_INCLUDE_SITEMAP_ENTRY, $event);
// Only add in a sitemap entry if it meets our criteria
if ($enabled && $path !== null && $metaBundle->metaSitemapVars->sitemapUrls && $robotsEnabled) {
if ($event->include) {
// Get the url and canonicalUrl
try {
$url = UrlHelper::siteUrl($path, null, null, $metaBundle->sourceSiteId);
Expand Down
67 changes: 64 additions & 3 deletions src/helpers/UrlHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@ class UrlHelper extends CraftUrlHelper
*/
public static function siteUrl(string $path = '', $params = null, string $scheme = null, int $siteId = null): string
{
$siteUrl = Seomatic::$settings->siteUrlOverride;
try {
$siteUrl = self::getSiteUrlOverrideSetting($siteId);
} catch (\Throwable $e) {
// That's okay
}
if (!empty($siteUrl)) {
$siteUrl = MetaValue::parseString($siteUrl);
// Extract out just the path part
$parts = self::decomposeUrl($path);
$path = $parts['path'] . $parts['suffix'];
$url = rtrim($siteUrl, '/') . '/' . ltrim($path, '/');
$url = self::mergeUrlWithPath($siteUrl, $path);
// Handle trailing slashes properly for generated URLs
$generalConfig = Craft::$app->getConfig()->getGeneral();
if ($generalConfig->addTrailingSlashesToUrls && !preg_match('/\.[^\/]+$/', $url)) {
Expand All @@ -51,12 +55,36 @@ public static function siteUrl(string $path = '', $params = null, string $scheme
$url = rtrim($url, '/');
}

return $url;
return DynamicMeta::sanitizeUrl(parent::urlWithParams($url, $params ?? []), false, false);
}

return DynamicMeta::sanitizeUrl(parent::siteUrl($path, $params, $scheme, $siteId), false, false);
}

/**
* Merge the $url and $path together, combining any overlapping path segments
*
* @param string $url
* @param string $path
* @return string
*/
public static function mergeUrlWithPath(string $url, string $path): string
{
$overlap = 0;
$urlOffset = strlen($url);
$pathLength = strlen($path);
$pathOffset = 0;
while ($urlOffset > 0 && $pathOffset < $pathLength) {
$urlOffset--;
$pathOffset++;
if (str_starts_with($path, substr($url, $urlOffset, $pathOffset))) {
$overlap = $pathOffset;
}
}

return rtrim($url, '/') . '/' . ltrim(substr($path, $overlap), '/');
}

/**
* Return the page trigger and the value of the page trigger (null if it doesn't exist)
*
Expand Down Expand Up @@ -183,6 +211,39 @@ public static function urlHasSubDir(string $url): bool
return !empty(parse_url(trim($url, '/'), PHP_URL_PATH));
}

/**
* Return the siteUrlOverride setting, which can be a string or an array of site URLs
* indexed by the site handle
*
* @param int|null $siteId
* @return string
* @throws Exception
* @throws SiteNotFoundException
*/
public static function getSiteUrlOverrideSetting(?int $siteId = null): string
{
// If the override is a string, just return it
$siteUrlOverride = Seomatic::$settings->siteUrlOverride;
if (is_string($siteUrlOverride)) {
return $siteUrlOverride;
}
// If the override is an array, pluck the appropriate one by handle
if (is_array($siteUrlOverride)) {
$sites = Craft::$app->getSites();
$site = $sites->getCurrentSite();
if ($siteId !== null) {
$site = $sites->getSiteById($siteId, true);
if (!$site) {
throw new Exception('Invalid site ID: ' . $siteId);
}
}

return $siteUrlOverride[$site->handle] ?? '';
}

return '';
}

// Protected Methods
// =========================================================================

Expand Down
11 changes: 8 additions & 3 deletions src/models/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,16 @@ class Settings extends VarsModel
public $generatorEnabled = true;

/**
* @var string
* @var string|array
* SEOmatic uses the Craft `siteUrl` to generate the external URLs. If you
* are using it in a non-standard environment, such as a headless GraphQL or
* ElementAPI server, you can override what it uses for the `siteUrl` below.
*/
* This can be either a simple string, or an array of strings indexed by the site
* handle, for multi-site setups. e.g.:
* 'siteUrlOverride' => [
* 'default' => 'http://example.com/',
* 'spanish' => 'http://example.com/es/',
* ], */
public $siteUrlOverride = '';

/**
Expand Down Expand Up @@ -308,7 +313,7 @@ public function rules(): array
['maxTitleLength', 'default', 'value' => 70],
['maxDescriptionLength', 'integer', 'min' => 10],
['maxDescriptionLength', 'default', 'value' => 155],
['siteUrlOverride', 'string'],
['siteUrlOverride', 'safe'],
['siteUrlOverride', 'default', 'value' => ''],
[
[
Expand Down
4 changes: 2 additions & 2 deletions src/services/MetaBundles.php
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ public function invalidateMetaBundleByElement($element, bool $isNew = false)
$this->updateMetaBundle($metaBundle, $sourceSiteId);
if ($metaBundle
&& $metaBundle->metaSitemapVars->sitemapUrls
&& $element->scenario !== Element::SCENARIO_ESSENTIALS
&& !$element->resaving
&& Seomatic::$settings->regenerateSitemapsAutomatically) {
$sitemapInvalidated = true;
Seomatic::$plugin->sitemaps->invalidateSitemapCache(
Expand All @@ -763,7 +763,7 @@ public function invalidateMetaBundleByElement($element, bool $isNew = false)
// If we've invalidated a meta bundle, we need to invalidate the sitemap index, too
if ($metaBundleInvalidated
&& $sitemapInvalidated
&& $element->scenario !== Element::SCENARIO_ESSENTIALS) {
&& !$element->resaving) {
Seomatic::$plugin->sitemaps->invalidateSitemapIndexCache();
}
}
Expand Down
1 change: 0 additions & 1 deletion src/services/Sitemaps.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ class Sitemaps extends Component implements SitemapInterface
const SEOMATIC_SITEMAPCUSTOM_CONTAINER = Seomatic::SEOMATIC_HANDLE . SitemapCustomTemplate::TEMPLATE_TYPE;

const SEARCH_ENGINE_SUBMISSION_URLS = [
'google' => 'https://www.google.com/ping?sitemap=',
];

// Protected Properties
Expand Down
Loading

0 comments on commit cc2dc54

Please sign in to comment.