<?php

namespace StatonLab\TripalTestSuite\Database;

use Exception;

class PublishRecords
{
    /**
     * Holds the ids to publish.
     *
     * @var array
     */
    protected $ids;

    /**
     * Data table to publish.
     *
     * @var string
     */
    protected $data_table;

    /**
     * Bundles corresponding to the publishable data.
     *
     * @var array
     */
    protected $bundles;

    /**
     * Published data to delete.
     *
     * @var array
     */
    protected $published = [];

    /**
     * PublishRecords constructor.
     *
     * @param string data_table Chado table name such as feature.
     * @param array $ids CURRENTLY UNSUPPORTED.
     * @param string $primary_key The primary key name such as feature_id. If not
     *                             provided, the key is obtained using the tripal API.
     */
    public function __construct($data_table, array $ids = [], $primary_key = null)
    {
        $this->ids = $ids;
        $this->data_table = $data_table;
        $this->primary_key = $primary_key !== null ? $primary_key : chado_get_schema($data_table)['primary key'][0];
    }

    /**
     * Perform the publishing.
     *
     * @throws Exception
     */
    public function publish()
    {
        // Find the bundle
        $this->bundles = db_query('SELECT name, bundle_id FROM chado_bundle
                             JOIN tripal_bundle ON tripal_bundle.id = chado_bundle.bundle_id
                             WHERE data_table = :data_table', [':data_table' => $this->data_table])->fetchAll();

        if (empty($this->bundles)) {
            throw new Exception("Unable to find bundles for $this->data_table. Publishing failed!");
        }

        // Perform the publishing
        foreach ($this->bundles as $bundle) {
            // Publish Records
            try {
                $published = $this->tripalChadoPublishRecords($bundle->name, $this->ids);
            } catch (Exception $exception) {
                $this->delete();
                throw $exception;
            }

            $this->published[] = $published;
        }
    }

    /**
     * Deletes the published entities.
     */
    public function delete()
    {
        // For each bundle get the published entities and delete them
        foreach ($this->published as $entities) {
            entity_delete_multiple('TripalEntity', $entities);
        }
    }

    /**
     * Publishes content in Chado as a new TripalEntity entity.
     * This is a modified version of the publisher that accepts record ids.
     *
     * @param $values
     *   A key/value associative array that supports the following keys:
     *   - bundle_name:  The name of the the TripalBundle (e.g. bio_data_1).
     * @param array $ids Ids of chado data to publish.
     *
     * @return array
     *   Array of created entity ids.
     *
     *
     * @author https://github.com/tripal/tripal
     * @see tripal_chado_publish_records()
     * @throws Exception
     */
    protected function tripalChadoPublishRecords($bundle_name, array $ids = [])
    {
        // Load the bundle entity so we can get information about which Chado
        // table/field this entity belongs to.
        $bundle = tripal_load_bundle_entity(['name' => $bundle_name]);
        if (! $bundle) {
            throw new Exception("Could not publish record: The bundle name must be provided");
        }
        $chado_entity_table = chado_get_bundle_entity_table($bundle);

        // Get the mapping of the bio data type to the Chado table.
        $chado_bundle = db_select('chado_bundle', 'cb')
            ->fields('cb')
            ->condition('bundle_id', $bundle->id)
            ->execute()
            ->fetchObject();

        if (! $chado_bundle) {
            throw new Exception("Cannot find mapping of bundle to Chado tables. Could not publish record.");
        }

        $table = $chado_bundle->data_table;
        $type_column = $chado_bundle->type_column;
        $type_linker_table = $chado_bundle->type_linker_table;
        $cvterm_id = $chado_bundle->type_id;
        $type_value = $chado_bundle->type_value;

        // Get the table information for the Chado table.
        if (! $this->primary_key) {
            $table_schema = chado_get_schema($table);
            $pkey_field = $table_schema['primary key'][0];
        } else {
            $pkey_field = $this->primary_key;
        }

        // Construct the SQL for identifying which records should be published.
        $args = [];
        $select = "SELECT T.$pkey_field as record_id ";
        $from = "FROM {".$table."} T
                 LEFT JOIN [".$chado_entity_table."] CE on CE.record_id = T.$pkey_field";
        if (empty($ids)) {
            $where = " WHERE CE.record_id IS NULL ";
        } else {
            $where = " WHERE T.$pkey_field IN (:ids)";
            $args[':ids'] = $ids;
        }

        // Handle records that are mapped to property tables.
        if ($type_linker_table and $type_column and $type_value) {
            $propschema = chado_get_schema($type_linker_table);
            $fkeys = $propschema['foreign keys'][$table]['columns'];
            foreach ($fkeys as $leftkey => $rightkey) {
                if ($rightkey == $pkey_field) {
                    $from .= " INNER JOIN {".$type_linker_table."} LT ON T.$pkey_field = LT.$leftkey ";
                }
            }
            $where .= "AND LT.$type_column = :cvterm_id and LT.value = :prop_value";
            $args[':cvterm_id'] = $cvterm_id;
            $args[':prop_value'] = $type_value;
        }

        // Handle records that are mapped to cvterm linking tables.
        if ($type_linker_table and $type_column and ! $type_value) {
            $cvtschema = chado_get_schema($type_linker_table);
            $fkeys = $cvtschema['foreign keys'][$table]['columns'];
            foreach ($fkeys as $leftkey => $rightkey) {
                if ($rightkey == $pkey_field) {
                    $from .= " INNER JOIN {".$type_linker_table."} LT ON T.$pkey_field = LT.$leftkey ";
                }
            }
            $where .= "AND LT.$type_column = :cvterm_id";
            $args[':cvterm_id'] = $cvterm_id;
        }

        // Handle records that are mapped via a type_id column in the base table.
        if (! $type_linker_table and $type_column) {
            $where .= "AND T.$type_column = :cvterm_id";
            $args[':cvterm_id'] = $cvterm_id;
        }

        // Handle the case where records are in the cvterm table and mapped via a single
        // vocab.  Here we use the type_value for the cv_id.
        if ($table == 'cvterm' and $type_value) {
            $where .= "AND T.cv_id = :cv_id";
            $args[':cv_id'] = $type_value;
        }

        // Handle the case where records are in the cvterm table but we want to
        // use all of the child terms.
        if ($table == 'cvterm' and ! $type_value) {
            $where .= "AND T.cvterm_id IN (
                        SELECT CVTP.subject_id
                        FROM {cvtermpath} CVTP
                        WHERE CVTP.object_id = :cvterm_id)";
            $args[':cvterm_id'] = $cvterm_id;
        }

        // Perform the query.
        $sql = $select.$from.$where;
        $records = chado_query($sql, $args);
        $entities = [];

        while ($record = $records->fetchObject()) {
            // First save the tripal_entity record.
            $record_id = $record->record_id;
            /** @var \TripalEntityController $ec */
            $ec = entity_get_controller('TripalEntity');
            $entity = $ec->create([
                'bundle' => $bundle_name,
                'term_id' => $bundle->term_id,
                // Add in the Chado details for when the hook_entity_create()
                // is called and our tripal_chado_entity_create() implementation
                // can deal with it.
                'chado_record' => chado_generate_var($table, [$pkey_field => $record_id]),
                'chado_record_id' => $record_id,
            ]);
            $entity = $entity->save();
            if (! $entity) {
                throw new Exception('Could not create entity.');
            }

            // Next save the chado entity record.
            $entity_record = [
                'entity_id' => $entity->id,
                'record_id' => $record_id,
            ];

            $result = db_insert($chado_entity_table)->fields($entity_record)->execute();
            if (! $result) {
                throw new Exception('Could not create mapping of entity to Chado record.');
            }

            $entities[] = $entity->id;
        }

        return $entities;
    }
}


<?php

/**
 * TechDivision\Import\Attribute\Callbacks\OptionValueAndSwatchHandler
 *
 * 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-attribute
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\Attribute\Callbacks;

use Doctrine\Common\Collections\Collection;
use TechDivision\Import\SystemLoggerTrait;
use TechDivision\Import\Utils\EntityStatus;
use TechDivision\Import\ConfigurationInterface;
use TechDivision\Import\Attribute\Utils\MemberNames;
use TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface;

/**
 * A handler implementation that creates, whether it exists or not, the option as well as the swatch/value.
 *
 * @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-attribute
 * @link      http://www.techdivision.com
 */
class OptionValueAndSwatchHandler implements OptionValueAndSwatchHandlerInterface
{

    /**
     * The trait that provides system logger functionality.
     *
     * @var \TechDivision\Import\SystemLoggerTrait
     */
    use SystemLoggerTrait;

    /**
     * The swatch type loader instance.
     *
     * @var \TechDivision\Import\Attribute\Callbacks\SwatchTypeLoaderInterface
     */
    protected $swatchTypeLoader;

    /**
     * The attribute bunch processor instance.
     *
     * @var \TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface
     */
    protected $attributeBunchProcessor;

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

    /**
     * Initialize the handler with the passed logger collection, swatch type loader and attribute bunch processor instance.
     *
     * @param \TechDivision\Import\ConfigurationInterface                              $configuration      The configuration instance
     * @param \Doctrine\Common\Collections\Collection                                  $systemLoggers      The collection containing the system loggers
     * @param \TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface $attributeProcessor The attribute bunch processor instance
     * @param \TechDivision\Import\Attribute\Callbacks\SwatchTypeLoaderInterface       $swatchTypeLoader   The swatch type loader instance
     */
    public function __construct(
        ConfigurationInterface $configuration,
        Collection $systemLoggers,
        AttributeBunchProcessorInterface $attributeProcessor,
        SwatchTypeLoaderInterface $swatchTypeLoader
    ) {
        $this->configuration = $configuration;
        $this->systemLoggers = $systemLoggers;
        $this->swatchTypeLoader = $swatchTypeLoader;
        $this->attributeBunchProcessor = $attributeProcessor;
    }

    /**
     * Returns the swatch type loader instance.
     *
     * @return \\TechDivision\Import\Attribute\Callbacks\SwatchTypeLoaderInterface The swatch type loader instance
     */
    protected function getSwatchTypeLoader()
    {
        return $this->swatchTypeLoader;
    }

    /**
     * Returns the attribute bunch processor instance.
     *
     * @return \TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface The attribute bunch processor instance
     */
    protected function getAttributeBunchProcessor()
    {
        return $this->attributeBunchProcessor;
    }

    /**
     * Returns the configuration instance.
     *
     * @return \TechDivision\Import\ConfigurationInterface The configuration instance
     */
    protected function getConfiguration()
    {
        return $this->configuration;
    }

    /**
     * Queries whether or not an option as well as its swatch/value for the attribute with the given code
     * already exists. If not the option and/or the swatch/value will be created dynamically.
     *
     * @param string  $attributeCode The attribute code to create the option as well as the swatch/value for
     * @param integer $storeId       The store ID
     * @param mixed   $value         The store specific option swatch/value
     *
     * @return void
     */
    public function createOptionValueOrSwatchIfNecessary($attributeCode, $storeId, $value)
    {

        // queryh whether or not we've a swatch attribute
        if ($type = $this->getSwatchTypeLoader()->loadSwatchType($this->getEntityTypeId(), $attributeCode)) {
            $this->createOptionSwatchIfNecessary($attributeCode, $storeId, $value, $type);
        } else {
            $this->createOptionValueIfNecessary($attributeCode, $storeId, $value);
        }
    }

    /**
     * Queries whether or not an option as well as its value for the attribute with the given code
     * already exists. If not the option and/or the value will be created dynamically.
     *
     * @param string  $attributeCode The attribute code to create the option as well as the value for
     * @param integer $storeId       The store ID
     * @param mixed   $value         The store specific option swatch/value
     *
     * @return void
     */
    public function createOptionValueIfNecessary($attributeCode, $storeId, $value)
    {

        // try to load the attribute option value and add the option ID
        if ($this->loadAttributeOptionValueByEntityTypeIdAndAttributeCodeAndStoreIdAndValue($entityTypeId = $this->getEntityTypeId(), $attributeCode, $storeId, $value)) {
            return;
        }

        // initialize the option ID
        $optionId = $this->createOptionByValueIfNecessary($entityTypeId, $attributeCode, $storeId, $value);

        // create the value for the option
        $this->persistAttributeOptionValue($this->prepareAttributeOptionValue($optionId, $storeId, $value));

        // log a message that the value has successfully been created
        $this->getSystemLogger()->info(sprintf('Successfully created option value for attribute with code "%s", store ID "%d" and value "%s"', $attributeCode, $storeId, $value));
    }

    /**
     * Queries whether or not an option as well as its swatch/value for the attribute with the given code
     * already exists. If not the option and/or the swatch will be created dynamically.
     *
     * @param string  $attributeCode The attribute code to create the option as well as the swatch for
     * @param integer $storeId       The store ID
     * @param mixed   $value         The store specific option swatch/value
     * @param string  $type          The type to create the swatch for
     *
     * @return void
     */
    public function createOptionSwatchIfNecessary($attributeCode, $storeId, $value, $type)
    {

        // try to load the attribute option value and add the option ID
        if ($this->loadAttributeOptionSwatchByEntityTypeIdAndAttributeCodeAndStoreIdAndValueAndType($entityTypeId = $this->getEntityTypeId(), $attributeCode, $attributeCode, $storeId, $value)) {
            return;
        }

        // initialize the option ID
        $optionId = $this->createOptionBySwatchIfNecessary($entityTypeId, $attributeCode, $storeId, $value, $type);

        // create the swatch for the option
        $this->persistAttributeOptionSwatch($this->prepareAttributeOptionSwatch($optionId, $storeId, $value, $type));

        // log a message that the swwatch has successfully been created
        $this->getSystemLogger()->info(sprintf('Successfully created option swatch for attribute with code "%s", store ID "%d" and value "%s"', $attributeCode, $storeId, $value));
    }

    /**
     * Returns the configured entity type code.
     *
     * @return string The entity type code from the configuration
     */
    protected function getEntityTypeCode()
    {
        return $this->getConfiguration()->getEntityTypeCode();
    }

    /**
     * Returns the entity type ID for the configured entity type code.
     *
     * @return integer The entity type ID of the configured entity type
     */
    protected function getEntityTypeId()
    {

        // load the entity type for the configured entity type code
        $entityType = $this->getAttributeBunchProcessor()->loadEntityTypeByEntityTypeCode($this->getEntityTypeCode());

        // return the entity type ID
        return $entityType[MemberNames::ENTITY_TYPE_ID];
    }

    /**
     * Queries whether or not an option for the attribute with the given code already exists.
     * If not the option and/or the swatch/value will be created dynamically.
     *
     * @param integer $entityTypeId  The entity type ID of the option to create
     * @param string  $attributeCode The attribute code to create the option as well as the swatch/value for
     * @param integer $storeId       The store ID
     * @param mixed   $value         The store specific option swatch/value
     *
     * @return integer The ID of the option
     */
    protected function createOptionByValueIfNecessary($entityTypeId, $attributeCode, $storeId, $value)
    {

        // initialize the option ID
        $optionId = 0;

        // try to load the attribute option by the given attribute code, store ID + value
        if ($attributeOption = $this->loadAttributeOptionByEntityTypeIdAndAttributeCodeAndStoreIdAndValue($entityTypeId, $attributeCode, $storeId, $value)) {
            // set the ID of the loaded option
            $optionId = $attributeOption[MemberNames::OPTION_ID];
        } else {
            // create a new option and set the ID
            $optionId = $this->persistAttributeOption($this->prepareAttributeOption($entityTypeId, $attributeCode));
            // log a message that a new option has been created
            $this->getSystemLogger()->info(sprintf('Successfully created new option for attribute with code "%s"', $attributeCode));
        }

        // return the option ID
        return $optionId;
    }

    /**
     * Queries whether or not an option for the attribute with the given code already exists.
     * If not the option and/or the swatch/value will be created dynamically.
     *
     * @param integer $entityTypeId  The entity type ID of the option to create
     * @param string  $attributeCode The attribute code to create the option as well as the swatch/value for
     * @param integer $storeId       The store ID
     * @param mixed   $value         The store specific option swatch/value
     * @param string  $type          The type of the swatch
     *
     * @return integer The ID of the option
     */
    protected function createOptionBySwatchIfNecessary($entityTypeId, $attributeCode, $storeId, $value, $type)
    {

        // initialize the option ID
        $optionId = 0;

        // try to load the attribute option by the given attribute code, store ID + value
        if ($attributeOption = $this->loadAttributeOptionByEntityTypeIdAndAttributeCodeAndStoreIdAndSwatchAndType($entityTypeId, $attributeCode, $storeId, $value, $type)) {
            // set the ID of the loaded option
            $optionId = $attributeOption[MemberNames::OPTION_ID];
        } else {
            // create a new option and set the ID
            $optionId = $this->persistAttributeOption($this->prepareAttributeOption($attributeCode));
            // log a message that a new option has been created
            $this->getSystemLogger()->info(sprintf('Successfully created new option for attribute with code "%s"', $attributeCode));
        }

        // return the option ID
        return $optionId;
    }

    /**
     * Prepare the attributes of the entity that has to be persisted.
     *
     * @param integer $entityTypeId  The entity type ID of the option
     * @param string  $attributeCode The attribute code of the option
     *
     * @return array The prepared option
     */
    protected function prepareAttributeOption($entityTypeId, $attributeCode)
    {

        // load the attribute ID
        $attribute = $this->loadAttributeByEntityTypeIdAndAttributeCode($entityTypeId, $attributeCode);

        // initialize the sort order
        $sortOrder = 0;

        // try to load the option with the highest sort order and raise it by one if available
        if ($attributeOption = $this->loadAttributeOptionByAttributeIdAndHighestSortOrder($attributeId = $attribute[MemberNames::ATTRIBUTE_ID])) {
            $sortOrder = (integer) $attributeOption[MemberNames::SORT_ORDER] + 1;
        }

        // return the prepared attribute option
        return $this->initializeEntity(
            array(
                MemberNames::ATTRIBUTE_ID => $attributeId,
                MemberNames::SORT_ORDER   => $sortOrder
            )
        );
    }

    /**
     * Prepare the attributes of the entity that has to be persisted.
     *
     * @param integer $optionId The option ID of the option value to create
     * @param integer $storeId  The store ID to create the option value for
     * @param mixed   $value    The option value itself
     *
     * @return array The prepared option value
     */
    protected function prepareAttributeOptionValue($optionId, $storeId, $value)
    {

        // return the prepared option value
        return $this->initializeEntity(
            array(
                MemberNames::OPTION_ID => $optionId,
                MemberNames::STORE_ID  => $storeId,
                MemberNames::VALUE     => $value
            )
        );
    }

    /**
     * Prepare the attributes of the entity that has to be persisted.
     *
     * @param integer $optionId The option ID of the swatch to create
     * @param integer $storeId  The store ID to create the swatch for
     * @param mixed   $value    The swatch value itself
     * @param string  $type     The type of the swatch
     *
     * @return array The prepared option swatch
     */
    protected function prepareAttributeOptionSwatch($optionId, $storeId, $value, $type)
    {

        // return the prepared option swatch
        return $this->initializeEntity(
            array(
                MemberNames::OPTION_ID => $optionId,
                MemberNames::STORE_ID  => $storeId,
                MemberNames::VALUE     => $value,
                MemberNames::TYPE      => $type
            )
        );
    }

    /**
     * Initialize's and return's a new entity with the status 'create'.
     *
     * @param array $attr The attributes to merge into the new entity
     *
     * @return array The initialized entity
     */
    protected function initializeEntity(array $attr = array())
    {
        return array_merge(array(EntityStatus::MEMBER_NAME => EntityStatus::STATUS_CREATE), $attr);
    }

    /**
     * Return's the unique identifier of the actual row, e. g. a products SKU.
     *
     * @return mixed The row's unique identifier
     */
    protected function getUniqueIdentifier()
    {
        return $this->getValue('sku');
    }

    /**
     * Return's the store ID of the actual row, or of the default store
     * if no store view code is set in the CSV file.
     *
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
     *
     * @return integer The ID of the actual store
     * @throws \Exception Is thrown, if the store with the actual code is not available
     */
    protected function getRowStoreId($default = null)
    {
        return $this->getSubject()->getRowStoreId($default);
    }

    /**
     * Return's the EAV attribute with the passed entity type ID and code.
     *
     * @param integer $entityTypeId  The entity type ID of the EAV attribute to return
     * @param string  $attributeCode The code of the EAV attribute to return
     *
     * @return array The EAV attribute
     */
    protected function loadAttributeByEntityTypeIdAndAttributeCode($entityTypeId, $attributeCode)
    {
        return $this->getAttributeBunchProcessor()->loadAttributeByEntityTypeIdAndAttributeCode($entityTypeId, $attributeCode);
    }

    /**
     * Load's and return's the EAV attribute option with the passed entity type ID, code, store ID and value.
     *
     * @param string  $entityTypeId  The entity type ID of the EAV attribute to load the option for
     * @param string  $attributeCode The code of the EAV attribute option to load
     * @param integer $storeId       The store ID of the attribute option to load
     * @param string  $value         The value of the attribute option to load
     *
     * @return array The EAV attribute option
     */
    protected function loadAttributeOptionByEntityTypeIdAndAttributeCodeAndStoreIdAndValue($entityTypeId, $attributeCode, $storeId, $value)
    {
        return $this->getAttributeBunchProcessor()->loadAttributeOptionByEntityTypeIdAndAttributeCodeAndStoreIdAndValue($entityTypeId, $attributeCode, $storeId, $value);
    }

    /**
     * Load's and return's the EAV attribute option value with the passed entity type ID, code, store ID and value.
     *
     * @param string  $entityTypeId  The entity type ID of the EAV attribute to load the option value for
     * @param string  $attributeCode The code of the EAV attribute option to load
     * @param integer $storeId       The store ID of the attribute option to load
     * @param string  $value         The value of the attribute option to load
     *
     * @return array The EAV attribute option value
     */
    protected function loadAttributeOptionValueByEntityTypeIdAndAttributeCodeAndStoreIdAndValue($entityTypeId, $attributeCode, $storeId, $value)
    {
        return $this->getAttributeBunchProcessor()->loadAttributeOptionValueByEntityTypeIdAndAttributeCodeAndStoreIdAndValue($entityTypeId, $attributeCode, $storeId, $value);
    }

    /**
     * Load's and return's the EAV attribute option with the passed entity type ID and code, store ID, swatch and type.
     *
     * @param string  $entityTypeId  The entity type ID of the EAV attribute to load the option for
     * @param string  $attributeCode The code of the EAV attribute option to load
     * @param integer $storeId       The store ID of the attribute option to load
     * @param string  $swatch        The swatch of the attribute option to load
     * @param string  $type          The swatch type of the attribute option to load
     *
     * @return array The EAV attribute option
     */
    protected function loadAttributeOptionByEntityTypeIdAndAttributeCodeAndStoreIdAndSwatchAndType($entityTypeId, $attributeCode, $storeId, $swatch, $type)
    {
        return $this->getAttributeBunchProcessor()->loadAttributeOptionByEntityTypeIdAndAttributeCodeAndStoreIdAndSwatchAndType($entityTypeId, $attributeCode, $storeId, $swatch, $type);
    }

    /**
     * Load's and return's the EAV attribute option swatch with the passed entity type ID, code, store ID, value and type.
     *
     * @param string  $entityTypeId  The entity type ID of the EAV attribute to load the option swatch for
     * @param string  $attributeCode The code of the EAV attribute option swatch to load
     * @param integer $storeId       The store ID of the attribute option swatch to load
     * @param string  $value         The value of the attribute option swatch to load
     * @param string  $type          The type of the attribute option swatch to load
     *
     * @return array The EAV attribute option swatch
     */
    protected function loadAttributeOptionSwatchByEntityTypeIdAndAttributeCodeAndStoreIdAndValueAndType($entityTypeId, $attributeCode, $storeId, $value, $type)
    {
        return $this->getAttributeBunchProcessor()->loadAttributeOptionSwatchByEntityTypeIdAndAttributeCodeAndStoreIdAndValueAndType($entityTypeId, $attributeCode, $storeId, $value, $type);
    }

    /**
     * Returns the EAV attribute option of attribute with the passed ID with the highest sort order.
     *
     * @param integer $attributeId The ID of the attribute to return the EAV option with the highest sort order for
     *
     * @return array|null The EAV attribute option with the highest sort order
     */
    protected function loadAttributeOptionByAttributeIdAndHighestSortOrder($attributeId)
    {
        return $this->getAttributeBunchProcessor()->loadAttributeOptionByAttributeIdAndHighestSortOrder($attributeId);
    }

    /**
     * Persist's the passed EAV attribute option data and return's the ID.
     *
     * @param array       $attributeOption The attribute option data to persist
     * @param string|null $name            The name of the prepared statement that has to be executed
     *
     * @return string The ID of the persisted attribute
     */
    protected function persistAttributeOption(array $attributeOption, $name = null)
    {
        return $this->getAttributeBunchProcessor()->persistAttributeOption($attributeOption);
    }

    /**
     * Persist's the passed EAV attribute option value data and return's the ID.
     *
     * @param array       $attributeOptionValue The attribute option value data to persist
     * @param string|null $name                 The name of the prepared statement that has to be executed
     *
     * @return void
     */
    protected function persistAttributeOptionValue(array $attributeOptionValue, $name = null)
    {
        $this->getAttributeBunchProcessor()->persistAttributeOptionValue($attributeOptionValue);
    }

    /**
     * Persist the passed attribute option swatch.
     *
     * @param array       $attributeOptionSwatch The attribute option swatch to persist
     * @param string|null $name                  The name of the prepared statement that has to be executed
     *
     * @return void
     */
    protected function persistAttributeOptionSwatch(array $attributeOptionSwatch, $name = null)
    {
        $this->getAttributeBunchProcessor()->persistAttributeOptionSwatch($attributeOptionSwatch);
    }
}


<?php

namespace StatonLab\TripalTestSuite\Database;

use StatonLab\TripalTestSuite\Exceptions\TripalTestBootstrapException;

trait LoadsDatabaseSeeders
{
    /**
     * List of loaded seeders.
     *
     * @var array
     */
    protected $seeders = [];

    /**
     * Load the database seeders.
     *
     * @return mixed
     * @throws TripalTestBootstrapException
     */
    public function loadDatabaseSeeders()
    {
        $workingDir = getcwd();
        if (file_exists("$workingDir/tests/DatabaseSeeders")) {
            foreach (glob("$workingDir/tests/DatabaseSeeders/*.php") as $seeder) {
                require_once $seeder;

                // Extract the class name
                /** @var \StatonLab\TripalTestSuite\Database\Seeder $className */
                $className = $this->getClassName($seeder);

                if (! class_exists($className)) {
                    $error = "Database seeder class $className not found. Make sure the filename and the class name match.";
                    throw new TripalTestBootstrapException($error);
                }

                $this->seeders[] = $className;
            }
        }

        return $this->seeders;
    }

    /**
     * Get the class name from the file name.
     *
     * @param string $file
     * @return string
     */
    protected function getClassName($file)
    {
        return trim('Tests\\DatabaseSeeders\\'.basename($file, '.php'), '\\');
    }
}


<?php

/**
 * TechDivision\Import\Attribute\Observers\AttributeOptionSwatchObserver
 *
 * 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-attribute
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\Attribute\Observers;

use TechDivision\Import\Utils\StoreViewCodes;
use TechDivision\Import\Attribute\Utils\ColumnKeys;
use TechDivision\Import\Attribute\Utils\MemberNames;
use TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface;

/**
 * Observer that create's the attribute option swatchs found in the additional CSV file.
 *
 * @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-attribute
 * @link      http://www.techdivision.com
 */
class AttributeOptionSwatchObserver extends AbstractAttributeImportObserver
{

    /**
     * The attribute processor instance.
     *
     * @var \TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface
     */
    protected $attributeBunchProcessor;

    /**
     * Initializes the observer with the passed subject instance.
     *
     * @param \TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface $attributeBunchProcessor The attribute bunch processor instance
     */
    public function __construct(AttributeBunchProcessorInterface $attributeBunchProcessor)
    {
        $this->attributeBunchProcessor = $attributeBunchProcessor;
    }

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

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

        // prepare and insert the attribute option swatch
        if ($attr = $this->prepareAttributes()) {
            $this->persistAttributeOptionSwatch($this->initializeAttribute($attr));
        }
    }

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

        // load the option ID
        $optionId = $this->getLastOptionId();

        // load the store ID, value + type
        $storeId = $this->getRowStoreId(StoreViewCodes::ADMIN);
        $value = $this->getValue(ColumnKeys::SWATCH_VALUE);
        $type = $this->getValue(ColumnKeys::SWATCH_TYPE);

        // load the attribute option swatch value/type
        if ($value !== null && $type !== null) {
            // return the prepared attribute option
            return $this->initializeEntity(
                array(
                    MemberNames::OPTION_ID  => $optionId,
                    MemberNames::STORE_ID   => $storeId,
                    MemberNames::VALUE      => $value,
                    MemberNames::TYPE       => $type
                )
            );
        }
    }

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

    /**
     * Return's the attribute bunch processor instance.
     *
     * @return \TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface The attribute bunch processor instance
     */
    protected function getAttributeBunchProcessor()
    {
        return $this->attributeBunchProcessor;
    }

    /**
     * Return's the ID of the option that has been created recently.
     *
     * @return integer The option ID
     */
    protected function getLastOptionId()
    {
        return $this->getSubject()->getLastOptionId();
    }

    /**
     * Persist the passed attribute option swatch.
     *
     * @param array $attributeOptionSwatch The attribute option swatch to persist
     *
     * @return void
     */
    protected function persistAttributeOptionSwatch(array $attributeOptionSwatch)
    {
        return $this->getAttributeBunchProcessor()->persistAttributeOptionSwatch($attributeOptionSwatch);
    }
}


<?php

/**
 * TechDivision\Import\Attribute\Subjects\AbstractAttributeSubject
 *
 * 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-attribute
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\Attribute\Subjects;

use TechDivision\Import\Subjects\AbstractSubject;
use TechDivision\Import\Subjects\EntitySubjectInterface;
use TechDivision\Import\Utils\RegistryKeys;
use TechDivision\Import\Attribute\Utils\MemberNames;
use TechDivision\Import\Utils\EntityTypeCodes;

/**
 * The abstract product subject implementation that provides basic attribute
 * handling business logic.
 *
 * @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-attribute
 * @link      http://www.techdivision.com
 */
abstract class AbstractAttributeSubject extends AbstractSubject implements AttributeSubjectInterface, EntitySubjectInterface
{

    /**
     * The array with the available entity types.
     *
     * @var array
     */
    protected $entityTypes = array();

    /**
     * Mappings for attribute code => CSV column header.
     *
     * @var array
     */
    protected $headerMappings = array();

    /**
     * The default entity type code.
     *
     * @var string
     */
    protected $defaultEntityTypeCode;

    /**
     * Mapping for the virtual entity type code to the real Magento 2 EAV entity type code.
     *
     * @var array
     */
    protected $entityTypeCodeMappings = array(
        EntityTypeCodes::EAV_ATTRIBUTE             => EntityTypeCodes::CATALOG_PRODUCT,
        EntityTypeCodes::CATALOG_PRODUCT           => EntityTypeCodes::CATALOG_PRODUCT,
        EntityTypeCodes::CATALOG_CATEGORY          => EntityTypeCodes::CATALOG_CATEGORY,
        EntityTypeCodes::CATALOG_PRODUCT_PRICE     => EntityTypeCodes::CATALOG_PRODUCT,
        EntityTypeCodes::CATALOG_PRODUCT_INVENTORY => EntityTypeCodes::CATALOG_PRODUCT
    );

    /**
     * Intializes the previously loaded global data for exactly one bunch.
     *
     * @param string $serial The serial of the actual import
     *
     * @return void
     */
    public function setUp($serial)
    {

        // load the status of the actual import
        $status = $this->getRegistryProcessor()->getAttribute($serial);

        // load the global data we've prepared initially
        $this->entityTypes = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ENTITY_TYPES];

        // initialize the default entity type code with the value from the configuration
        $this->defaultEntityTypeCode = $this->entityTypeCodeMappings[$this->getConfiguration()->getConfiguration()->getEntityTypeCode()];

        // prepare the callbacks
        parent::setUp($serial);
    }

    /**
     * Return's the header mappings for the actual entity.
     *
     * @return array The header mappings
     */
    public function getHeaderMappings()
    {
        return $this->headerMappings;
    }

    /**
     * Returns the default entity type code.
     *
     * @return string The default entity type code
     */
    public function getDefaultEntityTypeCode()
    {
        return $this->defaultEntityTypeCode;
    }

    /**
     * Return's the entity type for the passed code, of if no entity type code has
     * been passed, the default one from the configuration will be used.
     *
     * @param string|null $entityTypeCode The entity type code
     *
     * @return array The requested entity type
     * @throws \Exception Is thrown, if the entity type with the passed code is not available
     */
    public function getEntityType($entityTypeCode = null)
    {

        // set the default entity type code, if non has been passed
        if ($entityTypeCode === null) {
            $entityTypeCode = $this->getDefaultEntityTypeCode();
        }

        // query whether or not, the entity type with the passed code is available
        if (isset($this->entityTypes[$entityTypeCode])) {
            return $this->entityTypes[$entityTypeCode];
        }

        // throw an exception, if not
        throw new \Exception(
            sprintf(
                'Can\'t find entity type with code %s in file %s on line %d',
                $entityTypeCode,
                $this->getFilename(),
                $this->getLineNumber()
            )
        );
    }

    /**
     * Returns the entity type ID for the passed code, or if no entity type code has
     * been passed, the default one from the configuration will be used.
     *
     * @param string|null $entityTypeCode The entity type code
     *
     * @return integer The actual entity type ID
     */
    public function getEntityTypeId($entityTypeCode = null)
    {

        // load the entity type for the given code, or the default one otherwise
        $entityType = $this->getEntityType($entityTypeCode ? $entityTypeCode : $this->getDefaultEntityTypeCode());

        // return the entity type ID
        return $entityType[MemberNames::ENTITY_TYPE_ID];
    }
}


<?php

/**
 * TechDivision\Import\Attribute\Callbacks\CreateSelectOptionValueCallback
 *
 * 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-attribute
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\Attribute\Callbacks;

use TechDivision\Import\Utils\StoreViewCodes;
use TechDivision\Import\Callbacks\AbstractCallback;
use TechDivision\Import\Observers\AttributeCodeAndValueAwareObserverInterface;

/**
 * A callback implementation that creates the option values, if not exists, for the passed
 * multiselect with the passed attribute code.
 *
 * @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-attribute
 * @link      http://www.techdivision.com
 */
class CreateSelectOptionValueCallback extends AbstractCallback
{

    /**
     * The option value and swatch handler instance.
     *
     * @var \TechDivision\Import\Attribute\Callbacks\OptionValueAndSwatchHandlerInterface
     */
    protected $optionValueAndSwatchHandler;

    /**
     * Initialize the callback with the passed option value and swatch handler instance.
     *
     * @param \TechDivision\Import\Attribute\Callbacks\OptionValueAndSwatchHandlerInterface $optionValueAndSwatchHandler The option value and swatch handler instance
     */
    public function __construct(OptionValueAndSwatchHandlerInterface $optionValueAndSwatchHandler)
    {
        $this->optionValueAndSwatchHandler = $optionValueAndSwatchHandler;
    }

    /**
     * Returns the option value and swatch handler instance.
     *
     * @return \TechDivision\Import\Attribute\Callbacks\OptionValueAndSwatchHandlerInterface The option value and swatch handler instance
     */
    protected function getOptionValueAndSwatchHandler()
    {
        return $this->optionValueAndSwatchHandler;
    }

    /**
     * Return's the product SKU as unique identifier of the actual row.
     *
     * @return mixed The row's unique identifier
     */
    protected function getUniqueIdentifier()
    {
        return $this->getValue('sku');
    }

    /**
     * Will be invoked by a observer it has been registered for.
     *
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer
     *
     * @return mixed The modified value
     */
    public function handle(AttributeCodeAndValueAwareObserverInterface $observer)
    {

        // set the observer
        $this->setObserver($observer);

        // load the attribute code and value
        $attributeCode = $observer->getAttributeCode();
        $attributeValue = $observer->getAttributeValue();

        // load the store ID
        $storeId = $this->getStoreId(StoreViewCodes::ADMIN);

        // create the option + swatch/value if necessary
        $this->getOptionValueAndSwatchHandler()->createOptionValueOrSwatchIfNecessary($attributeCode, $storeId, $attributeValue);

        // return the attribute value
        return $attributeValue;
    }
}


<?php

/**
 * TechDivision\Import\Attribute\Observers\AttributeOptionValueExportObserver
 *
 * 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-attribute
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\Attribute\Observers;

use TechDivision\Import\Attribute\Utils\ColumnKeys;

/**
 * Observer that exports the attribute options to an additional CSV file for further processing.
 *
 * @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-attribute
 * @link      http://www.techdivision.com
 */
class AttributeOptionValueExportObserver extends AbstractAttributeExportObserver
{

    /**
     * The artefact type.
     *
     * @var string
     */
    const ARTEFACT_TYPE = 'option-value-import';

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

        // do NOT export the value if we're NOT in the admin store view
        if ($this->isAdminStore()) {
            return;
        }

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

        // initialize the array with the artefacts
        $artefacts = array();

        // load the attribute option values for the custom store views
        $attributeOptionValues = $this->getValue(ColumnKeys::ATTRIBUTE_OPTION_VALUES, array(), function ($value) {
            return $this->explode($value, $this->getMultipleFieldDelimiter());
        });

        // iterate over the attribute option values and export them
        foreach ($attributeOptionValues as $key => $attributeOptionValue) {
            // load the artefacts with the admin store values
            $adminValueArtefacts = $this->getArtefactsByTypeAndEntityId(AttributeOptionExportObserver::ARTEFACT_TYPE, $this->getLastEntityId());

            // initialize and add the new artefact
            $artefacts[] = $this->newArtefact(
                array(
                    ColumnKeys::STORE_VIEW_CODE   => $this->getValue(ColumnKeys::STORE_VIEW_CODE),
                    ColumnKeys::ATTRIBUTE_CODE    => $this->getValue(ColumnKeys::ATTRIBUTE_CODE),
                    ColumnKeys::ADMIN_STORE_VALUE => $adminValueArtefacts[$key][ColumnKeys::VALUE],
                    ColumnKeys::VALUE             => $attributeOptionValue
                ),
                array(
                    ColumnKeys::STORE_VIEW_CODE   => ColumnKeys::STORE_VIEW_CODE,
                    ColumnKeys::ATTRIBUTE_CODE    => ColumnKeys::ATTRIBUTE_CODE,
                    ColumnKeys::ADMIN_STORE_VALUE => ColumnKeys::ADMIN_STORE_VALUE,
                    ColumnKeys::VALUE             => ColumnKeys::VALUE
                )
            );
        }

        // add the array with the artefacts
        $this->addArtefacts($artefacts, false);
    }

    /**
     * Return's the artefact type used for export.
     *
     * @return string The artefact type
     */
    protected function getArtefactType()
    {
        return AttributeOptionValueExportObserver::ARTEFACT_TYPE;
    }
}


<?php

/**
 * TechDivision\Import\Attribute\Observers\AttributeLabelObserver
 *
 * 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-attribute
 * @link      http://www.techdivision.com
 */

namespace TechDivision\Import\Attribute\Observers;

use TechDivision\Import\Utils\StoreViewCodes;
use TechDivision\Import\Attribute\Utils\ColumnKeys;
use TechDivision\Import\Attribute\Utils\MemberNames;
use TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface;

/**
 * Observer that create's the EAV attribute label.
 *
 * @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-attribute
 * @link      http://www.techdivision.com
 */
class AttributeLabelObserver extends AbstractAttributeImportObserver
{

    /**
     * The attribute processor instance.
     *
     * @var \TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface
     */
    protected $attributeBunchProcessor;

    /**
     * Initializes the observer with the passed subject instance.
     *
     * @param \TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface $attributeBunchProcessor The attribute bunch processor instance
     */
    public function __construct(AttributeBunchProcessorInterface $attributeBunchProcessor)
    {
        $this->attributeBunchProcessor = $attributeBunchProcessor;
    }

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

        // do nothing, if we're in admin store view
        if ($this->isAdminStore()) {
            return;
        }

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

        // query whether or not an value for the attribute label is available
        if ($attributeLabel = $this->prepareAttributes()) {
            // prepare and persist the attribue label
            $this->persistAttributeLabel($this->initializeAttribute($attributeLabel));
        }
    }

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

        // load the frontend label value
        if ($frontendLabel = $this->getValue(ColumnKeys::FRONTEND_LABEL)) {
            // load the last attribute ID
            $attributeId = $this->getLastAttributeId();

            // load the store ID
            $storeId = $this->getRowStoreId(StoreViewCodes::ADMIN);

            // return the prepared attribute label
            return $this->initializeEntity(
                array(
                    MemberNames::ATTRIBUTE_ID  => $attributeId,
                    MemberNames::STORE_ID      => $storeId,
                    MemberNames::VALUE         => $frontendLabel
                )
            );
        }
    }

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

    /**
     * Return's the attribute bunch processor instance.
     *
     * @return \TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface The attribute bunch processor instance
     */
    protected function getAttributeBunchProcessor()
    {
        return $this->attributeBunchProcessor;
    }

    /**
     * Return's the ID of the attribute that has been created recently.
     *
     * @return integer The attribute ID
     */
    protected function getLastAttributeId()
    {
        return $this->getSubject()->getLastAttributeId();
    }

    /**
     * Persist the passed attribute label.
     *
     * @param array $attributeLabel The attribute label to persist
     *
     * @return void
     */
    protected function persistAttributeLabel(array $attributeLabel)
    {
        return $this->getAttributeBunchProcessor()->persistAttributeLabel($attributeLabel);
    }
}


<?php

namespace StatonLab\TripalTestSuite\Concerns;

use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use StatonLab\TripalTestSuite\Mocks\TestResponseMock;
use StatonLab\TripalTestSuite\Services\MenuCaller;
use StatonLab\TripalTestSuite\Services\TestResponse;
use PHPUnit\Framework\Assert as PHPUnit;

trait MakesHTTPRequests
{
    /**
     * @var array
     */
    private $_cookies;

    /**
     * Send a GET request.
     *
     * @param string $uri
     * @param array $params
     * @param array $headers
     * @return TestResponse
     */
    public function get($uri, $params = [], $headers = [])
    {
        return $this->_call('GET', $uri, [
            'query' => $params,
            'headers' => $headers,
        ]);
    }

    /**
     * Sends a POST request.
     *
     * @param string $uri
     * @param array $params
     * @param array $headers
     * @return TestResponse
     */
    public function post($uri, $params = [], $headers = [])
    {
        return $this->_call('POST', $uri, [
            'form_params' => $params,
            'headers' => $headers,
        ]);
    }

    /**
     * Sends a DELETE request.
     *
     * @param string $uri
     * @param array $params
     * @param array $headers
     * @return \StatonLab\TripalTestSuite\Services\TestResponse
     */
    public function delete($uri, $params = [], $headers = [])
    {
        return $this->_call('DELETE', $uri, [
            'query' => $params,
            'headers' => $headers,
        ]);
    }

    /**
     * Sends a PUT request.
     *
     * @param string $uri
     * @param array $params
     * @param array $headers
     * @return \StatonLab\TripalTestSuite\Services\TestResponse
     */
    public function put($uri, $params = [], $headers = [])
    {
        return $this->_call('PUT', $uri, [
            'form_params' => $params,
            'headers' => $headers,
        ]);
    }

    /**
     * Sends a PATCH request.
     *
     * @param string $uri
     * @param array $params
     * @param array $headers
     * @return \StatonLab\TripalTestSuite\Services\TestResponse
     */
    public function patch($uri, $params = [], $headers = [])
    {
        return $this->_call('PATCH', $uri, [
            'form_params' => $params,
            'headers' => $headers,
        ]);
    }

    /**
     * Sends the request.
     *
     * @param string $method
     * @param string $uri
     * @params array $params
     *
     * @throws \Exception
     * @return TestResponse
     */
    private function _call($method, $uri, $params = [])
    {
        if (! str_begins_with('http', $uri)) {
            $client = new MenuCaller();

            return $client->setPath($uri)->setMethod($method)->addParams($params)->send();
        }

        try {
            $client = new Client();
            $uri = $this->_prepareURI($uri);

            if (! empty($this->_cookies)) {
                $domain = str_replace('http://', '', $GLOBALS['base_url']);
                $domain = str_replace('https://', '', $domain);
                $cookieJar = CookieJar::fromArray($this->_cookies, $domain);
                $params['cookies'] = $cookieJar;
            }

            return new TestResponse($client->request($method, $uri, $params));
        } catch (\GuzzleHttp\Exception\GuzzleException $exception) {
            PHPUnit::fail("Failed to send $method request to $uri. ".$exception->getMessage());
        }

        return null;
    }

    /**
     * Prepare url for request.
     *
     * @param $uri
     * @return string
     */
    private function _prepareURI($uri)
    {
        global $base_url;

        if (str_begins_with($uri, '/') || ! str_begins_with($uri, 'http')) {
            return trim($base_url.'/'.trim($uri, '/'), '/');
        }

        return trim($uri, '/');
    }
}


<?php

namespace ByJG\Mail\Wrapper;

use ByJG\Mail\Envelope;
use ByJG\Mail\Exception\InvalidEMailException;
use ByJG\Mail\Exception\MailApiException;
use ByJG\Util\CurlException;
use ByJG\Util\MultiPartItem;
use ByJG\Util\WebRequest;

class MailgunApiWrapper extends PHPMailerWrapper
{
    /**
     * @return \ByJG\Util\WebRequest
     */
    public function getRequestObject()
    {
        $domainName = $this->uri->getHost();
        $request = new WebRequest("https://api.mailgun.net/v3/$domainName/messages");
        $request->setCredentials('api', $this->uri->getUsername());

        return $request;
    }

    /**
     * malgun://api:APIKEY@DOMAINNAME
     *
     * @param Envelope $envelope
     * @return bool
     * @throws MailApiException
     * @throws InvalidEMailException
     * @throws CurlException
     */
    public function send(Envelope $envelope)
    {
        $this->validate($envelope);

        $message = [
            new MultiPartItem('from', $envelope->getFrom()),
            new MultiPartItem('subject', $envelope->getSubject()),
            new MultiPartItem('html', $envelope->getBody()),
            new MultiPartItem('text', $envelope->getBodyText()),
        ];

        foreach ((array)$envelope->getTo() as $to) {
            $message[] = new MultiPartItem('to', $to);
        }

        foreach ((array)$envelope->getBCC() as $bcc) {
            $message[] = new MultiPartItem('bcc', $bcc);
        }

        if (!empty($envelope->getReplyTo())) {
            $message[] = new MultiPartItem('h:Reply-To', $envelope->getReplyTo());
        }

        foreach ((array)$envelope->getCC() as $cc) {
            $message[] = new MultiPartItem('cc', $cc);
        }

        foreach ((array)$envelope->getAttachments() as $name => $attachment) {
            $message[] = new MultiPartItem(
                $attachment['disposition'],
                file_get_contents($attachment['content']),
                $name,
                $attachment['content-type']
            );
        }

        $request = $this->getRequestObject();
        $result = $request->postMultiPartForm($message);
        $resultJson = json_decode($result, true);
        if (!isset($resultJson['id'])) {
            throw new MailApiException('Mailgun: ' . $resultJson['message']);
        }

        return true;
    }
}


<?php

namespace ByJG\Mail\Wrapper;

use Aws\Credentials\Credentials;
use Aws\Ses\SesClient;
use ByJG\Mail\Envelope;
use ByJG\Mail\Exception\InvalidEMailException;
use ByJG\Mail\Exception\InvalidMessageFormatException;

class AmazonSesWrapper extends PHPMailerWrapper
{

    /**
     * @return SesClient
     */
    public function getSesClient()
    {
        //Send the message (which must be base 64 encoded):
        return new SesClient([
            'credentials' => new Credentials(
                $this->uri->getUsername(),
                $this->uri->getPassword()
            ),
            'region' => $this->uri->getHost(),
            'version' => '2010-12-01'
        ]);
    }

    /**
     * ses://accessid:aswsecret@region
     *
     * @param Envelope $envelope
     * @return bool
     * @throws InvalidEMailException
     * @throws InvalidMessageFormatException
     * @throws \PHPMailer\PHPMailer\Exception
     */
    public function send(Envelope $envelope)
    {
        $this->validate($envelope);

        $mail = $this->prepareMailer($envelope);

        // Call the preSend to set all PHPMailer variables and get the correct header and body;
        $message = $mail->getFullMessageEnvelope();

        $ses = $this->getSesClient();

        $ses->sendRawEmail(
            [
                'RawMessage' => [
                    'Data' => $message,
                ]
            ]
        );

        return true;
    }
}


<?php

/*
 * This file is part of the PHP New Relic package.
 *
 * (c) Alex Soban <me@soban.co>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SobanVuex\NewRelic;

/**
 * OOP Wrapper for the New Relic PHP Agent.
 */
final class Agent
{
    /**
     * Extension status.
     *
     * @var bool
     */
    private $loaded;

    /**
     * Instantiate the agent wrapper.
     *
     * Passing an application name will call the agent api right away.
     *
     * @param string|array $name    (optional) Name(s) of app metrics should be reported under in New Relic
     *                              user interface
     * @param string       $license (optional) Specify a different license key to report metrics to a different
     *                              New Relic account
     */
    public function __construct($name = null, string $license = null)
    {
        $this->loaded = extension_loaded('newrelic');

        if (isset($name, $license)) {
            $this->setAppname($name, $license);
        } elseif (isset($name)) {
            $this->setAppname($name);
        }
    }

    /**
     * Check if the New Relic extension is loaded.
     *
     * @return bool
     */
    public function isLoaded(): bool
    {
        return $this->loaded;
    }

    /**
     * Accepts a distributed trace payload.
     *
     * Agent version 8.4 or higher.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelicacceptdistributedtracepayload-php-agent-api
     * @see self::createDistributedTracePayload
     *
     * @param string $payload JSON formatted string created by using Agent::addCustomParameter
     *
     * @since 2.1.0
     */
    public function acceptDistributedTracePayload(string $payload)
    {
        if ($this->isLoaded()) {
            newrelic_accept_distributed_trace_payload($payload);
        }
    }

    /**
     * Accepts a distributed trace payload.
     *
     * Agent version 8.4 or higher.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelicacceptdistributedtracepayloadhttpsafe-php-agent-api
     * @see self::createDistributedTracePayload
     *
     * @param string $payload JSON formatted string created by using self::createDistributedTracePayload
     * @param string $type    (optional) A string overriding the default transport type.
     *
     * @return bool
     *
     * @since 2.1.0
     */
    public function acceptDistributedTracePayloadHttpsafe(string $payload, string $type = 'HTTP'): bool
    {
        if (!$this->isLoaded()) {
            return false;
        }

        return newrelic_accept_distributed_trace_payload_httpsafe($payload, $type);
    }

    /**
     * Attaches a custom attribute (key/value pair) to the current transaction.
     *
     * Agent version 4.4.5.35 or higher.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_add_custom_parameter
     *
     * @param string $key   The name of the custom attribute. Only the first 255 characters are retained
     * @param mixed  $value The value to associate with this custom attribute
     *
     * @return bool
     */
    public function addCustomParameter(string $key, $value): bool
    {
        if (!$this->isLoaded()) {
            return false;
        }

        return newrelic_add_custom_parameter($key, $value);
    }

    /**
     * Specify functions or methods for the agent to instrument with custom instrumentation.
     *
     * Compatible with all agent versions.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_add_custom_tracer
     *
     * @param string $callable The name can be formatted either as function_name for procedural functions,
     *                         or as "ClassName::method" for methods
     *
     * @return bool
     */
    public function addCustomTracer(string $callable): bool
    {
        if (!$this->isLoaded()) {
            return false;
        }

        return newrelic_add_custom_tracer($callable);
    }

    /**
     * Manually specify that a transaction is a background job or a web transaction.
     *
     * Compatible with all agent versions.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_background_job
     *
     * @param bool $flag (optional) Defaults to true
     */
    public function backgroundJob(bool $flag = true)
    {
        if ($this->isLoaded()) {
            newrelic_background_job($flag);
        }
    }

    /**
     * Enable or disable the capture of URL parameters.
     *
     * Compatible with all agent versions.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_capture_params
     *
     * @param bool $enable (optional) Defaults to true
     */
    public function captureParams(bool $enable = true)
    {
        if ($this->isLoaded()) {
            newrelic_capture_params($enable);
        }
    }

    /**
     * Creates a distributed trace payload.
     *
     * Agent version 8.4 or higher.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newreliccreatedistributedtracepayload-php-agent-api
     *
     * @return \newrelic\DistributedTracePayload
     *
     * @since 2.1.0
     */
    public function createDistributedTracePayload(): ?\newrelic\DistributedTracePayload
    {
        if ($this->isLoaded()) {
            return newrelic_create_distributed_trace_payload();
        }
    }

    /**
     * Add a custom metric (in milliseconds) to time a component of your app not captured by default.
     *
     * Compatible with all agent versions.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newreliccustommetric-php-agent-api
     *
     * @param string $metric Name your custom metrics with a Custom/ prefix (for example, Custom/MyMetric)
     * @param float  $value  Records timing in milliseconds. For example: a value of 4 is stored as .004 seconds
     *                       in New Relic's systems
     *
     * @return bool
     */
    public function customMetric(string $metric, float $value): bool
    {
        if (!$this->isLoaded()) {
            return false;
        }

        return newrelic_custom_metric($metric, $value);
    }

    /**
     * Disable automatic injection of the New Relic Browser snippet on particular pages.
     *
     * Agent version 2.6.5.41 or higher.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_disable_autorum
     * @see self::getBrowserTimingFooter
     * @see self::getBrowserTimingHeader
     *
     * @return bool
     */
    public function disableAutorum(): bool
    {
        if (!$this->isLoaded()) {
            return false;
        }

        return newrelic_disable_autorum();
    }

    /**
     * Stop timing the current transaction, but continue instrumenting it.
     *
     * Compatible with all agent versions.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_end_of_transaction
     * @see self::endTransaction
     */
    public function endOfTransaction()
    {
        if ($this->isLoaded()) {
            newrelic_end_of_transaction();
        }
    }

    /**
     * Stop instrumenting the current transaction immediately.
     *
     * Agent version 3.0.5.95 or higher.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_end_transaction
     * @see self::endOfTransaction
     * @see self::startTransaction
     *
     * @param bool $ignore (optional) Defaults to false
     *
     * @return bool
     */
    public function endTransaction(bool $ignore = false): bool
    {
        if (!$this->isLoaded()) {
            return false;
        }

        return newrelic_end_transaction($ignore);
    }

    /**
     * Returns a New Relic Browser snippet to inject at the end of the HTML output.
     *
     * Compatible with all agent versions.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_get_browser_timing_footer
     * @see self::disableAutorum
     * @see self::getBrowserTimingHeader
     *
     * @param bool $tags (optional) Defaults to true. If true or omitted, the string is enclosed in a <script>
     *                   element for easy inclusion in the page's HTML
     *
     * @return string
     */
    public function getBrowserTimingFooter(bool $tags = true): string
    {
        if (!$this->isLoaded()) {
            return '';
        }

        return newrelic_get_browser_timing_footer($tags);
    }

    /**
     * Returns a New Relic Browser snippet to inject in the head of your HTML output.
     *
     * Compatible with all agent versions.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_get_browser_timing_header
     * @see self::disableAutorum
     * @see self::getBrowserTimingFooter
     *
     * @param bool $tags (optional) Defaults to true. If true or omitted, the string is enclosed in a <script>
     *                   element for easy inclusion in the page's HTML.
     *
     * @return string
     */
    public function getBrowserTimingHeader(bool $tags = true): string
    {
        if (!$this->isLoaded()) {
            return '';
        }

        return newrelic_get_browser_timing_header($tags);
    }

    /**
     * Ignore the current transaction when calculating Apdex.
     *
     * Compatible with all agent versions.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_ignore_apdex
     */
    public function ignoreApdex()
    {
        if ($this->isLoaded()) {
            newrelic_ignore_apdex();
        }
    }

    /**
     * Do not instrument the current transaction.
     *
     * Compatible with all agent versions.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_ignore_transaction
     */
    public function ignoreTransaction()
    {
        if ($this->isLoaded()) {
            newrelic_ignore_transaction();
        }
    }

    /**
     * Set custom name for current transaction.
     *
     * Compatible with all agent versions.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_name_transaction
     *
     * @param string $name Name of the transaction
     *
     * @return bool
     */
    public function nameTransaction(string $name): bool
    {
        if (!$this->isLoaded()) {
            return false;
        }

        return newrelic_name_transaction($name);
    }

    /**
     * Use these calls to collect errors that the PHP agent does not collect automatically and to set the callback
     * for your own error and exception handler.
     *
     * Agent version 2.6 or higher.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_notice_error
     *
     * @param string|\Throwable|\Exception|int $message|$e|$e|$errno Provide an error message that will be meaningful
     *                                                               to you when it displays in error traces.
     * @param \Throwable|\Exception|string     $e|$e|$errstr         (optional)
     * @param string                           $errfile              (optional)
     * @param int                              $errline              (optional)
     * @param string                           $errcontext           (optional)
     */
    public function noticeError(...$arguments)
    {
        if ($this->isLoaded()) {
            newrelic_notice_error(...$arguments);
        }
    }

    /**
     * Record a custom event with the given name and attributes.
     *
     * Agent version 4.18.0.89 or higher.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_record_custom_event
     *
     * @param string $name       Name of the custom event
     * @param array  $attributes Supply custom attributes as an associative array
     *
     * @since 2.0.0
     */
    public function recordCustomEvent(string $name, array $attributes)
    {
        if ($this->isLoaded()) {
            newrelic_record_custom_event($name, $attributes);
        }
    }

    /**
     * Records a datastore segment.
     *
     * Agent version 7.5.0.199 or higher.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_record_datastore_segment
     *
     * @param callable $callback   The function that should be timed to create the datastore segment
     * @param array    $parameters An associative array of parameters describing the datastore call
     *
     * @return mixed The return value of $callback is returned. If an error occurs, false is returned, and an error
     *               at the E_WARNING level will be triggered
     *
     * @since 2.1.0
     */
    public function recordDatastoreSegment(callable $callback, array $parameters)
    {
        if (!$this->isLoaded()) {
            return false;
        }

        return newrelic_record_datastore_segment($callback, $parameters);
    }

    /**
     * Sets the New Relic app name, which controls data rollup.
     *
     * Agent version 3.1.5.111 or higher.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_set_appname
     *
     * @param string|array $name    Name(s) of app metrics should be reported under in New Relic user interface
     * @param string       $license (optional) Specify a different license key to report metrics to a different
     *                              New Relic account
     * @param bool         $xmit    (optional) Defaults to false
     *
     * @return bool
     */
    public function setAppname($name, string $license = null, bool $xmit = false): bool
    {
        if (!$this->isLoaded()) {
            return false;
        }

        $name = is_array($name) ? implode(';', $name) : $name;

        if (isset($license)) {
            return newrelic_set_appname($name, $license, $xmit);
        }

        return newrelic_set_appname($name);
    }

    /**
     * Create user-related custom attributes. newrelic_add_custom_parameter is more flexible.
     *
     * Agent version 3.1.5.111 or higher.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_set_user_attributes
     * @see self::addCustomParameter
     *
     * @param string $user    Specify a name or username to associate with this page view
     * @param string $account Specify the name of a user account to associate with this page view
     * @param string $product Specify the name of a product to associate with this page view
     *
     * @return bool
     */
    public function setUserAttributes(string $user, string $account, string $product): bool
    {
        if (!$this->isLoaded()) {
            return false;
        }

        return newrelic_set_user_attributes($user, $account, $product);
    }

    /**
     * Starts a new transaction, usually after manually ending a transaction.
     *
     * Agent version 3.0.5.95 or higher.
     *
     * @see https://docs.newrelic.com/docs/agents/php-agent/php-agent-api/newrelic_start_transaction
     * @see self::endTransaction
     *
     * @param string $appname The application name to associate with data from this transaction
     * @param string $license (optional) Provide a different license key if you want the transaction to report
     *                        to a different New Relic account
     *
     * @return bool
     */
    public function startTransaction(string $appname, string $license = null): bool
    {
        if (!$this->isLoaded()) {
            return false;
        }

        if (isset($license)) {
            return newrelic_start_transaction($appname, $license);
        }

        return newrelic_start_transaction($appname);
    }
}


<?php

declare(strict_types=1);

/**
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the MIT license.
 */

namespace RedirectHandlerModule\Controller\Plugin;

use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerAwareTrait;
use Zend\Mvc\Controller\ControllerManager;
use Zend\Mvc\Controller\Plugin\Redirect as BaseRedirect;
use Zend\Uri\Uri;

class Redirect extends BaseRedirect implements EventManagerAwareInterface
{
    use EventManagerAwareTrait;

    /**
     * @var array
     */
    private $config;

    /**
     * @var ControllerManager
     */
    private $manager;

    public function __construct(
        array $redirectHandlerConfig,
        ControllerManager $manager
    ) {
        $this->config = $redirectHandlerConfig;
        $this->manager = $manager;
    }

    /**
     * Redirect with Handling against url.
     *
     * @param string $url
     *
     * @return \Zend\Http\PhpEnvironment\Response
     */
    public function toUrl($url)
    {
        $allow_not_routed_url = $this->config['allow_not_routed_url'] ?? false;
        $exclude_urls         = $this->config['options']['exclude_urls'] ?? [];
        $exclude_hosts        = $this->config['options']['exclude_hosts'] ?? [];

        $uriTargetHost  = (new Uri($url))->getHost();
        if (true === $allow_not_routed_url ||
            \in_array($url, $exclude_urls) ||
            \in_array($uriTargetHost, $exclude_hosts)
        ) {
            return parent::toUrl($url);
        }

        $controller = $this->getController();

        $request = $controller->getRequest();
        $basePath = $request->getBasePath();

        $default_url = $this->config['default_url'] ?? '/';

        if ($basePath !== '') {
            if (\strpos($url, $basePath) !== 0) {
                $url = $basePath . $url;
            }

            if (\strpos($default_url, $basePath) !== 0) {
                $default_url  = $basePath . $default_url;
            }
        }

        $current_uri = $request->getRequestUri();
        $request->setUri($url);

        $uriTarget = (new Uri($url))->__toString();

        if ($current_uri === $uriTarget) {
            $this->getEventManager()->trigger('redirect-same-url');

            return;
        }

        $mvcEvent = $this->getEvent();
        $routeMatch = $mvcEvent->getRouteMatch();
        $currentRouteMatchName = $routeMatch->getMatchedRouteName();
        $router = $mvcEvent->getRouter();

        $uriCurrentHost = (new Uri($router->getRequestUri()))->getHost();
        if (($routeToBeMatched = $router->match($request))
            && (
                $uriTargetHost === null
                ||
                $uriCurrentHost === $uriTargetHost
            )

            && (
                (
                    ($routeToBeMatchedRouteName = $routeToBeMatched->getMatchedRouteName()) !== $currentRouteMatchName
                        && (
                            $this->manager->has($routeToBeMatched->getParam('controller'))
                            ||
                            $routeToBeMatched->getParam('middleware') !== false
                        )
                )
                ||
                (
                    $routeToBeMatched->getParam('action') != $routeMatch->getParam('action')
                )
                ||
                (
                    $routeToBeMatchedRouteName === $currentRouteMatchName
                )
            )

        ) {
            return parent::toUrl($url);
        }

        return parent::toUrl($default_url);
    }
}


<?php

/**
 * TechDivision\Import\Utils\ConfigurationUtil
 *
 * 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\Utils;

/**
 * Utility class containing the configuration keys.
 *
 * @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 ConfigurationUtil
{

    /**
     * This is a utility class, so protect it against direct
     * instantiation.
     */
    private function __construct()
    {
    }

    /**
     * This is a utility class, so protect it against cloning.
     *
     * @return void
     */
    private function __clone()
    {
    }

    /**
     * Prepare's the arguments for the passed reflection class by applying the values from the passed configuration array
     * to the apropriate arguments. If no value is found in the configuration, the constructor argument's default value is
     * used.
     *
     * @param \ReflectionClass $reflectionClass The reflection class to prepare the arguments for
     * @param array            $params          The constructor arguments from the configuration
     *
     * @return array The array with the constructor arguements
     */
    public static function prepareConstructorArgs(\ReflectionClass $reflectionClass, array $params)
    {

        // prepare the array for the initialized arguments
        $initializedParams = array();

        // prepare the array for the arguments in camel case (in configuration we use a '-' notation)
        $paramsInCamelCase = array();

        // convert the configuration keys to camelcase
        foreach ($params as $key => $value) {
            $paramsInCamelCase[lcfirst(str_replace('-', '', ucwords($key, '-')))] = $value;
        }

        // load the constructor's reflection parameters
        $constructorParameters = $reflectionClass->getConstructor()->getParameters();

        // prepare the arguments by applying the values from the configuration
        /** @var \ReflectionParameter $reflectionParameter */
        foreach ($constructorParameters as $reflectionParameter) {
            if (isset($paramsInCamelCase[$paramName = $reflectionParameter->getName()])) {
                $initializedParams[$paramName] = $paramsInCamelCase[$paramName];
            } elseif ($reflectionParameter->isOptional()) {
                $initializedParams[$paramName] = $reflectionParameter->getDefaultValue();
            } else {
                $initializedParams[$paramName] = null;
            }
        }

        // return the array with the prepared arguments
        return $initializedParams;
    }
}


<?php

/**
 * TechDivision\Import\Adapter\SerializerTrait
 *
 * 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\Adapter;

use TechDivision\Import\Serializers\SerializerInterface;

/**
 * The trait implementation that provides serializer functionality.
 *
 * @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 SerializerTrait
{

    /**
     * The serializer instance to use.
     *
     * @var \TechDivision\Import\Serializers\SerializerInterface
     */
    protected $serializer;

    /**
     * Sets the serializer instance.
     *
     * @param \TechDivision\Import\Serializers\SerializerInterface $serializer The serializer instance
     *
     * @return void
     */
    public function setSerializer(SerializerInterface $serializer)
    {
        $this->serializer = $serializer;
    }

    /**
     * Returns the serializer instance.
     *
     * @return \TechDivision\Import\Serializers\SerializerInterface The serializer instance
     */
    public function getSerializer()
    {
        return $this->serializer;
    }

    /**
     * Extracts the elements of the passed value by exploding them
     * with the also passed delimiter.
     *
     * @param string|null $value     The value to extract
     * @param string|null $delimiter The delimiter used to extrace the elements
     *
     * @return array|null The exploded values
     */
    public function explode($value = null, $delimiter = null)
    {
        return $this->getSerializer()->explode($value, $delimiter);
    }

    /**
     * Compacts the elements of the passed value by imploding them
     * with the also passed delimiter.
     *
     * @param array|null  $value     The values to compact
     * @param string|null $delimiter The delimiter use to implode the values
     *
     * @return string|null The compatected value
     */
    public function implode(array $value = null, $delimiter = null)
    {
        return $this->getSerializer()->implode($value, $delimiter);
    }

    /**
     * Serializes the elements of the passed array.
     *
     * @param array|null $unserialized The serialized data
     *
     * @return string The serialized array
     */
    public function serialize(array $unserialized = null)
    {
        return $this->getSerializer()->serialize($unserialized);
    }

    /**
     * Unserializes the elements of the passed string.
     *
     * @param string|null $serialized The value to unserialize
     *
     * @return array The unserialized values
     */
    public function unserialize($serialized = null)
    {
        return $this->getSerializer()->unserialize($serialized);
    }
}


<?php

/**
 * TechDivision\Import\Listeners\RenderAnsiArtListener
 *
 * 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\Listeners;

use Psr\Log\LogLevel;
use League\Event\EventInterface;
use League\Event\AbstractListener;
use TechDivision\Import\ApplicationInterface;
use TechDivision\Import\ConfigurationInterface;

/**
 * An listener implementation that invokes the renders the ANSI art.
 *
 * @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 RenderAnsiArtListener extends AbstractListener
{

    /**
     * The TechDivision company name as ANSI art.
     *
     * @var string
     */
    protected $ansiArt = ' _______        _     _____  _       _     _
|__   __|      | |   |  __ \(_)     (_)   (_)
   | | ___  ___| |__ | |  | |___   ___ ___ _  ___  _ __
   | |/ _ \/ __| \'_ \| |  | | \ \ / / / __| |/ _ \| \'_ \
   | |  __/ (__| | | | |__| | |\ V /| \__ \ | (_) | | | |
   |_|\___|\___|_| |_|_____/|_| \_/ |_|___/_|\___/|_| |_|
';

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

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

    /**
     * Handle the event.
     *
     * @param \League\Event\EventInterface              $event       The event that triggered the listener
     * @param \TechDivision\Import\ApplicationInterface $application The application instance
     *
     * @return void
     */
    public function handle(EventInterface $event, ApplicationInterface $application = null)
    {

        // write the TechDivision ANSI art icon to the console
        $application->log($this->ansiArt);

        // log the debug information, if debug mode is enabled
        if ($this->getConfiguration()->isDebugMode()) {
            // load the application from the DI container
            // log the system's PHP configuration
            $application->log(sprintf('PHP version: %s', phpversion()), LogLevel::DEBUG);
            $application->log(sprintf('App version: %s', $application->getVersion()), LogLevel::DEBUG);
            $application->log('-------------------- Loaded Extensions -----------------------', LogLevel::DEBUG);
            $application->log(implode(', ', $loadedExtensions = get_loaded_extensions()), LogLevel::DEBUG);
            $application->log('--------------------------------------------------------------', LogLevel::DEBUG);

            // write a warning for low performance, if XDebug extension is activated
            if (in_array('xdebug', $loadedExtensions)) {
                $application->log('Low performance exptected, as result of enabled XDebug extension!', LogLevel::WARNING);
            }
        }

        // log a message that import has been started
        $application->log(
            sprintf(
                'Now start import with serial %s [%s => %s]',
                $application->getSerial(),
                $this->getConfiguration()->getEntityTypeCode(),
                $this->getConfiguration()->getOperationName()
            ),
            LogLevel::INFO
        );
    }

    /**
     * Returns the configuration instance.
     *
     * @return \TechDivision\Import\ConfigurationInterface The configuration instance
     */
    protected function getConfiguration()
    {
        return $this->configuration;
    }
}


<?php

/**
 * TechDivision\Import\Utils\LoggerFactory
 *
 * 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\Utils;

use TechDivision\Import\ConfigurationInterface;
use TechDivision\Import\Configuration\LoggerConfigurationInterface;

/**
 * Logger factory implementation.
 *
 * @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 LoggerFactory
{

    /**
     * Creates a new logger instance based on the passed logger configuration.
     *
     * @param \TechDivision\Import\ConfigurationInterface                     $configuration       The system configuration
     * @param \TechDivision\Import\Configuration\LoggerConfigurationInterface $loggerConfiguration The logger configuration
     *
     * @return \Psr\Log\LoggerInterface The logger instance
     */
    public static function factory(
        ConfigurationInterface $configuration,
        LoggerConfigurationInterface $loggerConfiguration
    ) {

        // load the available processors from the configuration
        $availableProcessors = $loggerConfiguration->getProcessors();

        // initialize the processors
        $processors = array();
        /** @var \TechDivision\Import\Configuration\Logger\ProcessorConfigurationInterface $processorConfiguration */
        foreach ($availableProcessors as $processorConfiguration) {
            $reflectionClass = new \ReflectionClass($processorConfiguration->getType());
            $processors[] = $reflectionClass->newInstanceArgs(ConfigurationUtil::prepareConstructorArgs($reflectionClass, $processorConfiguration->getParams()));
        }

        // load the available handlers from the configuration
        $availableHandlers = $loggerConfiguration->getHandlers();

        // initialize the handlers
        $handlers = array();
        /** @var \TechDivision\Import\Configuration\Logger\HandlerConfigurationInterface $handlerConfiguration */
        foreach ($availableHandlers as $handlerConfiguration) {
            // query whether or not, we've a swift mailer configuration
            if ($swiftMailerConfiguration = $handlerConfiguration->getSwiftMailer()) {
                // load the factory that creates the swift mailer instance
                $factory = $swiftMailerConfiguration->getFactory();
                // create the swift mailer instance
                $swiftMailer = $factory::factory($swiftMailerConfiguration);

                // load the generic logger configuration
                $bubble = $handlerConfiguration->getParam(LoggerKeys::BUBBLE);
                $logLevel = $handlerConfiguration->getParam(LoggerKeys::LOG_LEVEL);

                // load sender/receiver configuration
                $to = $swiftMailerConfiguration->getParam(SwiftMailerKeys::TO);
                $from = $swiftMailerConfiguration->getParam(SwiftMailerKeys::FROM);
                $subject = $swiftMailerConfiguration->getParam(SwiftMailerKeys::SUBJECT);
                $contentType = $swiftMailerConfiguration->getParam(SwiftMailerKeys::CONTENT_TYPE);

                // initialize the message template
                $message = $swiftMailer->createMessage()
                    ->setSubject(sprintf('[%s] %s', $configuration->getSystemName(), $subject))
                    ->setFrom($from)
                    ->setTo($to)
                    ->setBody('', $contentType);

                // initialize the handler node
                $reflectionClass = new \ReflectionClass($handlerConfiguration->getType());
                $handler = $reflectionClass->newInstanceArgs(array($swiftMailer, $message, $logLevel, $bubble));
            } else {
                // initialize the handler node
                $reflectionClass = new \ReflectionClass($handlerConfiguration->getType());

                // load the params
                $params = $handlerConfiguration->getParams();

                // set the default log level, if not already set explicitly
                if (!isset($params[ConfigurationKeys::LEVEL])) {
                    $params[ConfigurationKeys::LEVEL] = $configuration->getLogLevel();
                }

                // create the handler instance
                $handler = $reflectionClass->newInstanceArgs(ConfigurationUtil::prepareConstructorArgs($reflectionClass, $params));
            }

            // if we've a formatter, initialize the formatter also
            if ($formatterConfiguration = $handlerConfiguration->getFormatter()) {
                $reflectionClass = new \ReflectionClass($formatterConfiguration->getType());
                $handler->setFormatter($reflectionClass->newInstanceArgs(ConfigurationUtil::prepareConstructorArgs($reflectionClass, $formatterConfiguration->getParams())));
            }

            // add the handler
            $handlers[] = $handler;
        }

        // prepare the logger params
        $loggerParams = array(
            ConfigurationKeys::NAME       => $loggerConfiguration->getChannelName(),
            ConfigurationKeys::HANDLERS   => $handlers,
            ConfigurationKeys::PROCESSORS => $processors
        );

        // append the params from the logger configuration
        $loggerParams = array_merge($loggerParams, $loggerConfiguration->getParams());

        // initialize the logger instance itself
        $reflectionClass = new \ReflectionClass($loggerConfiguration->getType());
        return $reflectionClass->newInstanceArgs(ConfigurationUtil::prepareConstructorArgs($reflectionClass, $loggerParams));
    }
}