You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

228 lines
8.5 KiB

  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * MapController.php
  5. * Copyright (c) 2020 james@firefly-iii.org
  6. *
  7. * This file is part of the Firefly III CSV importer
  8. * (https://github.com/firefly-iii/csv-importer).
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License as
  12. * published by the Free Software Foundation, either version 3 of the
  13. * License, or (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License
  21. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  22. */
  23. namespace App\Http\Controllers\Import;
  24. use App\Http\Controllers\Controller;
  25. use App\Http\Middleware\MappingComplete;
  26. use App\Services\CSV\Configuration\Configuration;
  27. use App\Services\CSV\Mapper\MapperInterface;
  28. use App\Services\CSV\Mapper\MapperService;
  29. use App\Services\Session\Constants;
  30. use App\Services\Storage\StorageService;
  31. use Illuminate\Contracts\Filesystem\FileNotFoundException;
  32. use Illuminate\Contracts\View\Factory;
  33. use Illuminate\Http\RedirectResponse;
  34. use Illuminate\Http\Request;
  35. use Illuminate\View\View;
  36. use InvalidArgumentException;
  37. use League\Csv\Exception;
  38. use Log;
  39. /**
  40. * Class MapController
  41. */
  42. class MapController extends Controller
  43. {
  44. /**
  45. * RoleController constructor.
  46. */
  47. public function __construct()
  48. {
  49. parent::__construct();
  50. app('view')->share('pageTitle', 'Map data');
  51. $this->middleware(MappingComplete::class);
  52. }
  53. /**
  54. * @return Factory|View
  55. * @throws Exception
  56. * @throws FileNotFoundException
  57. */
  58. public function index()
  59. {
  60. $mainTitle = 'Map data';
  61. $subTitle = 'Map values in CSV file to actual data in Firefly III';
  62. Log::debug('Now in mapController index');
  63. // get configuration object.
  64. $configuration = Configuration::fromArray(session()->get(Constants::CONFIGURATION));
  65. // the config in the session will miss important values, we must get those from disk:
  66. // 'mapping', 'do_mapping', 'roles' are missing.
  67. $diskArray = json_decode(StorageService::getContent(session()->get(Constants::UPLOAD_CONFIG_FILE)), true, JSON_THROW_ON_ERROR);
  68. $diskConfig = Configuration::fromArray($diskArray);
  69. $configuration->setMapping($diskConfig->getMapping());
  70. $configuration->setDoMapping($diskConfig->getDoMapping());
  71. $configuration->setRoles($diskConfig->getRoles());
  72. // then we can use them:
  73. $roles = $configuration->getRoles();
  74. $existingMapping = $configuration->getMapping();
  75. $doMapping = $configuration->getDoMapping();
  76. $data = [];
  77. foreach ($roles as $index => $role) {
  78. $info = config('csv_importer.import_roles')[$role] ?? null;
  79. $mappable = $info['mappable'] ?? false;
  80. if (null === $info) {
  81. continue;
  82. }
  83. if (false === $mappable) {
  84. continue;
  85. }
  86. $mapColumn = $doMapping[$index] ?? false;
  87. if (false === $mapColumn) {
  88. continue;
  89. }
  90. Log::debug(sprintf('Mappable role is "%s"', $role));
  91. $info['role'] = $role;
  92. $info['values'] = [];
  93. // create the "mapper" class which will get data from Firefly III.
  94. $class = sprintf('App\\Services\\CSV\\Mapper\\%s', $info['mapper']);
  95. if (!class_exists($class)) {
  96. throw new InvalidArgumentException(sprintf('Class %s does not exist.', $class));
  97. }
  98. Log::debug(sprintf('Associated class is %s', $class));
  99. /** @var MapperInterface $object */
  100. $object = app($class);
  101. $info['mapping_data'] = $object->getMap();
  102. $info['mapped'] = $existingMapping[$index] ?? [];
  103. Log::debug(sprintf('Mapping data length is %d', count($info['mapping_data'])));
  104. $data[$index] = $info;
  105. }
  106. // get columns from file
  107. $content = StorageService::getContent(session()->get(Constants::UPLOAD_CSV_FILE));
  108. $delimiter = (string) config(sprintf('csv_importer.delimiters.%s', $configuration->getDelimiter()));
  109. $data = MapperService::getMapData($content, $delimiter, $configuration->isHeaders(), $configuration->getSpecifics(), $data);
  110. return view('import.map.index', compact('mainTitle', 'subTitle', 'roles', 'data'));
  111. }
  112. /**
  113. * @param Request $request
  114. *
  115. * @return RedirectResponse
  116. */
  117. public function postIndex(Request $request): RedirectResponse
  118. {
  119. $values = $request->get('values') ?? [];
  120. $mapping = $request->get('mapping') ?? [];
  121. $values = !is_array($values) ? [] : $values;
  122. $mapping = !is_array($mapping) ? [] : $mapping;
  123. $data = [];
  124. $configuration = Configuration::fromArray(session()->get(Constants::CONFIGURATION));
  125. /**
  126. * Loop array with available columns.
  127. *
  128. * @var int $index
  129. * @var array $row
  130. */
  131. foreach ($values as $columnIndex => $column) {
  132. /**
  133. * Loop all values for this column
  134. *
  135. * @var int $valueIndex
  136. * @var string $value
  137. */
  138. foreach ($column as $valueIndex => $value) {
  139. $mappedValue = $mapping[$columnIndex][$valueIndex] ?? null;
  140. if (null !== $mappedValue && 0 !== $mappedValue && '0' !== $mappedValue) {
  141. $data[$columnIndex][$value] = (int) $mappedValue;
  142. }
  143. }
  144. }
  145. // at this point the $data array must be merged with the mapping as it is on the disk,
  146. // and then saved to disk once again in a new config file.
  147. $diskArray = json_decode(StorageService::getContent(session()->get(Constants::UPLOAD_CONFIG_FILE)), true, JSON_THROW_ON_ERROR);
  148. $diskConfig = Configuration::fromArray($diskArray);
  149. $originalMapping = $diskConfig->getMapping();
  150. // loop $data and save values:
  151. $mergedMapping = $this->mergeMapping($originalMapping, $data);
  152. $configuration->setMapping($mergedMapping);
  153. // store mapping in config object ( + session)
  154. session()->put(Constants::CONFIGURATION, $configuration->toSessionArray());
  155. // since the configuration saved in the session will omit 'mapping', 'do_mapping' and 'roles'
  156. // these must be set to the configuration file
  157. // no need to do this sooner because toSessionArray would have dropped them anyway.
  158. $configuration->setRoles($diskConfig->getRoles());
  159. $configuration->setDoMapping($diskConfig->getDoMapping());
  160. // then save entire thing to a new disk file:
  161. $configFileName = StorageService::storeArray($configuration->toArray());
  162. Log::debug(sprintf('Old configuration was stored under key "%s".', session()->get(Constants::UPLOAD_CONFIG_FILE)));
  163. session()->put(Constants::UPLOAD_CONFIG_FILE, $configFileName);
  164. Log::debug(sprintf('New configuration is stored under key "%s".', session()->get(Constants::UPLOAD_CONFIG_FILE)));
  165. // set map config as complete.
  166. session()->put(Constants::MAPPING_COMPLETE_INDICATOR, true);
  167. return redirect()->route('import.run.index');
  168. }
  169. /**
  170. * @param array $original
  171. * @param array $new
  172. * @return array
  173. */
  174. private function mergeMapping(array $original, array $new): array
  175. {
  176. Log::debug('Now merging disk mapping with new mapping');
  177. foreach ($new as $column => $mappedValues) {
  178. Log::debug(sprintf('Now working on column "%s"', $column));
  179. if (array_key_exists($column, $original)) {
  180. foreach ($mappedValues as $name => $value) {
  181. Log::debug(sprintf('Updated mapping of "%s" to ID "%s"', $name, $value));
  182. $original[$column][$name] = $value;
  183. }
  184. }
  185. if (!array_key_exists($column, $original)) {
  186. Log::debug('The original mapping has no map data for this column. We will set it now.');
  187. $original[$column] = $mappedValues;
  188. }
  189. }
  190. // original has been updated:
  191. return $original;
  192. }
  193. }