configuration = $this->job->configuration; $this->getMappableColumns(); // in order to actually map we also need all possible values from the CSV file. $content = $this->job->uploadFileContents(); /** @var Reader $reader */ $reader = Reader::createFromString($content); $reader->setDelimiter($this->configuration['delimiter']); $offset = $this->configuration['has-headers'] ? 1 : 0; $results = $reader->setOffset($offset)->fetch(); $this->validSpecifics = array_keys(config('csv.import_specifics')); $indexes = array_keys($this->data); $rowIndex = 0; foreach ($results as $rowIndex => $row) { $row = $this->runSpecifics($row); //do something here foreach ($indexes as $index) { // this is simply 1, 2, 3, etc. if (!isset($row[$index])) { // don't really know how to handle this. Just skip, for now. continue; } $value = $row[$index]; if (strlen($value) > 0) { // we can do some preprocessing here, // which is exclusively to fix the tags: if (!is_null($this->data[$index]['preProcessMap']) && strlen($this->data[$index]['preProcessMap']) > 0) { /** @var PreProcessorInterface $preProcessor */ $preProcessor = app($this->data[$index]['preProcessMap']); $result = $preProcessor->run($value); $this->data[$index]['values'] = array_merge($this->data[$index]['values'], $result); Log::debug($rowIndex . ':' . $index . 'Value before preprocessor', ['value' => $value]); Log::debug($rowIndex . ':' . $index . 'Value after preprocessor', ['value-new' => $result]); Log::debug($rowIndex . ':' . $index . 'Value after joining', ['value-complete' => $this->data[$index]['values']]); continue; } $this->data[$index]['values'][] = $value; } } } foreach ($this->data as $index => $entry) { $this->data[$index]['values'] = array_unique($this->data[$index]['values']); asort($this->data[$index]['values']); } // save number of rows, thus number of steps, in job: $steps = $rowIndex * 5; $extended = $this->job->extended_status; $extended['steps'] = $steps; $this->job->extended_status = $extended; $this->job->save(); return $this->data; } /** * @param ImportJob $job * * @return ConfigurationInterface */ public function setJob(ImportJob $job): ConfigurationInterface { $this->job = $job; return $this; } /** * Store the result. * * @param array $data * * @return bool */ public function storeConfiguration(array $data): bool { $config = $this->job->configuration; foreach ($data['mapping'] as $index => $data) { $config['column-mapping-config'][$index] = []; foreach ($data as $value => $mapId) { $mapId = intval($mapId); if ($mapId !== 0) { $config['column-mapping-config'][$index][$value] = intval($mapId); } } } // set thing to be completed. $config['column-mapping-complete'] = true; $this->job->configuration = $config; $this->job->save(); return true; } /** * @param string $column * * @return MapperInterface */ private function createMapper(string $column): MapperInterface { $mapperClass = config('csv.import_roles.' . $column . '.mapper'); $mapperName = sprintf('\\FireflyIII\\Import\Mapper\\%s', $mapperClass); /** @var MapperInterface $mapper */ $mapper = new $mapperName; return $mapper; } /** * @return bool */ private function getMappableColumns(): bool { $config = $this->job->configuration; /** * @var int $index * @var bool $mustBeMapped */ foreach ($config['column-do-mapping'] as $index => $mustBeMapped) { $column = $this->validateColumnName($config['column-roles'][$index] ?? '_ignore'); $shouldMap = $this->shouldMapColumn($column, $mustBeMapped); if ($shouldMap) { // create configuration entry for this specific column and add column to $this->data array for later processing. $this->data[$index] = [ 'name' => $column, 'index' => $index, 'options' => $this->createMapper($column)->getMap(), 'preProcessMap' => $this->getPreProcessorName($column), 'values' => [], ]; } } return true; } /** * @param string $column * * @return string */ private function getPreProcessorName(string $column): string { $name = ''; $hasPreProcess = config('csv.import_roles.' . $column . '.pre-process-map'); $preProcessClass = config('csv.import_roles.' . $column . '.pre-process-mapper'); if (!is_null($hasPreProcess) && $hasPreProcess === true && !is_null($preProcessClass)) { $name = sprintf('\\FireflyIII\\Import\\MapperPreProcess\\%s', $preProcessClass); } return $name; } /** * @param array $row * * @return array * @throws FireflyException */ private function runSpecifics(array $row): array { // run specifics here: // and this is the point where the specifix go to work. foreach ($this->configuration['specifics'] as $name => $enabled) { if (!in_array($name, $this->validSpecifics)) { throw new FireflyException(sprintf('"%s" is not a valid class name', $name)); } $class = config('csv.import_specifics.' . $name); /** @var SpecificInterface $specific */ $specific = app($class); // it returns the row, possibly modified: $row = $specific->run($row); } return $row; } /** * @param string $column * @param bool $mustBeMapped * * @return bool */ private function shouldMapColumn(string $column, bool $mustBeMapped): bool { $canBeMapped = config('csv.import_roles.' . $column . '.mappable'); return ($canBeMapped && $mustBeMapped); } /** * @param string $column * * @return string * @throws FireflyException */ private function validateColumnName(string $column): string { // is valid column? $validColumns = array_keys(config('csv.import_roles')); if (!in_array($column, $validColumns)) { throw new FireflyException(sprintf('"%s" is not a valid column.', $column)); } return $column; } }