Passed
Push — master ( 62782a...a28cef )
by Marcel
04:19
created

LocalExcel::readData()   B

Complexity

Conditions 8
Paths 32

Size

Total Lines 57
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 35
nc 32
nop 1
dl 0
loc 57
rs 8.1155
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Analytics
4
 *
5
 * SPDX-FileCopyrightText: 2019-2022 Marcel Scherello
6
 * SPDX-License-Identifier: AGPL-3.0-or-later
7
 */
8
9
namespace OCA\Analytics\Datasource;
10
11
use OCP\Files\IRootFolder;
0 ignored issues
show
Bug introduced by
The type OCP\Files\IRootFolder was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use OCP\Files\NotFoundException;
0 ignored issues
show
Bug introduced by
The type OCP\Files\NotFoundException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use OCP\IL10N;
0 ignored issues
show
Bug introduced by
The type OCP\IL10N was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
15
use PhpOffice\PhpSpreadsheet\IOFactory;
16
use PhpOffice\PhpSpreadsheet\Reader\Exception;
17
use PhpOffice\PhpSpreadsheet\Shared\Date;
18
use Psr\Log\LoggerInterface;
0 ignored issues
show
Bug introduced by
The type Psr\Log\LoggerInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
20
class LocalExcel implements IDatasource {
21
	private $logger;
22
	private $rootFolder;
23
	private $l10n;
24
25
	public function __construct(
26
		IL10N           $l10n,
27
		LoggerInterface $logger,
28
		IRootFolder     $rootFolder
29
	) {
30
		$this->l10n = $l10n;
31
		$this->logger = $logger;
32
		$this->rootFolder = $rootFolder;
33
	}
34
35
	/**
36
	 * @return string Display Name of the datasource
37
	 */
38
	public function getName(): string {
39
		return $this->l10n->t('Local') . ': Spreadsheet';
40
	}
41
42
	/**
43
	 * @return int digit unique datasource id
44
	 */
45
	public function getId(): int {
46
		return 7;
47
	}
48
49
	/**
50
	 * @return array available options of the data source
51
	 */
52
	public function getTemplate(): array {
53
		$template = array();
54
		$template[] = [
55
			'id' => 'link',
56
			'name' => $this->l10n->t('LocalCsv'),
57
			'placeholder' => $this->l10n->t('LocalCsv'),
58
			'type' => 'filePicker'
59
		];
60
		$template[] = [
61
			'id' => 'sheet',
62
			'name' => $this->l10n->t('Sheet'),
63
			'placeholder' => $this->l10n->t('sheet name')
64
		];
65
		$template[] = [
66
			'id' => 'range',
67
			'name' => $this->l10n->t('Cell range'),
68
			'placeholder' => $this->l10n->t('e.g. A1:C3,A5:C5')
69
		];
70
		return $template;
71
	}
72
73
	/**
74
	 * Read the Data
75
	 * @param $option
76
	 * @return array
77
	 * @throws NotFoundException
78
	 * @throws \OCP\Files\NotPermittedException
79
	 * @throws Exception
80
	 * @throws \PhpOffice\PhpSpreadsheet\Exception
81
	 */
82
	public function readData($option): array {
83
		include_once __DIR__ . '/../../vendor/autoload.php';
84
		$header = $dataClean = $data = array();
85
		$headerrow = $error = 0;
86
87
		$file = $this->rootFolder->getUserFolder($option['user_id'])->get($option['link']);
88
		$fileName = $file->getStorage()->getLocalFile($file->getInternalPath());
89
90
		$inputFileType = IOFactory::identify($fileName);
91
		$reader = IOFactory::createReader($inputFileType);
92
		//$reader->setReadDataOnly(true); disabled as idDate is not working otherwise
93
		if (strlen($option['sheet']) > 0) {
94
			$reader->setLoadSheetsOnly([$option['sheet']]);
95
		}
96
		$spreadsheet = $reader->load($fileName);
97
98
		// separated columns can be selected via ranges e.g. "A1:B9,C1:C9"
99
		// these ranges are read and linked
100
		$ranges = str_getcsv($option['range']);
101
		foreach ($ranges as $range) {
102
			$values = $spreadsheet->getActiveSheet()->rangeToArray($range,                // The worksheet range that we want to retrieve
103
				null,         // Value that should be returned for empty cells
104
				true,   // Should formulas be calculated (the equivalent of getCalculatedValue() for each cell)
105
				true,        // Should values be formatted (the equivalent of getFormattedValue() for each cell)
106
				false       // Should the array be indexed by cell row and cell column
107
			);
108
109
			$values = $this->convertExcelDate($spreadsheet, $values, $range);
110
111
			if (empty($data)) {
112
				// first range will fill the array with all rows
113
				$data = $values;
114
			} else {
115
				// further columns will be attached to the first ones
116
				foreach ($data as $key => $value) {
117
					$data[$key] = array_merge($value, $values[$key]);
118
				}
119
			}
120
		}
121
122
		// Remove columns that are completely null
123
		$data = $this->removeNullColumns($data);
124
125
		foreach ($data as $key => $value) {
126
			if ($headerrow === 0) {
127
				$header = array_values($value);
128
				$headerrow = 1;
129
			} else if (!$this->containsOnlyNull($value)) {
130
				array_push($dataClean, array_values($value));
131
			}
132
		}
133
134
		return [
135
			'header' => $header,
136
			'dimensions' => array_slice($header, 0, count($header) - 1),
137
			'data' => $dataClean,
138
			'error' => $error,
139
		];
140
	}
141
142
	private function removeNullColumns(array $data): array {
143
		// Transpose the data to work with columns as rows
144
		$transposedData = array_map(null, ...$data);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type callable expected by parameter $callback of array_map(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

144
		$transposedData = array_map(/** @scrutinizer ignore-type */ null, ...$data);
Loading history...
145
146
		// Filter out columns that contain only null values
147
		$filteredData = array_filter($transposedData, function ($column) {
148
			return !$this->containsOnlyNull($column);
149
		});
150
151
		// Transpose back to original row-column structure
152
		return array_map(null, ...$filteredData);
153
	}
154
155
	/**
156
	 * @param $array
157
	 * @return bool
158
	 */
159
	private function containsOnlyNull($array): bool {
160
		return !(array_reduce($array, function ($carry, $item) {
161
				return $carry += (is_null($item) ? 0 : 1);
162
			}, 0) > 0);
163
	}
164
165
	/**
166
	 * every cell is checked if it is an excel date (stored in number of days since 1990)
167
	 * then, the date format from excel is reapplied in php
168
	 * @param $spreadsheet
169
	 * @param $values
170
	 * @param $range
171
	 * @return array
172
	 * @throws \PhpOffice\PhpSpreadsheet\Exception
173
	 */
174
	private function convertExcelDate($spreadsheet, $values, $range): array {
175
		$map = [
176
			"yyyy" => "Y",
177
			"mm" => "m",
178
			"dd" => "d",
179
			"hh" => "H",
180
			"MM" => "i",
181
			"ss" => "s"
182
		];
183
184
		$start = str_getcsv($range, ':');
185
		$startCell = Coordinate::coordinateFromString($start[0]);
186
		$startColumn = (int)Coordinate::columnIndexFromString($startCell[0]);
187
		$startRow = (int)$startCell[1];
188
189
		foreach ($values as $rowIndex => $row) {
190
			foreach ($row as $columnIndex => $cellValue) {
191
				$columnLetter = Coordinate::stringFromColumnIndex($columnIndex + $startColumn);
192
				$rowNumber = $rowIndex + $startRow;
193
				$coordinate = $columnLetter . $rowNumber;
194
				$cell = $spreadsheet->getActiveSheet()->getCell($coordinate);
195
				if (Date::isDateTime($cell)) {
196
					$date = Date::excelToDateTimeObject($cell->getValue());
197
					$excelFormat = $cell->getStyle()->getNumberFormat()->getFormatCode();
198
					$excelFormat = rtrim($excelFormat, ";@");
199
					$targetFormat = strtr($excelFormat, $map);
200
					$values[$rowIndex][$columnIndex] = $date->format($targetFormat);
201
				}
202
			}
203
		}
204
		return $values;
205
	}
206
}