Skip to content

Commit

Permalink
[FEATURE] Add prioritisation of main entities
Browse files Browse the repository at this point in the history
Resolves: #77
  • Loading branch information
brotkrueml committed Nov 11, 2021
1 parent 559446e commit a37297c
Show file tree
Hide file tree
Showing 11 changed files with 458 additions and 48 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Prioritisation of main entities (#77)

## [2.1.0] - 2021-10-19

### Added
Expand Down
24 changes: 18 additions & 6 deletions Classes/Core/ViewHelpers/AbstractBaseTypeViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ abstract class AbstractBaseTypeViewHelper extends ViewHelper\AbstractViewHelper
protected const ARGUMENT_ID = '-id';
protected const ARGUMENT_IS_MAIN_ENTITY_OF_WEBPAGE = '-isMainEntityOfWebPage';

private bool $isMainEntityOfWebPage = false;
private int $isMainEntityOfWebPage = 0;
private string $parentPropertyName = '';
private ?TypeInterface $model = null;
private TypeStack $stack;
Expand All @@ -47,7 +47,7 @@ public function initializeArguments()
parent::initializeArguments();
$this->registerArgument(static::ARGUMENT_AS, 'string', 'Property name for a child node to merge under the parent node', false, '');
$this->registerArgument(static::ARGUMENT_ID, 'mixed', 'IRI or a node identifier to identify the node', false, '');
$this->registerArgument(static::ARGUMENT_IS_MAIN_ENTITY_OF_WEBPAGE, 'bool', 'Set to true, if the type is the primary content of the web page', false, false);
$this->registerArgument(static::ARGUMENT_IS_MAIN_ENTITY_OF_WEBPAGE, 'int', 'Set to true, if the type is the primary content of the web page', false, false);
}

protected function addTypeToSchemaManager(TypeInterface $model): void
Expand Down Expand Up @@ -75,8 +75,8 @@ protected function addTypeToSchemaManager(TypeInterface $model): void
}

if ($this->stack->isEmpty()) {
if ($this->isMainEntityOfWebPage) {
$this->schemaManager->addMainEntityOfWebPage($recent);
if ($this->isMainEntityOfWebPage > 0) {
$this->schemaManager->addMainEntityOfWebPage($recent, $this->isMainEntityOfWebPage === 2);
} else {
$this->schemaManager->addType($recent);
}
Expand Down Expand Up @@ -107,9 +107,21 @@ private function checkAsAttribute(): void

private function checkIsMainEntityOfWebPage(): void
{
$this->isMainEntityOfWebPage = (bool)($this->arguments[static::ARGUMENT_IS_MAIN_ENTITY_OF_WEBPAGE] ?? false);
$isMainEntityOfWebPage = $this->arguments[static::ARGUMENT_IS_MAIN_ENTITY_OF_WEBPAGE] ?? 0;
$this->isMainEntityOfWebPage = $isMainEntityOfWebPage === 'true' ? 1 : (int)$isMainEntityOfWebPage;

if ($this->isMainEntityOfWebPage && ! $this->stack->isEmpty()) {
if ($this->isMainEntityOfWebPage < 0 || $this->isMainEntityOfWebPage > 2) {
throw new ViewHelper\Exception(
\sprintf(
'The value of argument "%s" must be between 0 and 2, "%d" given (allowed: 0 = not a main entity, 1 = main entity, 2 = prioritised main entity',
static::ARGUMENT_IS_MAIN_ENTITY_OF_WEBPAGE,
$this->isMainEntityOfWebPage
),
1636570950
);
}

if ($this->isMainEntityOfWebPage > 0 && ! $this->stack->isEmpty()) {
throw new ViewHelper\Exception(
\sprintf(
'The argument "%s" must not be used in the child type "%s", only the main type is allowed',
Expand Down
64 changes: 64 additions & 0 deletions Classes/Manager/MainEntityOfWebPageBag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

/*
* This file is part of the "schema" extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/

namespace Brotkrueml\Schema\Manager;

use Brotkrueml\Schema\Core\Model\TypeInterface;

/**
* This bag holds the assigned main entity types. Additional types can be added
* via the add() method. The add method returns an array of not prioritised types
* when a type with priority is added.
*
* @internal
*/
final class MainEntityOfWebPageBag implements \Countable
{
/**
* @var TypeInterface[]
*/
private array $types = [];
private bool $isPrioritised = false;

/**
* @return TypeInterface[]
*/
public function add(TypeInterface $type, bool $typeIsPrioritised): array
{
if (! $typeIsPrioritised && $this->isPrioritised) {
return [$type];
}

$notPrioritisedTypes = [];
if ($typeIsPrioritised && ! $this->isPrioritised) {
$notPrioritisedTypes = $this->types;
$this->types = [];
$this->isPrioritised = true;
}

$this->types[] = $type;

return $notPrioritisedTypes;
}

/**
* @return TypeInterface[]
*/
public function getTypes(): array
{
return $this->types;
}

public function count(): int
{
return \count($this->types);
}
}
33 changes: 17 additions & 16 deletions Classes/Manager/SchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,12 @@ final class SchemaManager implements SingletonInterface
*/
private array $breadcrumbLists = [];

/**
* @var TypeInterface[]
*/
private array $mainEntitiesOfWebPage = [];
private MainEntityOfWebPageBag $mainEntityOfWebPageBag;

public function __construct(RendererInterface $renderer = null)
{
$this->renderer = $renderer ?? new Renderer();
$this->mainEntityOfWebPageBag = new MainEntityOfWebPageBag();
}

/**
Expand Down Expand Up @@ -82,8 +80,8 @@ private function isWebPageType(TypeInterface $type): bool

private function setWebPage(TypeInterface $webPage): void
{
$breadcrumb = $webPage->getProperty(static::WEBPAGE_PROPERTY_BREADCRUMB);
$webPage->clearProperty(static::WEBPAGE_PROPERTY_BREADCRUMB);
$breadcrumb = $webPage->getProperty(self::WEBPAGE_PROPERTY_BREADCRUMB);
$webPage->clearProperty(self::WEBPAGE_PROPERTY_BREADCRUMB);

if ($breadcrumb instanceof BreadcrumbList) {
$this->addBreadcrumbList($breadcrumb);
Expand All @@ -95,8 +93,8 @@ private function setWebPage(TypeInterface $webPage): void
}
}

$mainEntity = $webPage->getProperty(static::WEBPAGE_PROPERTY_MAIN_ENTITY);
$webPage->clearProperty(static::WEBPAGE_PROPERTY_MAIN_ENTITY);
$mainEntity = $webPage->getProperty(self::WEBPAGE_PROPERTY_MAIN_ENTITY);
$webPage->clearProperty(self::WEBPAGE_PROPERTY_MAIN_ENTITY);

if ($mainEntity instanceof TypeInterface) {
$this->addMainEntityOfWebPage($mainEntity);
Expand Down Expand Up @@ -132,9 +130,12 @@ public function hasWebPage(): bool
/**
* Add a main entity of the WebPage
*/
public function addMainEntityOfWebPage(TypeInterface $mainEntity): self
public function addMainEntityOfWebPage(TypeInterface $mainEntity, bool $isPrioritised = false): self
{
$this->mainEntitiesOfWebPage[] = $mainEntity;
$notPrioritisedTypes = $this->mainEntityOfWebPageBag->add($mainEntity, $isPrioritised);
foreach ($notPrioritisedTypes as $type) {
$this->addType($type);
}

return $this;
}
Expand All @@ -152,7 +153,7 @@ public function renderJsonLd(): string
$this->preparePropertiesForWebPage();
$this->renderer->addType($this->webPage);
} else {
$this->renderer->addType(...$this->breadcrumbLists, ...$this->mainEntitiesOfWebPage);
$this->renderer->addType(...$this->breadcrumbLists, ...$this->mainEntityOfWebPageBag->getTypes());
}

$this->renderer->addType(...$this->types);
Expand All @@ -166,18 +167,18 @@ public function renderJsonLd(): string
private function preparePropertiesForWebPage(): void
{
if ($this->breadcrumbLists !== []) {
$this->webPage->clearProperty(static::WEBPAGE_PROPERTY_BREADCRUMB);
$this->webPage->clearProperty(self::WEBPAGE_PROPERTY_BREADCRUMB);

foreach ($this->breadcrumbLists as $breadcrumb) {
$this->webPage->addProperty(static::WEBPAGE_PROPERTY_BREADCRUMB, $breadcrumb);
$this->webPage->addProperty(self::WEBPAGE_PROPERTY_BREADCRUMB, $breadcrumb);
}
}

$numberOfMainEntities = \count($this->mainEntitiesOfWebPage);
$numberOfMainEntities = \count($this->mainEntityOfWebPageBag);
if ($numberOfMainEntities === 1) {
$this->webPage->addProperty(static::WEBPAGE_PROPERTY_MAIN_ENTITY, $this->mainEntitiesOfWebPage[0]);
$this->webPage->addProperty(self::WEBPAGE_PROPERTY_MAIN_ENTITY, $this->mainEntityOfWebPageBag->getTypes()[0]);
} elseif ($numberOfMainEntities > 1) {
$this->webPage->addProperty(static::WEBPAGE_PROPERTY_MAIN_ENTITY, $this->mainEntitiesOfWebPage);
$this->webPage->addProperty(self::WEBPAGE_PROPERTY_MAIN_ENTITY, $this->mainEntityOfWebPageBag->getTypes());
}
}
}
9 changes: 9 additions & 0 deletions Documentation/Changelog/Index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ All notable changes to this project will be documented in this file.
The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.0.0/>`_\ , and this project adheres
to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`_.

`Unreleased <https://github.com/brotkrueml/schema/compare/v2.1.0...HEAD>`_
------------------------------------------------------------------------------

Added
^^^^^


* Prioritisation of main entities (#77)

`2.1.0 <https://github.com/brotkrueml/schema/compare/v2.0.2...v2.1.0>`_ - 2021-10-19
----------------------------------------------------------------------------------------

Expand Down
9 changes: 7 additions & 2 deletions Documentation/Developer/Api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -497,14 +497,19 @@ Return value

.. _api-schema-manager-addmainentityofwebpage:

.. option:: addMainEntityOfWebPage($mainEntity)
.. option:: addMainEntityOfWebPage($mainEntity, $isPrioritised = false)

Adds a :ref:`main entity <main-entity-of-web-page>` to the web page.

Parameter
Parameters
:php:`TypeInterface $mainEntity`
The type model to be added.

:php:`bool $isPrioritised`
.. versionadded:: 2.2.0
Set to :php:`true` to :ref:`prioritise <main-entity-prioritisation>` a
main entity.

Return value
Reference to itself.

Expand Down
85 changes: 85 additions & 0 deletions Documentation/Developer/MainEntity.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,89 @@ You can set the view helper argument :html:`-isMainEntityOfWebPage` only in the
main type view helper, not in a child type view helper.


.. index::
single: Prioritisation (main entity)

.. _main-entity-prioritisation:

Prioritisation
==============

.. versionadded:: 2.2.0

Main entities can be prioritised. This is sometimes necessary when different
main entities are defined in different places (for example in a controller,
a Fluid page template or in a content element).

Let's look at an example: In a page template for a blog post, the main entity
is defined as type `BlogPosting`. There are content elements on the page that
display questions and answers for a FAQ. The content element sets the web page
type to 'FAQPage' and also defines the questions as the main entities (to
display as rich snippets on the search results page). However, Google Search
Console shows an error because `BlogPosting` is not allowed as the main entity
of a `FAQPage`.


With the API
------------

The main entity of the API example above can be prioritised by setting the
second argument to :php:`true`::

$schemaManager->addMainEntityOfWebPage($product, true);


With view helpers
-----------------

Let's look at the example described in the introduction. In a page template, a
`BlogPosting` type is defined as the main entity of the page:

.. code-block:: html
:emphasize-lines: 3

<!-- This is defined in a page template -->
<schema:type.blogPosting
-isMainEntityOfWebPage="1"
name="A blog post"
/>

And the FAQ is rendered in the template of a content element. To prioritise
these types, :html:`-isMainEntityOfWebPage` is set to `2`:

.. code-block:: html
:emphasize-lines: 5

<!-- This is defined in a content element template -->
<schema:type.fAQPage/>
<f:for each="{questions}" as="question">
<schema:type.question
-isMainEntityOfWebPage="2"
name="{question.title}"
/>
</f:for>

This results in the following output:

.. code-block:: json
:emphasize-lines: 5-11
{
"@context": "https://schema.org/",
"@graph": [{
"@type": "FAQPage",
"mainEntity":[{
"@type": "Question",
"name": "Question #1"
}, {
"@type": "Question",
"name": "Question #2"
}]
}, {
"@type": "BlogPosting",
"name": "A blog post"
}]
}
.. _more than one question: https://developers.google.com/search/docs/data-types/faqpage
9 changes: 6 additions & 3 deletions Documentation/Developer/ViewHelpers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,8 @@ argument:

.. option:: -isMainEntityOfWebPage

This argument defines the type as the
:ref:`main entity <main-entity-of-web-page>` of a
:ref:`web page <web-page-type>`:
This argument defines the type as a :ref:`main entity <main-entity-of-web-page>`
of a :ref:`web page <web-page-type>`:

.. code-block:: html
:emphasize-lines: 3
Expand Down Expand Up @@ -231,6 +230,10 @@ which results in the output:
}
}
.. versionadded:: 2.2.0
Main entities can be prioritised, please have a look into the
:ref:`main-entity-prioritisation` section.


.. index::
single: Multiple type view helper
Expand Down
Loading

0 comments on commit a37297c

Please sign in to comment.