<?php declare(strict_types=1);
/**
* @package Memo\MemoFoundationBundle
* @author Media Motion AG
* @license LGPL-3.0+
* @copyright Media Motion AG
*/
namespace Memo\FoundationBundle\Module;
use Contao\Model\Collection;
use Contao\StringUtil;
use Contao\System;
use Memo\CategoryBundle\Model\CategoryModel;
use Memo\FoundationBundle\Service\ToolboxService;
use Memo\JobBundle\Model\JobAttributeModel;
use SimplePie\Parse\Date;
use Terminal42\ChangeLanguage\PageFinder;
use Spatie\SchemaOrg\Schema;
use Spatie\SchemaOrg\Graph;
use Spatie\SchemaOrg\JobPosting;
use Spatie\SchemaOrg\Organization;
use Spatie\SchemaOrg\Place;
use Spatie\SchemaOrg\PostalAddress;
class FoundationModule extends \Module
{
/**
* @return string
*/
protected function compile()
{
if (TL_MODE == 'FE')
{
return '### Frontend-Modul ###';
}
else
{
return '### Backend-Modul ###';
}
}
public function setMetaData( Collection $colItem)
{
// Get Default Language
$strDefaultLanguage = \System::getContainer()->get('memo.foundation.language')->getDefaultLanguage();
// Get the Model, Tablename and load the DCA
$arrAllModels = $colItem->getModels();
$strItemModel = get_class($arrAllModels[0]);
$strItemTable = $strItemModel::getTable();
\Controller::loadDataContainer($strItemTable);
$arrPageMeta = [];
// Get Archive Model and Table
$arrTitleFields = $GLOBALS['TL_DCA'][$strItemTable]['fields']['serpPreview']['eval']['titleFields'];
$arrDescriptionFields = $GLOBALS['TL_DCA'][$strItemTable]['fields']['serpPreview']['eval']['descriptionFields'];
// Translate Item to current language
if($GLOBALS['TL_LANGUAGE'] != $strDefaultLanguage) {
$colItem[0]->getTranslatedModel($GLOBALS['TL_LANGUAGE']);
}
// Set the Meta-Title
$strTitleField = 'title';
if(is_array($arrTitleFields)) {
foreach($arrTitleFields as $strField) {
if($colItem[0]->$strField != ''){
$strTitleField = $strField;
break;
}
}
}
// If possible, get the full title, that is composed of multiple fields
if($strTitleField == 'title' && method_exists($colItem[0], 'getFullTitle')){
$arrPageMeta['title'] = $colItem[0]->getFullTitle();
} else {
$arrPageMeta['title'] = $colItem[0]->$strTitleField;
}
// Set the Meta-Robots
if ($colItem[0]->robots) {
$arrPageMeta['robots'] = $colItem[0]->robots;
} elseif ($colItem[0]->pid && $objItemArchive = $colItem[0]->getRelated('pid')) {
$arrPageMeta['robots'] = $objItemArchive->robots ?: 'index,follow';
}
// Set the Meta-Description
$strDescriptionField = 'description';
if (is_array($arrDescriptionFields)) {
foreach ($arrDescriptionFields as $strField) {
if ($colItem[0]->$strField != '') {
$strDescriptionField = $strField;
break;
}
}
}
if (is_string($colItem[0]->$strDescriptionField)) {
$arrPageMeta['description'] = strip_tags($colItem[0]->$strDescriptionField);
} else {
$arrPageMeta['description'] = '';
}
ToolboxService::setPageHeaderMeta($arrPageMeta);
}
public function setJsonLdData( Collection $colItem)
{
if(!$colItem) {
return;
}
$objItem = $colItem[0];
// Get Default Language
$strDefaultLanguage = \System::getContainer()->get('memo.foundation.language')->getDefaultLanguage();
// Translate Item to current language
if($GLOBALS['TL_LANGUAGE'] != $strDefaultLanguage) {
$objItem->getTranslatedModel($GLOBALS['TL_LANGUAGE']);
}
// Get the Model, Tablename and load the DCA
$arrAllModels = $colItem->getModels();
$strItemModel = get_class($arrAllModels[0]);
$strItemTable = $strItemModel::getTable();
\Controller::loadDataContainer($strItemTable);
// Get class of object to handle different specific cases
$strClass = get_class($objItem);
// Case :: Jobs
if($strClass == "Memo\JobBundle\Model\JobModel"){
$objJobPosting = new JobPosting();
// If possible, get the full title, that is composed of multiple fields
if(method_exists($objItem, 'getFullTitle')){
$objJobPosting->title($objItem->getFullTitle());
} else {
$objJobPosting->title($objItem->title);
}
// Get Structured Data Fields
if($colAttributes = JobAttributeModel::findAllPublished(array('sort' => 'sorting ASC'))) {
$arrProperties = array();
// Loop all attributes
foreach ($colAttributes as $objAttribute) {
// Get the value of this attribute
$strValue = $objItem->getAttributeValue($objAttribute->alias);
if ($objAttribute->struct == 'direct') {
if(array_key_exists($objAttribute->struct_data, $arrProperties)){
$arrProperties[$objAttribute->struct_data] .= $strValue;
} else {
$arrProperties[$objAttribute->struct_data] = $strValue;
}
}
}
// If there are properties, add them to the JobPosting
if($arrProperties && count($arrProperties) > 0){
foreach($arrProperties as $strKey => $strValue){
$objJobPosting->setProperty($strKey, $strValue);
}
}
}
// Get the Archive Data
if($objArchive = $objItem->getRelated('pid')){
// Add the hiringOrganization
if($objArchive->organization_name || $objArchive->organization_url || $objArchive->singleSRC){
$objOrganisation = new Organization();
if($objArchive->organization_name != ''){
$objOrganisation->name($objArchive->organization_name);
}
if($objArchive->organization_url != ''){
$objOrganisation->url($objArchive->organization_url);
}
if($objArchive->singleSRC){
$objImage = \FilesModel::findByUuid($objArchive->singleSRC);
// Get Absolute URL to Image
$strImage = \Contao\Controller::replaceInsertTags('{{env::path}}') . $objImage->path;
$objOrganisation->logo($strImage);
}
$objJobPosting->hiringOrganization($objOrganisation);
}
// Add JobLocation
if($objArchive->organization_address || $objArchive->organization_plz || $objArchive->organization_city || $objArchive->organization_country){
$objPlace = new Place();
$objPostalAddress = new PostalAddress();
if($objArchive->organization_address != ''){
$objPostalAddress->streetAddress($objArchive->organization_address);
}
if($objArchive->organization_plz != ''){
$objPostalAddress->postalCode($objArchive->organization_plz);
}
if($objArchive->organization_city != ''){
$objPostalAddress->addressLocality($objArchive->organization_city);
}
if($objArchive->organization_country != ''){
$objPostalAddress->addressCountry($objArchive->organization_country);
}
$objPlace->address($objPostalAddress);
$objJobPosting->jobLocation($objPlace);
}
// Add the datePosted
if($objItem->date){
$objJobPosting->datePosted(\Contao\Date::parse('Y-m-d', $objItem->date));
} else {
$objJobPosting->datePosted(\Contao\Date::parse('Y-m-d', $objItem->tstamp));
}
// Add the employmentType
if (($objItem->workload_from == 100 && ($objItem->workload_from == $objItem->workload_to || !$objItem->workload_to)) || $objItem->workload_to == 100 && !$objItem->workload_from) {
$objJobPosting->employmentType('FULL_TIME');
} elseif ($objItem->workload_from) {
$objJobPosting->employmentType('PART_TIME');
}
}
// Send response
ToolboxService::setJsonLd($objJobPosting);
} elseif($strClass == 'TODO'){
// @TODO: Add more specific cases
} else {
throw new \Exception('Class ' . $strClass . ' not yet supported for JSON-LD');
}
}
/**
* @param Collection $objItems
* @param null $objDetailPage
* @param bool $bolAddDetailLinkToImage
* @return array
*/
public function parseItems(Collection $objItems, $objPresetDetailPage=null, $bolAddDetailLinkToImage=true, $arrCategories=array())
{
// Get Default Language
$strDefaultLanguage = \System::getContainer()->get('memo.foundation.language')->getDefaultLanguage();
// Get Item Model and Table
$arrAllModels = $objItems->getModels();
$this->strItemModel = get_class($arrAllModels[0]);
$this->strItemTable = $this->strItemModel::getTable();
\Controller::loadDataContainer($this->strItemTable);
// Get Archive Model and Table
$this->strArchiveTable = $GLOBALS['TL_DCA'][$this->strItemTable]['config']['ptable'];
$this->strArchiveModel = $GLOBALS['TL_MODELS'][$this->strArchiveTable];
$colArchives = $this->strArchiveModel::findAll();
\Controller::loadDataContainer($this->strArchiveTable);
// Prepare all Archives in an array, for simple access (less DB requests)
if($colArchives)
{
// Get the page-finder
$objPageFinder = new PageFinder();
$objDetailPage = false;
foreach($colArchives as $objArchive)
{
if(!$objPresetDetailPage && $objArchive->jumpTo && $objPageModel = \PageModel::findByPk( $objArchive->jumpTo))
{
try {
$objDetailPage = $objPageFinder->findAssociatedForLanguage($objPageModel, $GLOBALS['TL_LANGUAGE']);
} catch (\InvalidArgumentException $e) {
$objDetailPage = \PageModel::findByPk( $objArchive->jumpTo);
}
} elseif ($objPresetDetailPage){
$objDetailPage = $objPresetDetailPage;
}
$arrArchives[$objArchive->id] = array(
'objArchive' => $objArchive,
'objDetailPage' => $objDetailPage
);
}
}
// Count Items
$intLimit = $objItems->count();
if ($intLimit < 1)
{
return array();
}
$intCount = 0;
$arrItems = array();
// Get all Cateogries for class-names
$colCategories = CategoryModel::findAll();
$arrAllCategories = array();
if($colCategories){
foreach($colCategories as $objCategory){
$arrAllCategories[$objCategory->id] = ($objCategory->alias != '') ? $objCategory->alias : $objCategory->id;
}
}
// Loop the Items and parse them
foreach ($objItems as $objItem)
{
$arrArchive = $arrArchives[$objItem->pid];
// Filter by categories
$arrItemCategories = false;
if(!is_null($objItem->categories)){
$arrItemCategories = unserialize($objItem->categories);
}
if(is_array($arrItemCategories) && is_array($arrCategories) && count($arrCategories) > 0){
$bolCategory = false;
foreach($arrItemCategories as $arrReference){
if($arrReference['category_id'] != '' && in_array($arrReference['category_id'], $arrCategories)){
$bolCategory = true;
}
}
if(!$bolCategory){
continue;
}
}
$arrItems[] = $this->parseItem(
$objItem,
$arrArchive,
$strDefaultLanguage,
$bolAddDetailLinkToImage,
((++$intCount == 1) ? ' first' : '') . (($intCount == $intLimit) ? ' last' : '') . ((($intCount % 2) == 0) ? ' odd' : ' even'),
$intCount,
$arrAllCategories
);
}
return $arrItems;
}
public function parseItem($objItem, $arrArchive=false, $strDefaultLanguage=false, $bolAddDetailLinkToImage=true, $strClass='', $intCount=0, $arrAllCategories)
{
// Get Default Language (if not set)
if (!$strDefaultLanguage)
{
$strDefaultLanguage = \System::getContainer()->get('memo.foundation.language')->getDefaultLanguage();
}
// Get URL Generator
$urlGenerator = \System::getContainer()->get('contao.routing.url_generator');
// Get Template
$objTemplate = new \FrontendTemplate($this->foundation_item_template);
// Translate Item to current language
if($GLOBALS['TL_LANGUAGE'] != $strDefaultLanguage)
{
$objItem->getTranslatedModel($GLOBALS['TL_LANGUAGE']);
}
if(method_exists($objItem, 'getFullTitle')) {
$objItem->title_full = $objItem->getFullTitle();
}
// Generate categorie-classes
$arrItemCategories = false;
if(!is_null($objItem->categories)){
$arrItemCategories = unserialize($objItem->categories);
}
$strCategoryClasses = '';
if(is_array($arrItemCategories) && count($arrItemCategories) > 0)
{
foreach($arrItemCategories as $arrCategoryReference)
{
$intCategorID = $arrCategoryReference['category_id'];
if($intCategorID != ''){
$strCategoryClasses .= ' category-' . $arrAllCategories[$intCategorID];
}
}
}
// Prepare Template
$objTemplate->setData($objItem->row());
$objTemplate->class .= $this->foundation_item_template . $strClass . $strCategoryClasses;
if(is_array($arrArchive))
{
$objTemplate->archive = $arrArchive['objArchive'];
}
// Add link
if($arrArchive['objDetailPage'])
{
// Check if its an Archive or not
if(method_exists($objItem, 'getURL')) {
$objTemplate->link = $objItem->getURL();
} else {
$objTemplate->link = $urlGenerator->generate($arrArchive['objDetailPage']->alias . '/{alias}',['alias' => $objItem->alias, 'auto_item' => 'alias']);
}
}
// Detect the image-fields
$arrFields = $GLOBALS['TL_DCA'][$this->strItemTable]['fields'];
$objTemplate->imgSizeGallery = $this->imgSizeGallery;
$objTemplate->imgSize = $this->imgSize;
foreach($arrFields as $strFieldKey => $arrField)
{
// Detect the dca-fields with images
if (
array_key_exists('inputType', $arrField)
&& $arrField['inputType'] == 'fileTree'
&& array_key_exists('eval', $arrField)
&& array_key_exists('extensions', $arrField['eval'])
&& preg_match('(jpg|png|svg|tif|webp)', $arrField['eval']['extensions']) === 1
&& array_key_exists('fieldType', $arrField['eval'])
&& $arrField['eval']['fieldType'] == 'radio'
)
{
$strTemplateImageKey = $strFieldKey . '_image';
$arrImage = (array) $this->parseImage($objItem->$strFieldKey, $objItem, $objTemplate, $bolAddDetailLinkToImage, false);
if(isset($arrImage) && is_array($arrImage) && array_key_first($arrImage) !== false && array_key_exists($strFieldKey, $arrImage) && file_exists($arrImage[$strFieldKey])){
$objTemplate->$strTemplateImageKey = $arrImage;
$objTemplate->addImage = true;
}
}
}
// Detect the gallery-fields
$arrFields = $GLOBALS['TL_DCA'][$this->strItemTable]['fields'];
foreach($arrFields as $strFieldKey => $arrField)
{
if (
array_key_exists('inputType', $arrField)
&& $arrField['inputType'] == 'fileTree'
&& array_key_exists('eval', $arrField)
&& array_key_exists('extensions', $arrField['eval'])
&& preg_match('(jpg|png|svg|tif|webp)', $arrField['eval']['extensions']) === 1
&& array_key_exists('fieldType', $arrField['eval'])
&& $arrField['eval']['fieldType'] == 'checkbox'
&& is_string($objItem->$strFieldKey)
)
{
$arrImageUids = unserialize($objItem->$strFieldKey);
if(is_array($arrImageUids))
{
$strTemplateImagesKey = $strFieldKey . '_images';
unset($arrImages);
$arrImages = array();
foreach($arrImageUids as $strImageUuid)
{
$objFile = \FilesModel::findByUuid($strImageUuid);
if($objFile && $objFile->type == 'file' && file_exists($objFile->path))
{
$arrImages[] = (array) $this->parseImage($strImageUuid, $objItem, $objTemplate, $bolAddDetailLinkToImage, true);
}
elseif($objFile && $objFile->type == 'folder')
{
$objSubfiles = \FilesModel::findByPid($objFile->uuid, array('order' => 'name'));
if ($objSubfiles === null)
{
continue;
}
while ($objSubfiles->next())
{
if ($objSubfiles->type == 'folder')
{
continue;
}
$objFile = new \Contao\File($objSubfiles->path);
if (!$objFile->isImage)
{
continue;
}
$arrImages[] = (array) $this->parseImage($objSubfiles->uuid, $objItem, $objTemplate, $bolAddDetailLinkToImage, true);
}
}
}
$objTemplate->$strTemplateImagesKey = $arrImages;
}
}
}
// Enable a hook to be loaded here
if (isset($GLOBALS['TL_HOOKS']['parseFoundationTemplateItem']) && \is_array($GLOBALS['TL_HOOKS']['parseFoundationTemplateItem']))
{
foreach ($GLOBALS['TL_HOOKS']['parseFoundationTemplateItem'] as $callback)
{
$this->import($callback[0]);
$this->{$callback[0]}->{$callback[1]}($objTemplate, $objItem->row(), $this);
}
}
// Return the parsed template
return $objTemplate->parse();
}
public function parseImage($strImageUuid, $objItem, $objTemplate, $bolAddDetailLinkToImage, $bolGallerySize=false)
{
// Add an image
if ( $strImageUuid != '' )
{
$objImage = \FilesModel::findByUuid($strImageUuid);
if ($objImage !== null && is_file(\System::getContainer()->getParameter('kernel.project_dir') . '/' . $objImage->path))
{
// Prepare the Image-Item and Template
$arrImageItem = array();
$arrImageItem['singleSRC'] = $objImage->path;
$objImageTemplate = new \stdClass();
// Get Gallery-Image or Single-Image Size?
$strImgSize = false;
if($bolGallerySize){
$strImgSize = $this->imgSizeGallery;
} elseif($this->imgSize && (unserialize($this->imgSize)[0] != '' || unserialize($this->imgSize)[2] != '')) {
$strImgSize = $this->imgSize;
} elseif($this->size && (unserialize($this->size)[0] != '' || unserialize($this->size)[2] != '')) {
$strImgSize = $this->size;
}
// Get Image Size
if ($strImgSize)
{
$arrSize = StringUtil::deserialize($strImgSize);
if ($arrSize[0] > 0 || $arrSize[1] > 0 || is_numeric($arrSize[2]))
{
$arrImageItem['size'] = $strImgSize;
} elseif (is_string($arrSize[2])) {
$arrImageItem['size'] = $strImgSize;
}
}
// Generate Image into ImageTemplate
$this->addImageToTemplate($objImageTemplate, $arrImageItem, null, null, $objImage);
// If there is no link defined in the Meta-Data, use the template-link (to the detail-page)
if (!$objImageTemplate->fullsize && !$objImageTemplate->imageUrl && $bolAddDetailLinkToImage)
{
$picture = $objImageTemplate->picture;
$objImageTemplate->picture = $picture;
$objImageTemplate->href = $objTemplate->link;
$objImageTemplate->linkTitle = \StringUtil::specialchars(sprintf($GLOBALS['TL_LANG']['MSC']['readMore'], $objItem->title), true);
if (isset($objImageTemplate->source) && $objImageTemplate->source == 'external' && isset($objImageTemplate->target) && $objImageTemplate->target && (!isset($objImageTemplate->attributes) || strpos($objImageTemplate->attributes, 'target="_blank"') === false))
{
$objImageTemplate->attributes .= ' target="_blank"';
}
}
return $objImageTemplate;
}
}
return false;
}
/**
* @param $strTable
* @return array
*/
public function getFindByOptions($strTable)
{
$arrFindByOptions = [];
$arrFindByOptions['order'] = ToolboxService::convertOrderString($this->foundation_order, $strTable);
return $arrFindByOptions;
}
/**
* get options for findBy Method of model
*
* @return array
*/
public function parseBackendTemplate()
{
if ( $this->name )
{
$strTemplateName = "## $this->name ##";
} elseif( $GLOBALS['TL_LANG']['CTE'][$this->type][0] ) {
$strTemplateName = '<b>'.$GLOBALS['TL_LANG']['CTE'][$this->type][0] . "</b><br /><em>" . $GLOBALS['TL_LANG']['CTE'][$this->type][1] . "</em>";
} else {
$strTemplateName = "## $this->type ##";
}
$this->Template->items = array( $strTemplateName );
}
}