<?php

/**
 * TechDivision\Import\Plugins\ArchivePlugin
 *
 * 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\Plugins;

use TechDivision\Import\Utils\RegistryKeys;

/**
 * Plugin that archives the artefacts.
 *
 * @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 ArchivePlugin extends AbstractPlugin
{

    /**
     * Process the plugin functionality.
     *
     * @return void
     * @throws \Exception Is thrown, if the plugin can not be processed
     */
    public function process()
    {

        // query whether or not, the import artefacts have to be archived
        if (!$this->getConfiguration()->haveArchiveArtefacts()) {
            $this->getSystemLogger()->info('Archiving functionality has not been activated');
            return;
        }

        // load the actual status
        $status = $this->getRegistryProcessor()->getAttribute($this->getSerial());

        // load the number of imported bunches from the status
        $bunches = $status[RegistryKeys::BUNCHES];

        // if no files have been imported, return immediately
        if ($bunches === 0) {
            $this->getSystemLogger()->info('Found no files to archive');
            return;
        }

        // clear the filecache
        clearstatcache();

        // query whether or not the configured source directory is available
        if (!is_dir($sourceDir = $status[RegistryKeys::SOURCE_DIRECTORY])) {
            throw new \Exception(sprintf('Configured source directory %s is not available!', $sourceDir));
        }

        // init file iterator on source directory
        $fileIterator = new \FilesystemIterator($sourceDir);

        // log the number of files that has to be archived
        $this->getSystemLogger()->info(sprintf('Found %d files to archive in directory %s', $bunches, $sourceDir));

        // try to load the archive directory
        $archiveDir = $this->getConfiguration()->getArchiveDir();

        // query whether or not the specified archive directory already exists
        if ($archiveDir === null) {
            // try to initialize a default archive directory by concatenating 'archive' to the target directory
            $archiveDir = sprintf('%s/archive', $this->getConfiguration()->getTargetDir());
        }

        // query whether or not the archive directory already exists
        if (!is_dir($archiveDir)) {
            // create the archive directory if possible
            if (mkdir($archiveDir, 0755, true)) {
                $this->getSystemLogger()->info(sprintf('Successfully created archived archive directory %s', $archiveDir));
            } else {
                // initialize the message that the archive directory can not be created
                $message = sprintf('Can\'t create archive directory %s', $archiveDir);

                // log a message if we're in debug mode, else throw an exception
                if ($this->getConfiguration()->isDebugMode()) {
                    $this->getSystemLogger()->error($message);
                } else {
                    throw new \Exception($message);
                }
            }
        }

        // create the ZIP archive
        $archive = new \ZipArchive();
        $archive->open($archiveFile = sprintf('%s/%s.zip', $archiveDir, $this->getSerial()), \ZipArchive::CREATE);

        // iterate through all files and add them to the ZIP archive
        foreach ($fileIterator as $filename) {
            $archive->addFile($filename);
        }

        // save the ZIP archive
        $archive->close();

        // finally remove the directory with the imported files
        $this->removeDir($sourceDir);

        // and and log a message that the import artefacts have been archived
        $this->getSystemLogger()->info(sprintf('Successfully archived imported files to %s!', $archiveFile));
    }
}


<?php

/**
 * TechDivision\Import\Actions\Processors\StoreUpdateProcessor
 *
 * 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\Actions\Processors;

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

/**
 * The store update processor 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 StoreUpdateProcessor extends AbstractUpdateProcessor
{

    /**
     * Return's the array with the SQL statements that has to be prepared.
     *
     * @return array The SQL statements to be prepared
     * @see \TechDivision\Import\Actions\Processors\AbstractBaseProcessor::getStatements()
     */
    protected function getStatements()
    {
        return array(SqlStatementKeys::UPDATE_STORE => $this->loadStatement(SqlStatementKeys::UPDATE_STORE));
    }

    /**
     * Update's the passed row.
     *
     * @param array       $row  The row to update
     * @param string|null $name The name of the prepared statement that has to be executed
     *
     * @return string The ID of the updated store
     */
    public function execute($row, $name = null)
    {
        parent::execute($row, $name);
        return $row[MemberNames::STORE_ID];
    }
}


<?php

/**
 * TechDivision\Import\Assembler\CategoryAssembler
 *
 * 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\Assembler;

use TechDivision\Import\Utils\MemberNames;
use TechDivision\Import\Repositories\CategoryRepository;
use TechDivision\Import\Repositories\CategoryVarcharRepository;

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

    /**
     * The repository to access categories.
     *
     * @var \TechDivision\Import\Repositories\CategoryRepository
     */
    protected $categoryRepository;

    /**
     * The repository to access category varchar values.
     *
     * @var \TechDivision\Import\Repositories\CategoryVarcharRepository
     */
    protected $categoryVarcharRepository;

    /**
     * Initialize the assembler with the passed instances.
     *
     * @param \TechDivision\Import\Repositories\CategoryRepository        $categoryRepository        The repository to access categories
     * @param \TechDivision\Import\Repositories\CategoryVarcharRepository $categoryVarcharRepository The repository instance
     */
    public function __construct(
        CategoryRepository $categoryRepository,
        CategoryVarcharRepository $categoryVarcharRepository
    ) {
        $this->categoryRepository = $categoryRepository;
        $this->categoryVarcharRepository = $categoryVarcharRepository;
    }

    /**
     * Returns an array with the available categories and their
     * resolved path as keys.
     *
     * @return array The array with the categories
     */
    public function getCategoriesWithResolvedPath()
    {

        // prepare the categories
        $categories = array();

        // load the categories from the database
        $availableCategories = $this->categoryRepository->findAll();

        // create the array with the resolved category path as keys
        foreach ($availableCategories as $category) {
            // expload the entity IDs from the category path
            $entityIds = explode('/', $category[MemberNames::PATH]);

            // cut-off the root category
            array_shift($entityIds);

            // continue with the next category if no entity IDs are available
            if (sizeof($entityIds) === 0) {
                continue;
            }

            // initialize the array for the path elements
            $path = array();
            foreach ($entityIds as $entityId) {
                $cat = $this->categoryVarcharRepository->findByEntityId($entityId);
                $path[] = $cat[MemberNames::VALUE];
            }

            // append the catogory with the string path as key
            $categories[implode('/', $path)] = $category;
        }

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

    /**
     * Return's an array with the available categories and their resolved path
     * as keys by store view ID.
     *
     * @param integer $storeViewId The store view ID to return the categories for
     *
     * @return array The available categories for the passed store view ID
     */
    public function getCategoriesWithResolvedPathByStoreView($storeViewId)
    {
        // prepare the categories
        $categories = array();

        // load the categories from the database
        $availableCategories = $this->categoryRepository->findAllByStoreView($storeViewId);

        // create the array with the resolved category path as keys
        foreach ($availableCategories as $category) {
            // expload the entity IDs from the category path
            $entityIds = explode('/', $category[MemberNames::PATH]);

            // cut-off the root category
            array_shift($entityIds);

            // continue with the next category if no entity IDs are available
            if (sizeof($entityIds) === 0) {
                continue;
            }

            // initialize the array for the path elements
            $path = array();
            foreach ($entityIds as $entityId) {
                $cat = $this->categoryVarcharRepository->findByEntityId($entityId);
                $path[] = $cat[MemberNames::VALUE];
            }

            // append the catogory with the string path as key
            $categories[implode('/', $path)] = $category;
        }

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


<?php

/**
 * TechDivision\Import\Callbacks\SelectCallback
 *
 * 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\Callbacks;

use TechDivision\Import\Utils\MemberNames;
use TechDivision\Import\Utils\RegistryKeys;
use TechDivision\Import\Utils\StoreViewCodes;
use TechDivision\Import\Observers\AttributeCodeAndValueAwareObserverInterface;

/**
 * A callback implementation that converts the passed select 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
 * @link      http://www.techdivision.com
 */
abstract class AbstractSelectCallback extends AbstractEavAwareCallback
{

    /**
     * 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);

        // try to load the attribute option value and return the option ID
        if ($eavAttributeOptionValue = $this->loadAttributeOptionValueByEntityTypeIdAndAttributeCodeAndStoreIdAndValue($this->getEntityTypeId(), $attributeCode, $storeId, $attributeValue)) {
            return $eavAttributeOptionValue[MemberNames::OPTION_ID];
        }

        // query whether or not we're in debug mode
        if ($this->isDebugMode()) {
            // log a warning and return immediately
            $this->getSystemLogger()->warning(
                $this->appendExceptionSuffix(
                    sprintf(
                        'Can\'t find select option value "%s" for attribute %s',
                        $attributeValue,
                        $attributeCode
                    )
                )
            );

            // add the missing option value to the registry
            $this->mergeAttributesRecursive(
                array(
                    RegistryKeys::MISSING_OPTION_VALUES => array(
                        $attributeCode => array(
                            $attributeValue => array(
                                $this->raiseCounter($attributeValue),
                                array($this->getUniqueIdentifier() => true)
                            )
                        )
                    )
                )
            );

            // return NULL, if the value can't be mapped to an option
            return;
        }

        // throw an exception if the attribute is NOT
        // available and we're not in debug mode
        throw new \Exception(
            $this->appendExceptionSuffix(
                sprintf(
                    'Can\'t find select option value "%s" for attribute %s',
                    $attributeValue,
                    $attributeCode
                )
            )
        );
    }
}


<?php

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

/**
 * A trait that provides header handling.
 *
 * @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 HeaderTrait
{

    /**
     * Contain's the column names from the header line.
     *
     * @var array
     */
    protected $headers = array();

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

    /**
     * Set's the array containing header row.
     *
     * @param array $headers The array with the header row
     *
     * @return void
     */
    public function setHeaders(array $headers)
    {
        $this->headers = $headers;
    }

    /**
     * Return's the array containing header row.
     *
     * @return array The array with the header row
     */
    public function getHeaders()
    {
        return $this->headers;
    }

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

    /**
     * Queries whether or not the header with the passed name is available.
     *
     * @param string $name The header name to query
     *
     * @return boolean TRUE if the header is available, else FALSE
     */
    public function hasHeader($name)
    {
        return isset($this->headers[$this->mapAttributeCodeByHeaderMapping($name)]);
    }

    /**
     * Return's the header value for the passed name.
     *
     * @param string $name The name of the header to return the value for
     *
     * @return mixed The header value
     * @throws \InvalidArgumentException Is thrown, if the header with the passed name is NOT available
     */
    public function getHeader($name)
    {

        // map column => attribute name
        $name = $this->mapAttributeCodeByHeaderMapping($name);

        // query whether or not, the header is available
        if (isset($this->headers[$name])) {
            return $this->headers[$name];
        }

        // throw an exception, if not
        throw new \InvalidArgumentException(sprintf('Header %s is not available', $name));
    }

    /**
     * Add's the header with the passed name and position, if not NULL.
     *
     * @param string $name The header name to add
     *
     * @return integer The new headers position
     */
    public function addHeader($name)
    {

        // add the header
        $this->headers[$name] = $position = sizeof($this->headers);

        // return the new header's position
        return $position;
    }

    /**
     * Map the passed attribute code, if a header mapping exists and return the
     * mapped mapping.
     *
     * @param string $attributeCode The attribute code to map
     *
     * @return string The mapped attribute code, or the original one
     */
    public function mapAttributeCodeByHeaderMapping($attributeCode)
    {

        // load the header mappings
        $headerMappings = $this->getHeaderMappings();

        // query weather or not we've a mapping, if yes, map the attribute code
        if (isset($headerMappings[$attributeCode])) {
            $attributeCode = $headerMappings[$attributeCode];
        }

        // return the (mapped) attribute code
        return $attributeCode;
    }
}


<?php

/**
 * TechDivision\Import\Subjects\AbstractSubject
 *
 * 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\Subjects;

use League\Event\EmitterInterface;
use Doctrine\Common\Collections\Collection;
use TechDivision\Import\RowTrait;
use TechDivision\Import\HeaderTrait;
use TechDivision\Import\SystemLoggerTrait;
use TechDivision\Import\Utils\ScopeKeys;
use TechDivision\Import\Utils\ColumnKeys;
use TechDivision\Import\Utils\EventNames;
use TechDivision\Import\Utils\MemberNames;
use TechDivision\Import\Utils\RegistryKeys;
use TechDivision\Import\Utils\Generators\GeneratorInterface;
use TechDivision\Import\Callbacks\CallbackInterface;
use TechDivision\Import\Observers\ObserverInterface;
use TechDivision\Import\Adapter\ImportAdapterInterface;
use TechDivision\Import\Exceptions\WrappedColumnException;
use TechDivision\Import\Services\RegistryProcessorInterface;
use TechDivision\Import\Configuration\SubjectConfigurationInterface;

/**
 * An abstract subject 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
 */
abstract class AbstractSubject implements SubjectInterface, FilesystemSubjectInterface, DateConverterSubjectInterface
{

    /**
     * The trait that provides basic filesystem handling functionality.
     *
     * @var \TechDivision\Import\Subjects\FilesystemTrait
     */
    use FilesystemTrait;

    /**
     * The trait that provides basic filesystem handling functionality.
     *
     * @var \TechDivision\Import\SystemLoggerTrait
     */
    use SystemLoggerTrait;

    /**
     * The trait that provides date converting functionality.
     *
     * @var \TechDivision\Import\DateConverterTrait
     */
    use DateConverterTrait;

    /**
     * The trait that provides header handling functionality.
     *
     * @var \TechDivision\Import\HeaderTrait
     */
    use HeaderTrait;

    /**
     * The trait that provides row handling functionality.
     *
     * @var \TechDivision\Import\RowTrait
     */
    use RowTrait;

    /**
     * The name of the file to be imported.
     *
     * @var string
     */
    protected $filename;

    /**
     * The actual line number.
     *
     * @var integer
     */
    protected $lineNumber = 0;

    /**
     * The actual operation name.
     *
     * @var string
     */
    protected $operationName ;

    /**
     * The import adapter instance.
     *
     * @var \TechDivision\Import\Adapter\ImportAdapterInterface
     */
    protected $importAdapter;

    /**
     * The system configuration.
     *
     * @var \TechDivision\Import\Configuration\SubjectConfigurationInterface
     */
    protected $configuration;

    /**
     * The RegistryProcessor instance to handle running threads.
     *
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
     */
    protected $registryProcessor;

    /**
     * The actions unique serial.
     *
     * @var string
     */
    protected $serial;

    /**
     * Array with the subject's observers.
     *
     * @var array
     */
    protected $observers = array();

    /**
     * Array with the subject's callbacks.
     *
     * @var array
     */
    protected $callbacks = array();

    /**
     * The subject's callback mappings.
     *
     * @var array
     */
    protected $callbackMappings = array();

    /**
     * The available root categories.
     *
     * @var array
     */
    protected $rootCategories = array();

    /**
     * The Magento configuration.
     *
     * @var array
     */
    protected $coreConfigData = array();

    /**
     * The available stores.
     *
     * @var array
     */
    protected $stores = array();

    /**
     * The available websites.
     *
     * @var array
     */
    protected $storeWebsites = array();

    /**
     * The default store.
     *
     * @var array
     */
    protected $defaultStore;

    /**
     * The store view code the create the product/attributes for.
     *
     * @var string
     */
    protected $storeViewCode;

    /**
     * The UID generator for the core config data.
     *
     * @var \TechDivision\Import\Utils\Generators\GeneratorInterface
     */
    protected $coreConfigDataUidGenerator;

    /**
     * UNIX timestamp with the date the last row counter has been logged.
     *
     * @var integer
     */
    protected $lastLog = 0;

    /**
     * The number of the last line that has been logged with the row counter
     * @var integer
     */
    protected $lastLineNumber = 0;

    /**
     * The event emitter instance.
     *
     * @var \League\Event\EmitterInterface
     */
    protected $emitter;

    /**
     * Initialize the subject instance.
     *
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor          The registry processor instance
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface $coreConfigDataUidGenerator The UID generator for the core config data
     * @param \Doctrine\Common\Collections\Collection                  $systemLoggers              The array with the system loggers instances
     * @param \League\Event\EmitterInterface                           $emitter                    The event emitter instance
     */
    public function __construct(
        RegistryProcessorInterface $registryProcessor,
        GeneratorInterface $coreConfigDataUidGenerator,
        Collection $systemLoggers,
        EmitterInterface $emitter
    ) {
        $this->emitter = $emitter;
        $this->systemLoggers = $systemLoggers;
        $this->registryProcessor = $registryProcessor;
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
    }

    /**
     * Return's the event emitter instance.
     *
     * @return \League\Event\EmitterInterface The event emitter instance
     */
    public function getEmitter()
    {
        return $this->emitter;
    }

    /**
     * Set's the name of the file to import
     *
     * @param string $filename The filename
     *
     * @return void
     */
    public function setFilename($filename)
    {
        $this->filename = $filename;
    }

    /**
     * Return's the name of the file to import.
     *
     * @return string The filename
     */
    public function getFilename()
    {
        return $this->filename;
    }

    /**
     * Set's the actual operation name.
     *
     * @param string $operationName The actual operation name
     *
     * @return void
     */
    public function setOperationName($operationName)
    {
        $this->operationName = $operationName;
    }

    /**
     * Return's the actual operation name.
     *
     * @return string
     */
    public function getOperationName()
    {
        return $this->operationName;
    }

    /**
     * Set's the actual line number.
     *
     * @param integer $lineNumber The line number
     *
     * @return void
     */
    public function setLineNumber($lineNumber)
    {
        $this->lineNumber = $lineNumber;
    }

    /**
     * Return's the actual line number.
     *
     * @return integer The line number
     */
    public function getLineNumber()
    {
        return $this->lineNumber;
    }

    /**
     * Return's the default callback mappings.
     *
     * @return array The default callback mappings
     */
    public function getDefaultCallbackMappings()
    {
        return array();
    }

    /**
     * Tries to format the passed value to a valid date with format 'Y-m-d H:i:s'.
     * If the passed value is NOT a valid date, NULL will be returned.
     *
     * @param string $value The value to format
     *
     * @return string|null The formatted date or NULL if the date is not valid
     */
    public function formatDate($value)
    {
        return $this->getDateConverter()->convert($value);
    }

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

    /**
     * Queries whether or not debug mode is enabled or not, default is TRUE.
     *
     * @return boolean TRUE if debug mode is enabled, else FALSE
     */
    public function isDebugMode()
    {
        return $this->getConfiguration()->isDebugMode();
    }

    /**
     * Set's the subject configuration.
     *
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration The subject configuration
     *
     * @return void
     */
    public function setConfiguration(SubjectConfigurationInterface $configuration)
    {
        $this->configuration = $configuration;
    }

    /**
     * Return's the subject configuration.
     *
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The subject configuration
     */
    public function getConfiguration()
    {
        return $this->configuration;
    }

    /**
     * Set's the import adapter instance.
     *
     * @param \TechDivision\Import\Adapter\ImportAdapterInterface $importAdapter The import adapter instance
     *
     * @return void
     */
    public function setImportAdapter(ImportAdapterInterface $importAdapter)
    {
        $this->importAdapter = $importAdapter;
    }

    /**
     * Return's the import adapter instance.
     *
     * @return \TechDivision\Import\Adapter\ImportAdapterInterface The import adapter instance
     */
    public function getImportAdapter()
    {
        return $this->importAdapter;
    }

    /**
     * Return's the RegistryProcessor instance to handle the running threads.
     *
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
     */
    public function getRegistryProcessor()
    {
        return $this->registryProcessor;
    }

    /**
     * Set's the unique serial for this import process.
     *
     * @param string $serial The unique serial
     *
     * @return void
     */
    public function setSerial($serial)
    {
        $this->serial = $serial;
    }

    /**
     * Return's the unique serial for this import process.
     *
     * @return string The unique serial
     */
    public function getSerial()
    {
        return $this->serial;
    }

    /**
     * Return's the source date format to use.
     *
     * @return string The source date format
     */
    public function getSourceDateFormat()
    {
        return $this->getConfiguration()->getSourceDateFormat();
    }

    /**
     * Return's the multiple field delimiter character to use, default value is comma (,).
     *
     * @return string The multiple field delimiter character
     */
    public function getMultipleFieldDelimiter()
    {
        return $this->getConfiguration()->getMultipleFieldDelimiter();
    }

    /**
     * Return's the multiple value delimiter character to use, default value is comma (|).
     *
     * @return string The multiple value delimiter character
     */
    public function getMultipleValueDelimiter()
    {
        return $this->getConfiguration()->getMultipleValueDelimiter();
    }

    /**
     * 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->stores = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORES];
        $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
        $this->storeWebsites  = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORE_WEBSITES];
        $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
        $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];

        // initialize the operation name
        $this->operationName = $this->getConfiguration()->getConfiguration()->getOperationName();

        // merge the callback mappings with the mappings from the child instance
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());

        // merge the header mappings with the values found in the configuration
        $this->headerMappings = array_merge($this->headerMappings, $this->getConfiguration()->getHeaderMappings());

        // load the available callbacks from the configuration
        $availableCallbacks = $this->getConfiguration()->getCallbacks();

        // merge the callback mappings the the one from the configuration file
        foreach ($availableCallbacks as $callbackMappings) {
            foreach ($callbackMappings as $attributeCode => $mappings) {
                // write a log message, that default callback configuration will
                // be overwritten with the one from the configuration file
                if (isset($this->callbackMappings[$attributeCode])) {
                    $this->getSystemLogger()->notice(
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
                    );
                }

                // override the attributes callbacks
                $this->callbackMappings[$attributeCode] = $mappings;
            }
        }
    }

    /**
     * Clean up the global data after importing the variants.
     *
     * @param string $serial The serial of the actual import
     *
     * @return void
     */
    public function tearDown($serial)
    {

        // load the registry processor
        $registryProcessor = $this->getRegistryProcessor();

        // update the source directory for the next subject
        $registryProcessor->mergeAttributesRecursive(
            $serial,
            array(
                RegistryKeys::SOURCE_DIRECTORY => $newSourceDir = $this->getNewSourceDir($serial),
                RegistryKeys::FILES => array($this->getFilename() => array(RegistryKeys::STATUS => 1))
            )
        );

        // log a debug message with the new source directory
        $this->getSystemLogger()->debug(
            sprintf('Subject %s successfully updated source directory to %s', get_class($this), $newSourceDir)
        );
    }

    /**
     * Return's the target directory for the artefact export.
     *
     * @return string The target directory for the artefact export
     */
    public function getTargetDir()
    {
        return $this->getNewSourceDir($this->getSerial());
    }

    /**
     * Return's the next source directory, which will be the target directory
     * of this subject, in most cases.
     *
     * @param string $serial The serial of the actual import
     *
     * @return string The new source directory
     */
    public function getNewSourceDir($serial)
    {
        return sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $serial);
    }

    /**
     * Register the passed observer with the specific type.
     *
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
     * @param string                                           $type     The type to register the observer with
     *
     * @return void
     */
    public function registerObserver(ObserverInterface $observer, $type)
    {

        // query whether or not the array with the callbacks for the
        // passed type has already been initialized, or not
        if (!isset($this->observers[$type])) {
            $this->observers[$type] = array();
        }

        // append the callback with the instance of the passed type
        $this->observers[$type][] = $observer;
    }

    /**
     * Register the passed callback with the specific type.
     *
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
     * @param string                                           $type     The type to register the callback with
     *
     * @return void
     */
    public function registerCallback(CallbackInterface $callback, $type)
    {

        // query whether or not the array with the callbacks for the
        // passed type has already been initialized, or not
        if (!isset($this->callbacks[$type])) {
            $this->callbacks[$type] = array();
        }

        // append the callback with the instance of the passed type
        $this->callbacks[$type][] = $callback;
    }

    /**
     * Return's the array with callbacks for the passed type.
     *
     * @param string $type The type of the callbacks to return
     *
     * @return array The callbacks
     */
    public function getCallbacksByType($type)
    {

        // initialize the array for the callbacks
        $callbacks = array();

        // query whether or not callbacks for the type are available
        if (isset($this->callbacks[$type])) {
            $callbacks = $this->callbacks[$type];
        }

        // return the array with the type's callbacks
        return $callbacks;
    }

    /**
     * Return's the array with the available observers.
     *
     * @return array The observers
     */
    public function getObservers()
    {
        return $this->observers;
    }

    /**
     * Return's the array with the available callbacks.
     *
     * @return array The callbacks
     */
    public function getCallbacks()
    {
        return $this->callbacks;
    }

    /**
     * Return's the callback mappings for this subject.
     *
     * @return array The array with the subject's callback mappings
     */
    public function getCallbackMappings()
    {
        return $this->callbackMappings;
    }

    /**
     * Imports the content of the file with the passed filename.
     *
     *
     * @param string $serial   The serial of the actual import
     * @param string $filename The filename to process
     *
     * @return void
     * @throws \Exception Is thrown, if the import can't be processed
     */
    public function import($serial, $filename)
    {

        try {
            // initialize the serial/filename
            $this->setSerial($serial);
            $this->setFilename($filename);

            // invoke the events that has to be fired before the artfact will be processed
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_PROCESS_START, $this);

            // load the system logger instance
            $systemLogger = $this->getSystemLogger();

            // prepare the flag filenames
            $inProgressFilename = sprintf('%s.inProgress', $filename);
            $importedFilename = sprintf('%s.imported', $filename);
            $failedFilename = sprintf('%s.failed', $filename);

            // query whether or not the file has already been imported
            if ($this->isFile($failedFilename) ||
                $this->isFile($importedFilename) ||
                $this->isFile($inProgressFilename)
            ) {
                // log a debug message and exit
                $systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename));
                return;
            }

            // flag file as in progress
            $this->touch($inProgressFilename);

            // track the start time
            $startTime = microtime(true);

            // initialize the last time we've logged the counter with the processed rows per minute
            $this->lastLog = time();

            // log a message that the file has to be imported
            $systemLogger->info(sprintf('Now start processing file %s', $filename));

            // let the adapter process the file
            $this->getImportAdapter()->import(array($this, 'importRow'), $filename);

            // track the time needed for the import in seconds
            $endTime = microtime(true) - $startTime;

            // log a message that the file has successfully been imported
            $systemLogger->info(sprintf('Successfully processed file %s with %d lines in %f s', $filename, $this->lineNumber, $endTime));

            // rename flag file, because import has been successfull
            if ($this->getConfiguration()->isCreatingImportedFile()) {
                $this->rename($inProgressFilename, $importedFilename);
            } else {
                $this->getFilesystemAdapter()->delete($inProgressFilename);
            }

            // invoke the events that has to be fired when the artfact has been successfully processed
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_PROCESS_SUCCESS, $this);
        } catch (\Exception $e) {
            // rename the flag file, because import failed and write the stack trace
            $this->rename($inProgressFilename, $failedFilename);
            $this->write($failedFilename, $e->__toString());

            // invoke the events that has to be fired when the artfact can't be processed
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_PROCESS_FAILURE, $this, $e);

            // do not wrap the exception if not already done
            if ($e instanceof WrappedColumnException) {
                throw $e;
            }

            // else wrap and throw the exception
            throw $this->wrapException(array(), $e);
        }
    }

    /**
     * Imports the passed row into the database. If the import failed, the exception
     * will be catched and logged, but the import process will be continued.
     *
     * @param array $row The row with the data to be imported
     *
     * @return void
     */
    public function importRow(array $row)
    {

        // initialize the row
        $this->row = $row;

        // raise the line number and reset the skip row flag
        $this->lineNumber++;
        $this->skipRow = false;

        // invoke the events that has to be fired before the artfact's row will be processed
        $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_ROW_PROCESS_START, $this);

        // initialize the headers with the columns from the first line
        if (sizeof($this->headers) === 0) {
            foreach ($this->row as $value => $key) {
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
            }
            return;
        }

        // load the available observers
        $availableObservers = $this->getObservers();

        // process the observers
        foreach ($availableObservers as $observers) {
            // invoke the pre-import/import and post-import observers
            /** @var \TechDivision\Import\Observers\ObserverInterface $observer */
            foreach ($observers as $observer) {
                // query whether or not we have to skip the row
                if ($this->skipRow) {
                    // log a debug message with the actual line nr/file information
                    $this->getSystemLogger()->warning(
                        $this->appendExceptionSuffix(
                            sprintf(
                                'Skip processing operation "%s" after observer "%s"',
                                $this->operationName,
                                get_class($observer)
                            )
                        )
                    );

                    // skip the row
                    break 2;
                }

                // if not, set the subject and process the observer
                if ($observer instanceof ObserverInterface) {
                    $this->row = $observer->handle($this);
                }
            }
        }

        // query whether or not a minute has been passed
        if ($this->lastLog < time() - 60) {
            // log the number processed rows per minute
            $this->getSystemLogger()->info(
                sprintf(
                    'Successfully processed "%d (%d)" rows per minute of file "%s"',
                    $this->lineNumber - $this->lastLineNumber,
                    $this->lineNumber,
                    $this->getFilename()
                )
            );

            // reset the last log time and the line number
            $this->lastLog = time();
            $this->lastLineNumber = $this->lineNumber;
        }

        // log a debug message with the actual line nr/file information
        $this->getSystemLogger()->debug(
            $this->appendExceptionSuffix(
                sprintf(
                    'Successfully processed operation "%s"',
                    $this->operationName
                )
            )
        );

        // invoke the events that has to be fired when the artfact's row has been successfully processed
        $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_ROW_PROCESS_SUCCESS, $this);
    }

    /**
     * Queries whether or not that the subject needs an OK file to be processed.
     *
     * @return boolean TRUE if the subject needs an OK file, else FALSE
     */
    public function isOkFileNeeded()
    {
        return $this->getConfiguration()->isOkFileNeeded();
    }

    /**
     * Return's the default store.
     *
     * @return array The default store
     */
    public function getDefaultStore()
    {
        return $this->defaultStore;
    }

    /**
     * Return's the default store view code.
     *
     * @return array The default store view code
     */
    public function getDefaultStoreViewCode()
    {
        return $this->defaultStore[MemberNames::CODE];
    }

    /**
     * Set's the store view code the create the product/attributes for.
     *
     * @param string $storeViewCode The store view code
     *
     * @return void
     */
    public function setStoreViewCode($storeViewCode)
    {
        $this->storeViewCode = $storeViewCode;
    }

    /**
     * Return's the store view code the create the product/attributes for.
     *
     * @param string|null $default The default value to return, if the store view code has not been set
     *
     * @return string The store view code
     */
    public function getStoreViewCode($default = null)
    {

        // return the store view code, if available
        if ($this->storeViewCode !== null) {
            return $this->storeViewCode;
        }

        // if NOT and a default code is available
        if ($default !== null) {
            // return the default value
            return $default;
        }

        // return the default store view code
        return $this->getDefaultStoreViewCode();
    }

    /**
     * Prepare's the store view code in the subject. If the store_view_code row doesn't contain
     * any value, the default code of the default store view will be set.
     *
     * @return void
     */
    public function prepareStoreViewCode()
    {

        // re-set the store view code
        $this->setStoreViewCode(null);

        // initialize the store view code
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
            $this->setStoreViewCode($storeViewCode);
        }
    }

    /**
     * Return's the store ID of the store with the passed store view code
     *
     * @param string $storeViewCode The store view code to return the store ID for
     *
     * @return integer The ID of the store with the passed ID
     * @throws \Exception Is thrown, if the store with the actual code is not available
     */
    public function getStoreId($storeViewCode)
    {

        // query whether or not, the requested store is available
        if (isset($this->stores[$storeViewCode])) {
            return (integer) $this->stores[$storeViewCode][MemberNames::STORE_ID];
        }

        // throw an exception, if not
        throw new \Exception(
            sprintf(
                'Found invalid store view code %s in file %s on line %d',
                $storeViewCode,
                $this->getFilename(),
                $this->getLineNumber()
            )
        );
    }

    /**
     * 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
     */
    public function getRowStoreId($default = null)
    {

        // initialize the default store view code, if not passed
        if ($default === null) {
            $default = $this->getDefaultStoreViewCode();
        }

        // load the store view code the create the product/attributes for
        return $this->getStoreId($this->getStoreViewCode($default));
    }

    /**
     * Return's the root category for the actual view store.
     *
     * @return array The store's root category
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
     */
    public function getRootCategory()
    {

        // load the actual store view code
        $storeViewCode = $this->getStoreViewCode($this->getDefaultStoreViewCode());

        // query weather or not we've a root category or not
        if (isset($this->rootCategories[$storeViewCode])) {
            return $this->rootCategories[$storeViewCode];
        }

        // throw an exception if the root category is NOT available
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
    }

    /**
     * Return's the Magento configuration value.
     *
     * @param string  $path    The Magento path of the requested configuration value
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
     * @param string  $scope   The scope the configuration value has been set
     * @param integer $scopeId The scope ID the configuration value has been set
     *
     * @return mixed The configuration value
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
     */
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
    {

        // initialize the core config data
        $coreConfigData = array(
            MemberNames::PATH => $path,
            MemberNames::SCOPE => $scope,
            MemberNames::SCOPE_ID => $scopeId
        );

        // generate the UID from the passed data
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);

        // iterate over the core config data and try to find the requested configuration value
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
        }

        // query whether or not we've to query for the configuration value on fallback level 'websites' also
        if ($scope === ScopeKeys::SCOPE_STORES) {
            // query whether or not the website with the passed ID is available
            foreach ($this->storeWebsites as $storeWebsite) {
                if ($storeWebsite[MemberNames::WEBSITE_ID] === $scopeId) {
                    // replace scope with 'websites' and website ID
                    $coreConfigData = array_merge(
                        $coreConfigData,
                        array(
                            MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
                            MemberNames::SCOPE_ID => $storeWebsite[MemberNames::WEBSITE_ID]
                        )
                    );

                    // generate the UID from the passed data, merged with the 'websites' scope and ID
                    $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);

                    // query whether or not, the configuration value on 'websites' level
                    if (isset($this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE])) {
                        return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
                    }
                }
            }
        }

        // replace scope with 'default' and scope ID '0'
        $coreConfigData = array_merge(
            $coreConfigData,
            array(
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
                MemberNames::SCOPE_ID => 0
            )
        );

        // generate the UID from the passed data, merged with the 'default' scope and ID 0
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);

        // query whether or not, the configuration value on 'default' level
        if (isset($this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE])) {
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
        }

        // if not, return the passed default value
        if ($default !== null) {
            return $default;
        }

        // throw an exception if no value can be found
        // in the Magento configuration
        throw new \Exception(
            sprintf(
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
                $path,
                $scope,
                $scopeId
            )
        );
    }

    /**
     * Resolve the original column name for the passed one.
     *
     * @param string $columnName The column name that has to be resolved
     *
     * @return string|null The original column name
     */
    public function resolveOriginalColumnName($columnName)
    {

        // try to load the original data
        $originalData = $this->getOriginalData();

        // query whether or not original data is available
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
            // query whether or not the original column name is available
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
            }

            // query whether or a wildcard column name is available
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
            }
        }

        // return the original column name
        return $columnName;
    }

    /**
     * Return's the original data if available, or an empty array.
     *
     * @return array The original data
     */
    public function getOriginalData()
    {

        // initialize the array for the original data
        $originalData = array();

        // query whether or not the column contains original data
        if ($this->hasOriginalData()) {
            // unerialize the original data from the column
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
        }

        // return an empty array, if not
        return $originalData;
    }

    /**
     * Query's whether or not the actual column contains original data like
     * filename, line number and column names.
     *
     * @return boolean TRUE if the actual column contains origin data, else FALSE
     */
    public function hasOriginalData()
    {
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
    }

    /**
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
     * line number and column names and use it for a detailed exception message.
     *
     * @param array      $columnNames The column names that should be resolved and wrapped
     * @param \Exception $parent      The exception we want to wrap
     * @param string     $className   The class name of the exception type we want to wrap the parent one
     *
     * @return \Exception the wrapped exception
     */
    public function wrapException(
        array $columnNames = array(),
        \Exception $parent = null,
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
    ) {

        // initialize the message
        $message = $parent->getMessage();

        // query whether or not has been a result of invalid data of a previous column of a CSV file
        if ($this->hasOriginalData()) {
            // load the original data
            $originalData = $this->getOriginalData();

            // replace old filename and line number of the original message
            $message = $this->appendExceptionSuffix(
                $this->stripExceptionSuffix($message),
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
            );
        } else {
            // append filename and line number to the original message
            $message = $this->appendExceptionSuffix(
                $this->stripExceptionSuffix($message),
                $this->filename,
                $this->lineNumber
            );
        }

        // query whether or not, column names has been passed
        if (sizeof($columnNames) > 0) {
            // prepare the original column names
            $originalColumnNames = array();
            foreach ($columnNames as $columnName) {
                $originalColumnNames[] = $this->resolveOriginalColumnName($columnName);
            }

            // append the column information
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
        }

        // create a new exception and wrap the parent one
        return new $className($message, null, $parent);
    }

    /**
     * Strip's the exception suffix containing filename and line number from the
     * passed message.
     *
     * @param string $message The message to strip the exception suffix from
     *
     * @return mixed The message without the exception suffix
     */
    public function stripExceptionSuffix($message)
    {
        return str_replace($this->appendExceptionSuffix(), '', $message);
    }

    /**
     * Append's the exception suffix containing filename and line number to the
     * passed message. If no message has been passed, only the suffix will be
     * returned
     *
     * @param string|null $message    The message to append the exception suffix to
     * @param string|null $filename   The filename used to create the suffix
     * @param string|null $lineNumber The line number used to create the suffx
     *
     * @return string The message with the appended exception suffix
     */
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
    {

        // query whether or not a filename has been passed
        if ($filename === null) {
            $filename = $this->getFilename();
        }

        // query whether or not a line number has been passed
        if ($lineNumber === null) {
            $lineNumber = $this->getLineNumber();
        }

        // if no message has been passed, only return the suffix
        if ($message === null) {
            return sprintf(' in file %s on line %d', $filename, $lineNumber);
        }

        // concatenate the message with the suffix and return it
        return sprintf('%s in file %s on line %d', $message, $filename, $lineNumber);
    }

    /**
     * Raises the value for the counter with the passed key by one.
     *
     * @param mixed $counterName The name of the counter to raise
     *
     * @return integer The counter's new value
     */
    public function raiseCounter($counterName)
    {

        // raise the counter with the passed name
        return $this->getRegistryProcessor()->raiseCounter(
            $this->getSerial(),
            $counterName
        );
    }

    /**
     * Merge the passed array into the status of the actual import.
     *
     * @param array $status The status information to be merged
     *
     * @return void
     */
    public function mergeAttributesRecursive(array $status)
    {

        // merge the passed status
        $this->getRegistryProcessor()->mergeAttributesRecursive(
            $this->getSerial(),
            $status
        );
    }
}


<?php

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

use TechDivision\Import\Utils\SwiftMailerKeys;
use TechDivision\Import\Configuration\SwiftMailerConfigurationInterface;

/**
 * Factory implementation for a swift mailer with a simple sendmail transport.
 *
 * @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 SendmailTransportMailerFactory
{

    /**
     * Creates a new swift mailer instance based on the passed configuration.
     *
     * @param \TechDivision\Import\Configuration\SwiftMailerConfigurationInterface $swiftMailerConfiguration The mailer configuration
     *
     * @return \Swift_Mailer The mailer instance
     */
    public static function factory(SwiftMailerConfigurationInterface $swiftMailerConfiguration)
    {

        // load the transport configuration
        $transportConfiguration = $swiftMailerConfiguration->getTransport();

        // load the transport factory
        $transportFactory = $transportConfiguration->getTransportFactory();

        // initialize and load the sendmail command parameter
        $command = '/usr/sbin/sendmail -bs';
        if ($transportConfiguration->hasParam(SwiftMailerKeys::COMMAND)) {
            $command = $transportConfiguration->getParam(SwiftMailerKeys::COMMAND);
        }

        // initialize and create the mailer transport instance
        $transport = $transportFactory::newInstance($command);

        // load the mailer factory
        $mailerFactory = $swiftMailerConfiguration->getMailerFactory();

        // initialize, create and return the swift mailer instance
        return $mailerFactory::newInstance($transport);
    }
}


<?php

/**
 * TechDivision\Import\Subjects\FileUploadTrait
 *
 * 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\Subjects;

/**
 * The trait implementation for the file upload 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 FileUploadTrait
{

    /**
     * The directory with the Magento media files => target directory for images (relative to the root directory).
     *
     * @var string
     */
    protected $mediaDir;

    /**
     * The directory with the images that have to be imported (relative to the root directory).
     *
     * @var string
     */
    protected $imagesFileDir;

    /**
     * The flag whether to copy the images or not.
     *
     * @var boolean
     */
    protected $copyImages = false;

    /**
     * Set's the flag to copy the images or not.
     *
     * @param boolean $copyImages The flag
     *
     * @return void
     */
    public function setCopyImages($copyImages)
    {
        $this->copyImages = $copyImages;
    }

    /**
     * Return's the flag to copy images or not.
     *
     * @return boolean The flag
     */
    public function hasCopyImages()
    {
        return $this->copyImages;
    }

    /**
     * Set's directory with the Magento media files => target directory for images.
     *
     * @param string $mediaDir The directory with the Magento media files => target directory for images
     *
     * @return void
     */
    public function setMediaDir($mediaDir)
    {
        $this->mediaDir = $mediaDir;
    }

    /**
     * Return's the directory with the Magento media files => target directory for images.
     *
     * @return string The directory with the Magento media files => target directory for images
     */
    public function getMediaDir()
    {
        return $this->mediaDir;
    }

    /**
     * Set's directory with the images that have to be imported.
     *
     * @param string $imagesFileDir The directory with the images that have to be imported
     *
     * @return void
     */
    public function setImagesFileDir($imagesFileDir)
    {
        $this->imagesFileDir = $imagesFileDir;
    }

    /**
     * Return's the directory with the images that have to be imported.
     *
     * @return string The directory with the images that have to be imported
     */
    public function getImagesFileDir()
    {
        return $this->imagesFileDir;
    }

    /**
     * Get new file name if the same is already exists.
     *
     * @param string $targetFilename The name of the exisising files
     *
     * @return string The new filename
     */
    public function getNewFileName($targetFilename)
    {

        // load the file information
        $fileInfo = pathinfo($targetFilename);

        // query whether or not, the file exists
        if ($this->getFilesystemAdapter()->isFile($targetFilename)) {
            // initialize the incex and the basename
            $index = 1;
            $baseName = $fileInfo['filename'] . '.' . $fileInfo['extension'];

            // prepare the new filename by raising the index
            while ($this->getFilesystemAdapter()->isFile($fileInfo['dirname'] . '/' . $baseName)) {
                $baseName = $fileInfo['filename'] . '_' . $index . '.' . $fileInfo['extension'];
                $index++;
            }

            // set the new filename
            $targetFilename = $baseName;
        } else {
            // if not, simply return the filename
            return $fileInfo['basename'];
        }

        // return the new filename
        return $targetFilename;
    }

    /**
     * Upload's the file with the passed name to the Magento
     * media directory. If the file already exists, the will
     * be given a new name that will be returned.
     *
     * @param string $filename The name of the file to be uploaded
     *
     * @return string The name of the uploaded file
     * @throws \Exception Is thrown, if the file with the passed name is not available
     */
    public function uploadFile($filename)
    {

        // trim the leading /, if available
        $trimmedFilename = ltrim($filename, '/');
        $mediaDir = ltrim($this->getMediaDir(), '/');
        $imagesFileDir = ltrim($this->getImagesFileDir(), '/');

        // prepare source/target filename
        $sourceFilename = sprintf('%s/%s', $imagesFileDir, $trimmedFilename);
        $targetFilename = sprintf('%s/%s', $mediaDir, $trimmedFilename);

        // query whether or not the image file to be imported is available
        if (!$this->getFilesystemAdapter()->isFile($sourceFilename)) {
            throw new \Exception(sprintf('Media file %s not available', $sourceFilename));
        }

        // prepare the target filename, if necessary
        $newTargetFilename = $this->getNewFileName($targetFilename);
        $targetFilename = str_replace(basename($targetFilename), $newTargetFilename, $targetFilename);

        // make sure, the target directory exists
        if (!$this->getFilesystemAdapter()->isDir($targetDirectory = dirname($targetFilename))) {
            $this->getFilesystemAdapter()->mkdir($targetDirectory, 0755);
        }

        // copy the image to the target directory
        $this->getFilesystemAdapter()->copy($sourceFilename, $targetFilename);

        // return the new target filename
        return str_replace($mediaDir, '', $targetFilename);
    }

    /**
     * Delete the file with the passed name.
     *
     * @param string $filename The name of the file to be deleted
     *
     * @return boolean TRUE on success, else FALSE
     */
    public function deleteFile($filename)
    {

        // trim the leading /, if available
        $trimmedFilename = ltrim($filename, '/');
        $mediaDir = ltrim($this->getMediaDir(), '/');

        // prepare source/target filename
        $targetFilename = sprintf('%s/%s', $mediaDir, $trimmedFilename);

        // query whether or not the image file to be deleted is available
        if (!$this->getFilesystemAdapter()->isFile($targetFilename)) {
            throw new \Exception(sprintf('Media file %s not available', $targetFilename));
        }

        // delte the image from the target directory
        $this->getFilesystemAdapter()->delete($targetFilename);
    }
}


<?php

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

namespace TechDivision\Import\Repositories;

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

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

    /**
     * The statement to load the available EAV entity types.
     *
     * @var \PDOStatement
     */
    protected $eavEntityTypeStmt;

    /**
     * The prepared statement to load an existing EAV entity type by its entity type code.
     *
     * @var \PDOStatement
     */
    protected $eavEntityTypeByEntityTypeACodeStmt;

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

        // initialize the prepared statements
        $this->eavEntityTypeStmt =
            $this->getConnection()->prepare($this->loadStatement(SqlStatementKeys::EAV_ENTITY_TYPES));
        $this->eavEntityTypeByEntityTypeACodeStmt =
            $this->getConnection()->prepare($this->loadStatement(SqlStatementKeys::EAV_ENTITY_TYPE_BY_ENTITY_TYPE_CODE));
    }

    /**
     * Return's an array with all available EAV entity types with the entity type code as key.
     *
     * @return array The available link types
     */
    public function findAll()
    {

        // initialize the array for the EAV entity types
        $eavEntityTypes = array();

        // try to load the EAV entity types
        $this->eavEntityTypeStmt->execute();

        // load the available EAV entity types
        $availableEntityTypes = $this->eavEntityTypeStmt->fetchAll(\PDO::FETCH_ASSOC);

        // prepare the EAV entity types => we need the entity type code as key
        foreach ($availableEntityTypes as $eavEntityType) {
            $eavEntityTypes[$eavEntityType[MemberNames::ENTITY_TYPE_CODE]] = $eavEntityType;
        }

        // return the array with the EAV entity types
        return $eavEntityTypes;
    }

    /**
     * Return's an EAV entity type with the passed entity type code.
     *
     * @param string $entityTypeCode The code of the entity type to return
     *
     * @return array The entity type with the passed entity type code
     */
    public function findOneByEntityTypeCode($entityTypeCode)
    {
        // load and return the EAV attribute with the passed params
        $this->eavEntityTypeByEntityTypeACodeStmt->execute(array(MemberNames::ENTITY_TYPE_CODE => $entityTypeCode));
        return $this->eavEntityTypeByEntityTypeACodeStmt->fetch(\PDO::FETCH_ASSOC);
    }
}


<?php

/**
 * TechDivision\Import\Serializers\ValueCsvSerializer
 *
 * 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\Serializers;

/**
 * Serializer to serialize/unserialize simple column values.
 *
 * @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 ValueCsvSerializer extends AbstractCsvSerializer
{

    /**
     * The delimiter to override the one from the configuration with.
     *
     * @var string
     */
    private $delimiter;

    /**
     * The delimiter to use instead the one from the configuration.
     *
     * @param string $delimiter The delimiter
     *
     * @return void
     */
    protected function setDelimiter($delimiter)
    {
        $this->delimiter = $delimiter;
    }

    /**
     * Returns the delimiter to use instead the one from the configuration.
     *
     * @return string The delimiter
     */
    protected function getDelimiter()
    {
        return $this->delimiter;
    }

    /**
     * Serializes the elements of the passed array by using the
     * instances CSV configuration options.
     *
     * @param array|null $unserialized The serialized data
     *
     * @return string|null The serialized array
     */
    public function serialize(array $unserialized = null)
    {

        // do nothing, if the passed value is empty or NULL
        if ($unserialized === null || $unserialized === '') {
            return;
        }

        // load the global configuration
        $configuration = $this->getConfiguration();

        // initializet delimiter, enclosure and escape char
        $delimiter = $this->getDelimiter() ? $this->getDelimiter() : $configuration->getDelimiter();
        $enclosure = $configuration->getEnclosure();
        $escape = $configuration->getEscape();

        // create hte callback method to enclose/escape the values
        $callback = function ($value) use ($enclosure, $escape) {
            return $enclosure . str_replace($escape, $escape . $escape, $value) . $enclosure;
        };

        // implode and return the enclosed/escaped values
        return implode($delimiter, array_map($callback, $unserialized));
    }

    /**
     * Unserializes the elements of the passed value by using the
     * instances CSV configuration options.
     *
     * @param string|null $serialized The value to unserialize
     *
     * @return array|null The exploded values
     */
    public function unserialize($serialized = null)
    {

        // do nothing, if the passed value is empty or NULL
        if ($serialized === null || $serialized === '') {
            return;
        }

        // load the global configuration
        $configuration = $this->getConfiguration();

        // initializet delimiter, enclosure and escape char
        $delimiter = $this->getDelimiter() ? $this->getDelimiter() : $configuration->getDelimiter();
        $enclosure = $configuration->getEnclosure();
        $escape = $configuration->getEscape();

        // parse and return the found data as array
        return str_getcsv($serialized, $delimiter, $enclosure, $escape);
    }

    /**
     * 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
     * @see \TechDivision\Import\Serializers\ValueCsvSerializer::unserialize()
     */
    public function explode($value = null, $delimiter = null)
    {

        // set the delimiter
        $this->setDelimiter($delimiter);

        // unserialize the value and return it
        return $this->unserialize($value);
    }

    /**
     * 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
     * @see \TechDivision\Import\Serializers\ValueCsvSerializer::serialize()
     */
    public function implode(array $value = null, $delimiter = null)
    {

        // set the delimiter
        $this->setDelimiter($delimiter);

        // serialize the value and return it
        return $this->serialize($value);
    }
}


<?php

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

use Zend\Filter\FilterInterface;

/**
 * Convert Literal Filter 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 ConvertLiteral implements FilterInterface
{

    /**
     * Symbol convert table
     *
     * @var array
     */
    protected $convertTable = array(
        '&' => 'and',
        '@' => 'at',
        '©' => 'c',
        '®' => 'r',
        'À' => 'a',
        'Á' => 'a',
        'Â' => 'a',
        'Ä' => 'a',
        'Å' => 'a',
        'Æ' => 'ae',
        'Ç' => 'c',
        'È' => 'e',
        'É' => 'e',
        'Ë' => 'e',
        'Ì' => 'i',
        'Í' => 'i',
        'Î' => 'i',
        'Ï' => 'i',
        'Ò' => 'o',
        'Ó' => 'o',
        'Ô' => 'o',
        'Õ' => 'o',
        'Ö' => 'o',
        'Ø' => 'o',
        'Ù' => 'u',
        'Ú' => 'u',
        'Û' => 'u',
        'Ü' => 'u',
        'Ý' => 'y',
        'ß' => 'ss',
        'à' => 'a',
        'á' => 'a',
        'â' => 'a',
        'ä' => 'a',
        'å' => 'a',
        'æ' => 'ae',
        'ç' => 'c',
        'è' => 'e',
        'é' => 'e',
        'ê' => 'e',
        'ë' => 'e',
        'ì' => 'i',
        'í' => 'i',
        'î' => 'i',
        'ï' => 'i',
        'ò' => 'o',
        'ó' => 'o',
        'ô' => 'o',
        'õ' => 'o',
        'ö' => 'o',
        'ø' => 'o',
        'ù' => 'u',
        'ú' => 'u',
        'û' => 'u',
        'ü' => 'u',
        'ý' => 'y',
        'þ' => 'p',
        'ÿ' => 'y',
        'Ā' => 'a',
        'ā' => 'a',
        'Ă' => 'a',
        'ă' => 'a',
        'Ą' => 'a',
        'ą' => 'a',
        'Ć' => 'c',
        'ć' => 'c',
        'Ĉ' => 'c',
        'ĉ' => 'c',
        'Ċ' => 'c',
        'ċ' => 'c',
        'Č' => 'c',
        'č' => 'c',
        'Ď' => 'd',
        'ď' => 'd',
        'Đ' => 'd',
        'đ' => 'd',
        'Ē' => 'e',
        'ē' => 'e',
        'Ĕ' => 'e',
        'ĕ' => 'e',
        'Ė' => 'e',
        'ė' => 'e',
        'Ę' => 'e',
        'ę' => 'e',
        'Ě' => 'e',
        'ě' => 'e',
        'Ĝ' => 'g',
        'ĝ' => 'g',
        'Ğ' => 'g',
        'ğ' => 'g',
        'Ġ' => 'g',
        'ġ' => 'g',
        'Ģ' => 'g',
        'ģ' => 'g',
        'Ĥ' => 'h',
        'ĥ' => 'h',
        'Ħ' => 'h',
        'ħ' => 'h',
        'Ĩ' => 'i',
        'ĩ' => 'i',
        'Ī' => 'i',
        'ī' => 'i',
        'Ĭ' => 'i',
        'ĭ' => 'i',
        'Į' => 'i',
        'į' => 'i',
        'İ' => 'i',
        'ı' => 'i',
        'Ĳ' => 'ij',
        'ĳ' => 'ij',
        'Ĵ' => 'j',
        'ĵ' => 'j',
        'Ķ' => 'k',
        'ķ' => 'k',
        'ĸ' => 'k',
        'Ĺ' => 'l',
        'ĺ' => 'l',
        'Ļ' => 'l',
        'ļ' => 'l',
        'Ľ' => 'l',
        'ľ' => 'l',
        'Ŀ' => 'l',
        'ŀ' => 'l',
        'Ł' => 'l',
        'ł' => 'l',
        'Ń' => 'n',
        'ń' => 'n',
        'Ņ' => 'n',
        'ņ' => 'n',
        'Ň' => 'n',
        'ň' => 'n',
        'ŉ' => 'n',
        'Ŋ' => 'n',
        'ŋ' => 'n',
        'Ō' => 'o',
        'ō' => 'o',
        'Ŏ' => 'o',
        'ŏ' => 'o',
        'Ő' => 'o',
        'ő' => 'o',
        'Œ' => 'oe',
        'œ' => 'oe',
        'Ŕ' => 'r',
        'ŕ' => 'r',
        'Ŗ' => 'r',
        'ŗ' => 'r',
        'Ř' => 'r',
        'ř' => 'r',
        'Ś' => 's',
        'ś' => 's',
        'Ŝ' => 's',
        'ŝ' => 's',
        'Ş' => 's',
        'ş' => 's',
        'Š' => 's',
        'š' => 's',
        'Ţ' => 't',
        'ţ' => 't',
        'Ť' => 't',
        'ť' => 't',
        'Ŧ' => 't',
        'ŧ' => 't',
        'Ũ' => 'u',
        'ũ' => 'u',
        'Ū' => 'u',
        'ū' => 'u',
        'Ŭ' => 'u',
        'ŭ' => 'u',
        'Ů' => 'u',
        'ů' => 'u',
        'Ű' => 'u',
        'ű' => 'u',
        'Ų' => 'u',
        'ų' => 'u',
        'Ŵ' => 'w',
        'ŵ' => 'w',
        'Ŷ' => 'y',
        'ŷ' => 'y',
        'Ÿ' => 'y',
        'Ź' => 'z',
        'ź' => 'z',
        'Ż' => 'z',
        'ż' => 'z',
        'Ž' => 'z',
        'ž' => 'z',
        'ſ' => 'z',
        'Ə' => 'e',
        'ƒ' => 'f',
        'Ơ' => 'o',
        'ơ' => 'o',
        'Ư' => 'u',
        'ư' => 'u',
        'Ǎ' => 'a',
        'ǎ' => 'a',
        'Ǐ' => 'i',
        'ǐ' => 'i',
        'Ǒ' => 'o',
        'ǒ' => 'o',
        'Ǔ' => 'u',
        'ǔ' => 'u',
        'Ǖ' => 'u',
        'ǖ' => 'u',
        'Ǘ' => 'u',
        'ǘ' => 'u',
        'Ǚ' => 'u',
        'ǚ' => 'u',
        'Ǜ' => 'u',
        'ǜ' => 'u',
        'Ǻ' => 'a',
        'ǻ' => 'a',
        'Ǽ' => 'ae',
        'ǽ' => 'ae',
        'Ǿ' => 'o',
        'ǿ' => 'o',
        'ə' => 'e',
        'Ё' => 'jo',
        'Є' => 'e',
        'І' => 'i',
        'Ї' => 'i',
        'А' => 'a',
        'Б' => 'b',
        'В' => 'v',
        'Г' => 'g',
        'Д' => 'd',
        'Е' => 'e',
        'Ж' => 'zh',
        'З' => 'z',
        'И' => 'i',
        'Й' => 'j',
        'К' => 'k',
        'Л' => 'l',
        'М' => 'm',
        'Н' => 'n',
        'О' => 'o',
        'П' => 'p',
        'Р' => 'r',
        'С' => 's',
        'Т' => 't',
        'У' => 'u',
        'Ф' => 'f',
        'Х' => 'h',
        'Ц' => 'c',
        'Ч' => 'ch',
        'Ш' => 'sh',
        'Щ' => 'sch',
        'Ъ' => '-',
        'Ы' => 'y',
        'Ь' => '-',
        'Э' => 'je',
        'Ю' => 'ju',
        'Я' => 'ja',
        'а' => 'a',
        'б' => 'b',
        'в' => 'v',
        'г' => 'g',
        'д' => 'd',
        'е' => 'e',
        'ж' => 'zh',
        'з' => 'z',
        'и' => 'i',
        'й' => 'j',
        'к' => 'k',
        'л' => 'l',
        'м' => 'm',
        'н' => 'n',
        'о' => 'o',
        'п' => 'p',
        'р' => 'r',
        'с' => 's',
        'т' => 't',
        'у' => 'u',
        'ф' => 'f',
        'х' => 'h',
        'ц' => 'c',
        'ч' => 'ch',
        'ш' => 'sh',
        'щ' => 'sch',
        'ъ' => '-',
        'ы' => 'y',
        'ь' => '-',
        'э' => 'je',
        'ю' => 'ju',
        'я' => 'ja',
        'ё' => 'jo',
        'є' => 'e',
        'і' => 'i',
        'ї' => 'i',
        'Ґ' => 'g',
        'ґ' => 'g',
        'א' => 'a',
        'ב' => 'b',
        'ג' => 'g',
        'ד' => 'd',
        'ה' => 'h',
        'ו' => 'v',
        'ז' => 'z',
        'ח' => 'h',
        'ט' => 't',
        'י' => 'i',
        'ך' => 'k',
        'כ' => 'k',
        'ל' => 'l',
        'ם' => 'm',
        'מ' => 'm',
        'ן' => 'n',
        'נ' => 'n',
        'ס' => 's',
        'ע' => 'e',
        'ף' => 'p',
        'פ' => 'p',
        'ץ' => 'C',
        'צ' => 'c',
        'ק' => 'q',
        'ר' => 'r',
        'ש' => 'w',
        'ת' => 't',
        '™' => 'tm',
        'α' => 'a',
        'ά' => 'a',
        'Ά' => 'a',
        'Α' => 'a',
        'β' => 'b',
        'Β' => 'b',
        'γ' => 'g',
        'Γ' => 'g',
        'δ' => 'd',
        'Δ' => 'd',
        'ε' => 'e',
        'έ' => 'e',
        'Ε' => 'e',
        'Έ' => 'e',
        'ζ' => 'z',
        'Ζ' => 'z',
        'η' => 'i',
        'ή' => 'i',
        'Η' => 'i',
        'θ' => 'th',
        'Θ' => 'th',
        'ι' => 'i',
        'ί' => 'i',
        'ϊ' => 'i',
        'ΐ' => 'i',
        'Ι' => 'i',
        'Ί' => 'i',
        'κ' => 'k',
        'Κ' => 'k',
        'λ' => 'l',
        'Λ' => 'l',
        'μ' => 'm',
        'Μ' => 'm',
        'ν' => 'n',
        'Ν' => 'n',
        'ξ' => 'x',
        'Ξ' => 'x',
        'ο' => 'o',
        'ό' => 'o',
        'Ο' => 'o',
        'Ό' => 'o',
        'π' => 'p',
        'Π' => 'p',
        'ρ' => 'r',
        'Ρ' => 'r',
        'σ' => 's',
        'ς' => 's',
        'Σ' => 's',
        'τ' => 't',
        'Τ' => 't',
        'υ' => 'u',
        'ύ' => 'u',
        'Υ' => 'y',
        'Ύ' => 'y',
        'φ' => 'f',
        'Φ' => 'f',
        'χ' => 'ch',
        'Χ' => 'ch',
        'ψ' => 'ps',
        'Ψ' => 'ps',
        'ω' => 'o',
        'ώ' => 'o',
        'Ω' => 'o',
        'Ώ' => 'o',
    );

    /**
     * Get chars convertation table.
     *
     * @return array The array with convertation table
     */
    protected function getConvertTable()
    {
        return $this->convertTable;
    }

    /**
     * Filter and return the value.
     *
     * @param string $string The value to filter
     *
     * @return string The filtered value
     */
    public function filter($string)
    {
        $string = strtr($string, $this->getConvertTable());
        return '"libiconv"' == ICONV_IMPL ? iconv(
            'UTF-8',
            'ascii//ignore//translit',
            $string
        ) : $string;
    }
}


<?php

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

/**
 * Adapter for a PHP filesystem 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 PhpFilesystemAdapter implements FilesystemAdapterInterface
{

    /**
     * Creates a new directroy.
     *
     * @param string  $pathname The directory path
     * @param integer $mode     The mode is 0700 by default, which means the widest possible access
     *
     * @return boolean TRUE on success, else FALSE
     * @link http://php.net/mkdir
     */
    public function mkdir($pathname, $mode = 0755)
    {
        return mkdir($pathname, $mode, true);
    }

    /**
     * Query whether or not the passed filename exists.
     *
     * @param string $filename The filename to query
     *
     * @return boolean TRUE if the passed filename exists, else FALSE
     * @link http://php.net/is_file
     */
    public function isFile($filename)
    {
        return is_file($filename);
    }

    /**
     * Tells whether the filename is a directory.
     *
     * @param string $filename Path to the file
     *
     * @return TRUE if the filename exists and is a directory, else FALSE
     * @link http://php.net/is_dir
     */
    public function isDir($filename)
    {
        return is_dir($filename);
    }

    /**
     * Creates an empty file with the passed filename.
     *
     * @param string $filename The name of the file to create
     *
     * @return boolean TRUE if the file can be created, else FALSE
     */
    public function touch($filename)
    {
        return touch($filename);
    }

    /**
     * Renames a file or directory.
     *
     * @param string $oldname The old name
     * @param string $newname The new name
     *
     * @return boolean TRUE on success, else FALSE
     * @link http://php.net/rename
     */
    public function rename($oldname, $newname)
    {
        return rename($oldname, $newname);
    }

    /**
     * Writes the passed data to file with the passed name.
     *
     * @param string $filename The name of the file to write the data to
     * @param string $data     The data to write to the file
     *
     * @return number The number of bytes written to the file
     * @link http://php.net/file_put_contents
     */
    public function write($filename, $data)
    {
        return file_put_contents($filename, $data);
    }

    /**
     * Delete the file with the passed name.
     *
     * @param string $filename The name of the file to be deleted
     *
     * @return boolean TRUE on success, else FALSE
     */
    public function delete($filename)
    {
        return unlink($filename);
    }

    /**
     * Copy's a file from source to destination.
     *
     * @param string $src  The source file
     * @param string $dest The destination file
     *
     * @return boolean TRUE on success, else FALSE
     * @link http://php.net/copy
     */
    public function copy($src, $dest)
    {
        return copy($src, $dest);
    }

    /**
     * List the filenames of a directory.
     *
     * @param string  $directory The directory to list
     * @param boolean $recursive Whether to list recursively
     *
     * @return array A list of filenames
     */
    public function listContents($directory = '', $recursive = false)
    {

        // parse the directory
        $files = glob($pattern = sprintf('%s/*', $directory), 0);

        // parse all subdirectories, if recursive parsing is wanted
        if ($recursive !== false) {
            // load the directories
            $dirs = glob(dirname($pattern). DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR|GLOB_NOSORT|GLOB_BRACE);
            // iterate over the subdirectories for its files
            foreach ($dirs as $dir) {
                $files = array_merge($files, $this->listContents($dir . DIRECTORY_SEPARATOR . basename($pattern), $recursive));
            }
        }

        // return the array with the files matching the glob pattern
        return $files;
    }
}