<?php

/**
 * TechDivision\Import\Repositories\CacheWarmer\EavAttributeOptionValueCacheWarmer
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 *
 * PHP version 5
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2016 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\Repositories\CacheWarmer;

use TechDivision\Import\Utils\MemberNames;
use TechDivision\Import\Utils\SqlStatementKeys;
use TechDivision\Import\Repositories\EavAttributeOptionValueRepositoryInterface;

/**
 * Cache warmer implementation to pre-load EAV attribute option value data.
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2016 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      http://www.techdivision.com
 */
class EavAttributeOptionValueCacheWarmer implements CacheWarmerInterface
{

    /**
     * The repository with the cache that has to be warmed.
     *
     * @var \TechDivision\Import\Repositories\EavAttributeOptionValueRepositoryInterface
     */
    protected $repository;

    /**
     * Initialize the cache warmer with the repository that has to be warmed.
     *
     * @param \TechDivision\Import\Repositories\EavAttributeOptionValueRepositoryInterface $repository The repository to warm
     */
    public function __construct(EavAttributeOptionValueRepositoryInterface $repository)
    {
        $this->repository = $repository;
    }

    /**
     * Warms the cache for the passed repository.
     *
     * @return void
     */
    public function warm()
    {

        // load the available EAV attribute option values
        $eavAttributeOptionValues = $this->repository->findAll();

        // prepare the caches for the statements
        foreach ($eavAttributeOptionValues as $eavAttributeOptionValue) {
            // prepare the cache key and add the option value to the cache
            $cacheKey1 = $this->repository->cacheKey(
                SqlStatementKeys::EAV_ATTRIBUTE_OPTION_VALUE_BY_OPTION_ID_AND_STORE_ID,
                array(
                    MemberNames::STORE_ID  => $eavAttributeOptionValue[MemberNames::STORE_ID],
                    MemberNames::OPTION_ID => $eavAttributeOptionValue[MemberNames::OPTION_ID]
                )
            );

            // prepare the cache key and add the option value to the cache
            $cacheKey2 = $this->repository->cacheKey(
                SqlStatementKeys::EAV_ATTRIBUTE_OPTION_VALUE_BY_ATTRIBUTE_CODE_AND_STORE_ID_AND_VALUE,
                array(
                    MemberNames::ATTRIBUTE_CODE => $eavAttributeOptionValue[MemberNames::ATTRIBUTE_CODE],
                    MemberNames::STORE_ID       => $eavAttributeOptionValue[MemberNames::STORE_ID],
                    MemberNames::VALUE          => $eavAttributeOptionValue[MemberNames::VALUE]
                )
            );

            // add the EAV attribute option value to the cache
            $this->repository->toCache(
                $eavAttributeOptionValue[MemberNames::VALUE_ID],
                $eavAttributeOptionValue,
                array(
                    $cacheKey1 => $eavAttributeOptionValue[MemberNames::VALUE_ID],
                    $cacheKey2 => $eavAttributeOptionValue[MemberNames::VALUE_ID]
                )
            );
        }
    }
}


<?php

/**
 * TechDivision\Import\Repositories\StoreWebsiteRepository
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 *
 * PHP version 5
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2016 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\Repositories;

use TechDivision\Import\Utils\MemberNames;
use TechDivision\Import\Utils\SqlStatementKeys;

/**
 * Repository implementation to load store website data.
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2016 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      http://www.techdivision.com
 */
class StoreWebsiteRepository extends AbstractRepository implements StoreWebsiteRepositoryInterface
{

    /**
     * The prepared statement to load the store websites.
     *
     * @var \PDOStatement
     */
    protected $storeWebsitesStmt;

    /**
     * Initializes the repository's prepared statements.
     *
     * @return void
     */
    public function init()
    {

        // initialize the prepared statements
        $this->storeWebsitesStmt =
            $this->getConnection()->prepare($this->loadStatement(SqlStatementKeys::STORE_WEBSITES));
    }

    /**
     * Return's an array with the available store websites and their
     * code as keys.
     *
     * @return array The array with all available store websites
     */
    public function findAll()
    {

        // initialize the array with the available store websites
        $storeWebsites = array();

        // execute the prepared statement
        $this->storeWebsitesStmt->execute();

        // load the available store websites
        $availableStoreWebsites = $this->storeWebsitesStmt->fetchAll();

        // fetch the store websites and assemble them as array with the codes as key
        foreach ($availableStoreWebsites as $storeWebsite) {
            $storeWebsites[$storeWebsite[MemberNames::CODE]] = $storeWebsite;
        }

        // return the array with the store websites
        return $storeWebsites;
    }
}


<?php

/**
 * TechDivision\Import\Repositories\StoreRepository
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 *
 * PHP version 5
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2016 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\Repositories;

use TechDivision\Import\Utils\MemberNames;
use TechDivision\Import\Utils\SqlStatementKeys;

/**
 * Repository implementation to load store data.
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2016 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      http://www.techdivision.com
 */
class StoreRepository extends AbstractRepository implements StoreRepositoryInterface
{

    /**
     * The prepared statement to load the available stores.
     *
     * @var \PDOStatement
     */
    protected $storesStmt;

    /**
     * The prepared statement to load the default store.
     *
     * @var \PDOStatement
     */
    protected $storeDefaultStmt;

    /**
     * Initializes the repository's prepared statements.
     *
     * @return void
     */
    public function init()
    {

        // initialize the prepared statements
        $this->storesStmt =
            $this->getConnection()->prepare($this->loadStatement(SqlStatementKeys::STORES));
        $this->storeDefaultStmt =
            $this->getConnection()->prepare($this->loadStatement(SqlStatementKeys::STORE_DEFAULT));
    }

    /**
     * Return's an array with the available stores and their
     * store codes as keys.
     *
     * @return array The array with all available stores
     */
    public function findAll()
    {

        // initialize the array with the available stores
        $stores = array();

        // execute the prepared statement
        $this->storesStmt->execute();

        // load the available stores
        $availableStores = $this->storesStmt->fetchAll();

        // fetch the stores and assemble them as array with the store code as key
        foreach ($availableStores as $store) {
            $stores[$store[MemberNames::CODE]] = $store;
        }

        // return the array with the stores
        return $stores;
    }

    /**
     * Return's the default store.
     *
     * @return array The default store
     */
    public function findOneByDefault()
    {

        // execute the prepared statement and return the default store
        $this->storeDefaultStmt->execute();
        return $this->storeDefaultStmt->fetch();
    }
}


<?php

/**
 * TechDivision\Import\Repositories\CategoryVarcharRepository
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 *
 * PHP version 5
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2016 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\Repositories;

use TechDivision\Import\Utils\SqlStatementKeys;

/**
 * Repository implementation to load category varchar data.
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2016 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      http://www.techdivision.com
 */
class CategoryVarcharRepository extends AbstractRepository implements CategoryVarcharRepositoryInterface
{

    /**
     * Initializes the repository's prepared statements.
     *
     * @return void
     */
    public function init()
    {
    }

    /**
     * Returns the category varchar values for the categories with
     * the passed with the passed entity IDs.
     *
     * @param array $entityIds The array with the category IDs
     *
     * @return mixed The category varchar values
     */
    public function findAllByEntityIds(array $entityIds)
    {

        // prepare the cache key
        $vals = implode(',', $entityIds);
        $sql = str_replace('?', $vals, $this->loadStatement(SqlStatementKeys::CATEGORY_VARCHARS_BY_ENTITY_IDS));

        // load the categories with the passed values and return them
        if ($stmt = $this->getConnection()->query($sql)) {
            return $stmt->fetchAll();
        }
    }

    /**
     * Returns the category varchar values for the categories with
     * the passed with the passed entity ID.
     *
     * @param int $entityId The category ID
     *
     * @return array The category varchar values
     */
    public function findByEntityId($entityId)
    {

        // prepare the cache key
        $sql = str_replace('?', $entityId, $this->loadStatement(SqlStatementKeys::CATEGORY_VARCHARS_BY_ENTITY_IDS));

        // load the categories with the passed values and return them
        if ($stmt = $this->getConnection()->query($sql)) {
            return $stmt->fetch();
        }
    }
}


<?php

/**
 * TechDivision\Import\Repositories\CustomerGroupRepository
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 *
 * PHP version 5
 *
 * @author    Klaas-Tido Rühl <kr@refusion.com>
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2019 REFUSiON GmbH <info@refusion.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      https://www.techdivision.com
 * @link      https://www.refusion.com
 */

namespace TechDivision\Import\Repositories;

use TechDivision\Import\Utils\MemberNames;
use TechDivision\Import\Utils\SqlStatementKeys;

/**
 * The default repository implementation for loading customer groups.
 *
 * @author    Klaas-Tido Rühl <kr@refusion.com>
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2019 REFUSiON GmbH <info@refusion.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      https://www.techdivision.com
 * @link      https://www.refusion.com
 */
class CustomerGroupRepository extends AbstractRepository implements CustomerGroupRepositoryInterface
{

    /**
     * The prepared statement to load the customer groups.
     *
     * @var \PDOStatement
     */
    protected $customerGroupsStmt;

    /**
     * Initializes the repository's prepared statements.
     *
     * @return void
     */
    public function init()
    {
        // initialize the prepared statements
        $this->customerGroupsStmt =
            $this->getConnection()->prepare($this->loadStatement(SqlStatementKeys::CUSTOMER_GROUPS));
    }

    /**
     * Returns an array with the available customer groups and their code as keys.
     *
     * @return array The array with the customer groups
     */
    public function findAll()
    {

        // initialize the array for the customer groups
        $customerGroups = [];

        // execute the prepared statement
        $this->customerGroupsStmt->execute();

        // load the available customer groups
        $availableCustomerGroups = $this->customerGroupsStmt->fetchAll();

        // fetch the customer groups and assemble them as array with the codes as key
        foreach ($availableCustomerGroups as $customerGroup) {
            $customerGroups[$customerGroup[MemberNames::CUSTOMER_GROUP_CODE]] = $customerGroup;
        }

        // return the customer groups
        return $customerGroups;
    }
}


<?php

/**
 * TechDivision\Import\Utils\PrimaryKeyUtil
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 *
 * PHP version 5
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2019 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\Utils;

use TechDivision\Import\ConfigurationInterface;

/**
 * Utility class for edition based primary key handling.
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2019 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      http://www.techdivision.com
 */
class PrimaryKeyUtil implements PrimaryKeyUtilInterface
{

    /**
     * The configuration instance.
     *
     * @var \TechDivision\Import\ConfigurationInterface
     */
    protected $configuration;

    /**
     * The mapping for the edition to primary key member name.
     *
     * @var array
     */
    protected $editionPrimaryKeyMemberNameMappings = array(
        EditionNamesInterface::EE => MemberNames::ROW_ID,
        EditionNamesInterface::CE => MemberNames::ENTITY_ID
    );

    /**
     * Construct a new instance.
     *
     * @param \TechDivision\Import\ConfigurationInterface $configuration The configuration instance
     */
    public function __construct(ConfigurationInterface $configuration)
    {
        $this->configuration = $configuration;
    }

    /**
     * Returns the primary key member name for the actual Magento edition.
     *
     * @return string The primary key member name
     * @throws \Exception Is thrown if the edition is not supported/available
     */
    public function getPrimaryKeyMemberName()
    {

        // make sure the edition name is in upper cases
        $editionName = strtoupper($this->configuration->getMagentoEdition());

        // return the primary key member name for the actual edition
        if (isset($this->editionPrimaryKeyMemberNameMappings[$editionName])) {
            return $this->editionPrimaryKeyMemberNameMappings[$editionName];
        }

        // throw an exception if the edition is NOT supported/available
        throw new \Exception(sprintf('Found not supported/available Magento edition name "%s"', $editionName));
    }
}


<?php

/**
 * TechDivision\Import\Observers\AttributeObserverTrait
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Open Software License (OSL 3.0)
 * that is available through the world-wide-web at this URL:
 * http://opensource.org/licenses/osl-3.0.php
 *
 * PHP version 5
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2016 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\Observers;

use TechDivision\Import\Utils\LoggerKeys;
use TechDivision\Import\Utils\MemberNames;
use TechDivision\Import\Utils\EntityStatus;
use TechDivision\Import\Utils\StoreViewCodes;
use TechDivision\Import\Utils\BackendTypeKeys;
use TechDivision\Import\Utils\ConfigurationKeys;

/**
 * Observer that creates/updates the EAV attributes.
 *
 * @author    Tim Wagner <t.wagner@techdivision.com>
 * @copyright 2016 TechDivision GmbH <info@techdivision.com>
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
 * @link      https://github.com/techdivision/import
 * @link      http://www.techdivision.com
 */
trait AttributeObserverTrait
{

    /**
     * The ID of the attribute to create the values for.
     *
     * @var integer
     */
    protected $attributeId;

    /**
     * The attribute code of the attribute to create the values for.
     *
     * @var string
     */
    protected $attributeCode;

    /**
     * The backend type of the attribute to create the values for.
     *
     * @var string
     */
    protected $backendType;

    /**
     * The attribute value to process.
     *
     * @var mixed
     */
    protected $attributeValue;

    /**
     * The array with the column keys that has to be cleaned up when their values are empty.
     *
     * @var array
     */
    protected $cleanUpEmptyColumnKeys;

    /**
     * The entity's existing attribues.
     *
     * @var array
     */
    protected $attributes;

    /**
     * The attribute code that has to be processed.
     *
     * @return string The attribute code
     */
    public function getAttributeCode()
    {
        return $this->attributeCode;
    }

    /**
     * The attribute value that has to be processed.
     *
     * @return string The attribute value
     */
    public function getAttributeValue()
    {
        return $this->attributeValue;
    }

    /**
     * Remove all the empty values from the row and return the cleared row.
     *
     * @return array The cleared row
     */
    protected function clearRow()
    {

        // query whether or not the column keys has been initialized
        if ($this->cleanUpEmptyColumnKeys === null) {
            // initialize the array with the column keys that has to be cleaned-up
            $this->cleanUpEmptyColumnKeys = array();

            // query whether or not column names that has to be cleaned up have been configured
            if ($this->getSubject()->getConfiguration()->hasParam(ConfigurationKeys::CLEAN_UP_EMPTY_COLUMNS)) {
                // if yes, load the column names
                $cleanUpEmptyColumns = $this->getSubject()->getCleanUpColumns();

                // translate the column names into column keys
                foreach ($cleanUpEmptyColumns as $cleanUpEmptyColumn) {
                    if ($this->hasHeader($cleanUpEmptyColumn)) {
                        $this->cleanUpEmptyColumnKeys[] = $this->getHeader($cleanUpEmptyColumn);
                    }
                }
            }
        }

        // remove all the empty values from the row, expected the columns has to be cleaned-up
        return array_filter(
            $this->row,
            function ($value, $key) {
                return ($value !== null && $value !== '') || in_array($key, $this->cleanUpEmptyColumnKeys);
            },
            ARRAY_FILTER_USE_BOTH
        );
    }

    /**
     * Returns the value(s) of the primary key column(s). As the primary key column can
     * also consist of two columns, the return value can be an array also.
     *
     * @return mixed The primary key value(s)
     */
    protected function getPrimaryKeyValue()
    {
        return $this->getValue($this->getPrimaryKeyColumnName());
    }

    /**
     * Process the observer's business logic.
     *
     * @return void
     */
    protected function process()
    {

        // initialize the store view code
        $this->prepareStoreViewCode();

        // load the store ID, use the admin store if NO store view code has been set
        $storeId = $this->getRowStoreId(StoreViewCodes::ADMIN);

        // load the entity's existing attributes
        $this->getAttributesByPrimaryKeyAndStoreId($this->getPrimaryKey(), $storeId);

        // load the store view - if no store view has been set, we assume the admin
        // store view, which will contain the default (fallback) attribute values
        $storeViewCode = $this->getSubject()->getStoreViewCode(StoreViewCodes::ADMIN);

        // query whether or not the row has already been processed
        if ($this->storeViewHasBeenProcessed($pk = $this->getPrimaryKeyValue(), $storeViewCode)) {
            // log a message
            $this->getSystemLogger()->warning(
                $this->appendExceptionSuffix(
                    sprintf(
                        'Attributes for %s "%s" + store view code "%s" has already been processed',
                        $this->getPrimaryKeyColumnName(),
                        $pk,
                        $storeViewCode
                    )
                )
            );

            // return immediately
            return;
        }

        // load the attributes by the found attribute set and the backend types
        $attributes = $this->getAttributes();
        $backendTypes = $this->getBackendTypes();

        // load the header keys
        $headers = array_flip($this->getHeaders());

        // remove all the empty values from the row
        $row = $this->clearRow();

        // iterate over the attributes and append them to the row
        foreach ($row as $key => $attributeValue) {
            // query whether or not attribute with the found code exists
            if (!isset($attributes[$attributeCode = $headers[$key]])) {
                // log a message in debug mode
                if ($this->isDebugMode()) {
                    $this->getSystemLogger()->debug(
                        $this->appendExceptionSuffix(
                            sprintf(
                                'Can\'t find attribute with attribute code %s',
                                $attributeCode
                            )
                        )
                    );
                }

                // stop processing
                continue;
            } else {
                // log a message in debug mode
                if ($this->isDebugMode()) {
                // log a message in debug mode
                    $this->getSystemLogger()->debug(
                        $this->appendExceptionSuffix(
                            sprintf(
                                'Found attribute with attribute code %s',
                                $attributeCode
                            )
                        )
                    );
                }
            }

            // if yes, load the attribute by its code
            $attribute = $attributes[$attributeCode];

            // load the backend type => to find the apropriate entity
            $backendType = $attribute[MemberNames::BACKEND_TYPE];
            if ($backendType === null) {
                // log a message in debug mode
                $this->getSystemLogger()->warning(
                    $this->appendExceptionSuffix(
                        sprintf(
                            'Found EMTPY backend type for attribute %s',
                            $attributeCode
                        )
                    )
                );
                // stop processing
                continue;
            }

            // do nothing on static backend type
            if ($backendType === BackendTypeKeys::BACKEND_TYPE_STATIC) {
                continue;
            }

            // query whether or not we've found a supported backend type
            if (isset($backendTypes[$backendType])) {
                // initialize attribute ID/code and backend type
                $this->attributeId = $attribute[MemberNames::ATTRIBUTE_ID];
                $this->attributeCode = $attributeCode;
                $this->backendType = $backendType;

                // initialize the persist method for the found backend type
                list ($persistMethod, , $deleteMethod) = $backendTypes[$backendType];

                // set the attribute value
                $this->attributeValue = $attributeValue;

                // prepare/initialize the attribute value
                $value = $this->initializeAttribute($this->prepareAttributes());

                // query whether or not the entity's value has to be persisted or deleted. if the value is
                // an empty string and the status is UPDATE, then the value exists and has to be deleted
                if ($value[MemberNames::VALUE] === '' && $value[EntityStatus::MEMBER_NAME] === EntityStatus::STATUS_UPDATE) {
                    $this->$deleteMethod(array(MemberNames::VALUE_ID => $value[MemberNames::VALUE_ID]));
                } elseif ($value[MemberNames::VALUE] !== '' && $value[MemberNames::VALUE] !== null) {
                    $this->$persistMethod($value);
                } else {
                    // log a debug message, because this should never happen
                    $this->getSubject()->getSystemLogger()->debug(sprintf('Found empty value for attribute "%s"', $attributeCode));
                }

                // continue with the next value
                continue;
            }

            // log the debug message
            $this->getSystemLogger()->debug(
                $this->getSubject()->appendExceptionSuffix(
                    sprintf(
                        'Found invalid backend type %s for attribute %s',
                        $backendType,
                        $attributeCode
                    )
                )
            );
        }
    }

    /**
     * Prepare the attributes of the entity that has to be persisted.
     *
     * @return array|null The prepared attributes
     */
    protected function prepareAttributes()
    {

        // laod the callbacks for the actual attribute code
        $callbacks = $this->getCallbacksByType($this->attributeCode);

        // invoke the pre-cast callbacks
        foreach ($callbacks as $callback) {
            $this->attributeValue = $callback->handle($this);
        }

        // load the ID of the product that has been created recently
        $lastEntityId = $this->getPrimaryKey();

        // load the store ID, use the admin store if NO store view code has been set
        $storeId = $this->getRowStoreId(StoreViewCodes::ADMIN);

        // cast the value based on the backend type
        $castedValue = $this->castValueByBackendType($this->backendType, $this->attributeValue);

        // prepare the attribute values
        return $this->initializeEntity(
            array(
               $this->getPrimaryKeyMemberName() => $lastEntityId,
                MemberNames::ATTRIBUTE_ID       => $this->attributeId,
                MemberNames::STORE_ID           => $storeId,
                MemberNames::VALUE              => $castedValue
            )
        );
    }

    /**
     * Initialize the category product with the passed attributes and returns an instance.
     *
     * @param array $attr The category product attributes
     *
     * @return array The initialized category product
     */
    protected function initializeAttribute(array $attr)
    {
        return $attr;
    }

    /**
     * Return's the array with callbacks for the passed type.
     *
     * @param string $type The type of the callbacks to return
     *
     * @return array The callbacks
     */
    protected function getCallbacksByType($type)
    {
        return $this->getSubject()->getCallbacksByType($type);
    }

    /**
     * Return's mapping for the supported backend types (for the product entity) => persist methods.
     *
     * @return array The mapping for the supported backend types
     */
    protected function getBackendTypes()
    {
        return $this->getSubject()->getBackendTypes();
    }

    /**
     * Return's the attributes for the attribute set of the product that has to be created.
     *
     * @return array The attributes
     * @throws \Exception
     */
    protected function getAttributes()
    {
        return $this->getSubject()->getAttributes();
    }

    /**
     * Intializes the existing attributes for the entity with the passed primary key.
     *
     * @param string  $pk      The primary key of the entity to load the attributes for
     * @param integer $storeId The ID of the store view to load the attributes for
     *
     * @return array The entity attributes
     */
    abstract protected function getAttributesByPrimaryKeyAndStoreId($pk, $storeId);

    /**
     * Return's the logger with the passed name, by default the system logger.
     *
     * @param string $name The name of the requested system logger
     *
     * @return \Psr\Log\LoggerInterface The logger instance
     * @throws \Exception Is thrown, if the requested logger is NOT available
     */
    abstract protected function getSystemLogger($name = LoggerKeys::SYSTEM);

    /**
     * Return's the PK to create the product => attribute relation.
     *
     * @return integer The PK to create the relation with
     */
    abstract protected function getPrimaryKey();

    /**
     * Return's the PK column name to create the product => attribute relation.
     *
     * @return string The PK column name
     */
    abstract protected function getPrimaryKeyMemberName();

    /**
     * Return's the column name that contains the primary key.
     *
     * @return string the column name that contains the primary key
     */
    abstract protected function getPrimaryKeyColumnName();

    /**
     * Queries whether or not the passed PK and store view code has already been processed.
     *
     * @param string $pk            The PK to check been processed
     * @param string $storeViewCode The store view code to check been processed
     *
     * @return boolean TRUE if the PK and store view code has been processed, else FALSE
     */
    abstract protected function storeViewHasBeenProcessed($pk, $storeViewCode);

    /**
     * Persist's the passed varchar attribute.
     *
     * @param array $attribute The attribute to persist
     *
     * @return void
     */
    abstract protected function persistVarcharAttribute($attribute);

    /**
     * Persist's the passed integer attribute.
     *
     * @param array $attribute The attribute to persist
     *
     * @return void
     */
    abstract protected function persistIntAttribute($attribute);

    /**
     * Persist's the passed decimal attribute.
     *
     * @param array $attribute The attribute to persist
     *
     * @return void
     */
    abstract protected function persistDecimalAttribute($attribute);

    /**
     * Persist's the passed datetime attribute.
     *
     * @param array $attribute The attribute to persist
     *
     * @return void
     */
    abstract protected function persistDatetimeAttribute($attribute);

    /**
     * Persist's the passed text attribute.
     *
     * @param array $attribute The attribute to persist
     *
     * @return void
     */
    abstract protected function persistTextAttribute($attribute);

    /**
     * Delete's the datetime attribute with the passed value ID.
     *
     * @param array       $row  The attributes of the entity to delete
     * @param string|null $name The name of the prepared statement that has to be executed
     *
     * @return void
     */
    abstract protected function deleteDatetimeAttribute(array $row, $name = null);

    /**
     * Delete's the decimal attribute with the passed value ID.
     *
     * @param array       $row  The attributes of the entity to delete
     * @param string|null $name The name of the prepared statement that has to be executed
     *
     * @return void
     */
    abstract protected function deleteDecimalAttribute(array $row, $name = null);

    /**
     * Delete's the integer attribute with the passed value ID.
     *
     * @param array       $row  The attributes of the entity to delete
     * @param string|null $name The name of the prepared statement that has to be executed
     *
     * @return void
     */
    abstract protected function deleteIntAttribute(array $row, $name = null);

    /**
     * Delete's the text attribute with the passed value ID.
     *
     * @param array       $row  The attributes of the entity to delete
     * @param string|null $name The name of the prepared statement that has to be executed
     *
     * @return void
     */
    abstract protected function deleteTextAttribute(array $row, $name = null);

    /**
     * Delete's the varchar attribute with the passed value ID.
     *
     * @param array       $row  The attributes of the entity to delete
     * @param string|null $name The name of the prepared statement that has to be executed
     *
     * @return void
     */
    abstract protected function deleteVarcharAttribute(array $row, $name = null);
}


<?php

namespace EventManagerIntegration\Helper;

use Philo\Blade\Blade;

class RenderBlade
{
    /**
     * Return markup from a Blade template
     * @param  string $view     View name
     * @param  array  $data     View data
     * @param  string $viewPath Path to the blade views
     * @return string           The markup
     */
    public static function blade($view, $data = array(), $viewPath = EVENTMANAGERINTEGRATION_VIEW_PATH)
    {
        if (!file_exists(EVENTMANAGERINTEGRATION_CACHE_DIR)) {
            mkdir(EVENTMANAGERINTEGRATION_CACHE_DIR, 0777, true);
        }

        $blade = new Blade($viewPath, EVENTMANAGERINTEGRATION_CACHE_DIR);
        return $blade->view()->make($view, $data)->render();
    }
}


<?php

namespace EventManagerIntegration\Api;

/**
 * Class Events
 * @package EventManagerIntegration\Api
 */
class Events
{
    private $postType = 'event';

    public function __construct()
    {
        add_action('rest_api_init', array($this, 'registerRestRoutes'));
    }

    /**
     * Registers all rest routes for events
     * @return void
     */
    public function registerRestRoutes()
    {
        register_rest_route(
            'wp/v2',
            '/'.$this->postType.'/'.'module',
            array(
                'methods' => \WP_REST_Server::READABLE,
                'callback' => array($this, 'getEvents'),
                'args' => $this->getCollectionParams(),
                'permission_callback' => array($this, 'validateNonce'),
            )
        );
    }

    /**
     * Validates nonce
     * @param $request
     * @return bool
     */
    public function validateNonce($request)
    {
        $nonce = $request->get_param('_wpnonce');

        return wp_verify_nonce($nonce, 'wp_rest');
    }

    /**
     * Get the query params for collections
     * @return array
     */
    public function getCollectionParams()
    {
        return array(
            'page' => array(
                'description' => 'Current page of the collection.',
                'type' => 'integer',
                'default' => 1,
                'sanitize_callback' => 'absint',
            ),
            'per_page' => array(
                'description' => 'Maximum number of items to be returned in result collection.',
                'type' => 'integer',
                'default' => 10,
                'sanitize_callback' => array($this, 'sanitizePerPage'),
            ),
            'start_date' => array(
                'description' => 'Get events from this date',
                'type' => 'string',
                'default' => date('Y-m-d H:i:s', strtotime("today midnight")),
                'sanitize_callback' => array($this, 'sanitizeDate'),
            ),
            'end_date' => array(
                'description' => 'Get events to this date',
                'type' => 'string',
                'default' => date('Y-m-d H:i:s', strtotime("today midnight")),
                'sanitize_callback' => array($this, 'sanitizeEndDate'),
            ),
            'categories' => array(
                'description' => 'Filter by categories',
                'type' => 'object',
                'default' => null,
            ),
            'tags' => array(
                'description' => 'Filter by tags',
                'type' => 'object',
                'default' => null,
            ),
            'groups' => array(
                'description' => 'Filter by groups',
                'type' => 'object',
                'default' => null,
            ),
            'lat' => array(
                'description' => 'Filter by coordinates',
                'type' => 'string',
                'default' => null,
            ),
            'lng' => array(
                'description' => 'Filter by coordinates',
                'type' => 'string',
                'default' => null,
            ),
            'distance' => array(
                'description' => 'Distance (km) radius from coordinates',
                'type' => 'float',
                'default' => null,
            ),
            'module_id' => array(
                'description' => 'The module ID',
                'type' => 'integer',
                'default' => 0,
                'sanitize_callback' => 'absint',
            ),
            'search_string' => array(
                'description' => 'Filter by search string',
                'type' => 'string',
                'default' => '',
                'sanitize_callback' => 'sanitize_text_field',
            ),
            'age_group' => array(
                'description' => 'Filter by age group.',
                'type' => 'object',
                'default' => null,
            ),
            'lang' => array(
                'description' => 'Filter by language.',
                'type' => 'string',
                'default' => null,
            ),
        );
    }

    /**
     * Sanitize date
     * @param $data
     * @return int
     */
    public function sanitizeDate($data)
    {
        $data = strtotime($data);

        if ($data == false) {
            $data = strtotime('today midnight');
        }

        return date('Y-m-d H:i:s', (int)$data);
    }

    /**
     * Sanitize end date. Add 1 day to include events occurring on end date
     * @param $data
     * @return int
     */
    public function sanitizeEndDate($data)
    {
        $data = strtotime($data);

        if ($data == false) {
            $data = strtotime('today midnight');
        }

        $data = strtotime('+1 day', $data) - 1;

        return date('Y-m-d H:i:s', $data);
    }

    /**
     * Return int between 1-100
     * @param $data
     * @return int
     */
    public function sanitizePerPage($data)
    {
        return intval($data);
    }

    /**
     * Get all events between two dates
     * @param object $request Object containing request details
     * @return array|\WP_REST_Response
     */
    public function getEvents($request)
    {
        // Get params
        $params = $request->get_params();

        // Set display limit to -1 to get all events
        $params['display_limit'] = -1;

        // Save coordinates to location array
        if (!empty($params['lat']) && !empty($params['lng'])) {
            $params['location'] = array(
                'lat' => $params['lat'],
                'lng' => $params['lng'],
            );
        }

        // Get events
        $events = \EventManagerIntegration\Helper\QueryEvents::getEventsByInterval($params, 1);
        // Return error if result is empty
        if (empty($events)) {
            return new \WP_REST_Response(
                array(
                    'message' => __('No events could be found.', 'event-integration'),
                    'state' => 'empty_result',
                ),
                404
            );
        }

        // Set total items & pages count
        $total = count($events);
        $totalPages = 1;

        // If per page param is not unlimited, get chunk of paged items
        if ($params['per_page'] !== -1) {
            // Calculate total pages
            $totalPages = ceil($total / $params['per_page']);
            $offset = ($params['page'] - 1) * $params['per_page'];
            $offset = $offset > 0 ? $offset : 0;
            $events = array_slice($events, $offset, $params['per_page']);
        }

        // Sanitize and add meta data to items list
        $events = $this->mapEventModuleData($params['module_id'], $events);

        $response = rest_ensure_response($events);
        // Set headers with total counts
        $response->header('X-WP-Total', $total);
        $response->header('X-WP-TotalPages', $totalPages);

        return $response;
    }

    /**
     * Sanitize and adds data to event list
     * @param int   $moduleId The module ID
     * @param array $events   List of events
     * @return array
     */
    public function mapEventModuleData($moduleId, $events)
    {
        $data = get_fields($moduleId);

        $template = $data['mod_event_display'] ?? 'list';

        $class = '\EventManagerIntegration\Module\Event\TemplateController\\'.ucwords($template).'Template';

        if (class_exists($class)) {
            $controller = new $class(array(), array(), $data);
            if (is_array($data) && isset($controller->data) && is_array($controller->data)) {
                $data = array_merge($data, $controller->data);
            }
        }

        foreach ($events as &$event) {
            // Set permalink url with date parameter
            $event->permalink = esc_url(
                add_query_arg('date', preg_replace('/\D/', '', $event->start_date), get_permalink($event->ID))
            );

            // Format occasion date
            $event->occasion = \EventManagerIntegration\App::formatEventDate($event->start_date, $event->end_date);

            // Set location
            $location = get_post_meta($event->ID, 'location', true);
            $event->location = !empty($location['title']) ? $location['title'] : null;

            // Get image url
            switch ($template) {
                case 'index':
                    if (function_exists('municipio_get_thumbnail_source') && municipio_get_thumbnail_source(
                            $event->ID,
                            array($data['imageDimensions']['width'], $data['imageDimensions']['height']),
                            $data['imageRatio']
                        )) {
                        $event->image_url = municipio_get_thumbnail_source(
                            $event->ID,
                            array($data['imageDimensions']['width'], $data['imageDimensions']['height']),
                            $data['imageRatio']
                        );
                    } elseif (!empty($data['mod_event_def_image'])) {
                        $src = wp_get_attachment_image_src(
                            $data['mod_event_def_image']['ID'],
                            municipio_to_aspect_ratio(
                                $data['imageRatio'],
                                array($data['imageDimensions']['width'], $data['imageDimensions']['height'])
                            )
                        );

                        $event->image_url = $src[0] ?? null;
                    }

                    break;
                default:
                    if (function_exists('municipio_get_thumbnail_source') && municipio_get_thumbnail_source(
                            $event->ID
                        )) {
                        $event->image_url = municipio_get_thumbnail_source($event->ID);
                    } elseif (!empty($data['mod_event_def_image'])) {
                        $src = wp_get_attachment_image_src(
                            $data['mod_event_def_image']['ID']
                        );

                        $event->image_url = $src[0] ?? null;
                    }
            }

        }

        return $events;
    }
}


<?php

namespace EventManagerIntegration\Helper;

class Translations
{
    /**
     * Set category languages and define translations
     */
    public static function defineCategoryTranslations()
    {
        // Bail if translation plugin is not active
        if (!is_plugin_active('polylang-pro/polylang.php') || empty($apiUrl = get_field('event_api_url', 'option'))) {
            return;
        }

        // Build request URL
        $apiUrl = rtrim($apiUrl, '/').'/event_categories?per_page=100';

        // Get categories from Event Manager API
        $result = \EventManagerIntegration\Parser::requestApi($apiUrl);

        if (!$result || is_wp_error($result) || !is_array($result)) {
            return;
        }

        // Lowercase term names
        $result = array_map(
            function ($category) {
                $category['name'] = strtolower($category['name']);

                return $category;
            },
            $result
        );

        // Get all local categories
        $categories = get_terms(
            array(
                'taxonomy' => 'event_categories',
                'hide_empty' => false,
                'lang' => '',
            )
        );

        // Collect list of matched new and original term ID
        $categoryIds = array();

        // Collect term IDs and set lang + translations to categories array
        foreach ($categories as &$category) {
            $category->lang = '';
            $category->translations = array();
            // See if the term exists in both(local and external API) lists
            if (false !== $key = array_search(strtolower($category->name), array_column($result, 'name'))) {
                $categoryIds[$result[$key]['id']] = $category->term_id;
                $category->lang = $result[$key]['lang'] ?? '';
                $category->translations = $result[$key]['translations'] ?? array();
            }
        }

        // Save term lang and define its translations
        foreach ($categories as $category) {
            // Save term lang
            if (!empty($category->lang)) {
                pll_set_term_language($category->term_id, $category->lang);
            }
            // Define translations
            if (!empty($category->translations)) {
                // Replace the original IDs from translations array with the local terms IDs
                $translations =
                    array_filter(
                        array_map(
                            function ($lang) use ($categoryIds) {
                                return array_key_exists($lang, $categoryIds) ? $categoryIds[$lang] : false;
                            },
                            $category->translations
                        )
                    );

                if (empty($translations)) {
                    continue;
                }
                // Save translations
                pll_save_term_translations($translations);
            }
        }

        return;
    }

    /**
     * Return terms for the selected language. Removes/replaces terms if wrong language.
     * @param array  $terms List of term objects
     * @param string $lang  Lang slug
     * @return array Modified list of term objects
     */
    public static function filterTermsByLanguage($terms, $lang = '')
    {
        // Bail if Polylang is not active or terms if empty
        if (!function_exists('pll_current_language') || empty($terms) || !is_array($terms)) {
            return $terms;
        }

        // Get current lang if param if empty
        $lang = !empty($lang) ? $lang : pll_current_language();

        // Loop through terms and remove/replace terms in wrong language
        foreach ($terms as $key => $term) {
            if (!isset($term->term_id)) {
                continue;
            }
            // Get the term lang
            $termLang = pll_get_term_language($term->term_id);

            // If the term is in wrong language try to replace with translated term
            if ($termLang !== $lang) {
                // Remove term from list
                unset($terms[$key]);

                // Get term translations
                $termTranslations = pll_get_term_translations($term->term_id);

                // Replace with translated term if it exists and does not return errors
                if (isset($termTranslations[$lang])) {
                    // Get the translated term object
                    $translatedTerm = get_term($termTranslations[$lang]);

                    if ($translatedTerm != false && !is_wp_error($translatedTerm)) {
                        // Add to the terms list if not already exists
                        if (!in_array($translatedTerm->term_id, array_column((array)$terms, 'term_id'))) {
                            $terms[] = $translatedTerm;
                        }
                    }
                }
            }
        }

        // Rebuild indexes and return terms
        return array_values($terms);
    }
}


<?php

namespace EventManagerIntegration;

class Event extends Entity\PostManager
{
    public $post_type = 'event';

    /**
     * Stuff to do before save
     * @return void
     */
    public function beforeSave()
    {
        $this->post_title = strip_tags(html_entity_decode($this->post_title));
        $this->post_content = html_entity_decode($this->post_content);
    }

    /**
     * Do after save
     * @return bool Used if post got removed or not
     */
    public function afterSave()
    {
        $this->saveOccasions();
        $this->saveCategories();
        $this->saveGroups();
        $this->saveTags();
        $this->saveLocation();
        $this->saveAddLocations();
        $this->saveLanguage();

        if (!empty($this->gallery)) {
            foreach ($this->gallery as $key => $image) {
                $this->setFeaturedImageFromUrl($image['url'], false);
            }
        }

        return true;
    }

    /**
     * Saves location as meta data
     * @return void
     */
    public function saveLocation()
    {
        if ($this->location != null) {
            if (!empty($this->location['parent']) && $this->location['parent']['id'] > 0) {
                $this->location['title'] = $this->location['parent']['title'].', '.$this->location['title'];
            }

            if (!empty($location['latitude']) && !empty($location['longitude'])) {
                update_post_meta($this->ID, 'latitude', $location['latitude']);
                update_post_meta($this->ID, 'longitude', $location['longitude']);
            }
            update_post_meta($this->ID, 'location', $this->location);
        }
    }

    /**
     * Saves additional locations as meta data
     * @return void
     */
    public function saveAddLocations()
    {
        if (is_array($this->additional_locations) && !empty($this->additional_locations)) {
            foreach ($this->additional_locations as &$location) {
                if (!empty($location['parent']) && $location['parent']['id'] > 0) {
                    $location['title'] = $location['parent']['title'].', '.$location['title'];
                }
            }
            update_post_meta($this->ID, 'additional_locations', $this->additional_locations);
        }
    }

    /**
     * Saves categories as taxonomy terms
     * @return void
     */
    public function saveCategories()
    {
        if (empty($this->categories)) {
            $terms = wp_get_post_terms($this->ID, 'event_categories', array('fields' => 'all'));
            if (!empty($terms)) {
                foreach ($terms as $term) {
                    wp_remove_object_terms($this->ID, $term->term_id, 'event_categories');
                }
                delete_post_meta($this->ID, 'categories');
            }
        } else {
            wp_set_object_terms($this->ID, $this->categories, 'event_categories', false);
        }
    }

    /**
     * Saves groups as taxonomy terms
     * @return void
     */
    public function saveGroups()
    {
        // Save group names as new array
        $event_groups = array();
        if (!empty($this->groups) && is_array($this->groups)) {
            foreach ($this->groups as $group) {
                $event_groups[] .= $group['name'];
            }
        }
        wp_set_object_terms($this->ID, $event_groups, 'event_groups', false);
    }

    /**
     * Saves tags as taxonomy terms
     * @return void
     */
    public function saveTags()
    {
        if (empty($this->tags)) {
            $terms = wp_get_post_terms($this->ID, 'event_tags', array('fields' => 'all'));
            if (!empty($terms)) {
                foreach ($terms as $term) {
                    wp_remove_object_terms($this->ID, $term->term_id, 'event_tags');
                }
                delete_post_meta($this->ID, 'tags');
            }
        } else {
            wp_set_object_terms($this->ID, $this->tags, 'event_tags', false);
        }
    }

    /**
     * Save event occasions to integrate_occasions table
     * @return boolean true if one or more occasions was saved, false if not.
     */
    public function saveOccasions()
    {
        global $wpdb;

        // Delete all occasions related to the event
        $db_table = $wpdb->prefix."integrate_occasions";
        $wpdb->delete($db_table, array('event_id' => $this->ID), array('%d'));

        // Remove post if occasions is missing
        if ($this->occasions_complete == null || empty($this->occasions_complete)) {
            wp_delete_post($this->ID, true);

            return false;
        }

        // Loop through occasions for the event
        foreach ($this->occasions_complete as $o) {
            $start_date = !empty($o['start_date']) ? $o['start_date'] : null;
            $end_date = !empty($o['end_date']) ? $o['end_date'] : null;
            $door_time = !empty($o['door_time']) ? $o['door_time'] : null;
            $status = !empty($o['status']) ? $o['status'] : null;
            $occ_exeption_information = !empty($o['occ_exeption_information']) ? $o['occ_exeption_information'] : null;
            $content_mode = !empty($o['content_mode']) ? $o['content_mode'] : null;
            $content = !empty($o['content']) ? $o['content'] : null;

            $wpdb->insert(
                $db_table,
                array(
                    'event_id' => $this->ID,
                    'start_date' => $start_date,
                    'end_date' => $end_date,
                    'door_time' => $door_time,
                    'status' => $status,
                    'exception_information' => $occ_exeption_information,
                    'content_mode' => $content_mode,
                    'content' => ($content),
                )
            );
        }

        return true;
    }

    /**
     * Apply language and translations to the event.
     * Compatible with the plugin 'Polylang'
     */
    public function saveLanguage()
    {
        // Bail if Polylang is not active
        if (!is_plugin_active('polylang-pro/polylang.php')) {
            return;
        }

        // Apply language to the event
        if (!empty($this->lang)) {
            pll_set_post_language($this->ID, $this->lang);
        }

        // Define which events are translations of each other
        if (!empty($this->translations)) {
            $translations = $this->translations;
            // Get locally installed languages
            $localLangs = pll_languages_list();
            // Remove all languages that don't exist locally
            $translations = array_filter(
                $translations,
                function ($translation) use ($localLangs) {
                    return in_array($translation, $localLangs);
                },
                ARRAY_FILTER_USE_KEY
            );

            // Replace the old IDs with new events IDs
            foreach ($translations as $key => &$id) {
                $id = $this->getEventByOriginalId($id);
                if (!$id) {
                    // Remove if event cant't be found
                    unset($translations[$key]);
                }
            }
            // Define event translations
            pll_save_post_translations($translations);
        }
    }

    /**
     * Find an event by it's original id
     * @param int $id Event ID
     * @return Int|bool
     */
    public function getEventByOriginalId($id)
    {
        $args = array(
            'meta_query' => array(
                array(
                    'key' => '_event_manager_id',
                    'value' => $id,
                ),
            ),
            'post_type' => $this->post_type,
            'posts_per_page' => '1',
        );

        $events = get_posts($args);

        if (is_wp_error($events) || !isset($events[0]->ID)) {
            return false;
        }

        return $events[0]->ID;
    }
}


<?php

namespace EventManagerIntegration\Parser;

use \EventManagerIntegration\Event as Event;

class EventManagerApi extends \EventManagerIntegration\Parser
{
    public function __construct($url)
    {
        parent::__construct($url);
    }

    public function start()
    {
        if (function_exists('kses_remove_filters')) {
            kses_remove_filters();
        }

        // Import publishing groups from API
        \EventManagerIntegration\App::importPublishingGroups();
        // Get site languages
        $languages = is_plugin_active('polylang-pro/polylang.php') ? (array)pll_languages_list() : array();
        $eventIds = array();
        $checkApiDiff = true;
        $i = 0;
        do {
            $language = $languages[$i] ?? '';
            // Loop through paginated API request
            $page = 1;
            while ($page) {
                $url = add_query_arg(
                    array(
                        'page' => $page,
                        'per_page' => 25,
                        'lang' => $language,
                    ),
                    $this->url
                );

                $events = \EventManagerIntegration\Parser::requestApi($url);

                if (is_wp_error($events)) {
                    // Skip check of events diff on error
                    $checkApiDiff = false;
                    $page = false;
                    break;
                } elseif ($events) {
                    // Save events to database
                    foreach ($events as $event) {
                        $event['lang'] = !empty($event['lang']) ? $event['lang'] : $language;
                        $this->saveEvent($event);
                        if (isset($event['id'])) {
                            $eventIds[] = $event['id'];
                        }
                    }
                } else {
                    $page = false;
                    break;
                }
                $page++;
            }

            $i++;

        } while (count($languages) > $i);

        // Delete events that has been deleted from the API
        if ($checkApiDiff === true && !empty($eventIds)) {
            $this->removeDeletedEvents($eventIds);
        }
        // Clean up
        $this->removeExpiredOccasions();
        $this->removeExpiredEvents();

        // Sync category translations
        \EventManagerIntegration\Helper\Translations::defineCategoryTranslations();
    }

    /**
     * Save event to database
     * @param  object $event Event data
     * @return void
     * @throws \Exception
     */
    public function saveEvent($event)
    {
        $post_title                     = !empty($event['title']['rendered']) ? $event['title']['rendered'] : null;
        $post_content                   = !empty($event['content']['rendered']) ? $event['content']['rendered'] : null;
        $featured_media                 = !empty($event['featured_media']['source_url']) ? $event['featured_media']['source_url'] : null;
        $categories                     = !empty($event['event_categories']) ? $this->sanitizeCategories($event['event_categories']) : array();
        $tags                           = !empty($event['event_tags']) ? array_map('strtolower', array_map('trim', $event['event_tags'])) : array();
        $groups                         = !empty($event['user_groups']) ? $event['user_groups'] : array();
        $occasions                      = !empty($event['occasions']) ? $event['occasions'] : null;
        $event_link                     = !empty($event['event_link']) ? $event['event_link'] : null;
        $additional_links               = !empty($event['additional_links']) ? $event['additional_links'] : null;
        $related_events                 = !empty($event['related_events']) ? $event['related_events'] : null;
        $organizers                     = !empty($event['organizers']) ? $event['organizers'] : null;
        $supporters                     = !empty($event['supporters']) ? $event['supporters'] : null;
        $booking_link                   = !empty($event['booking_link']) ? $event['booking_link'] : null;
        $booking_phone                  = !empty($event['booking_phone']) ? $event['booking_phone'] : null;
        $booking_email                  = !empty($event['booking_email']) ? $event['booking_email'] : null;
        $age_restriction                = !empty($event['age_restriction']) ? $event['age_restriction'] : null;
        $membership_cards               = !empty($event['membership_cards']) ? $event['membership_cards'] : null;
        $price_information              = !empty($event['price_information']) ? $event['price_information'] : null;
        $ticket_includes                = !empty($event['ticket_includes']) ? $event['ticket_includes'] : null;
        $price_adult                    = isset($event['price_adult']) && $event['price_adult'] !== '' ? $event['price_adult'] : null;
        $price_children                 = isset($event['price_children']) && $event['price_children'] !== '' ? $event['price_children'] : null;
        $children_age                   = !empty($event['children_age']) ? $event['children_age'] : null;
        $price_student                  = isset($event['price_student']) && $event['price_student'] !== '' ? $event['price_student'] : null;
        $price_senior                   = isset($event['price_senior']) && $event['price_senior'] !== '' ? $event['price_senior'] : null;
        $senior_age                     = !empty($event['senior_age']) ? $event['senior_age'] : null;
        $booking_group                  = !empty($event['booking_group']) ? $event['booking_group'] : null;
        $gallery                        = !empty($event['gallery']) ? $event['gallery'] : null;
        $facebook                       = !empty($event['facebook']) ? $event['facebook'] : null;
        $twitter                        = !empty($event['twitter']) ? $event['twitter'] : null;
        $instagram                      = !empty($event['instagram']) ? $event['instagram'] : null;
        $google_music                   = !empty($event['google_music']) ? $event['google_music'] : null;
        $spotify                        = !empty($event['spotify']) ? $event['spotify'] : null;
        $soundcloud                     = !empty($event['soundcloud']) ? $event['soundcloud'] : null;
        $deezer                         = !empty($event['deezer']) ? $event['deezer'] : null;
        $youtube                        = !empty($event['youtube']) ? $event['youtube'] : null;
        $vimeo                          = !empty($event['vimeo']) ? $event['vimeo'] : null;
        $post_status                    = get_field('event_post_status', 'option') ? get_field('event_post_status', 'option') : 'publish';
        $location                       = !empty($event['location']) ? $event['location'] : null;
        $additional_locations           = !empty($event['additional_locations']) ? $event['additional_locations'] : null;
        $latitude                       = is_array($location) && ! empty($location['latitude']) ? $location['latitude'] : null;
        $longitude                      = is_array($location) && ! empty($location['longitude']) ? $location['longitude'] : null;
        $last_update                    = !empty($event['modified_gmt']) ? $event['modified_gmt'] : null;
        $ticket_stock                   = isset($event['ticket_stock']) && $event['ticket_stock'] !== '' ? $event['ticket_stock'] : null;
        $tickets_remaining              = isset($event['tickets_remaining']) && $event['tickets_remaining'] !== '' ? $event['tickets_remaining'] : null;
        $additional_ticket_retailers    = !empty($event['additional_ticket_retailers']) ? $event['additional_ticket_retailers'] : null;
        $additional_ticket_types        = !empty($event['additional_ticket_types']) ? $event['additional_ticket_types'] : null;
        $price_range                    = !empty($event['price_range']) ? $event['price_range'] : null;
        $ticket_release_date            = !empty($event['ticket_release_date']) ? $event['ticket_release_date'] : null;
        $contact_information            = !empty($event['contact_information']) ? $event['contact_information'] : null;
        $contact_phone                  = !empty($event['contact_phone']) ? $event['contact_phone'] : null;
        $contact_email                  = !empty($event['contact_email']) ? $event['contact_email'] : null;
        $age_group_from                 = !empty($event['age_group_from']) ? $event['age_group_from'] : null;
        $age_group_to                   = !empty($event['age_group_to']) ? $event['age_group_to'] : null;
        $accessibility                  = !empty($event['accessibility']) ? $event['accessibility'] : null;
        $lang                           = !empty($event['lang']) ? $event['lang'] : null;
        $translations                   = !empty($event['translations']) ? $event['translations'] : null;

        // Check if event passes taxonomy filters
        $pass_tax_filter = $this->checkFilters(
            $this->filterTaxonomies($categories, 0),
            $this->filterTaxonomies($tags, 1)
        );

        // Check if event already exist and get the post status
        $event_id = $this->checkIfEventExists($event['id']);
        if ($event_id) {
            $post_status = get_post_status($event_id);
        } elseif (is_array($occasions) && !empty($occasions)) {
            // Unpublish the event if occasion is longer than limit option
            $unpublish_limit = get_field('event_unpublish_limit', 'option');
            foreach ($occasions as $occasion) {
                $start = new \DateTime($occasion['start_date']);
                $end = new \Datetime($occasion['end_date']);
                $difference = $end->diff($start)->format("%a");
                if ($unpublish_limit != null && $unpublish_limit >= 0 && ($difference > $unpublish_limit)) {
                    $post_status = 'draft';
                }
            }
        }

        // Save event if it passed taxonomy and group filters
        if ($pass_tax_filter) {
            try {
                $event = new Event(
                    array(
                        'post_title' => $post_title,
                        'post_content' => $post_content,
                        'post_status' => $post_status,
                    ),
                    array(
                        '_event_manager_id' => $event['id'],
                        'categories' => $categories,
                        'tags' => $tags,
                        'groups' => $groups,
                        'occasions_complete' => $occasions,
                        'event_link' => $event_link,
                        'additional_links' => $additional_links,
                        'related_events' => $related_events,
                        'location' => $location,
                        'latitude' => $latitude,
                        'longitude' => $longitude,
                        'additional_locations' => $additional_locations,
                        'organizers' => $organizers,
                        'supporters' => $supporters,
                        'booking_link' => $booking_link,
                        'booking_phone' => $booking_phone,
                        'booking_email' => $booking_email,
                        'age_restriction' => $age_restriction,
                        'membership_cards' => $membership_cards,
                        'price_information' => $price_information,
                        'ticket_includes' => $ticket_includes,
                        'price_adult' => $price_adult,
                        'price_children' => $price_children,
                        'children_age' => $children_age,
                        'price_student' => $price_student,
                        'price_senior' => $price_senior,
                        'senior_age' => $senior_age,
                        'booking_group' => $booking_group,
                        'gallery' => $gallery,
                        'facebook' => $facebook,
                        'twitter' => $twitter,
                        'instagram' => $instagram,
                        'google_music' => $google_music,
                        'spotify' => $spotify,
                        'soundcloud' => $soundcloud,
                        'deezer' => $deezer,
                        'youtube' => $youtube,
                        'vimeo' => $vimeo,
                        'last_update' => $last_update,
                        'ticket_stock' => $ticket_stock,
                        'tickets_remaining' => $tickets_remaining,
                        'additional_ticket_retailers' => $additional_ticket_retailers,
                        'additional_ticket_types' => $additional_ticket_types,
                        'price_range' => $price_range,
                        'ticket_release_date' => $ticket_release_date,
                        'contact_email' => $contact_email,
                        'contact_phone' => $contact_phone,
                        'contact_information' => $contact_information,
                        'age_group_from' => $age_group_from,
                        'age_group_to' => $age_group_to,
                        'accessibility' => $accessibility,
                        'lang' => $lang,
                        'translations' => $translations
                    )
                );
            } catch (\Exception $e) {
                error_log(print_r($e, true));
                return;
            }
            $createSuccess = $event->save();

            if ($createSuccess && !empty($featured_media)) {
                $event->setFeaturedImageFromUrl($featured_media, true);
            }
        }
    }

    /**
     * Sanitizes categories (capitalize, trim spaces, decode special chars)
     * @param array $categories List of categories
     * @return array
     */
    public function sanitizeCategories($categories): array
    {
        // Bail if not array
        if (!is_array($categories)) {
            return $categories;
        }

        return array_map(
            function ($category) {
                return ucfirst(trim(htmlspecialchars_decode($category)));
            },
            $categories
        );
    }

    /**
     * Check if the event passes one of the taxonomy filters
     * @param  bool $cat_filter Bool, equals true if event passed category filter
     * @param  bool $tag_filter Bool, equals true if event passed tag filter
     * @return bool
     */
    public function checkFilters($cat_filter, $tag_filter): bool
    {
        $tax_filter = (empty(get_field('event_filter_cat', 'options')) && empty(
            get_field(
                'event_filter_tag',
                'options'
            )
            )) ? true : false;

        // Check category filters
        if (!empty(get_field('event_filter_cat', 'options'))) {
            $tax_filter = $cat_filter;
        }
        // Check tag filters
        if (!$tax_filter && !empty(get_field('event_filter_tag', 'options'))) {
            $tax_filter = $tag_filter;
        }

        return $tax_filter;
    }

    /**
     * Remove expired occasions from databse
     * @return void
     */
    public function removeExpiredOccasions()
    {
        global $wpdb;
        $date_limit = strtotime("midnight now") - 1;
        // Get all occasions from database
        $db_table = $wpdb->prefix."integrate_occasions";
        $occasions = $wpdb->get_results("SELECT * FROM $db_table ORDER BY start_date DESC", ARRAY_A);

        if (count($occasions) == 0) {
            return;
        }

        foreach ($occasions as $o) {
            // Delete the occasion if expired
            if (strtotime($o['end_date']) < $date_limit) {
                $id = $o['ID'];
                $wpdb->delete($db_table, array('ID' => $id));
            }
        }

        return;
    }

    /**
     * Remove events that has been deleted from API
     * @param $ids
     */
    public function removeDeletedEvents($ids)
    {
        global $wpdb;
        $table = $wpdb->prefix . "integrate_occasions";
        // Get all locally stored events
        $localEvents = $wpdb->get_results("SELECT event_id FROM {$table} GROUP BY event_id", ARRAY_N);
        $localEvents = array_map(function($id) {
            return $id[0];
        }, $localEvents);
        // Collect local IDs of the API events
        $apiEvents = array_map(function($id) {
            $post = get_posts(array(
                'post_type' => 'event',
                'post_status' => array('publish', 'draft'),
                'meta_key' => '_event_manager_id',
                'meta_value' => $id,
                'posts_per_page' => 1
            ));
            return $post[0]->ID ?? null;
        }, array_unique($ids));
        $apiEvents = array_filter($apiEvents);
        // Collect events that is stored locally but is missing in the API
        $diffEvents = array_diff($localEvents, $apiEvents);
        // Loop through the diff and delete its occasions
        if (!empty($diffEvents)) {
            foreach ($diffEvents as $event) {
                $wpdb->delete($table, array('event_id' => $event));
            }
        }
    }

    /**
     * Remove expired events from databse
     * @return void
     */
    public function removeExpiredEvents()
    {
        global $wpdb;
        $post_type = 'event';
        // Get all events from databse
        $query = "SELECT ID FROM $wpdb->posts WHERE post_type = %s AND (post_status = %s or post_status = %s)";
        $completeQuery = $wpdb->prepare($query, $post_type, 'publish', 'draft');
        $events = $wpdb->get_results($completeQuery);

        if (count($events) == 0) {
            return;
        }

        $db_table = $wpdb->prefix."integrate_occasions";
        $query = "SELECT ID, event_id FROM $db_table WHERE event_id = %s";
        // Loop through events and check if occasions exist
        foreach ($events as $e) {
            $completeQuery = $wpdb->prepare($query, $e->ID);
            $results = $wpdb->get_results($completeQuery);
            // Delete event if occasions is empty
            if (count($results) == 0) {
                wp_delete_post($e->ID, true);
            }
        }

        return;
    }

    /**
     * Filter by taxonomies, if add or not to add
     * @param  array $taxonomies All taxonomies
     * @param  int   $type       Type of taxonomy, 0 = category, 1 = tag
     * @return bool
     */
    public function filterTaxonomies($taxonomies, $type)
    {
        $passes = true;
        $tax_array = ($type == 0) ? get_field('event_filter_cat', 'options') : get_field('event_filter_tag', 'options');

        if (!empty($tax_array)) {
            $filters = array_map('trim', explode(',', $tax_array));
            $taxLower = array_map('strtolower', $taxonomies);
            $passes = false;

            foreach ($filters as $filter) {
                if (in_array(strtolower($filter), $taxLower)) {
                    $passes = true;
                }
            }
        }

        return $passes;
    }

    /**
     * Delete empty event taxonomies
     * @return void
     */
    public function deleteEmptyTaxonomies()
    {
        if (!empty(get_object_taxonomies('event'))) {
            foreach (get_object_taxonomies('event') as $taxonomy) {
                // Skip Event Groups and categories
                if ($taxonomy == 'event_groups' || $taxonomy == 'event_categories') {
                    continue;
                }

                $terms = get_terms(
                    array(
                        'taxonomy' => $taxonomy,
                        'hide_empty' => false,
                        'childless' => true,
                    )
                );

                foreach ($terms as $term) {
                    if ($term->count == 0) {
                        wp_delete_term($term->term_id, $taxonomy);
                    }
                }
            }
        }
    }
}


<?php

namespace EventManagerIntegration\Helper;

class ApiUrl
{
    /**
     * Return API url with filter parameters
     */
    public static function buildApiUrl()
    {
        global $wpdb;
        $dbTable = $wpdb->prefix . "integrate_occasions";
        $occasion = $wpdb->get_results(
            "SELECT $dbTable.start_date
            FROM        $dbTable
            LEFT JOIN   $wpdb->posts ON ($wpdb->posts.ID = $dbTable.event_id)
            WHERE       $wpdb->posts.post_type = 'event'
            AND         $wpdb->posts.post_status = 'publish'
            ORDER BY    $dbTable.start_date
            ASC LIMIT 1", ARRAY_A);

        $fromDate = (is_array($occasion) && isset($occasion[0]['start_date']) && strtotime($occasion[0]['start_date']) < strtotime('now')) ? date('Y-m-d', strtotime($occasion[0]['start_date'])) : date('Y-m-d');
        $daysAhead = !empty(get_field('days_ahead', 'options')) ? absint(get_field('days_ahead', 'options')) : 30;
        $toDate = date('Y-m-d', strtotime("midnight now + {$daysAhead} days"));
        $importLocation = get_field('event_import_from_location', 'option');
        // Get nearby events from location
        $point = get_field('event_import_geographic', 'option');
        $latLng = ($point && $importLocation == 'point') ? '&latlng=' . $point['lat'] . ',' . $point['lng'] : '';
        $distance = (get_field('event_geographic_distance', 'option') && $importLocation == 'point') ? '&distance=' . get_field('event_geographic_distance', 'option') : '';
        // Get events within area
        $areaLatLng = '';
        $areaPoints = get_option('event_import_area', 'option');
        if ($importLocation == 'area' && is_array($areaPoints) && !empty($areaPoints)) {
            foreach ($areaPoints as $areaPoint) {
                if (isset($areaPoint['lat']) && isset($areaPoint['lng'])) {
                    $areaLatLng .= '&arealatlng[]=' . $areaPoint['lat'] . ',' . $areaPoint['lng'];
                }
            }
        }
        // Filter by selected groups
        $groups = '';
        $selectedGroups = json_decode(json_encode(get_field('event_filter_group', 'option')), true);
        if (is_array($selectedGroups) && !empty($selectedGroups)) {
            $selectedGroups = array_column($selectedGroups, 'slug');
            $allGroups = array_column(get_option('event_user_groups', array()), 'id', 'slug');
            // Create array with selected group IDs
            $groupIds = array_map(function ($a) use ($allGroups) {
                return isset($allGroups[$a]) ? $allGroups[$a] : null;
            }, $selectedGroups);
            $groups = '&group-id=' . implode(',', array_filter($groupIds));
        }

        // Adds internal event parameter
        $internal = (get_field('event_internal_events', 'option')) ? '&internal=1' : '';

        // Build API-url
        if ($apiUrl = get_field('event_api_url', 'option')) {
            return rtrim($apiUrl, '/') . '/event/time?start=' . $fromDate . '&end=' . $toDate . $latLng . $distance . $areaLatLng . $groups . $internal;
        }

        return;
    }
}


<?php

namespace EventManagerIntegration\Module;

class Location extends \Modularity\Module
{
    public $slug = 'location';
    public $supports = array();

    public function init()
    {
        $this->nameSingular = __('Location', 'event-integration');
        $this->namePlural = __('Locations', 'event-integration');
        $this->description = __('Displays a specific location', 'event-integration');

        add_action('add_meta_boxes_mod-' . $this->slug, array($this, 'registerMetaBoxes'), 10, 2);
        add_action('save_post_mod-' . $this->slug, array($this, 'saveLocation'), 10, 3);
    }

    public function data() : array
    {
        $fields = json_decode(json_encode(get_fields($this->ID)));
        $data['location'] = get_field('mod_location_data', $this->ID);

        $location_fields = (isset($fields->mod_location_fields) && is_array($fields->mod_location_fields)) ? $fields->mod_location_fields : array();
        $data['address'] = (in_array('address', $location_fields)) ? true : false;
        $data['open_hours'] = (in_array('open_hours', $location_fields)) ? true : false;
        $data['organizers'] = (in_array('organizers', $location_fields)) ? true : false;
        $data['prices'] = (in_array('prices', $location_fields)) ? true : false;
        $data['links'] = (in_array('links', $location_fields)) ? true : false;

        $data['classes'] = implode(' ', apply_filters('Modularity/Module/Classes', array('box', 'box-panel'), $this->post_type, $this->args));
        return $data;
    }

    /**
     * Register location meta box
     * @return void
     */
    public function registerMetaBoxes($post)
    {
        add_meta_box(
            'mod-location-meta',
            __('Choose location', 'event-integration'),
            array($this, 'renderLocationList'),
            $post->post_type,
            'normal',
            'default'
        );
    }

    /**
     * Display a list with location from API
     * @param  Obj $post Current post object
     * @return string Markup
     */
    public function renderLocationList($post)
    {
        $api_url = get_field('event_api_url', 'option');
        $location_endpoint = rtrim($api_url, '/') . '/location/complete';
        @$json = file_get_contents($location_endpoint);

        if ($json == false) {
            $markup = '' . __('Something went wrong, pleae check your API url.', 'event-integration') . '';
        } else {
            $locations = json_decode($json);
            $location_data = get_field('mod_location_data', $post->ID);
            $current = ($location_data) ? $location_data->id : false;

            if (!empty($locations)) {
                $markup = '<select name="mod_location">';
                foreach ($locations as $location) {
                    $selected = ($current == $location->id) ? 'selected' : '';
                    $markup .= '<option value="' . $location->id . '" ' . $selected . '>' . $location->title . '</option>';
                }
                $markup .= '</select>';
            } else {
                $markup = '' . __('No locations found', 'event-integration') . '';
            }
        }

        echo $markup;
    }

    /**
     * Save location module metadata on save
     * @param int   $post_id    The post ID
     * @param post  $post       The post object
     * @param bool  $update     Whether this is an existing post being updated or not
     */
    public function saveLocation($post_id, $post, $update)
    {
        $api_url = get_field('event_api_url', 'option');
        $location_endpoint = rtrim($api_url, '/') . '/location/';

        if (isset($_POST['mod_location'])) {
            $location_endpoint = rtrim($api_url, '/') . '/location/' . $_POST['mod_location'] . '?_embed';
            @$json = file_get_contents($location_endpoint);
            if ($json != false) {
                $location = json_decode($json);
                update_post_meta($post_id, 'mod_location_data', $location);
            }
        }

        return;
    }

    /**
     * Available "magic" methods for modules:
     * init()            What to do on initialization (if you must, use __construct with care, this will probably break stuff!!)
     * data()            Use to send data to view (return array)
     * style()           Enqueue style only when module is used on page
     * script            Enqueue script only when module is used on page
     * adminEnqueue()    Enqueue scripts for the module edit/add page in admin
     * template()        Return the view template (blade) the module should use when displayed
     */
}


<?php

namespace EventManagerIntegration\Helper;

class CacheBust
{
    /**
     * Returns the revved/cache-busted file name of an asset.
     * @param string $name Asset name (array key) from rev-mainfest.json
     * @param boolean $returnName Returns $name if set to true while in dev mode
     * @return string filename of the asset (including directory above)
     */
    public static function name($name, $returnName = true)
    {
        if ($returnName == true && defined('DEV_MODE') && DEV_MODE == true) {
            return $name;
        }

        $revManifest = self::getRevManifest();

        if (!isset($revManifest[$name])) {
            return;
        }

        return $revManifest[$name];
    }

    /**
     * Decode assets json to array
     * @return array containg assets filenames
     */
    public static function getRevManifest()
    {
        $jsonPath = EVENTMANAGERINTEGRATION_PATH . apply_filters('EventManagerIntegration/Helper/CacheBust/RevManifestPath', 'dist/rev-manifest.json');

        if (file_exists($jsonPath)) {
            return json_decode(file_get_contents($jsonPath), true);
        } elseif (WP_DEBUG) {
            echo '
Error: Assets not built. Go to ' . EVENTMANAGERINTEGRATION_PATH . ' and run gulp. See '. EVENTMANAGERINTEGRATION_PATH . 'README.md for more info.
';
        }
    }
}


<?php

/**
 * Customizes the admin edit event view to show complete event data
 */

namespace EventManagerIntegration\Admin;

class AdminDisplayEvent extends \EventManagerIntegration\PostTypes\Events
{

    public $post_type = 'event';

    public function __construct()
    {
        add_action('admin_menu', array($this, 'removeMetaBoxes'));
        add_action('add_meta_boxes', array($this, 'addMetaBoxes'));
    }

    /**
     * Remove meta boxes from edit Event view
     */
    public function removeMetaBoxes()
    {
        if (function_exists('get_field')) {
            if (get_field('event_update_button', 'option') == false) {
                remove_meta_box('submitdiv', $this->post_type, 'side');
            }
        }
        remove_meta_box('acf-group_56c33cf1470dc', $this->post_type, 'side');
        remove_meta_box('pageparentdiv', $this->post_type, 'side');
    }

    /*
     * Adding meta boxes to be displayed on the event editor view.
     */
    public function addMetaBoxes()
    {
        add_meta_box(
            'event-info-box',
            esc_html__('Event', 'event-integration'),
            array($this, 'eventInfo'),
            $this->post_type,
            'normal',
            'high'
        );

        add_meta_box(
            'event-meta-box',
            esc_html__('Meta data', 'event-integration'),
            array($this, 'eventMeta'),
            $this->post_type,
            'normal',
            'low'
        );

        add_meta_box(
            'event-gallery-box',
            esc_html__('Gallery', 'event-integration'),
            array($this, 'eventGallery'),
            $this->post_type,
            'normal',
            'low'
        );

        add_meta_box(
            'event-image-box',
            esc_html__('Featured image', 'event-integration'),
            array($this, 'eventImage'),
            $this->post_type,
            'side',
            'high'
        );

        add_meta_box(
            'event-occasion-box',
            esc_html__('Occasions', 'event-integration'),
            array($this, 'eventOccasions'),
            $this->post_type,
            'side',
            'high'
        );
    }

    /*
     * Display event-info-box
     */
    public function eventInfo($object, $box)
    {
        $id = $object->ID;
        ?>

<?php echo get_the_title($id); ?>

<?php echo get_post_field('post_content', $id); ?>

        <?php
    }

    /*
     * Display event-occasion-box
     */
    public function eventOccasions($object, $box)
    {
        $occasions = \EventManagerIntegration\Helper\QueryEvents::getEventOccasions($object->ID);
        ?>

        <?php foreach ($occasions as $o): ?>

                <?php echo \EventManagerIntegration\App::formatEventDate($o->start_date, $o->end_date) ?>

        <?php endforeach ?>

        <?php
    }

    /*
     * Display event-meta-box
     */
    public function eventMeta($object, $box)
    {
        $id = $object->ID;
        $get_meta = get_post_meta($id);
        $meta = array();
        foreach ($get_meta as $key => $value) {
            if (is_array($value) && count($value) == 1) {
                $meta[$key] = \EventManagerIntegration\Shortcodes\SingleEvent::unserData($value[0]);
            } else {
                $meta[$key] = $value;
            }
        }

        $ret = '';

        // Information
        $event_info = \EventManagerIntegration\Shortcodes\SingleEvent::eventInfo(false, false, $meta);
        if (! empty($event_info)) {
            $ret .= '

' . __('Information', 'event-integration') . '

';
            $ret .= $event_info;
        }

        // Location
        $event_location = \EventManagerIntegration\Shortcodes\SingleEvent::eventLocation($meta);
        if (isset($meta['location'])) {
            $ret .= '

'.__('Location', 'event-integration').'

';
            $ret .= $event_location;
        }

        // Booking information
        $booking_info = \EventManagerIntegration\Shortcodes\SingleEvent::eventBooking($meta);
        if (strpos($booking_info, '
')) {
            $ret .= '

'.__('Booking', 'event-integration').'

';
            $ret .= $booking_info;
        }

        // Contact
        if (! empty($contact = \EventManagerIntegration\Shortcodes\SingleEvent::eventContact($meta))) {
            $ret .= '

'.__('Contact', 'event-integration').'

';
            $ret .= '
' . $contact . '
';
        }

        // Organizers
        $event_organizer = \EventManagerIntegration\Shortcodes\SingleEvent::eventOrganizer($meta);
        if (! empty($meta['organizers'])) {
            $ret .= '

'.__('Organizer', 'event-integration').'

';
            $ret .= $event_organizer;
        }

        // Social media and streaming links
        $link_keys = array('facebook', 'twitter', 'instagram', 'google_music', 'spotify', 'soundcloud', 'deezer', 'youtube', 'vimeo');
        $links_exist = (count(array_intersect_key(array_flip($link_keys), $meta)) > 0) ? true : false;
        $event_links = \EventManagerIntegration\Shortcodes\SingleEvent::eventLinks($meta);
        if ($links_exist) {
            $ret .= '

' . __('Links', 'event-integration') . '

';
            $ret .= $event_links;
        }

        $even_taxonomies = \EventManagerIntegration\Shortcodes\SingleEvent::eventTaxonomies($meta);
        $ret .= '

' . __('Taxonomies', 'event-integration') . '

';
        $ret .= $even_taxonomies;

        echo $ret;
    }

    /*
     * Display event-gallery-box
     */
    public function eventGallery($object, $box)
    {
        $id = $object->ID;
        $args = array(
            'post_parent'    => $id,
            'post_type'      => 'attachment',
            'numberposts'    => -1,
            'post_status'    => 'any',
            'post_mime_type' => 'image',
            'orderby'        => 'menu_order',
            'order'           => 'ASC'
        );

        $images = get_posts($args);

        if ($images) {
            foreach($images as $image) {
                echo wp_get_attachment_image($image->ID, array('300', '200'), '');
            }
        }
    }

    /*
     * Display event-image-box
     */
    public function eventImage($object, $box)
    {
        ?>

            <?php if (get_the_post_thumbnail($object->ID)): ?>
                <?php echo get_the_post_thumbnail( $object->ID, array( 266, 266), array('class' => 'featured-image')); ?>
            <?php else: ?>

<?php _e('Image missing', 'event-integration') ?>

            <?php endif; ?>

    <?php
    }

    /**
     * Unserialize and implode arrays
     * @param  mixed $data data to be formatted
     * @return string
     */
    public function outputMeta($data) {
        $unserialized = @unserialize($data);
        if ($unserialized !== false) {
            return $this->multiImplode($unserialized, ', ');
        } else {
            return $data;
        }
    }

    /**
     * Implode arrays into strings
     * @param  array  $array  array to be imploded
     * @param  string $glue   used to separate array objects
     * @return string
     */
    public function multiImplode($array, $glue) {
    $string = '';

    foreach ($array as $key => $item) {
        if (empty($item)) {
            continue;
        }
        if (is_array($item)) {
            $string .= '
' . $this->multiImplode($item, $glue) . '
';
        } else {
            $string .= $item . $glue;
        }
    }
    $string = substr($string, 0, 0-strlen($glue));

    return $string;
    }

}