<commit_before><?php

namespace Oro\Bundle\ImportExportBundle\Converter;

use Symfony\Component\EventDispatcher\EventDispatcherInterface;

use Oro\Bundle\ImportExportBundle\Utils\ArrayUtil;
use Oro\Bundle\ImportExportBundle\Event\Events;
use Oro\Bundle\ImportExportBundle\Event\FormatConversionEvent;
use Oro\Bundle\ImportExportBundle\Exception\LogicException;

abstract class AbstractTableDataConverter extends DefaultDataConverter
{
    const BACKEND_TO_FRONTEND = 'backend_to_frontend';
    const FRONTEND_TO_BACKEND = 'frontend_to_backend';

    /** @var EventDispatcherInterface|null */
    protected $dispatcher;

    /** @var array */
    protected $backendHeader;

    /** @var array */
    protected $backendToFrontendHeader;

    /** @var array */
    protected $headerConversionRules;

    /**
     * {@inheritDoc}
     */
    public function convertToExportFormat(array $exportedRecord, $skipNullValues = true)
    {
        $exportedRecord = $this->dispatchFormatConversionEvent(Events::BEFORE_EXPORT_FORMAT_CONVERSION, $exportedRecord)
            ->getRecord();

        $plainDataWithBackendHeader = parent::convertToExportFormat($exportedRecord, $skipNullValues);
        $filledPlainDataWithBackendHeader = $this->fillEmptyColumns(
            $this->receiveBackendHeader(),
            $plainDataWithBackendHeader
        );
        $filledPlainDataWithFrontendHints = $this->replaceKeys(
            $this->receiveBackendToFrontendHeader(),
            $filledPlainDataWithBackendHeader
        );

        return $this->dispatchFormatConversionEvent(
            Events::AFTER_EXPORT_FORMAT_CONVERSION,
            $exportedRecord,
            $filledPlainDataWithFrontendHints
        )
        ->getResult();
    }

    /**
     * {@inheritDoc}
     */
    public function convertToImportFormat(array $importedRecord, $skipNullValues = true)
    {
        $importedRecord = $this->dispatchFormatConversionEvent(Events::BEFORE_IMPORT_FORMAT_CONVERSION, $importedRecord)
            ->getRecord();

        $plainDataWithFrontendHeader = $this->removeEmptyColumns($importedRecord, $skipNullValues);

        $frontendHeader               = array_keys($plainDataWithFrontendHeader);
        $frontendToBackendHeader      = $this->convertHeaderToBackend($frontendHeader);
        $plainDataWithBackendHeader   = $this->replaceKeys(
            $frontendToBackendHeader,
            $plainDataWithFrontendHeader
        );
        $complexDataWithBackendHeader = parent::convertToImportFormat($plainDataWithBackendHeader, $skipNullValues);

        $filteredComplexDataWithBackendHeader = ArrayUtil::filterEmptyArrays($complexDataWithBackendHeader);

        return $this->dispatchFormatConversionEvent(
            Events::AFTER_IMPORT_FORMAT_CONVERSION,
            $importedRecord,
            $filteredComplexDataWithBackendHeader
        )
            ->getResult();
    }

    /**
     * @param EventDispatcherInterface $dispatcher
     */
    public function setDispatcher(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    /**
     * @param string $eventName
     * @param array $record
     * @param array $result
     *
     * @return FormatConversionEvent
     */
    protected function dispatchFormatConversionEvent($eventName, array $record, array $result = [])
    {
        $event = new FormatConversionEvent($record, $result);
        if ($this->dispatcher && $this->dispatcher->hasListeners($eventName)) {
            $this->dispatcher->dispatch($eventName, $event);
        }

        return $event;
    }

    /**
     * @param array $header
     * @param array $data
     * @return array
     * @throws LogicException
     */
    protected function fillEmptyColumns(array $header, array $data)
    {
        $dataDiff = array_diff(array_keys($data), $header);
        // if data contains keys that are not in header
        if ($dataDiff) {
            throw new LogicException(
                sprintf('Backend header doesn\'t contain fields: %s', implode(', ', $dataDiff))
            );
        }

        $result = array();
        foreach ($header as $headerKey) {
            $result[$headerKey] = array_key_exists($headerKey, $data) ? $data[$headerKey] : '';
        }

        return $result;
    }

    /**
     * @param array $data
     * @param bool $skipNullValues
     * @return array
     */
    protected function removeEmptyColumns(array $data, $skipNullValues)
    {
        $data = array_map(
            function ($value) {
                if ($value === '') {
                    return null;
                }

                return $value;
            },
            $data
        );

        return array_filter(
            $data,
            function ($value) use ($skipNullValues) {
                if (is_null($value) && $skipNullValues) {
                    return false;
                }

                return true;
            }
        );
    }

    /**
     * @return array
     */
    protected function receiveBackendHeader()
    {
        if (null === $this->backendHeader) {
            $this->backendHeader = $this->getBackendHeader();<commit_msg>CRM-8387: Leads export failed after editing and adding 2nd address (#12907)<commit_after><?php

namespace Oro\Bundle\ImportExportBundle\Converter;

use Symfony\Component\EventDispatcher\EventDispatcherInterface;

use Oro\Bundle\ImportExportBundle\Utils\ArrayUtil;
use Oro\Bundle\ImportExportBundle\Event\Events;
use Oro\Bundle\ImportExportBundle\Event\FormatConversionEvent;
use Oro\Bundle\ImportExportBundle\Exception\LogicException;

abstract class AbstractTableDataConverter extends DefaultDataConverter
{
    const BACKEND_TO_FRONTEND = 'backend_to_frontend';
    const FRONTEND_TO_BACKEND = 'frontend_to_backend';

    /** @var EventDispatcherInterface|null */
    protected $dispatcher;

    /** @var array */
    protected $backendHeader;

    /** @var array */
    protected $backendToFrontendHeader;

    /** @var array */
    protected $headerConversionRules;

    /**
     * {@inheritDoc}
     */
    public function convertToExportFormat(array $exportedRecord, $skipNullValues = true)
    {
        $exportedRecord = $this->dispatchFormatConversionEvent(Events::BEFORE_EXPORT_FORMAT_CONVERSION, $exportedRecord)
            ->getRecord();

        $plainDataWithBackendHeader = parent::convertToExportFormat($exportedRecord, $skipNullValues);
        $filledPlainDataWithBackendHeader = $this->fillEmptyColumns(
            $this->receiveBackendHeader(),
            $plainDataWithBackendHeader
        );
        $filledPlainDataWithFrontendHints = $this->replaceKeys(
            $this->receiveBackendToFrontendHeader(),
            $filledPlainDataWithBackendHeader
        );

        return $this->dispatchFormatConversionEvent(
            Events::AFTER_EXPORT_FORMAT_CONVERSION,
            $exportedRecord,
            $filledPlainDataWithFrontendHints
        )
        ->getResult();
    }

    /**
     * {@inheritDoc}
     */
    public function convertToImportFormat(array $importedRecord, $skipNullValues = true)
    {
        $importedRecord = $this->dispatchFormatConversionEvent(Events::BEFORE_IMPORT_FORMAT_CONVERSION, $importedRecord)
            ->getRecord();

        $plainDataWithFrontendHeader = $this->removeEmptyColumns($importedRecord, $skipNullValues);

        $frontendHeader               = array_keys($plainDataWithFrontendHeader);
        $frontendToBackendHeader      = $this->convertHeaderToBackend($frontendHeader);
        $plainDataWithBackendHeader   = $this->replaceKeys(
            $frontendToBackendHeader,
            $plainDataWithFrontendHeader
        );
        $complexDataWithBackendHeader = parent::convertToImportFormat($plainDataWithBackendHeader, $skipNullValues);

        $filteredComplexDataWithBackendHeader = ArrayUtil::filterEmptyArrays($complexDataWithBackendHeader);

        return $this->dispatchFormatConversionEvent(
            Events::AFTER_IMPORT_FORMAT_CONVERSION,
            $importedRecord,
            $filteredComplexDataWithBackendHeader
        )
            ->getResult();
    }

    /**
     * @param EventDispatcherInterface $dispatcher
     */
    public function setDispatcher(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    /**
     * @param string $eventName
     * @param array $record
     * @param array $result
     *
     * @return FormatConversionEvent
     */
    protected function dispatchFormatConversionEvent($eventName, array $record, array $result = [])
    {
        $event = new FormatConversionEvent($record, $result);
        if ($this->dispatcher && $this->dispatcher->hasListeners($eventName)) {
            $this->dispatcher->dispatch($eventName, $event);
        }

        return $event;
    }

    /**
     * @param array $header
     * @param array $data
     * @throws LogicException
     */
    protected function validateColumns(array $header, array $data)
    {
        $dataDiff = array_diff(array_keys($data), $header);
        // if data contains keys that are not in header, refresh header and check again
        if ($dataDiff) {
            $header = $this->getRefreshedBackendHeader();
            $dataDiff = array_diff(array_keys($data), $header);
            if ($dataDiff) {
                throw new LogicException(
                    sprintf('Backend header doesn\'t contain fields: %s', implode(', ', $dataDiff))
                );
            }
        }
    }

    /**
     * @param array $header
     * @param array $data
     * @return array
     * @throws LogicException
     */
    protected function fillEmptyColumns(array $header, array $data)
    {
        $this->validateColumns($header, $data);

        $result = array();
        foreach ($header as $headerKey) {
            $result[$headerKey] = array_key_exists($headerKey, $data) ? $data[$headerKey] : '';
        }

        return $result;
    }

    /**
     * @param array $data
     * @param bool $skipNullValues
     * @return array
     */
    protected function removeEmptyColumns(array $data, $skipNullValues)
    {
        $data = array_map(
            function ($value) {
                if ($value === '') {
                    return null;
                }

                return $value;
            },
            $data
        );

        return array_filter(
            $data,
            function ($value) use ($skipNullValues) {
                if (is_null($value) && $skipNullValues) {
                    return false;
                }

                return true;
            }
        );
    }

    /**
     * @return array
     */
    protected function getRefreshedBackendHeader()
    {
        $this->backendHeader = null;
        return $this->receiveBackendHeader();
    }

    /**
     * @return array
     */
    protected function receiveBackendHeader()
    {
        if (null === $this->backendHeader) {
            $this->backendHeader = $this->getBackendHeader();
{'fraction_non_alphanumeric': 0.10574045524621384, 'fraction_numerical': 0.0009068649678062936, 'mean_word_length': 2.4440974391005623, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 55, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '984508', 'n_tokens_mistral': 2965, 'n_tokens_neox': 2907, 'n_words': 526}