HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/SBogers95/rentman.io/app/Komma/Kms/Transfer/AbstractCsvImportService.php
<?php

namespace App\Komma\Kms\Transfer;

use Illuminate\Support\MessageBag;

abstract class AbstractCsvImportService extends AbstractCsvTransferService
{
    /** @var string A name of an attribute key that will receive the import errors if any */
    protected $errorsAttributeKey = '';

    /**
     * Imports the row into the database
     *
     * @param array $row
     * @param int $lineNumber
     * @return string A success or fail message
     */
    abstract public function import(array $row, int $lineNumber):string;

    /**
     * Validates a row and returns an array with
     * validation errors if any or an empty array if no errors
     *
     * @param array $row
     * @param int $lineNumber
     * @return array
     */
    public function validate(array $row, int $lineNumber): array
    {
        $mismatchErrors = $this->checkForColumnCountMismatches($row, $lineNumber);
        if (count($mismatchErrors) > 0) {
            return $mismatchErrors;
        }

        return $this->validateRowData($row, $lineNumber);
    }

    /**
     * Loops over all column maps and checks if their column numbers really map to a value.
     *
     * @param array $row
     * @param int $lineNumber
     * @return array
     */
    private function checkForColumnCountMismatches(array $row, int $lineNumber):array
    {
        $columnMapsCollection = collect($this->getColumnMaps());
        $headerString = implode(', ', $this->getHeaderNames());

        $validationErrors = $columnMapsCollection->flatten()->map(function (ColumnMap $columnMap) use ($row, $lineNumber, $headerString) {
            if ($columnMap->getColumnNumber() > count($row)) {
                $error = __('kms/transfer.validation_error.column_count_mismatch', ['headers' => $headerString]);

                return $error;
            }
        })->filter(function ($errorOrNull) {
            return $errorOrNull !== null;
        })->toArray();

        if (count($validationErrors) == 0) {
            return [];
        }

        return $validationErrors;
    }

    /**
     * Validates the rows data and returns an array with
     * validation errors if any or an empty array if no errors
     *
     * @param array $row
     * @param int $lineNumber
     * @return array
     */
    private function validateRowData(array $row, int $lineNumber):array
    {
        $columnMapsCollection = collect($this->getColumnMaps());

        $validationErrors = $columnMapsCollection->flatten()->map(function (ColumnMap $columnMap) use ($row, $lineNumber) {
            $validationRegex = $columnMap->getValidationRegex();

            $value = $row[$columnMap->getColumnNumber()];
            $value = preg_replace("/\r|\n/", '', $value);

            if ($value == '' && $columnMap->isOptional()) {
                return null;
            }

            try {
                if ($validationRegex == '') {
                    return null;
                } //If no validation regex is defined we skip the validation and assume the data is right
                $match = preg_match($validationRegex, $value);

                if ($match == false) {
                    return __('kms/transfer.validation_error.error_on_line', ['row_number' => $lineNumber]).': '.$columnMap->getValidationErrorText();
                }
            } catch (\ErrorException $exception) {
                return __('kms/transfer.validation_error.error_on_line', ['row_number' => $lineNumber]).': '.$columnMap->getValidationErrorText().'. Regex: "'.$validationRegex.'". Exception: '.$exception->getMessage();
            }
        })->filter(function ($value) {
            return $value !== null;
        })->toArray();

        return $validationErrors;
    }

    /**
     * Loads a file containing CSV data and passes each row to the import method.
     *
     * @param string $filePath
     * @return MessageBag with keys 'errors' and 'success'
     */
    public function importFromFile(string $filePath)
    {
        return $this->readCsvFileAndStartProcessing($filePath);
    }

    /**
     * Reads a csv file and delegates importing.
     * If an error occurs it redirect backs with errors. Use key row_validation_errors for more info
     *
     * @param string $filePath
     * @return MessageBag with two keys 'errors' and 'success'. That represent success and error messages for the imported lines
     */
    private function readCsvFileAndStartProcessing(string $filePath):MessageBag
    {
        \Log::debug('AbstractCsvImportService:114');
        $fileHandle = fopen($filePath, 'r');
        $lineNumber = 1;

        $messageBag = new MessageBag();
//        echo __('kms/transfer.processing').'<br/>';

        $bigErrorCounter = 0;
        $bigSuccessCounter = 0;
        $emptyRowCounter = 0;

        //Prevent memory exhaustion errors
        $maxMemoryForImport = 128000000; //128 Megabytes (128000000 bytes). Php default
        \Debugbar::disable();
        set_time_limit(0);
        ini_set('memory_limit', $maxMemoryForImport);

        while (($row = fgetcsv($fileHandle, 100000, ';')) !== false) {
            if (memory_get_usage() >= $maxMemoryForImport * .8) {
                $messageBag->add('errors', __('kms/transfer.memory_exhaustion_import'));

                return $messageBag;
            }

            //Skip a row that is completely empty
            $rowAsCSV = implode(';', $row);
            if (str_replace(';', '', $rowAsCSV) == '') {
                unset($rowAsCSV); //Force garbage collection
                $emptyRowCounter++;

                if ($emptyRowCounter >= 10) {
                    break;
                }
                continue;
            }

            //Import a row
            if (! self::HASHEADERROW || ($lineNumber > 1 && self::HASHEADERROW)) {
                \DB::transaction(function () use ($row, $lineNumber, &$messageBag, &$bigErrorCounter, &$bigSuccessCounter) {
                    $errorsFromValidation = $this->validate($row, $lineNumber);
                    if (count($errorsFromValidation) > 0) {
                        foreach ($errorsFromValidation as $error) {
                            if ($messageBag->count() < 1000) { //we only log the first x amount or so messages. Over that we risk memory exhaustion via the KmsFlashMessageComposer concatenation
                                $messageBag->add('errors', $error);
                            } else {
                                $bigErrorCounter++;
                            }
                        }
                    } else {
                        $successFromImport = $this->import($row, $lineNumber);
                        if ($messageBag->count() < 1000) { //we only log the first x amount or so messages. Over that we risk memory exhaustion via the KmsFlashMessageComposer concatenation
                            $messageBag->add('successes', $successFromImport);
                        } else {
                            $bigSuccessCounter++;
                        }
                    }
                });
            }

            unset($row); //Force garbage collection

            $lineNumber++;
        }
        fclose($fileHandle);

        if ($bigSuccessCounter > 0) {
            $messageBag->add('successes', __('kms/transfer.general_extra_success_amount', ['amount' => $bigSuccessCounter]));
        }
        if ($bigErrorCounter > 0) {
            $messageBag->add('errors', __('kms/transfer.general_extra_error_amount', ['amount' => $bigErrorCounter]));
        }

        return $messageBag;
    }
}