Code Monkey home page Code Monkey logo

seomate's Introduction

SEOMate plugin for Craft CMS

SEO, mate! It's important. That's why SEOMate provides the tools you need to craft all the meta tags, sitemaps and JSON-LD microdata you need - in one highly configurable, open and friendly package - with a super-light footprint.

SEOMate aims to do less! Unlike other SEO plugins for Craft, there are no control panel settings or fieldtypes. Instead, you configure everything from the plugin's config file, which makes it easy and quick to set up, bootstrap and version control your configuration. All the data is pulled from native Craft fields, which makes for less maintenance over time, and keeps you in control of your data.

Additionally, SEOMate adds a super-awesome SEO/social media preview to your Control Panel. The SEO preview taps into Craft's native Preview Targets, giving your clients a nice and familiar interface for previewing how their content will appear on Google, Facebook and Twitter.

Screenshot

Requirements

This plugin requires Craft CMS 5.0.0 or later.

Installation

To install the plugin, either install it from the plugin store, or follow these instructions:

  1. Install with composer via composer require vaersaagod/seomate from your project directory.
  2. Install the plugin in the Craft Control Panel under Settings → Plugins, or from the command line via ./craft install/plugin seomate.
  3. For SEOMate to do anything, you need to configure it. But first, continue reading!

SEOMate Overview

SEOMate focuses on providing developers with the tools they need to craft their site's SEO in three main areas; meta data, sitemaps, and JSON-LD microdata.

Meta data

SEOMate doesn't provide any custom field types for entering meta data. Instead, you use native field types that come with Craft, and just tell SEOMate which fields to use.

You do this by configuring field profiles for the different field setups in your site. Sections and category groups can be mapped to these profiles, or the desired profile can be set at the template level.

The key config settings for meta data is fieldProfiles, profileMap, defaultProfile, defaultMeta and additionalMeta. Refer to the "Adding meta data" section on how to include the meta data in your page, and how to (optionally) override it at the template level.

Sitemaps

SEOMate lets you create completely configuration based sitemaps for all your content. The sitemaps are automatically updated with new elements, and will automatically be split into multiple sitemaps for scalability.

To enable sitemaps, set the sitemapEnabled config setting to true and configure the contents of your sitemaps with sitemapConfig. Refer to the "Enabling sitemaps" section on how to enable and set up your sitemaps.

JSON-LD

SEOMate provides a thin wrapper around the excellent spatie/schema-org package used for generating JSON-LD data structures. SEOMate exposes the craft.schema template variable, that directly ties into the fluent Schema API.

This method uses the exact same approach and signature as Rias' Schema plugin. If you're only looking for a way to output JSON-LD, we suggest you use that plugin instead.

SEO preview

SEOMate provides a fancy "SEO Preview" preview target, for any and all elements with URLs, featuring photo realistic approximations of how your content will appear in Google SERPs, or when shared on Facebook, Twitter/X and LinkedIn.

img.png

If you don't like the SEO preview, or if you'd like it to only appear for entries in specific sections, check out the previewEnabled config setting.

Things that SEOMate doesn't do...

So much!


Adding meta data

Out of the box, SEOMate doesn't add anything to your markup. To get started, add {% hook 'seomateMeta' %} to the <head> of your layout. Then, if you haven't already, create a config file named seomate.php in your config folder alongside your other Craft config files. This file can use multi-environment configs, exactly the same as any other config file in Craft, but in the following examples we'll skip that part to keep things a bit more tidy.

All the config settings are documented in the Configuring section, and there are quite a few! But to get you going, these are some fundamental concepts:

Field profiles

A field profile in SEOMate is, essentially, a mapping of metadata attributes to the fields that SEOMate should look at for those attributes' metadata values.

To get started, create a profile called "standard" in fieldProfiles, and set that profile as the default field profile using the defaultProfile setting:

<?php

return [
    'defaultProfile' => 'standard',
    
    'fieldProfiles' => [
        'standard' => [
            'title' => ['seoTitle', 'heading', 'title'],
            'description' => ['seoDescription', 'summary'],
            'image' => ['seoImage', 'mainImage']
        ]
    ],
];

The above tells SEOMate to use the field profile standard to get element metadata from, as a default. So, everytime a page template that has an element (i.e. entry, category or product) is loaded, SEOMate will start by checking if that element has a field named seoTitle, and that this field has a value that can be used for the title meta tag. If a field named seoTitle does not exist – or if it's empty – SEOMate continues to check if there is a field named heading, and does the same thing. If heading is empty, it checks for title.
And so on, for every key in the field profile.

💡 In addition to field handles, field profiles can also contain functions (i.e. closures), and/or
Twig object templates. For documentation and examples for closures and object templates,
see the fieldProfiles setting!

Mapping different field profiles to elements

Now, let's say we have a section with handle news that has a slightly different field setup than our other sections, so for entries in that section we want to pull data from some other fields. We'll add another field profile to fieldProfiles, and make a mapping between the profile and the section handle in profileMap:

<?php

return [
    'defaultProfile' => 'standard',
    
    'fieldProfiles' => [
        'standard' => [
            'title' => ['seoTitle', 'heading', 'title'],
            'description' => ['seoDescription', 'summary'],
            'image' => ['seoImage', 'mainImage']
        ],
        'newsprofile' => [
            'title' => ['seoTitle', 'heading', 'title'],
            'og:title' => ['ogTitle', 'heading', 'title'],
            'description' => ['seoDescription', 'newsExcerpt', 'introText'],
            'image' => ['seoImage', 'heroImage', 'newsBlocks.image:image']
            'og:image' => ['ogImage', 'heroImage', 'newsBlocks.image:image']
            'twitter:image' => ['twitterImage', 'heroImage', 'newsBlocks.image:image']
        ]
    ],
    
    'profileMap' => [
        'news' => 'newsprofile',
    ],    
];

The mapping between the "news" section and the profile is simple enough: the key in profileMap can be the handle for a section, entry type, category group, or Commerce product type, and the value should be the key for the profile in fieldProfiles that you want to use for matching elements.

In this profile we also we have also used a couple of other SEOMate features.

First, notice that we have chosen to specify a field profile for og:title, og:image and twitter:image that we didn't have in the default profile. By default, the autofillMap defines that if no value are set for og:title and twitter:title, we want to autofill those meta tags with the value from title. So in the standard profile, those values will be autofilled, while in the newsprofile we choose to customize some of them.

Secondly, we can specify to pull a value from a Matrix subfield by using the syntax matrixFieldHandle.blockTypeHandle:subFieldHandle.

Profile map specificity

In some cases there might be a need to create more specific field profile mappings. For example, you might have a section with the handle news and a category group with the handle news, and you need their elements to use different profiles. This can be achieved by using prefixes section: and/or categoryGroup:, e.g.

'profileMap' => [
   'section:news' => 'newsprofile', // Will match entries in a section "news"
   'categoryGroup:news' => 'newscategoryprofile', // Will match categories in a group "news"
],

Another use case for specific field profiles is if you need a certain entry type to use a specific profile, in which case the entryType: prefix is the ticket:

'profileMap' => [
   'section:news' => 'newsprofile', // Will match entries in a section "news"
   'categoryGroup:news' => 'newscategoryprofile', // Will match categories in a group "news"
   'pages' => 'pagesprofile',
   'entryType:listPage' => 'listpageprofile', // Will match entries with an entry type "listPage"
],

The specific field profiles (i.e. the ones using the {sourceType}: prefix) will take precedence over unspecific ones. That means that – with the above config – entries in a section "page" will use the "pagesprofile" profile, unless they're using an entry type with the handle listPage, in which case the "listpageprofile" profile will be used. And, the "listpageprofile" will also be used for entries in other sections, if they're using that same entry type.

The following field profile specificity prefixes are supported:

  • Entries: section:{sectionHandle} and entryType:{entryTypeHandle}
  • Categories: categoryGroup:{categoryGroupHandle}
  • Commerce products: productType:{productTypeHandle}
  • Users: user

Default meta data

Field profiles are great for templates that have an element associated with them. But what about the ones that don't? Or – what if there is no valid image in any of those image fields defined in the matching field profile?
This is where defaultMeta comes into play. Let's say that we have a global set with handle globalSeo, with fields that we want to fall back on if everything else fails:

<?php

return [
    'defaultMeta' => [
        'title' => ['globalSeo.seoTitle'],
        'description' => ['globalSeo.seoDescription'],
        'image' => ['globalSeo.seoImages']
    ],
        
    'defaultProfile' => 'standard',
        
    'fieldProfiles' => [
        'standard' => [
            'title' => ['seoTitle', 'heading', 'title'],
            'description' => ['seoDescription', 'summary'],
            'image' => ['seoImage', 'mainImage']
        ],
        'newsprofile' => [
            'title' => ['seoTitle', 'heading', 'title'],
            'og:title' => ['ogTitle', 'heading', 'title'],
            'description' => ['seoDescription', 'newsExcerpt', 'introText'],
            'image' => ['seoImage', 'heroImage', 'newsBlocks.image:image']
            'og:image' => ['ogImage', 'heroImage', 'newsBlocks.image:image']
            'twitter:image' => ['twitterImage', 'heroImage', 'newsBlocks.image:image']
        ]
    ],
    
    'profileMap' => [
        'news' => 'newsprofile',
    ],    
];

The defaultMeta setting works almost exactly the same as fieldProfiles, except that it looks for objects and fields in you current Twig context, hence the use of globals.

Additional meta data

Lastly, we want to add some additional metadata like og:type and twitter:card, and for that we have... additionalMeta:

<?php

return [
    'defaultMeta' => [
        'title' => ['globalSeo.seoTitle'],
        'description' => ['globalSeo.seoDescription'],
        'image' => ['globalSeo.seoImages']
    ],
        
    'defaultProfile' => 'standard',
        
    'fieldProfiles' => [
        'standard' => [
            'title' => ['seoTitle', 'heading', 'title'],
            'description' => ['seoDescription', 'summary'],
            'image' => ['seoImage', 'mainImage']
        ],
        'newsprofile' => [
            'title' => ['seoTitle', 'heading', 'title'],
            'og:title' => ['ogTitle', 'heading', 'title'],
            'description' => ['seoDescription', 'newsExcerpt', 'introText'],
            'image' => ['seoImage', 'heroImage', 'newsBlocks.image:image']
            'og:image' => ['ogImage', 'heroImage', 'newsBlocks.image:image']
            'twitter:image' => ['twitterImage', 'heroImage', 'newsBlocks.image:image']
        ]
    ],
    
    'profileMap' => [
        'news' => 'newsprofile',
    ],
    
    'additionalMeta' => [
        'og:type' => 'website',
        'twitter:card' => 'summary_large_image',
        
        'fb:profile_id' => '{{ settings.facebookProfileId }}',
        'twitter:site' => '@{{ settings.twitterHandle }}',
        'twitter:author' => '@{{ settings.twitterHandle }}',
        'twitter:creator' => '@{{ settings.twitterHandle }}',
        
        'og:see_also' => function ($context) {
            $someLinks = [];
            $matrixBlocks = $context['globalSeo']?->someLinks?->all();
            
            if (!empty($matrixBlocks)) {
                foreach ($matrixBlocks as $matrixBlock) {
                    $someLinks[] = $matrixBlock->someLinkUrl ?? '';
                }
            }
            
            return $someLinks;
        },
    ],
];

The additionalMeta setting takes either a string or an array, or a function that returns either of those, as the value for each property. Any Twig in the values are parsed, in the current context.

Customizing the meta output template

SEOMate comes with a generic template that outputs the meta data it generates. You can override this with your own template using the metaTemplate config setting.

Overriding meta data and settings from your templates

You can override the metadata and config settings directly from your templates by creating a seomate object and overriding accordingly:

{% set seomate = {
    profile: 'specialProfile',
    element: craft.entries.section('newsListing').one(),
    canonicalUrl: someOtherUrl,
    
    config: {
        includeSitenameInTitle: false
    },
    
    meta: {
        title: 'Custom title',
        'twitter:author': '@someauthor'     
    },
} %}

All relevant config settings can be overridden inside the config key, and all metadata inside the meta key. You can also tell seomate to use a specific profile with the profile setting. And to use some other element as the base element to get metadata from, or provide one if the current template doesn't have one, in the element key. And you can customize the canonicalUrl as needed. And... more.


Enabling sitemaps

To enable sitemaps for your site, you need to set the sitemapEnabled config setting to true, and configure the contents of your sitemaps with sitemapConfig. In its simplest form, you can supply an array of section handles to the elements key, with the sitemap settings you want:

'sitemapEnabled' => true,
'sitemapLimit' => 100,
'sitemapConfig' => [
    'elements' => [
        'news' => ['changefreq' => 'weekly', 'priority' => 1],
        'projects' => ['changefreq' => 'weekly', 'priority' => 0.5],
    ],
],

A sitemap index will be created at sitemap.xml at the root of your site, with links to sitemaps for each section, split into chunks based on sitemapLimit.

You can also do more complex element criterias, and manually add custom paths:

'sitemapEnabled' => true,
'sitemapLimit' => 100,
'sitemapConfig' => [
    'elements' => [
        'news' => ['changefreq' => 'weekly', 'priority' => 1],
        'projects' => ['changefreq' => 'weekly', 'priority' => 0.5],
        'frontpages' => [
            'elementType' => \craft\elements\Entry::class,
            'criteria' => ['section' => ['homepage', 'newsFrontpage', 'projectsFrontpage']],
            'params' => ['changefreq' => 'daily', 'priority' => 1],
        ],
        'newscategories' => [
            'elementType' => \craft\elements\Category::class,
            'criteria' => ['group' => 'newsCategories'],
            'params' => ['changefreq' => 'weekly', 'priority' => 0.2],
        ],
        'semisecret' => [
            'elementType' => \craft\elements\Entry::class,
            'criteria' => ['section' => 'semiSecret', 'notThatSecret' => true],
            'params' => ['changefreq' => 'daily', 'priority' => 0.5],
        ],
    ],
    'custom' => [
        '/cookies' => ['changefreq' => 'weekly', 'priority' => 1],
        '/terms-and-conditions' => ['changefreq' => 'weekly', 'priority' => 1],
    ],
],

Using the expanded criteria syntax, you can add whatever elements to your sitemaps.

Multi-site sitemaps

For multi-site installs, SEOMate will automatically create sitemaps for each site. If the outputAlternate config setting is enabled, sitemaps will include alternate URLs in xhtml:link entries.


Configuring

SEOMate can be configured by creating a file named seomate.php in your Craft config folder, and overriding settings as needed.

cacheEnabled [bool]

Default: 'true'
Enables/disables caching of generated metadata. The cached data will be automatically cleared when an element is saved. To clear the metadata cache manually, Craft's "Clear Caches" CP utility can be used, or the core clear-caches CLI command.

cacheDuration [int|string]

Default: 3600
Duration of meta cache in seconds. Can be set to an integer (seconds), or a valid PHP date interval string (e.g. 'PT1H').

previewEnabled [bool|array]

Default: true
Enable the "SEO Preview" preview target in the Control Panel everywhere (true), nowhere (false) or only for particular sections, category groups, entry types or Commerce product types (array of section and/or category group handles; e.g. ['news', 'events', 'homepage', 'section:blog', 'entryType:listPage'], etc).
Regardless of this config setting, the "SEO Preview" preview target is only ever added to sections and category groups with URLs.

previewLabel [string|null]

Default: "SEO Preview"
Defines the text label for the "SEO Preview" button and preview target inside the Control Panel.

siteName [string|array|null]

Default: null
Defines the site name to be used in metadata. Can be a plain string, or an array with site handles as keys. Example:

'siteName' => 'My site'

// or

'siteName' => [
    'default' => 'My site',
    'other' => 'Another site',
]

If not set, SEOMate will try to get any site name defined in Craft's general config for the current site. If that doesn't work, the current site's name will be used.

metaTemplate [string]

Default: ''
SEOMate comes with a default meta template the outputs the configured meta tags. But, every project is different, so if you want to customize the output you can use this setting to provide a custom template (it needs to be in your site's template path).

includeSitenameInTitle [bool]

Default: true
Enables/disabled if the site name should be displayed as part of the meta title.

sitenameTitleProperties [array]

Default: ['title']
Defines which meta title properties the site name should be added to. By default, the site name is only added to the title meta tag.

Example that also adds it to og:title and twitter:title tags:

'sitenameTitleProperties' => ['title', 'og:title', 'twitter:title']

sitenamePosition [string]

Default: 'after'
Defines if the site name should be placed before or after the rest of the meta content.

sitenameSeparator [string]

Default: '|'
The separator between the meta tag content and the site name.

outputAlternate [bool|Closure]

Default: true
Enables/disables output of alternate URLs in meta tags and sitemaps.

Alternate URLs are meant to provide search engines with alternate URLs
for localized versions of the current page's content.

If you have a normal multi-locale website, you'll probably want to leave this setting enabled (i.e. set to true). However, if you're running a multi-site website where the
sites are distinct, you'll might want to set it to false, to prevent alternate URLs
from being output at all.

For the Advanced Use Case (tm) – e.g. multi-sites that have a mix of translated and
distinct content
, it's also possible to break free from the shackles of the binary boolean,
and configure the outputAlternate setting with a closure function (that returns either true
or false).

The outputAlternate closure will receive two parameters; $element (the current element) and
$alternateElement (the element from a different site, i.e. the potential alternate). This makes
it possible to compose custom logic, in order to determine if that alternate element's URL
should be output or not.

An example: the below closure would make SEOMate only output alternate URLs if the language for
the alternate element is different from the element's language:

'outputAlternate' => static fn($element, $alternateElement) => $element->language !== $alternateElement->language,

If this closure returns true, SEOMate will create an alternate URL for the $alternateElement.
If it returns false (or any other falsey value), SEOMate will quietly pretend the $alternateElement
does not exist.

For more information about alternate URLs, refer to this article.

alternateFallbackSiteHandle [string|null]

Default: null
Sets the site handle for the site that should be the fallback for unmatched languages, ie the alternate URL with hreflang="x-default".

Usually, this should be the globabl site that doesn't target a specific country. Or a site with a holding page where the user can select language. For more information about alternate URLs, (refer to this article)[https://support.google.com/webmasters/answer/189077].

altTextFieldHandle [string|null]

Default: null
If you have a field for alternate text on your assets, you should set this to your field's handle. This will pull and output the text for the og:image:alt and twitter:image:alt properties.

defaultProfile [string|null]

Default: ''
Sets the default meta data profile to use (see the fieldProfiles config setting).

fieldProfiles [array]

Default: []
Field profiles defines "waterfalls" for which fields should be used to fill which meta tags. You can have as many or as few profiles as you want. You can define a default profile using the defaultProfile setting, and you can map your sections and category groups using the profileMap setting. You can also override which profile to use, directly from your templates.

Example:

'defaultProfile' => 'default',

'fieldProfiles' => [
    'default' => [
        'title' => ['seoTitle', 'heading', 'title'],
        'description' => ['seoDescription', 'summary'],
        'image' => ['seoImage', 'mainImage']
    ],
    'products' => [
        'title' => ['seoTitle', 'heading', 'title'],
        'description' => ['seoDescription', 'productDescription', 'summary'],
        'image' => ['seoImage', 'mainImage', 'heroMedia:media.image']
    ],
    'landingPages' => [
        'title' => ['seoTitle', 'heading', 'title'],
        'description' => ['seoDescription'],
        'image' => ['seoImage', 'heroArea:video.image', 'heroArea:singleImage.image', 'heroArea:twoImages.images', 'heroArea:slideshow.images']
    ],
],

Field waterfalls are parsed from left to right. Empty or missing values are ignored, and SEOMate continues to look for a valid value in the next field.

Closures and object templates

In addition to field handle references, field profiles can also contain functions (i.e. closures) and/or Twig object templates.

Field profile closures take a single argument $element (i.e. the element SEOMate is rendering meta data for).
Here's how a closure can look inside a field profile:

'fieldProfiles' => [
    'default' => [
        'title' => ['seoTitle', static function ($element) { return "$element->title - ($element->productCode)"; }],
    ],
]

Generally, closures should return a string value (or null). The exception is image meta tags
(e.g. 'image', 'og:image', etc.), where SEOMate will expect an asset (or null) returned:

'fieldProfiles' => [
    'default' => [
        'image' => [static function ($element) { return $element->seoImage->one() ?? null; }],
    ],
]

Object templates are well documented in the official Craft docs.
Here's how they can be used in field profiles (the two examples are using short- and longhand syntaxes, respectively):

'fieldProfiles' => [
    'default' => [
        'title' => ['seoTitle', '{title} - ({productCode})', '{{ object.title }} - ({{ object.productCode }})'],
    ],
]

Object templates can only render strings, which make them less useful for image meta tags (that expect an asset returned).
But if you really want to, you can render an asset ID, which SEOMate will use to query for the actual asset:

'defaultMeta' => [
    'default' => [
        'image' => ['{seoImage.one().id}'],
    ],
]

profileMap [array]

Default: []
The profile map provides a way to map elements to different field profiles defined in fieldProfiles, via their sections, entry types, category groups and Commerce product types. If no matching profile in this mapping is found, the profile defined in defaultProfile will be used.

The keys in the profileMap should be a string containing one or several (comma-separated) element source handles, such as a section handle, entry type handle, category group handle or Commerce product type handle. These keys can be specific, such as section:news (to explicitly match entries belonging to a "news" section) or unspecific, such as simply news (which would match elements belong to either a section, entry type, category group or product type with the handle 'news').

Keys in profileMap are matched to elements from most to least specific, e.g. for an element with an entry type listPage, if the profileMap contained both a listPage and an entryType:listPage key, the latter would be used for that element.

The following field profile specificity prefixes are supported:

  • Entries: section:{sectionHandle} and entryType:{entryTypeHandle}
  • Categories: categoryGroup:{categoryGroupHandle}
  • Commerce products: productType:{productTypeHandle}
  • Users: user

Example:

'profileMap' => [
    'news' => 'newsProfile',
    'section:products' => 'productsProfile',
    'section:frontpage,section:campaigns' => 'landingPagesProfile',
    'entryType:listPage' => 'listPageProfile',
    'categoryGroup:newsCategories' => 'newsCategoriesProfile',
],

defaultMeta [array]

Default: []
This setting defines the default meta data that will be used if no valid meta data was found for the current element (ie, none of the fields provided in the field profile existed, or they all had empty values).

The waterfall looks for meta data in the global Twig context. In the example below, we're falling back to using fields in two global sets, with handles globalSeo and settings respectively:

'defaultMeta' => [
    'title' => ['globalSeo.seoTitle'],
    'description' => ['globalSeo.seoDescription', 'settings.companyInfo'],
    'image' => ['globalSeo.seoImages']
],

Closures and object templates

In addition to field handle references, defaultMeta can also contain functions (i.e. closures) and/or Twig object templates.

Field profile closures take a single argument $context (i.e. an array; the global Twig context).
Here's how a closure can look inside defaultMeta:

'defaultMeta' => [
    'title' => [static function ($context) { return $context['siteName'] . ' is awesome!'; }],
]

Generally, closures should return a string value (or null). The exception is image meta tags
(e.g. 'image', 'og:image', etc.), where SEOMate will expect an asset (or null) returned:

'defaultMeta' => [
    'image' => [static function ($context) { return $context['defaultSeoImage']->one() ?? null; }],
]

Object templates are well documented in the official Craft docs.
Here's how they can be used in defaultMeta (note that for defaultMeta, the object variable refers to the global
Twig context):

'defaultMeta' => [
    'title' => ['{siteName} is awesome!', '{{ object.siteName }} is awesome!'],
]

Object templates can only render strings, which make them less useful for image meta tags (that expect an asset returned).
But if you really want to, you can render an asset ID, which SEOMate will use to query for the actual asset:

'defaultMeta' => [
    'image' => ['{defaultSeoImage.one().id}'],
]

additionalMeta [array]

Default: []

The additional meta setting defines all other meta data that you want SEOMate to output. This is a convenient way to add more global meta data, that is used throughout the site. Please note that you don't have to use this, you could also just add the meta data directly to your meta, or html head, template.

The key defines the meta data property to output, and the value could be either a plain text, some twig that will be parsed based on the current context, an array which will result in multiple tags of this property being output, or a function.

In the example below, some properties are plain text (og:type and twitter:card), some contains twig (for instance fb:profile_id), and for og:see_also we provide a function that returns an array.

'additionalMeta' => [
    'og:type' => 'website',
    'twitter:card' => 'summary_large_image',
    
    'fb:profile_id' => '{{ settings.facebookProfileId }}',
    'twitter:site' => '@{{ settings.twitterHandle }}',
    'twitter:author' => '@{{ settings.twitterHandle }}',
    'twitter:creator' => '@{{ settings.twitterHandle }}',
    
    'og:see_also' => function ($context) {
        $someLinks = [];
        $matrixBlocks = $context['globalSeo']->someLinks->all() ?? null;
        
        if ($matrixBlocks && count($matrixBlocks) > 0) {
            foreach ($matrixBlocks as $matrixBlock) {
                $someLinks[] = $matrixBlock->someLinkUrl ?? '';
            }
        }
        
        return $someLinks;
    },
],

metaPropertyTypes [array]

Default: (see below)
This setting defines the type and limitations of the different meta tags. Currently, there are two valid types, text and image.

Example/default value:

[
    'title,og:title,twitter:title' => [
        'type' => 'text',
        'minLength' => 10,
        'maxLength' => 60
    ],
    'description,og:description,twitter:description' => [
        'type' => 'text',
        'minLength' => 50,
        'maxLength' => 300
    ],
    'image,og:image,twitter:image' => [
        'type' => 'image'
    ],
]

applyRestrictions [bool]

Default: false
Enables/disables enforcing of restrictions defined in metaPropertyTypes.

validImageExtensions [array]

Default: ['jpg', 'jpeg', 'gif', 'png']
Valid filename extensions for image property types.

truncateSuffix [string]

Default: '…'
Suffix to add to truncated meta values.

returnImageAsset [bool]

Default: false
By default, assets will be transformed by SEOMate, and the resulting URL is cached and passed to the template.

By enabling this setting, the asset itself will instead be returned to the template. This can be useful if you want to perform more complex transforms, or output more meta tags where you need more asset data, that can only be done at the template level. Please note that you'll probably want to provide a custom metaTemplate, and that caching will not work (you should instead use your own template caching).

useImagerIfInstalled [bool]

Default: true
If Imager is installed, SEOMate will automatically use it for transforms (they're mates!), but you can disable this setting to use native Craft transforms instead.

imageTransformMap [array]

Default: (see below)
Defines the image transforms that are to be used for the different meta image properties. All possible options of Imager or native Craft transforms can be used.

Default value:

[
    'image' => [
        'width' => 1200,
        'height' => 675,
        'format' => 'jpg',
    ],
    'og:image' => [
        'width' => 1200,
        'height' => 630,
        'format' => 'jpg',
    ],
    'twitter:image' => [
        'width' => 1200,
        'height' => 600,
        'format' => 'jpg',
    ],
]

Example where the Facebook and Twitter images has been sharpened, desaturated and given a stylish blue tint (requires Imager):

'imageTransformMap' => [
    'image' => [
        'width' => 1200,
        'height' => 675,
        'format' => 'jpg'
    ],
    'og:image' => [
        'width' => 1200,
        'height' => 630,
        'format' => 'jpg',
        'effects' => [
            'sharpen' => true,
            'modulate' => [100, 0, 100], 
            'colorBlend' => ['rgb(0, 0, 255)', 0.5]
        ]
    ],
    'twitter:image' => [
        'width' => 1200,
        'height' => 600,
        'format' => 'jpg',
        'effects' => [
            'sharpen' => true,
            'modulate' => [100, 0, 100], 
            'colorBlend' => ['rgb(0, 0, 255)', 0.5]
        ]
    ],
],

autofillMap [array]

Default: (see below)
Map of properties that should be automatically filled by another property, if they're empty after the profile has been parsed.

Default value:

[
    'og:title' => 'title',
    'og:description' => 'description',
    'og:image' => 'image',
    'twitter:title' => 'title',
    'twitter:description' => 'description',
    'twitter:image' => 'image',
]

tagTemplateMap [array]

Default: (see below)
Map of output templates for the meta properties.

Example/default value:

[
    'default' => '<meta name="{{ key }}" content="{{ value }}">',
    'title' => '<title>{{ value }}</title>',
    '/^og:/,/^fb:/' => '<meta property="{{ key }}" content="{{ value }}">',
]

sitemapEnabled [bool]

Default: false
Enables/disables sitemaps.

sitemapName [string]

Default: 'sitemap'
Name of sitemap. By default it will be called sitemap.xml.

sitemapLimit [int]

Default: 500
Number of URLs per sitemap. SEOMate will automatically make a sitemap index and split up your sitemap into chunks with a maximum number of URLs as per this setting. A lower number could ease the load on your server when the sitemap is being generated.

sitemapConfig [array]

Default: []
Defines the content of the sitemaps. The configuration consists of two main keys, elements and custom. In elements, you can define sitemaps that will automatically query for elements in certain sections or based on custom criterias.

In custom you add paths that are added to a separate custom sitemap, and you may also add links to manually generated sitemaps in additionalSitemaps. Both of these settings can be a flat array of custom urls or sitemap paths that you want to add, or a nested array where the keys are site handles, to specify custom urls/sitemaps that are site specific, or '*', for additional ones. See the example below.

In the example below, we get all elements from the sections with handles projects and news, query for entries in four specific sections and all categories in group newsCategories. In addition to these, we add two custom urls, and two additional sitemaps.

'sitemapConfig' => [
    'elements' => [
        'projects' => ['changefreq' => 'weekly', 'priority' => 0.5],
        'news' => ['changefreq' => 'weekly', 'priority' => 0.5],
        
        'indexpages' => [
            'elementType' => \craft\elements\Entry::class,
            'criteria' => ['section' => ['frontpage', 'newsListPage', 'membersListPage', 'aboutPage']],
            'params' => ['changefreq' => 'daily', 'priority' => 0.5],
        ],
        'newscategories' => [
            'elementType' => \craft\elements\Category::class,
            'criteria' => ['group' => 'newsCategories'],
            'params' => ['changefreq' => 'weekly', 'priority' => 0.2],
        ],
    ],
    'custom' => [
        '/custom-1' => ['changefreq' => 'weekly', 'priority' => 1],
        '/custom-2' => ['changefreq' => 'weekly', 'priority' => 1],
    ],
    'additionalSitemaps' => [
        '/sitemap-from-other-plugin.xml',
        '/manually-generated-sitemap.xml'
    ]
],

Example with site specific custom urls and additional sitemaps:

'sitemapConfig' => [
    /* ... */ 
    
    'custom' => [
        '*' => [
            '/custom-global-1' => ['changefreq' => 'weekly', 'priority' => 1],
            '/custom-global-2' => ['changefreq' => 'weekly', 'priority' => 1],
        ],
        'english' => [
            '/custom-english' => ['changefreq' => 'weekly', 'priority' => 1],
        ],
        'norwegian' => [
            '/custom-norwegian' => ['changefreq' => 'weekly', 'priority' => 1],
        ]
    ],
    'additionalSitemaps' => [
        '*' => [
            '/sitemap-from-other-plugin.xml',
            '/sitemap-from-another-plugin.xml',
        ],
        'english' => [
            '/manually-generated-english-sitemap.xml',
        ],
        'norwegian' => [
            '/manually-generated-norwegian-sitemap.xml',
        ]
    ]
],

Using the expanded criteria syntax, you can query for whichever type of element, as long as they are registered as a valid element type in Craft.

The main sitemap index will be available on the root of your site, and named according to the sitemapName config setting (sitemap.xml by default). The actual sitemaps will be named using the pattern sitemap_<elementKey>_<page>.xml for elements and sitemap_custom.xml for the custom urls.

sitemapSubmitUrlPatterns [array]

Default: (see below) URL patterns that your sitemaps are submitted to.

Example/default value:

'sitemapSubmitUrlPatterns' => [
    'http://www.google.com/webmasters/sitemaps/ping?sitemap=',
    'http://www.bing.com/webmaster/ping.aspx?siteMap=',
];

Template variables

craft.seomate.getMeta([config=[]])

Returns an object with the same meta data that is passed to the meta data template.

{% set metaData = craft.seomate.getMeta() %}
Meta Title: {{ metaData.meta.title }} 
Canonical URL: {{ metaData.canonicalUrl }} 

You can optionally pass in a config object the same way you would in your template overrides, to customize the data, or use a custom element as the source:

{% set metaData = craft.seomate.getMeta({
    profile: 'specialProfile',
    element: craft.entries.section('newsListing').one(),
    canonicalUrl: someOtherUrl,
    
    config: {
        includeSitenameInTitle: false
    },
    
    meta: {
        title: 'Custom title',
        'twitter:author': '@someauthor'     
    },
}) %}

craft.schema

You can access all the different schemas in the spatie/schema-org package through this variable endpoint. If you're using PHPStorm and the Symfony plugin, you can get full autocompletion by assigning type hinting (see example below)

Example:

{# @var schema \Spatie\SchemaOrg\Schema #}
{% set schema = craft.schema %}

{{ schema.recipe
    .dateCreated(entry.dateCreated)
    .dateModified(entry.dateUpdated)
    .datePublished(entry.postDate)
    .copyrightYear(entry.postDate | date('Y'))
    .name(entry.title)
    .headline(entry.title)
    .description(entry.summary | striptags)
    .url(entry.url)
    .mainEntityOfPage(entry.url)
    .inLanguage('nb_no')
    .author(schema.organization
        .name('The Happy Chef')
        .url('https://www.thehappychef.xyz/')
    )
    .recipeCategory(categories)
    .recipeCuisine(entry.cuisine)
    .keywords(ingredientCategories | merge(categories) | join(', '))
    .recipeIngredient(ingredients)
    .recipeInstructions(steps)
    .recipeYield(entry.portions ~ ' porsjoner')
    .cookTime('PT'~entry.cookTime~'M')
    .prepTime('PT'~entry.prepTime~'M')
    .image(schema.imageObject
        .url(image.url)
        .width(schema.QuantitativeValue.value(image.getWidth()))
        .height(schema.QuantitativeValue.value(image.getHeight()))
    )
| raw }}

Again, if you're only looking for a way to output JSON-LD, we suggest you use Rias' Schema plugin instead.

craft.seomate.renderMetaTag(key, value)

Renders a meta tag based on key and value. Uses the tagTemplateMap config setting to determine how the markup should look.

Does exactly the same thing as the renderMetaTag twig function.

craft.seomate.breadcrumbSchema(breadcrumb)

A convenient method for outputting a JSON-LD breadcrumb. The method takes an array of objects with properties for url and name, and outputs a valid Schema.org JSON-LD data structure.

Example:

{% set breadcrumb = [
    {
        'url': siteUrl,
        'name': 'Frontpage'
    },
    {
        'url': currentCategory.url,
        'name': currentCategory.title
    },
    {
        'url': entry.url,
        'name': entry.title
    }
] %}

{{ craft.seomate.breadcrumbSchema(breadcrumb) }}

Twig functions

renderMetaTag(key, value)

Renders a meta tag based on key and value. Uses the tagTemplateMap config setting to determine how the markup should look.

Does exactly the same thing as the craft.seomate.renderMetaTag template variable.


Price, license and support

The plugin is released under the MIT license, meaning you can do what ever you want with it as long as you don't blame us. It's free, which means there is absolutely no support included, but you might get it anyway. Just post an issue here on github if you have one, and we'll see what we can do.

Changelog

See CHANGELOG.MD.

Credits

Brought to you by Værsågod

Icon designed by Freepik from Flaticon.

seomate's People

Contributors

aelvan avatar daltonrooney avatar dgsiegel avatar jrm98 avatar mmikkel avatar mosnar avatar nkpindolia avatar rungta avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

seomate's Issues

Template override not working on multi locale sites

I have two locale sites and the default site is fine with template override but another site ignores the override.

Override code is something like below before {% hook 'seomateMeta' %}

{% set seomate = {
    meta: {
      description: 'test',
    }
  } %}

PHP version 7.4.21
MySQL 5.7.32
Craft Solo 3.7.8

FR: Support for noindex meta tag, per-environment

I'd love to be able to apply "noindex" to every page on my site based on the environment. This is specifically to prevent staging sites from ending up in the Google index, which seems to happen even when robots.txt is set to disallow.

If there were a simple way to set this per-page as well (based on a lightswitch field value, perhaps) that would be a nice bonus!

How to overwrite image within a twig-template ?

       {% set seomate = {
            image: imageObject????
        } %}

Is it possible to overwrite the default image within a twig template?

I need to use the second image of an image field as default image on a special section type.

Is commerce supported?

I tried to use the seomateMeta hook for product pages but it throws an error.

Argument 1 passed to craft\helpers\UrlHelper::isAbsoluteUrl() must be of the type string,
null given, 
called in .../vendor/vaersaagod/seomate/src/services/UrlsService.php on line 164

On further inspection I also notice the lack of SEO Preview mode on products. Could you advise?

Error when i try use Multi-select field

When i try to use multiselect field (that field is nested in matrix field) in seomate config file i get below error:

PHP Recoverable Error – yii\base\ErrorException
Object of class craft\fields\data\MultiOptionsFieldData could not be converted to string

My code snippet:

    'fieldProfiles' => [
        'standard' => [
            'title' => ['seoFields:settings.alternativeTitle', 'title'],
            'description' => ['seoFields:settings.description'],
            'image' => ['seoFields:settings.image'],
            'keywords' => ['seoFields:settings.keywords'], 
            'robots' => ['seoFields:settings.robotsMulti'], 

        ],
    ],

Template-based override not working

First off - thank you for this amazing plugin. I've really loved it.

In this case I'm trying to override the image setup using the seomate object within the template, but it's not working. I tried dumping the seomate object after the hook but that isn't showing the updated object information, just the default from the config file.

As per #38, I ensured it's not within a block in the entry template. Would it matter if my template is extending another template?

As you can see below - the entry is extending the base-html-layout, which is extending the base-web-layout.twig, which also extends another template (not included).

E.g.

{# entry.twig #}
{% extends "base-html-layout.twig" %}
{% set seoImage = craft.imager.transformImage(entry.video.thumbLg, [
    { width: 1200, height: 675, format: 'jpg' }, 
    ], { ratio: 1/1, jpegQuality: 100 }) %}
    
{% set seomate = {		
		meta: {
			image: seoImage,   
			title: 'test',   
		},
} %}
{# base-html-layout.twig #}
{% extends craft.app.request.isAjax() and not craft.app.request.getIsPreview()
    ? "base-ajax-layout.twig"
    : "base-web-layout.twig"
%}

{% block headContent %}
    {% block headMeta %}
    {% endblock headMeta %}
{% endblock headContent %}

{% block bodyContent %}
{% endblock bodyContent %}
{# base-web-layout.twig #}
{% extends "_layouts/global-variables.twig" %}

{%- block htmlPage -%}
{# {% minify %} #}
<!DOCTYPE html>
	<html lang="{{ craft.app.language |slice(0,2) }}">
		<head>
			{# -- Page content that should be included in the <head> -- #}
			{% block headContent %}
			{% endblock headContent %}

			{% hook 'seomateMeta' %}
			
		</head>
                <body>
			{% block bodyContent %}{% endblock bodyContent %}
                </body>
        </html>

Empty sitemap.xml

Perhaps I'm being a donkey, but I have seomate.php with this in:

<?php

return [
    'sitemapEnabled' => true,
    'sitemapLimit' => 100,
    'sitemapConfig' => [
        'elements' => [
            'news' => ['changefreq' => 'weekly', 'priority' => 1],
        ],
    ],
];

But the sitemap.xml is just showing as:

<sitemapindex><script id="custom-useragent-string"/><!--Created on: 2019-07-26 16:49:26--></sitemapindex>

I have a News section with a handle of news that contains entries. Have a I missed something?

SEO image - wrong URL

Here is URL that seo image metatag had:

<meta property="og:image" content="http://bcweb.pl/test/esn/web/index.php?p=actions/assets/generate-transform&transformId=149">

And interestingly, transform WAS generated on server. But URL still stayed like this.

[3.2] SEO preview no longer works

It looks like there might have been a breaking change in the last commits before 3.2 was released; as the SEO Preview has stopped working since RC3.

No console errors. Running SEOmate 1.0.7

Open graph is not accepted by twitter or discord

Hi!
I'm not sure if it is a problem with plugin or something else, but my website open graph is not accepted by twitter or discord. Heres example page:
http://craftsnippets.com/articles/matrix-within-a-matrix-possible-solutions-for-craft-cms

And here's twitter post with this link - it wasn't expanded into open graph image:
https://twitter.com/PiotrPogorzel/status/1297969538017955840

I tested my website with facebook open graph debugger and it seems ok:
https://developers.facebook.com/tools/debug/?q=http%3A%2F%2Fcraftsnippets.com%2Farticles%2Fmatrix-within-a-matrix-possible-solutions-for-craft-cms

edit:
after few minutes, open graph appeared on twitter, some maybe there's nothing wrong after all. But heres link to discord where link was not expanded:
https://discord.com/channels/456442477667418113/456445573097324555/747527997644472441

Errors when eager loading entries

Problem

SEOMate throws an error when using eager-loading on asset/relation fields for the page's current entry. The problem is that SEOMate expects all relation fields to be an ElementQuery and tries to call one() or all() on them, which doesn't work if the fields are already eager loaded.

Example

SEOMate config

<?php
return [
    'fieldProfiles' => [
	'default' => [
		'image' => ['image'],
	],
    ],
];

Template

{% do eagerLoad(entry, ['image']) %}

Result

Error
Call to a member function all() on array
in /src/vendor/vaersaagod/seomate/src/helpers/SEOMateHelper.php at line 162

(see below for stack trace)

Possible solution

The problematic line in this case:

$assets = $scope[$handle]->all() ?? null;

A possible solution would be to add a type check before trying to execute ElementQuery queries, something like this:

$elements = $scope[$handle];
$assets = ($elements instanceof ElementQuery) ? $elements->all() : $elements;

Full stack trace

Error: Call to a member function all() on array in /src/vendor/vaersaagod/seomate/src/helpers/SEOMateHelper.php:162
Stack trace:
#0 /src/vendor/vaersaagod/seomate/src/services/MetaService.php(175): vaersaagod\seomate\helpers\SEOMateHelper::getPropertyDataByScopeAndHandle(Object(craft\elements\Entry), 'image', 'image')
#1 /src/vendor/vaersaagod/seomate/src/services/MetaService.php(158): vaersaagod\seomate\services\MetaService->getElementPropertyDataByFields(Object(craft\elements\Entry), 'image', Array)
#2 /src/vendor/vaersaagod/seomate/src/services/MetaService.php(139): vaersaagod\seomate\services\MetaService->generateElementMetaByProfile(Object(craft\elements\Entry), Array)
#3 /src/vendor/vaersaagod/seomate/src/services/MetaService.php(63): vaersaagod\seomate\services\MetaService->getElementMeta(Object(craft\elements\Entry), NULL)
#4 /src/vendor/vaersaagod/seomate/src/SEOMate.php(237): vaersaagod\seomate\services\MetaService->getContextMeta(Array)
#5 /src/vendor/craftcms/cms/src/web/View.php(1276): vaersaagod\seomate\SEOMate->onRegisterMetaHook(Array)
#6 /src/storage/runtime/compiled_templates/4b/4b2c44a6015cefb6a4c05340cf59c0e37485a7efceebe7b867cbcdcadac4d37a.php(50): craft\web\View->invokeHook('seomateMeta', Array)
#7 /src/vendor/twig/twig/src/Template.php(407): __TwigTemplate_87324b2e0d31f3e9d681217f96a386402061434a0e90e52237808e20992535a5->doDisplay(Array, Array)
#8 /src/vendor/craftcms/cms/src/web/twig/Template.php(52): Twig\Template->displayWithErrorHandling(Array, Array)
#9 /src/vendor/twig/twig/src/Template.php(380): craft\web\twig\Template->displayWithErrorHandling(Array, Array)
#10 /src/vendor/craftcms/cms/src/web/twig/Template.php(34): Twig\Template->display(Array, Array)
#11 /src/storage/runtime/compiled_templates/97/970eb2c72f5b817deb7c4f4713a40dbfc73ed60f3fc5405a0c4edff08a2c4314.php(56): craft\web\twig\Template->display(Array, Array)
#12 /src/vendor/twig/twig/src/Template.php(407): __TwigTemplate_b1093385c726217cb623e102fcae78a38f922fae40477b3dc0cc549c282a21fe->doDisplay(Array, Array)
#13 /src/vendor/craftcms/cms/src/web/twig/Template.php(52): Twig\Template->displayWithErrorHandling(Array, Array)
#14 /src/vendor/twig/twig/src/Template.php(380): craft\web\twig\Template->displayWithErrorHandling(Array, Array)
#15 /src/vendor/craftcms/cms/src/web/twig/Template.php(34): Twig\Template->display(Array, Array)
#16 /src/vendor/twig/twig/src/Template.php(392): craft\web\twig\Template->display(Array)
#17 /src/vendor/twig/twig/src/Extension/CoreExtension.php(1209): Twig\Template->render(Array)
#18 /src/storage/runtime/compiled_templates/d3/d32439cd11b8d1c5fe2aa5b1980b0612d63596d2994d8364f9dd21afb1c2b72f.php(39): twig_include(Object(craft\web\twig\Environment), Array, '_publications/p...', Array, true)
#19 /src/vendor/twig/twig/src/Template.php(407): __TwigTemplate_a263b6b643c872cb32027411f5cd2c2a078d124197ba1fd6f3a99b6d49e8cded->doDisplay(Array, Array)
#20 /src/vendor/craftcms/cms/src/web/twig/Template.php(52): Twig\Template->displayWithErrorHandling(Array, Array)
#21 /src/vendor/twig/twig/src/Template.php(380): craft\web\twig\Template->displayWithErrorHandling(Array, Array)
#22 /src/vendor/craftcms/cms/src/web/twig/Template.php(34): Twig\Template->display(Array, Array)
#23 /src/vendor/twig/twig/src/Template.php(392): craft\web\twig\Template->display(Array)
#24 /src/vendor/twig/twig/src/TemplateWrapper.php(45): Twig\Template->render(Array, Array)
#25 /src/vendor/twig/twig/src/Environment.php(318): Twig\TemplateWrapper->render(Array)
#26 /src/vendor/craftcms/cms/src/web/View.php(344): Twig\Environment->render('_publications/e...', Array)
#27 /src/vendor/craftcms/cms/src/web/View.php(394): craft\web\View->renderTemplate('_publications/e...', Array)
#28 /src/vendor/craftcms/cms/src/web/Controller.php(243): craft\web\View->renderPageTemplate('_publications/e...', Array)
#29 /src/vendor/craftcms/cms/src/controllers/TemplatesController.php(95): craft\web\Controller->renderTemplate('_publications/e...', Array)
#30 [internal function]: craft\controllers\TemplatesController->actionRender('_publications/e...', Array)
#31 /src/vendor/yiisoft/yii2/base/InlineAction.php(57): call_user_func_array(Array, Array)
#32 /src/vendor/yiisoft/yii2/base/Controller.php(157): yii\base\InlineAction->runWithParams(Array)
#33 /src/vendor/craftcms/cms/src/web/Controller.php(187): yii\base\Controller->runAction('render', Array)
#34 /src/vendor/yiisoft/yii2/base/Module.php(528): craft\web\Controller->runAction('render', Array)
#35 /src/vendor/craftcms/cms/src/web/Application.php(299): yii\base\Module->runAction('templates/rende...', Array)
#36 /src/vendor/yiisoft/yii2/web/Application.php(103): craft\web\Application->runAction('templates/rende...', Array)
#37 /src/vendor/craftcms/cms/src/web/Application.php(284): yii\web\Application->handleRequest(Object(craft\web\Request))
#38 /src/vendor/yiisoft/yii2/base/Application.php(386): craft\web\Application->handleRequest(Object(craft\web\Request))
#39 /src/web/index.php(21): yii\base\Application->run()
#40 {main}

Cache does not support pagination?

I have a category archive, and I'd like the meta title to be "Category Title: Page 1", "Category Title: Page 2", etc, depending on the current pagination. I'm passing in the title via entry context, then loading in my template like:

{% set seomate = {
  meta: {
    title: article_index_context.pageSeoTitle,
  }
} %}

This works correctly when the SEOMate cache is disabled but not when the cache is turned on.

Would it be possible to disable caching on a per-template basis like this:

{% set seomate = {
  cacheEnabled: false,
  meta: {
    title: article_index_context.pageSeoTitle,
  }
} %}

Or somehow update the cache key to support page numbers?

TypeError when Open Graph image does not exist

Thanks for seomate—it is a well-built plugin! The one thing we run into quite often is a fatal type error when the og:image file does not exist. This happens when a developer pulls a SQL dump from the live environment into their dev environment.

Is there are a way to ignore the missing file?

TypeError: Argument 1 passed to craft\helpers\UrlHelper::isAbsoluteUrl() must be of the type string, null given, called in /var/www/vendor/vaersaagod/seomate/src/helpers/SEOMateHelper.php on line 319 and defined in /var/www/vendor/craftcms/cms/src/helpers/UrlHelper.php:28
Stack trace:
#0 /var/www/vendor/vaersaagod/seomate/src/helpers/SEOMateHelper.php(319): craft\helpers\UrlHelper::isAbsoluteUrl(NULL)
#1 /var/www/vendor/vaersaagod/seomate/src/services/MetaService.php(315): vaersaagod\seomate\helpers\SEOMateHelper::ensureAbsoluteUrl(NULL)
#2 /var/www/vendor/vaersaagod/seomate/src/services/MetaService.php(234): vaersaagod\seomate\services\MetaService->getTransformedUrl(Object(craft\elements\Asset), Array, Object(vaersaagod\seomate\models\Settings))
#3 /var/www/vendor/vaersaagod/seomate/src/services/MetaService.php(86): vaersaagod\seomate\services\MetaService->transformMetaAssets(Array, Object(vaersaagod\seomate\models\Settings))
#4 /var/www/vendor/vaersaagod/seomate/src/SEOMate.php(240): vaersaagod\seomate\services\MetaService->getContextMeta(Array)
#5 /var/www/vendor/craftcms/cms/src/web/View.php(1575): vaersaagod\seomate\SEOMate->onRegisterMetaHook(Array)
#6 /var/www/storage/runtime/compiled_templates/26/265bf91e1ea2bb98300427f7afbfa78fd5119ede2332fb0a54aad9942935af5e.php(67): craft\web\View->invokeHook('seomateMeta', Array)
#7 /var/www/vendor/twig/twig/src/Template.php(407): __TwigTemplate_4dfcc93b60741ff8242b76cb1ef68ce71d72387755cfd0cc86fa36bfa9db1d94->doDisplay(Array, Array)
#8 /var/www/vendor/twig/twig/src/Template.php(380): Twig\Template->displayWithErrorHandling(Array, Array)
#9 /var/www/storage/runtime/compiled_templates/33/334be0af66dc13a1d9e1efd3aa5e5c62308ffaf0dca0d901938e9be4adcc4705.php(45): Twig\Template->display(Array, Array)
#10 /var/www/vendor/twig/twig/src/Template.php(407): __TwigTemplate_2dd2101fad16bd4df22c76a1977e91f87bc3315e99f66c90e4cdc8c8244d2bbb->doDisplay(Array, Array)
#11 /var/www/vendor/twig/twig/src/Template.php(380): Twig\Template->displayWithErrorHandling(Array, Array)
#12 /var/www/vendor/twig/twig/src/Template.php(392): Twig\Template->display(Array)
#13 /var/www/vendor/twig/twig/src/TemplateWrapper.php(45): Twig\Template->render(Array, Array)
#14 /var/www/vendor/twig/twig/src/Environment.php(318): Twig\TemplateWrapper->render(Array)
#15 /var/www/vendor/craftcms/cms/src/web/View.php(392): Twig\Environment->render('<redacted>>/_ind...', Array)
#16 /var/www/vendor/craftcms/cms/src/web/View.php(453): craft\web\View->renderTemplate('<redacted>>/_ind...', Array)
#17 /var/www/vendor/craftcms/cms/src/web/Controller.php(251): craft\web\View->renderPageTemplate('<redacted>>/_ind...', Array, 'site')
#18 /var/www/vendor/craftcms/cms/src/controllers/TemplatesController.php(100): craft\web\Controller->renderTemplate('<redacted>>/_ind...', Array)
#19 [internal function]: craft\controllers\TemplatesController->actionRender('<redacted>>/_ind...', Array)
#20 /var/www/vendor/yiisoft/yii2/base/InlineAction.php(57): call_user_func_array(Array, Array)
#21 /var/www/vendor/yiisoft/yii2/base/Controller.php(180): yii\base\InlineAction->runWithParams(Array)
#22 /var/www/vendor/craftcms/cms/src/web/Controller.php(189): yii\base\Controller->runAction('render', Array)
#23 /var/www/vendor/yiisoft/yii2/base/Module.php(528): craft\web\Controller->runAction('render', Array)
#24 /var/www/vendor/craftcms/cms/src/web/Application.php(285): yii\base\Module->runAction('templates/rende...', Array)
#25 /var/www/vendor/yiisoft/yii2/web/Application.php(103): craft\web\Application->runAction('templates/rende...', Array)
#26 /var/www/vendor/craftcms/cms/src/web/Application.php(270): yii\web\Application->handleRequest(Object(craft\web\Request))
#27 /var/www/vendor/yiisoft/yii2/base/Application.php(386): craft\web\Application->handleRequest(Object(craft\web\Request))
#28 /var/www/web/index.php(22): yii\base\Application->run()
#29 {main}

Add self-referential hreflang for multi-locale sites

It seems that every webpage that has multi-locale versions should have self-referential hreflangs along with hreflangs to the other versions.

As per the link from the SEOMate docs itself:

Each variation of the page should include a set of elements in the element, one link for each page variant including itself.

It would be great if this can be added soon. Thanks!

Undefined variable: newWidth

After latest update, i get error when i try to seo image:

Undefined variable: newWidth
1. in /var/www/de/vendor/craftcms/cms/src/image/Raster.php line 74

outputAlternate should be disabled if translated page is not active

I have a lot of 404 for translated pages that don't exist (yet).
I assume it's related to the fact that a page has the alternate meta for each language in the head, even if the translation isn't published.
Is there a way to output the alternate ONLY if the translated content is active ?

Maybe adding something like this in the UrlsService (line 135) might solve this:

$enabledSites = $craft->getElements()->getEnabledSiteIdsForElement($element->getId());

if ($url !== false && $url !== null && in_array($site->id, $enabledSites)) { 
    ...
}

Don't include site name for specific sections

With my homepage, I would like to have title without sitename appended.

I know i can disable specific settings in specific templates using Twig, like this:

{% set seomate = {
    config: {
        includeSitenameInTitle: false
    },
} %}

Can this be done using only config file? So i can map this setting to specific section handles.

Sitemap for section generates error Call to undefined method vaersaagod\seomate\SEOMate::find()

Application Info
PHP version 7.3.1
Craft edition & version Craft Pro 3.2.5.1
SEOMate 1.0.7

When calling the sitemap url of a specific section when the elementType is not supplied in the config, I get the error Call to undefined method vaersaagod\seomate\SEOMate::find()
This does not work (as in the basic example in readme):

'elements' => [
  'nieuwsItems' => ['changefreq' => 'weekly', 'priority' => 1],
 ],

However when supplying the elementType is does work

'elements' => [
  'nieuwsItems' => [
     'elementType' => \craft\elements\Entry::class,
     'criteria' => [
	'section' => ['nieuwsItems']
      ],
     'params' => ['changefreq' => 'weekly', 'priority' => 1],
 ],

additionalMeta setting not working

Everything work perfect except additionalMeta setting.
I add the setting as documented :

'additionalMeta' => [
        'og:type' => 'website',
        'twitter:card' => 'summary_large_image',
    ],

But it doesn't output tags..

My Environment:
PHP version | 7.3.6
MySQL 5.7.26
Craft Pro 3.3.19

Commerce field profile not mapping

I'm trying to do a very simple map of a commerce products section to a profile, but for whatever reason, viewing a product, every time it will pick up the default profile.

I'm on Craft 3.4.8, Commerce 3.0.10 and seomate 1.1.2

Here's a simplified version of what I'm attempting to use:

    'defaultProfile' => 'standard',
    
    'fieldProfiles' => [
        'standard' => [
            'title' => ['seoTitle', 'title'],
            'description' => ['seoDescription'],
            'image' => ['seoImage'],
        ],

        'products' => [
            'title' => ['seoTitle','title'],
            'description' => ['seoDescription', 'productDescription:text.body', 'importBody'],
            'image' => ['productImages'],    
        ],
    ],
    'profileMap' => [
        'products' => 'products',
    ], 

Any ideas? I've tried assigning the profile from the template too but that makes no difference. Wondering if Commerce 3 is breaking things?

GTM and analytics support

It seems that I could output analytics or GTM code using tagTemplateMap settings, but it would be good if that was built in.

Using isNew property to check for newly created Element

Description

Since Craft 3.7.5, the isNew property is no longer reliable when determining whether the entry is being saved for the first time. It is recommended to use the newly created (as of Craft 3.7.5) firstSave property instead.

Steps to reproduce

  1. Line 136 in the SEOMate class uses the isNew property to check if the Element is newly created

Additional info

  • Craft CMS version: 3.7.14
  • SEOMate plugin version: 1.1.11
  • PHP version: 7.4.1
  • Database driver & version: MySQL 5.5.5

How to add relational fields to field profiles?

Can't seem to use values from a category or related entry in field profiles right now - had to use a custom template. It would be a good idea to have support for these through the config file itself. Thanks.

FR: GraphlQL Support

Hi there
I am currently evaluating different SEO plugins and I wonder whether graphql support is planned or not.
Thank you and Cheers

Template overrides in preview

First time using seomate and it's great - however I need to set a custom meta title on some entries which is comprised of some field data and custom string between. Thus using the template overrides seems the right way to go.

When I apply template overrides they are not shown in the preview. I guess this might be a technical limitation but it seems a shame as the preview is great for a client to understand what they are doing.

I guess using a preparse field could be a work around?

Thanks

Tom

How to get matrix field value from global?

I have such setup that was made based on docs and it results in empty meta description.

    'defaultMeta' => [
            'description' => ['seo.seoFields:settings.globalDescription'],
    ],

seo is handle of global, seoFields is handle of matrix field, settings is handle of matrix block, globalDescription is handle of subfield within matrix.

Am I doing something wrong?

Properties returned by functions

I tried to pass some string to title field instead of using values from fields. To do that i deduced i need to use function instead of field handle. Like it was showed in docs:

      'og:see_also' => function ($context) {
            $someLinks = [];
            $matrixBlocks = $context['globalSeo']->someLinks->all() ?? null;
            
            if ($matrixBlocks && count($matrixBlocks) > 0) {
                foreach ($matrixBlocks as $matrixBlock) {
                    $someLinks[] = $matrixBlock->someLinkUrl ?? '';
                }
            }
            
            return $someLinks;
        },

So i tried to test it, like this:

            'title' => function ($context) {
                return 'test';
        },

Unfortunetly it does not work, title takes value of site name if i try this.

Craft 3.2 Support

I was taking some time to test this plugin for Craft 3.2 and it doesn't look like it's quite there. Particularly, the Live Preview functionality appears to be broken.

I started digging into it and found that the first issue is that the "Live Preview" button ID changed in 3.2, so that will need to be updated to:

var $lpBtn = $('#preview-btn');

After changing this, the button appeared as expected; however, clicking did nothing. I have not tested actual plugin rendering functionality yet; however, I that part doesn't seem likely to have broken.

Edit
After additional digging, it looks like the LivePreview class is deprecated and the entire system is rewritten, so this may be a bit of a headache to fix.

sitemap not found - Template not found: sitemap.xml

Hi there,

The route for the sitemap.xml is telling me template not found... this is my config:

<?php
return [
    'defaultProfile' => 'standard',
    'fieldProfiles' => [
        'standard' => [
            'title' => ['seoTitle', 'title'],
            'description' => ['seoDescription', 'summary', 'excerpt'],
            'image' => ['headerImage']
        ],
				'insights' => [
            'title' => ['seoTitle', 'title'],
            'description' => ['excerpt'],
            'image' => ['headerImage']					
				]
    ],
    'profileMap' => [
        'insights' => 'insights',
    ],    		
    'sitemapEnabled' => true,
    'sitemapLimit' => 100,
    'sitemapConfig' => [
        'elements' => [
            'book' => ['changefreq' => 'weekly', 'priority' => 1],
            'cafe' => ['changefreq' => 'weekly', 'priority' => 0.5],
            'instructors' => ['changefreq' => 'weekly', 'priority' => 0.5],
        ],
    ],      
];

I'm running on latest versions of Craft and Seomate

sitemap.xml not being rendered

Hello, I can’t get the sitemap.xml to be rendered (in the web/ directory I suppose?), no file is created. This is my setup, maybe I missed a point on how to render the actual sitemap.xml file:

'
return [

'sitenameSeparator' => '–',

'defaultProfile' => 'standard',

'fieldProfiles' => [
    'standard' => [
        'title' => ['seoTitle', 'heading', 'title'],
        'description' => ['seoDescription', 'projectDescription'],
        'image' => ['seoImage', 'mainImage']
    ]
],

'sitemapEnabled' => true,
'sitemapLimit' => 100,
'sitemapConfig' => [
    'elements' => [
        'projects' => ['changefreq' => 'weekly', 'priority' => 0.5],
        'about' => ['changefreq' => 'weekly', 'priority' => 0.5],
    ],
],

];

'

Make `additionalMeta` act as fallback instead of override

Currently any values in the additionalMeta field end up overwriting values set in templates by creating an seomate object. Can the behaviour be altered to make the additionalMeta values act as defaults, which are used only if those fields are not already set either via the profile map, or via the template config? It would enable use cases such as this:

/* config/seomate.php */
return [
   'additionalMeta' => [ 'og:type' => 'website' ],
];
{# template/news/_entry.twig #}
{% set seomate = {
   meta: { 'og:type': 'article' },
} %}

Access generated page title in twig

Sorry if I'm being daft but have looked through the source code and readme for a while with no luck.

I'd like to be able to use the built meta title for a given page to log into some analytics, is there a way to output the title that seomate generates via twig in an entry template?

Thanks for your time

Replace empty meta values with null

Unfortunately Craft saves many empty fieldtypes as empty string (e.g. the Color field). As a result many meta tags contain empty values.

I recommend replacing empty string values with null.

Escaping meta output?

I just noticed my client put a term in quotes in the shortDescription field, which we're using for the SEOMate description field. The quotes are not escaped and are creating invalid HTML in the head. Would it be possible to apply an escape filter before outputting?

Cannot get global value for specific profile

Here is my config:

<?php
return [
    'cacheEnabled' => false,
    'defaultProfile' => 'standard',
    
    'defaultMeta' => [
            'description' => ['seo.seoFields:settings.globalDescription'],
            'image' => ['seo.seoFields:settings.seoImage'],
    ],

    'fieldProfiles' => [
        'standard' => [
            'title' => ['title'],
        ],
        'home' => [
            'title' => ['seo.seoFields:settings.globalTitle'],
        ]
    ],

    'profileMap' => [
        'home' => 'home',
    ],   

];

This fragment works correctly, description value is taken from global field:

    'defaultMeta' => [
            'description' => ['seo.seoFields:settings.globalDescription'],
            'image' => ['seo.seoFields:settings.seoImage'],
    ],

This fragment does not work. Title shows site name instead:

        'home' => [
            'title' => ['seo.seoFields:settings.globalTitle'],
        ]

Setting for ignoring missing globals from defaultMeta

When i try to get data from some global in defaultMeta srtting and such global does not exists, i get Undefined index error.

It would be useful if we could supress such errors, for example when we use some seomate config on new craft istall that does not have globals set up yet.

Not working: Overriding meta data in template

I'm having trouble overriding meta data directly from my templates. I have a "blog" channel section where I created a seomate object in my "_entry.twig" template to override the seomate.php config settings.

{% set category = entry.blogCategories.one() %}
{% set seomate = {
	meta: {
		title: entry.title ~ ' | ' ~ category,
	}
} %}

However, this override doesn't take. When I run a dump of the seomate object, the object itself shows that the override worked. But the object doesn't seem to affect the actual output of the rendered meta tags.

Do I need to create the object first, then add the {% hook 'seomateMeta' %} after it? I currently have the hook nested inside the <head> element, which is inside my _layout.
So I'm wondering if I need to sort this relationship out in my _entry template (i.e. creating a head block in my _layout template and calling in that block in my _entry template.).

Add migration for common seo fields

I was thinking about making seomate easier to use when starting new project.

How about adding migration for some common seo fields - like description, seo image and robots? I usually group these fields within matrix field that has max and min limit set to 1.

There could also be migratiln for global with page-wide seo settings.

Such migration would not be mandatory, but it would be usefull to have such thing.

[FR] - dedicated SEO field

I think seomate would benefit from the dedicated SEO field. I know that it might be against plugin's philosophy, but hear me out.

While I used seomate in my recent projects, I always set up the same set of fields, which is a bit of the chore (well I know I can use project config file for that but this is not always possible). And native Craft fields don't always have the same functionality as dedicated SEO fields. For example, SEO title field may show you how many characters are left from 70 characters allowed for title, but still allow you to enter more.

Along SEO field, there could also be some general SEO setings section in the control panel, with basic stuff like global description, global SEO image etc. This might be disabled by default to avoid breaking things upon update because people get their global settings from other sources already.

To sum it up - I like how seomate uses config files to decide which craft fields are source of SEO values. But plugin would be even better if it offered some dedicated SEO fields in the control panel.

Set sitename from global set field value

It seems that right now we cannot set sitename (value that is appended to title) from global set field value.

I tried this and it didn't worked:

'siteName' => ['globalSeo.globalseofields:settings.description'],

While this generated array to string conversion error:

'siteName' => [
    'default' => ['globalSeo.globalseofields:settings.description'],
],

Setting sitename from string value works fine:

'siteName' => 'x',

Add category to sitemap

Hello,

I want to add a category to sitemaps, but by adding the category handle like an entry handle i don't get any output.

Is this option not available in seomate?

regards

open graph image - letterbox mode

This is open graph image and image it was generated from:

https://imgur.com/a/nfkehQM

As you can see, image was cropped. This might cause problems in case of things like pictures of products in webshops - these pictures might be uploaded in specific proportions they might vary per product.

Letterbox is transform mode introduced by imager/imager-x plugin. It works by adding a white area on top and bottom/left and right parts of the image in order to fit it into a specific size.

I think it would be perfect for situations like this. Letterbox mode could be enabled per-template, so for example only product pages open graph images are generated like this.

Solspace Calendar URLs in Sitemap

I've got Calendar events showing up in a sitemap, so that is working, with:

'games' => [
                'elementType' => \Solspace\Calendar\Elements\Event::class,
                'criteria' => [
                    'calendar' => 'varsity',
                    'rangeStart' => date('Y').'-01-01',
                    'rangeEnd' => date('Y').'-01-01'
                ],
                'params' => [
                    'changefreq' => 'weekly',
                    'priority' => 0.9
                ]
            ]

The output is:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
<!-- Created on: 2021-09-29 11:26:57 -->
<url>
<loc/>
<lastmod>2021-09-18T07:57:26-07:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc/>
<lastmod>2021-09-18T08:08:37-07:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc/>
<lastmod>2021-09-26T10:07:36-07:00</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
...
</urlset>

As you can see, the url item is missing. I am unsure how to specify this in seomate.php, or if it should be added automatically? I do have the URI specified with a template in the calendar settings, so I think everything should be in place https://d.pr/i/KEP49F

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.