. */ namespace App\Http\Controllers\Import; use App\Http\Controllers\Controller; use App\Http\Middleware\MappingComplete; use App\Services\CSV\Configuration\Configuration; use App\Services\CSV\Mapper\MapperInterface; use App\Services\CSV\Mapper\MapperService; use App\Services\Session\Constants; use App\Services\Storage\StorageService; use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\View\View; use InvalidArgumentException; use League\Csv\Exception; use Log; /** * Class MapController */ class MapController extends Controller { /** * RoleController constructor. */ public function __construct() { parent::__construct(); app('view')->share('pageTitle', 'Map data'); $this->middleware(MappingComplete::class); } /** * @return Factory|View * @throws Exception * @throws FileNotFoundException */ public function index() { $mainTitle = 'Map data'; $subTitle = 'Map values in CSV file to actual data in Firefly III'; Log::debug('Now in mapController index'); // get configuration object. $configuration = Configuration::fromArray(session()->get(Constants::CONFIGURATION)); // the config in the session will miss important values, we must get those from disk: // 'mapping', 'do_mapping', 'roles' are missing. $diskArray = json_decode(StorageService::getContent(session()->get(Constants::UPLOAD_CONFIG_FILE)), true, JSON_THROW_ON_ERROR); $diskConfig = Configuration::fromArray($diskArray); $configuration->setMapping($diskConfig->getMapping()); $configuration->setDoMapping($diskConfig->getDoMapping()); $configuration->setRoles($diskConfig->getRoles()); // then we can use them: $roles = $configuration->getRoles(); $existingMapping = $configuration->getMapping(); $doMapping = $configuration->getDoMapping(); $data = []; foreach ($roles as $index => $role) { $info = config('csv_importer.import_roles')[$role] ?? null; $mappable = $info['mappable'] ?? false; if (null === $info) { continue; } if (false === $mappable) { continue; } $mapColumn = $doMapping[$index] ?? false; if (false === $mapColumn) { continue; } Log::debug(sprintf('Mappable role is "%s"', $role)); $info['role'] = $role; $info['values'] = []; // create the "mapper" class which will get data from Firefly III. $class = sprintf('App\\Services\\CSV\\Mapper\\%s', $info['mapper']); if (!class_exists($class)) { throw new InvalidArgumentException(sprintf('Class %s does not exist.', $class)); } Log::debug(sprintf('Associated class is %s', $class)); /** @var MapperInterface $object */ $object = app($class); $info['mapping_data'] = $object->getMap(); $info['mapped'] = $existingMapping[$index] ?? []; Log::debug(sprintf('Mapping data length is %d', count($info['mapping_data']))); $data[$index] = $info; } // get columns from file $content = StorageService::getContent(session()->get(Constants::UPLOAD_CSV_FILE)); $delimiter = (string) config(sprintf('csv_importer.delimiters.%s', $configuration->getDelimiter())); $data = MapperService::getMapData($content, $delimiter, $configuration->isHeaders(), $configuration->getSpecifics(), $data); return view('import.map.index', compact('mainTitle', 'subTitle', 'roles', 'data')); } /** * @param Request $request * * @return RedirectResponse */ public function postIndex(Request $request): RedirectResponse { $values = $request->get('values') ?? []; $mapping = $request->get('mapping') ?? []; $values = !is_array($values) ? [] : $values; $mapping = !is_array($mapping) ? [] : $mapping; $data = []; $configuration = Configuration::fromArray(session()->get(Constants::CONFIGURATION)); /** * Loop array with available columns. * * @var int $index * @var array $row */ foreach ($values as $columnIndex => $column) { /** * Loop all values for this column * * @var int $valueIndex * @var string $value */ foreach ($column as $valueIndex => $value) { $mappedValue = $mapping[$columnIndex][$valueIndex] ?? null; if (null !== $mappedValue && 0 !== $mappedValue && '0' !== $mappedValue) { $data[$columnIndex][$value] = (int) $mappedValue; } } } // at this point the $data array must be merged with the mapping as it is on the disk, // and then saved to disk once again in a new config file. $diskArray = json_decode(StorageService::getContent(session()->get(Constants::UPLOAD_CONFIG_FILE)), true, JSON_THROW_ON_ERROR); $diskConfig = Configuration::fromArray($diskArray); $originalMapping = $diskConfig->getMapping(); // loop $data and save values: $mergedMapping = $this->mergeMapping($originalMapping, $data); $configuration->setMapping($mergedMapping); // store mapping in config object ( + session) session()->put(Constants::CONFIGURATION, $configuration->toSessionArray()); // since the configuration saved in the session will omit 'mapping', 'do_mapping' and 'roles' // these must be set to the configuration file // no need to do this sooner because toSessionArray would have dropped them anyway. $configuration->setRoles($diskConfig->getRoles()); $configuration->setDoMapping($diskConfig->getDoMapping()); // then save entire thing to a new disk file: $configFileName = StorageService::storeArray($configuration->toArray()); Log::debug(sprintf('Old configuration was stored under key "%s".', session()->get(Constants::UPLOAD_CONFIG_FILE))); session()->put(Constants::UPLOAD_CONFIG_FILE, $configFileName); Log::debug(sprintf('New configuration is stored under key "%s".', session()->get(Constants::UPLOAD_CONFIG_FILE))); // set map config as complete. session()->put(Constants::MAPPING_COMPLETE_INDICATOR, true); return redirect()->route('import.run.index'); } /** * @param array $original * @param array $new * @return array */ private function mergeMapping(array $original, array $new): array { Log::debug('Now merging disk mapping with new mapping'); foreach ($new as $column => $mappedValues) { Log::debug(sprintf('Now working on column "%s"', $column)); if (array_key_exists($column, $original)) { foreach ($mappedValues as $name => $value) { Log::debug(sprintf('Updated mapping of "%s" to ID "%s"', $name, $value)); $original[$column][$name] = $value; } } if (!array_key_exists($column, $original)) { Log::debug('The original mapping has no map data for this column. We will set it now.'); $original[$column] = $mappedValues; } } // original has been updated: return $original; } }