Passed
Push — master ( 3eb674...1876ce )
by Marcel
03:15 queued 12s
created

Excel::convertExcelDate()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 32
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 24
nc 4
nop 3
dl 0
loc 32
rs 9.536
c 1
b 0
f 0
1
<?php
2
/**
3
 * Analytics
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the LICENSE.md file.
7
 *
8
 * @author Marcel Scherello <[email protected]>
9
 * @copyright 2019-2022 Marcel Scherello
10
 */
11
12
namespace OCA\Analytics\Datasource;
13
14
use OCP\Files\IRootFolder;
15
use OCP\Files\NotFoundException;
16
use OCP\IL10N;
17
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
0 ignored issues
show
Bug introduced by
The type PhpOffice\PhpSpreadsheet\Cell\Coordinate 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...
18
use PhpOffice\PhpSpreadsheet\IOFactory;
0 ignored issues
show
Bug introduced by
The type PhpOffice\PhpSpreadsheet\IOFactory 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
use PhpOffice\PhpSpreadsheet\Reader\Exception;
0 ignored issues
show
Bug introduced by
The type PhpOffice\PhpSpreadsheet\Reader\Exception 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...
20
use PhpOffice\PhpSpreadsheet\Shared\Date;
0 ignored issues
show
Bug introduced by
The type PhpOffice\PhpSpreadsheet\Shared\Date 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...
21
use Psr\Log\LoggerInterface;
22
23
class Excel implements IDatasource
24
{
25
    private $logger;
26
    private $rootFolder;
27
    private $l10n;
28
29
    public function __construct(
30
        IL10N $l10n,
31
        LoggerInterface $logger,
32
        IRootFolder $rootFolder
33
    )
34
    {
35
        $this->l10n = $l10n;
36
        $this->logger = $logger;
37
        $this->rootFolder = $rootFolder;
38
    }
39
40
    /**
41
     * @return string Display Name of the datasource
42
     */
43
    public function getName(): string
44
    {
45
        return $this->l10n->t('Local file') . ': Spreadsheet';
46
    }
47
48
    /**
49
     * @return int digit unique datasource id
50
     */
51
    public function getId(): int
52
    {
53
        return 7;
54
    }
55
56
    /**
57
     * @return array available options of the data source
58
     */
59
    public function getTemplate(): array
60
    {
61
        $template = array();
62
        $template[] = ['id' => 'link', 'name' => $this->l10n->t('File'), 'placeholder' => $this->l10n->t('File')];
63
        $template[] = ['id' => 'sheet', 'name' => $this->l10n->t('Sheet'), 'placeholder' => $this->l10n->t('sheet name')];
64
        $template[] = ['id' => 'range', 'name' => $this->l10n->t('Cell range'), 'placeholder' => $this->l10n->t('e.g. A1:C3,A5:C5')];
65
        return $template;
66
    }
67
68
    /**
69
     * Read the Data
70
     * @param $option
71
     * @return array
72
     * @throws NotFoundException
73
     * @throws \OCP\Files\NotPermittedException
74
     * @throws Exception
75
     * @throws \PhpOffice\PhpSpreadsheet\Exception
76
     */
77
    public function readData($option): array
78
    {
79
        include_once __DIR__ . '/../../vendor/autoload.php';
80
        $header = $dataClean = $data = array();
81
        $headerrow = $error = 0;
82
83
        $file = $this->rootFolder->getUserFolder($option['user_id'])->get($option['link']);
84
        $fileName = $file->getStorage()->getLocalFile($file->getInternalPath());
85
86
        $inputFileType = IOFactory::identify($fileName);
87
        $reader = IOFactory::createReader($inputFileType);
88
        //$reader->setReadDataOnly(true); disabled as idDate is not working otherwise
89
        if (strlen($option['sheet']) > 0) {
90
            $reader->setLoadSheetsOnly([$option['sheet']]);
91
        }
92
        $spreadsheet = $reader->load($fileName);
93
94
        // separated columns can be selected via ranges e.g. "A1:B9,C1:C9"
95
        // these ranges are read and linked
96
        $ranges = str_getcsv($option['range']);
97
        foreach ($ranges as $range) {
98
            $values = $spreadsheet->getActiveSheet()->rangeToArray(
99
                $range,                // The worksheet range that we want to retrieve
100
                NULL,         // Value that should be returned for empty cells
101
                TRUE,   // Should formulas be calculated (the equivalent of getCalculatedValue() for each cell)
102
                TRUE,        // Should values be formatted (the equivalent of getFormattedValue() for each cell)
103
                FALSE       // Should the array be indexed by cell row and cell column
104
            );
105
106
            $values = $this->convertExcelDate($spreadsheet, $values, $range);
107
108
            if (empty($data)) {
109
                // first range will fill the array with all rows
110
                $data = $values;
111
            } else {
112
                // further columns will be attached to the first ones
113
                foreach ($data as $key => $value) {
114
                    $data[$key] = array_merge($value, $values[$key]);
115
                }
116
117
            }
118
        }
119
120
        foreach ($data as $key => $value) {
121
            if ($headerrow === 0) {
122
                $header = array_values($value);
123
                $headerrow = 1;
124
            } else if (!$this->containsOnlyNull($value)) {
125
                array_push($dataClean, array_values($value));
126
            }
127
        }
128
129
        return [
130
            'header' => $header,
131
            'dimensions' => array_slice($header, 0, count($header) - 1),
132
            'data' => $dataClean,
133
            'error' => $error,
134
        ];
135
    }
136
137
    /**
138
     * @param $array
139
     * @return bool
140
     */
141
    private function containsOnlyNull($array): bool
142
    {
143
        return !(array_reduce($array, function ($carry, $item) {
144
                return $carry += (is_null($item) ? 0 : 1);
145
            }, 0) > 0);
146
    }
147
148
    /**
149
     * every cell is checked if it is an excel date (stored in number of days since 1990)
150
     * then, the date format from excel is reapplied in php
151
     * @param $spreadsheet
152
     * @param $values
153
     * @param $range
154
     * @return array
155
     * @throws \PhpOffice\PhpSpreadsheet\Exception
156
     */
157
    private function convertExcelDate($spreadsheet, $values, $range): array
158
    {
159
        $map = [
160
            "yyyy" => "Y",
161
            "mm" => "m",
162
            "dd" => "d",
163
            "hh" => "H",
164
            "MM" => "i",
165
            "ss" => "s"
166
        ];
167
168
        $start = str_getcsv($range, ':');
169
        $startCell = Coordinate::coordinateFromString($start[0]);
170
        $startColumn = (int)Coordinate::columnIndexFromString($startCell[0]);
171
        $startRow = (int)$startCell[1];
172
173
        foreach ($values as $rowIndex => $row) {
174
            foreach ($row as $columnIndex => $cellValue) {
175
                $columnLetter = Coordinate::stringFromColumnIndex($columnIndex + $startColumn);
176
                $rowNumber = $rowIndex + $startRow;
177
                $coordinate = $columnLetter . $rowNumber;
178
                $cell = $spreadsheet->getActiveSheet()->getCell($coordinate);
179
                if (Date::isDateTime($cell)) {
180
                    $date = Date::excelToDateTimeObject($cell->getValue());
181
                    $excelFormat = $cell->getStyle()->getNumberFormat()->getFormatCode();
182
                    $excelFormat = rtrim($excelFormat, ";@");
183
                    $targetFormat = strtr($excelFormat, $map);
184
                    $values[$rowIndex][$columnIndex] = $date->format($targetFormat);
185
                }
186
            }
187
        }
188
        return $values;
189
    }
190
}