Search by

therealworld / seo-module

therealworld

Creates a sitemap.xml for products, categories, texts and other items from other modules in OXID7. Allows to set the title tags.

Package info

bitbucket.org/therealworld/seo-module

Homepage

Issues

Type:oxideshop-module

pkg:composer/therealworld/seo-module

Statistics

Installs: 1 209

Dependents: 0

Suggesters: 0


README

Deutsche Fassung: README.de.md

vendor-logo the-real-world.de

description

The module takes care of what search engines see of the shop:

  • sitemap.xml for articles, categories, CMS pages, manufacturers and vendors, built incrementally by a scheduler task, split into several files with an index file, optionally gzip-compressed, with language alternates and image links. Other modules add their own object types (e.g. news, events, people, ...).
  • SEO title (<title>) per article, category, CMS page, manufacturer and vendor, maintained in the admin's SEO tab or generated from a pattern when the object is saved. Meta description and keywords are generated from patterns as well.
  • Substance rule for thin pages: category, manufacturer and vendor pages without enough sellable articles and without a description text of their own get noindex, follow and are left out of the sitemap.
  • Sold-out articles can answer with 410 Gone or 404 Not Found instead of the core's redirect to the start page.
  • Structured data: article detail pages carry a schema.org Product as JSON-LD.
Composer packagetherealworld/seo-module
Module idtrwseo
RequiresOXID eShop CE >=v7.4, PHP ^8.3, therealworld/tools-plugin >=v3.7, therealworld/scheduler-module >=v2.2

Installation

composer require therealworld/seo-module
vendor/bin/oe-console trw:module:setup trwseo

trw:module:setup (from therealworld/clirun-plugin) installs the module, runs its migrations and activates it. Without the clirun-plugin use the standard OXID steps (oe:module:install, the module migrations, oe:module:activate trwseo).

The migrations create the tables trwseositemapexport and trwseoupdatemeta and add the columns OXISSITEMAP and OXSEOTITLE (_1, _2, _3) to oxarticles, oxcategories, oxcontents, oxvendor and oxmanufacturers. On activation the DB views are regenerated.

The scheduler module (trwscheduler) must be active: the sitemap export and the meta update run as its tasks.

The sitemap

How it is built

The task "sitemap.xml Export" (scheduler, default crontab 10 2 * * *, i.e. daily at 2:10 am) works in two phases:

  1. Collect: for every SEO model it writes the URLs into trwseositemapexport. The table is kept between runs; only objects changed within the last "Update of the last days" are collected again. Rows of objects that no longer qualify (deleted, deactivated, excluded by a condition, thin, model switched off) are removed at the start of each run. If the table is empty, the run collects everything.
  2. Write: the rows are written to sitemap1.xml, sitemap2.xml, ... in the shop's document root. A file is closed at 50,000 entries or at the configured file size, and sitemap.xml is written as the index pointing to these files. Numbered files left over from a larger earlier run are deleted.

With "Compress?" on, every file is additionally written as .gz and the index refers to the .gz files.

Large shops do not need to finish in one scheduler tick: the task continues where it stopped.

What gets in

Per object type an object is exported when

  • the type is switched on ("Active"),
  • the object's own flag "sitemap.xml?" is set (SEO tab of the object in the admin; default for new columns: on for articles, categories and manufacturers, off for CMS pages and vendors),
  • the optional "SQL-Condition" matches,
  • for categories: it is not an external link (OXEXTLINK empty),
  • for categories, manufacturers and vendors: it passes the substance rule (below).

Optionally the export adds alternate URLs for further languages (xhtml:link) and image URLs of articles, categories, manufacturers and vendors (image:image).

Submit https://<shop>/sitemap.xml (or sitemap.xml.gz) to the search engines and/or reference it in robots.txt; the module does not do that.

Thin pages: the substance rule

A category, manufacturer or vendor page is considered thin when both apply:

  • it counts fewer sellable articles than the configured minimum ("... minimum articles for indexing", default 1), and
  • it has no description text of its own (categories: long description, manufacturers and vendors: short description).

A thin page

  • renders <meta name="robots" content="NOINDEX, FOLLOW" />, and
  • is not written into the sitemap.

Both use the same SQL statement, so page and sitemap cannot disagree. The rule is evaluated on every request; a page that gets articles again is indexable again immediately.

Details:

  • Categories count the sellable articles of their whole subtree, so a parent node with filled subcategories is not thin.
  • "Sellable" means: parent articles that pass the shop's active check.
  • 0 switches the rule off for that object type. Articles and CMS pages have no such rule (an article page is its own substance; sold-out articles are handled by the status code below).
  • Independent of the rule, every page after the first of a paged article list gets NOINDEX, FOLLOW.

Sold-out articles

An article that still exists but cannot be bought any more (inactive, or out of stock with stock flag "not orderable"; for variants also a non-visible parent) is answered by the core with a redirect to the start page. To a search engine that is a soft 404. "Articles no longer available" changes that:

ValueEffect
Redirect to the start page (default)core behaviour, unchanged
410 Gonestatus 410 with the shop's 404 page; right for single copies, search engines drop the URL fastest
404 Not Foundstatus 404 with the shop's 404 page; when the article may come back

The error page template message/err_404 receives blTRWSeoGone (true for 410), so a theme can word the sold-out case differently.

SEO title, meta description, keywords

  • The SEO tab of articles, categories, CMS pages, manufacturers and vendors gets two fields: "SEO Meta Title" and "sitemap.xml?".
  • A filled SEO title replaces the page's <title>. An empty or broken one (only the fixed text of the pattern, e.g. " nur hier!", or only separators like " - ") falls back to the regular title.
  • When an object is saved and its SEO title is empty or broken, it is generated from the pattern of the group "Metadata Defaults" (one line per language, de => ...). Meta description and keywords (oxobject2seodata) are only generated when they are empty.
  • Placeholders: %title everywhere; articles and categories also %shortdesc and %longdesc, articles also %keywords (search keys); manufacturers, vendors and CMS pages %desc. Further placeholders come from other modules: if you need e.g. the authors or brand names of an article in its title, a module can replace its own placeholder (say %author) in replaceAdditonalSeoContent() on the article chain (see Further hooks).

Meta update task

The task "Update Metadata" has no default crontab and is started manually in the scheduler (Service > Task-Scheduler). It saves every object of every SEO model in every language again, so missing SEO titles, descriptions and keywords are generated. Its behaviour is controlled by the group "Metadata Update" (see below).

CLI: repair broken SEO titles

vendor/bin/oe-console trw:seo:repairtitles [--dry-run] [--model=oxarticles] [--limit=N]

Finds stored SEO titles that are broken (see above) and generates them again from the pattern; objects with nothing to build from get an empty SEO title (and fall back to the regular title). Asks for confirmation, writes the column directly (no save()), and empties the page cache afterwards.

  • --dry-run: only report
  • --model: only this SEO model
  • --limit: at most this many titles per model and language

Structured data (JSON-LD)

Article detail pages get a schema.org Product in <head>: name (max. 150 characters), description, existing pictures, article number (sku), gtin13 (from EAN/ISBN, only with a valid check digit) and an Offer with gross price, currency, availability and URL.

  • Shipping details (shippingDetails) only appear when a shipping module fills the delivery price provider of the tools-plugin (e.g. trwshipping).
  • The return policy (hasMerchantReturnPolicy) only appears when a withdrawal module fills the merchant return policy provider of the tools-plugin.

Configuration

Extensions > Modules > the-real-world - SEO > Settings

Sitemap Product / Categories / Content-Pages / Vendors / Manufacturers

The same four options exist per object type:

OptionDefaultEffect
ActiveonWrite this object type into the sitemap at all. Off removes its rows on the next run.
Changefrequencydaily<changefreq> of the URLs
URL-Priority0.5<priority> of the URLs
SQL-Conditionempty (CMS pages: OXFOLDER="CMSFOLDER_USERINFO")Additional conditions on the object table, FIELD=value, several separated by \|, e.g. OXHIDDEN=0\|OXSHOWSUFFIX=1. Only equality, combined with AND.

Additionally:

GroupOptionDefaultEffect
Sitemap ProductArticles no longer availableRedirect to the start pagesee Sold-out articles
Sitemap CategoriesCategories: minimum articles for indexing1see Thin pages; 0 = off
Sitemap ManufacturersManufacturers: minimum articles for indexing1as above
Sitemap VendorsVendors: minimum articles for indexing1as above

additional Sitemap-Options

OptionDefaultEffect
Index languagesnoneFurther languages (besides the default language) whose URLs are added as alternates. Multi-select.
Index Images?onAdd image URLs (articles, categories, manufacturers, vendors)

Export

OptionDefaultEffect
Number of positions that are processed per tick10Rows per batch in collect and write; lower it if the server struggles
Size of the XML files10MBSize at which a sitemap file is closed and the next one started (5MB or 10MB)
Compress?onWrite .gz files additionally and reference them in the index
Update of the last days7Objects changed within this many days are collected again on each run. A large value (e.g. 99999) forces a full re-export.

Metadata Defaults

Patterns for SEO title, meta description and meta keywords of categories, articles, manufacturers, vendors and CMS pages (15 options), one line per language (de => ..., en => ...). Defaults e.g. article title %title nur hier! / %title only here!, category title Beste Angebote für %title / Best deals for %title. A pattern only applies when the field is still empty (or the title is broken) at save time.

Metadata Update

Affects the manual task "Update Metadata" only.

OptionDefaultEffect
Clear metadata before manual update?offEmpty SEO title, description and keywords first, so all are generated anew
Update SEO URLs during meta update?offAlso renew the SEO URLs: non-fixed URLs are marked as expired (the shop builds them again on the next access); fixed URLs are skipped
Delete SEO URLs before update?offOnly with the option above: delete the URLs and their history (oxseo, oxseohistory) instead of expiring them

Logging

The tasks write info entries ("Data collected for type ...", "sitemap.xml created ...", removed obsolete files) and errors (sitemap file not writable, no index written) via the tools-plugin's ToolsLog.

Limits and caveats

  • The sitemap files are written into the shop's document root; it must be writable.
  • SQL-Condition only supports FIELD=value pairs; values are compared as strings with AND.
  • A full re-export happens when the export table is empty, or with a large "Update of the last days".
  • The substance rule needs the scheduler run to update the sitemap; the robots tag changes immediately.

For developers

Chain extensions

  • Controllers: ArticleDetailsController (status code, SEO title, JSON-LD), ArticleListController (isNoIndex(), SEO title, canonical URL — also covers manufacturer and vendor lists), ContentController, ManufacturerListController, VendorListController (SEO title).
  • Admin: ArticleSeo, CategorySeo, ContentSeo, ManufacturerSeo, VendorSeo (fields oxseotitle, oxissitemap), ModuleConfiguration (language multi-select).
  • Models: Article, Category, Content, Manufacturer, Vendor (pattern-based meta data on save(), setSaveSeoData(false) switches it off for bulk imports).

Templates / blocks

  • layout/base.html.twig (all frontend themes): block head_meta_robots outputs NOINDEX, FOLLOW when the view sets isNoIndex; block head_link_canonical appends the JSON-LD on details.
  • Admin: object_seo.html.twig (block admin_object_seo_form) and module_config.html.twig (block admin_module_config_var_type_arr).

Adding an SEO model from another module

SeoSitemapGenerateData and SeoMetaGenerateData are registered as module classes and can be extended through the chain (e.g. by a module that brings news or event pages):

  1. Append the table name to the hidden setting arrTRWSeoAdditionalSeoModels (and to arrTRWSeoAdditionalObjectsWithImages for images), typically in the module's onActivate.
  2. Provide the settings boolTRWSeoActive<model>, strTRWSeoChangeFreq<model>, strTRWSeoPriority<model>, strTRWSeoSQLCondition<model> and optionally numTRWSeoNoIndexMinItems<model> in the own module; they are read with the model name as module id.
  3. Override getExternList() and getExternNameTimeStampField() (sitemap), getWantedIdsQuery() (stale-row cleanup; null leaves rows alone) and getSeoTitleModel() (meta update, trw:seo:repairtitles).

Extending the substance rule

SeoSitemapGenerateData::getSeoSubstanceScoreParts() returns named SQL expressions that are summed and compared against the threshold; getSeoSubstanceBase() names the view of a model. Add a part (e.g. the number of articles linked to a person), or a base for a new model. A part that should be enough on its own weighs as much as the threshold (see buildTextScorePart()). hasSeoSubstance($model, $id) answers the question for a single object and is what the frontend uses — call it from an own detail controller to set isNoIndex.

Further hooks

  • SeoTitleModelInterface + trait SeoTitleBuilder: generated SEO titles for own models; replaceAdditonalSeoContent(string) on a model fills own placeholders.
  • Article::extendSeoStructuredData(array): array on the article chain adds JSON-LD fields (e.g. a module for second-hand goods can set offers.itemCondition to https://schema.org/UsedCondition).

Database

  • trwseositemapexport: collected sitemap rows (incremental)
  • trwseoupdatemeta: work list of the meta update task
  • Columns OXISSITEMAP, OXSEOTITLE[_1|_2|_3] on the five core tables; down() keeps them on purpose.
  • Migrations in migration/data, table oxmigrations_trwseo.