<?php
namespace App\Util;
use App\Annotations\Seo\Meta;
use App\Entity\Asset;
use App\Entity\MetaTemplates;
use App\Entity\OgImage;
use App\Entity\OpenGraphInterface;
use App\Entity\Page;
use App\Model\Seo\SerializationContext;
use DateTimeInterface;
use Doctrine\Common\Annotations\AnnotationReader;
use Leogout\Bundle\SeoBundle\Provider\SeoGeneratorProvider;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use ReflectionClass;
use ReflectionException;
use ReflectionProperty;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
class Seo
{
/**
* @var SeoGeneratorProvider
*/
protected $seoGenerator;
/**
* @var CacheManager
*/
protected $cacheManager;
/**
* @var UploaderHelper
*/
protected $uploaderHelper;
/**
* @var Request
*/
protected $request;
/**
* @var ParameterBagInterface
*/
private $parameterBag;
/**
* Seo constructor.
*/
public function __construct(
SeoGeneratorProvider $seoGenerator,
CacheManager $cacheManager,
UploaderHelper $uploaderHelper,
RequestStack $requestStack,
ParameterBagInterface $parameterBag
) {
$this->seoGenerator = $seoGenerator;
$this->cacheManager = $cacheManager;
$this->uploaderHelper = $uploaderHelper;
$this->request = $requestStack->getCurrentRequest();
$this->parameterBag = $parameterBag;
}
public function setSeoInformationForPage(array $metaData, Page $page, ?OgImage $defaultOgImage): void
{
$pageTitle = $metaData['metaTitle'] ?? null;
if (!$pageTitle) {
$pageTitle = $page->getMetaTitle();
}
if (!$pageTitle) {
$pageTitle = $page->getTitle();
}
// The metaData.ogImage must be invalid before Page.ogImage is allowed to be used
if (false === (
\array_key_exists('ogImage', $metaData)
&& (\is_string($metaData['ogImage']) && '' !== $metaData['ogImage'])
)
&& $page->getOgImage() instanceof OgImage
) {
$pageOgImage = $this->renderOgImage($page->getOgImage(), $page);
if ('' !== $pageOgImage) {
$metaData['ogImage'] = $pageOgImage;
}
}
$ogImage = !empty($metaData['ogImage']) ? $metaData['ogImage'] : null;
if (\is_object($ogImage) && (is_subclass_of($ogImage, Asset::class) || Asset::class === \get_class($ogImage))) {
/** @var Asset $ogImageAsset */
$ogImageAsset = $metaData['ogImage'];
$ogImageAssetPath = $this->uploaderHelper->asset($ogImageAsset, 'assetFile');
if ($ogImageAssetPath) {
$ogImage = $this->cacheManager->getBrowserPath(
$this->uploaderHelper->asset($ogImageAsset, 'assetFile'),
'w1200'
);
}
}
// If all other fallbacks fail, use the default og image
if (empty($ogImage)) {
if ($defaultOgImage instanceof OgImage &&
$defaultOgImage->getImage() instanceof Asset &&
null !== $defaultOgImage->getImage()->getFileName()) {
$ogImage = $this->cacheManager->getBrowserPath(
$this->uploaderHelper->asset($defaultOgImage->getImage(), 'assetFile'),
'w1200'
);
}
}
$this->seoGenerator->get('gn_og')
->setTitle(sprintf('%s | %s', $pageTitle, $this->parameterBag->get('site_company_name')))
->setDescription(
strip_tags(!empty($metaData['metaDescription']) ? $metaData['metaDescription'] : $page->getMetaDescription())
)
->setUrl($this->request->getUri())
->setImageWithSize(\is_string($ogImage) ? $ogImage : '')
->setType('website')
;
}
public function renderOgImage(OgImage $ogImage, ?object $object): string
{
if (null === $ogImage->getImage() || null === $ogImage->getImage()->getFileName()) {
return '';
}
$getter = 'get'.$ogImage->getField();
if (!method_exists($object, $getter)) {
return $this->cacheManager->getBrowserPath(
$this->uploaderHelper
->asset($ogImage->getImage(), 'assetFile'),
'w1200'
);
}
$options = [
'text' => $object->{$getter}(),
];
if ($ogImage->getTextSize()) {
$options['text_size'] = $ogImage->getTextSize();
}
if ($ogImage->getTextColor()) {
$options['text_color'] = $ogImage->getTextColor();
}
if ($ogImage->getTextPosition()) {
$options['position'] = $ogImage->getTextPosition();
}
return $this->cacheManager->getBrowserPath(
$this->uploaderHelper
->asset($ogImage->getImage(), 'assetFile'),
'text_overlay',
['text_overlay' => $options]
);
}
/**
* @param OpenGraphInterface $entity
*
* @return array
*/
public function extractMetaData(MetaTemplates $templates, object $entity)
{
$context = new SerializationContext();
if (null !== $templates->getDateFormat()) {
$context->dateFormat = $templates->getDateFormat();
}
$values = $this->calculateFieldValues(
$entity,
$context
);
$callback = function ($result) use ($values) {
if (isset($result[1])) {
$key = trim($result[1]);
return !empty($values[$key]) ? $values[$key] : '';
}
return '';
};
$regex = '/\{{([a-zA-Z0-9\s_-]+?)\}}/';
$metaTitle = preg_replace_callback(
$regex,
$callback,
$templates->getTitleTemplate()
);
$metaDescription = preg_replace_callback(
$regex,
$callback,
$templates->getDescriptionTemplate()
);
return [$metaTitle, $metaDescription];
}
public function calculateFields(string $objectClass): array
{
$fields = [];
/** @var ReflectionProperty $reflectionProperty */
foreach ($this->getMetaProperties($objectClass) as $reflectionProperty) {
/** @var Meta $annotation */
$fieldName = $reflectionProperty->getName();
if (!empty($annotation->name)) {
$fieldName = $annotation->name;
}
$fields[] = $fieldName;
}
return $fields;
}
private function calculateFieldValues(object $entity, SerializationContext $context): array
{
$reader = new AnnotationReader();
$fields = [];
/** @var ReflectionProperty $metaProperty */
foreach ($this->getMetaProperties(\get_class($entity)) as $metaProperty) {
/** @var Meta $annotation */
$annotation = $reader->getPropertyAnnotation($metaProperty, Meta::class);
$propertyGetter = 'get'.$metaProperty->getName();
if ($entity->$propertyGetter() instanceof DateTimeInterface) {
$fields[$metaProperty->getName()] = $entity->$propertyGetter()->format($context->dateFormat);
} else {
$fields[$metaProperty->getName()] = (string) $entity->$propertyGetter();
}
if ($annotation->supersededBy) {
$getter = 'get'.$annotation->supersededBy;
if (!method_exists($entity, $getter)) {
throw new \RuntimeException(sprintf('Function %s for superseding property %s must exist', $getter, $annotation->supersededBy));
}
$supersededValue = $entity->$getter();
if (!empty($supersededValue)) {
$fields[$metaProperty->getName()] = $supersededValue;
}
}
if ($annotation->name) {
$fields[$annotation->name] = $fields[$metaProperty->getName()];
}
}
return $fields;
}
private function getMetaProperties(string $objectClass): array
{
$reader = new AnnotationReader();
try {
$reflectionClass = new ReflectionClass($objectClass);
} catch (ReflectionException $e) {
return [];
}
$properties = [];
foreach ($reflectionClass->getProperties() as $reflectionProperty) {
$annotation = $reader->getPropertyAnnotation($reflectionProperty, Meta::class);
if (!$annotation) {
continue;
}
$properties[] = $reflectionProperty;
}
return $properties;
}
}