diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php index 3ad1105cb..6aeee0525 100644 --- a/app/Config/Autoload.php +++ b/app/Config/Autoload.php @@ -201,6 +201,7 @@ class Autoload extends AutoloadConfig 'form', 'cookie', 'tabular', + 'export', 'locale', 'security' ]; diff --git a/app/Config/Mimes.php b/app/Config/Mimes.php index c2db7340c..e90d5f92c 100644 --- a/app/Config/Mimes.php +++ b/app/Config/Mimes.php @@ -279,6 +279,10 @@ class Mimes ], 'txt' => 'text/plain', 'text' => 'text/plain', + 'sql' => [ + 'application/sql', + 'text/plain', + ], 'log' => [ 'text/plain', 'text/x-log', diff --git a/app/Controllers/Items.php b/app/Controllers/Items.php index 33529261b..542d6e024 100644 --- a/app/Controllers/Items.php +++ b/app/Controllers/Items.php @@ -108,7 +108,7 @@ class Items extends Secure_Controller **/ public function getSearch(): ResponseInterface { - $search = $this->request->getGet('search', FILTER_SANITIZE_FULL_SPECIAL_CHARS); + $search = $this->request->getGet('search', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? ''; $limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT); $offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT); $order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS); @@ -162,6 +162,76 @@ class Items extends Secure_Controller return $this->response->setJSON(['total' => $totalRows, 'rows' => $dataRows]); } + /** + * Streams items matching the current search/filters as a downloadable file. + * With no limit/offset given, exports every matching row; otherwise exports just that page. + * @noinspection PhpUnused + **/ + public function getExportAll(string $format): ResponseInterface + { + $search = $this->request->getGet('search', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? ''; + $limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT); + $offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT); + $order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS); + + $this->item_lib->set_item_location($this->request->getGet('stock_location')); + + $definitionNamesWithTypes = $this->attribute->getDefinitionsByFlags(Attribute::SHOW_IN_ITEMS, true); + $definitionIds = array_keys($definitionNamesWithTypes); + + $sort = $this->sanitizeSortColumn(itemSortColumns($definitionIds), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'items.item_id'); + + $filters = [ + 'start_date' => $this->request->getGet('start_date'), + 'end_date' => $this->request->getGet('end_date'), + 'stock_location_id' => $this->item_lib->get_item_location(), + 'empty_upc' => false, + 'low_inventory' => false, + 'is_serialized' => false, + 'no_description' => false, + 'search_custom' => false, + 'is_deleted' => false, + 'temporary' => false, + 'definition_ids' => $definitionIds + ]; + + $requestFilters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true); + $filters = array_merge($filters, $requestFilters); + + if (!empty($filters['search_custom'])) { + $searchableDefinitions = $this->attribute->getDefinitionsByFlags(Attribute::SHOW_IN_ITEMS | Attribute::SHOW_IN_SEARCH); + $filters['definition_ids'] = array_keys($searchableDefinitions); + } + + $dbStart = microtime(true); + $items = $this->item->search($search, $filters, $limit, $offset, $sort, $order); + $itemResults = $items->getResult(); + $dbMs = (int) round((microtime(true) - $dbStart) * 1000); + + $taxPercentsByItemId = $this->buildTaxPercentsByItem($itemResults); + + $buildStart = microtime(true); + $exportRows = []; + foreach ($itemResults as $item) { + $exportRows[] = getItemExportRow($item, $definitionNamesWithTypes, $taxPercentsByItemId); + } + $buildMs = (int) round((microtime(true) - $buildStart) * 1000); + + $writeStart = microtime(true); + $file = generateExportFile($format, itemExportHeaders(), $exportRows, 'items_' . date('Ymd_His'), $this->db->prefixTable('items')); + $writeMs = (int) round((microtime(true) - $writeStart) * 1000); + + log_message('debug', "Export $format: db={$dbMs}ms build={$buildMs}ms write={$writeMs}ms rows=" . count($exportRows)); + + if ($format === 'pdf') { + return $this->response->setContentType('application/pdf') + ->setHeader('Content-Disposition', 'attachment; filename="' . $file['filename'] . '"') + ->setBody($file['body']); + } + + return $this->response->download($file['filename'], $file['body'], true); + } + /** * @return array item_id => formatted tax percents string */ diff --git a/app/Controllers/Reports.php b/app/Controllers/Reports.php index f01249515..1b6742142 100644 --- a/app/Controllers/Reports.php +++ b/app/Controllers/Reports.php @@ -1851,7 +1851,7 @@ class Reports extends Secure_Controller $quantity_purchased .= ' [' . $this->stock_location->get_location_name($drow['item_location']) . ']'; } - $attribute_values = expand_attribute_values($definition_names, $drow); + $attribute_values = expandAttributeValues($definition_names, $drow); $details_data[$row['sale_id']][] = array_merge([ $drow['name'], @@ -1991,7 +1991,7 @@ class Reports extends Secure_Controller $quantity_purchased .= ' [' . $this->stock_location->get_location_name($drow['item_location']) . ']'; } - $attribute_values = expand_attribute_values($definition_names, $drow); + $attribute_values = expandAttributeValues($definition_names, $drow); $details_data[$row['receiving_id']][] = array_merge([ $drow['item_number'], diff --git a/app/Helpers/export_helper.php b/app/Helpers/export_helper.php new file mode 100644 index 000000000..ac9df169c --- /dev/null +++ b/app/Helpers/export_helper.php @@ -0,0 +1,339 @@ + label] pairs, e.g. item_headers() + * @param iterable $rows Array/iterable of objects or arrays keyed by the same column_key values + * @param string $tableName Underlying DB table name, used for the SQL export's INSERT statements + */ +function generateExportFile(string $format, array $headers, iterable $rows, string $baseFilename, string $tableName): array +{ + $writers = [ + 'csv' => fn () => writeExportCsv($headers, $rows, ','), + 'txt' => fn () => writeExportCsv($headers, $rows, "\t"), + 'json' => fn () => writeExportJson($headers, $rows), + 'xml' => fn () => writeExportXml($headers, $rows), + 'sql' => fn () => writeExportSql($headers, $rows, $tableName), + 'xlsx' => fn () => writeExportXlsx($headers, $rows), + 'pdf' => fn () => writeExportPdf($headers, $rows), + ]; + + if (!isset($writers[$format])) { + throw new InvalidArgumentException("Unsupported export format: $format"); + } + + $extensions = [ + 'csv' => 'csv', 'txt' => 'txt', 'json' => 'json', 'xml' => 'xml', + 'sql' => 'sql', 'xlsx' => 'xlsx', 'pdf' => 'pdf', + ]; + + return [ + 'filename' => $baseFilename . '.' . $extensions[$format], + 'body' => $writers[$format](), + ]; +} + +const EXPORT_BLANKABLE_COLUMNS = ['tax_percents']; +const EXPORT_INTEGER_COLUMNS = ['items.item_id']; +const EXPORT_QUANTITY_COLUMNS = ['quantity']; +const EXPORT_CURRENCY_COLUMNS = ['cost_price', 'unit_price']; +const EXPORT_NUMERIC_IF_NUMERIC_COLUMNS = ['item_number']; + +function exportRowValues(array $headers, object|array $row): array +{ + $values = []; + + foreach ($headers as $header) { + $key = array_key_first($header); + $property = str_contains($key, '.') ? substr($key, strrpos($key, '.') + 1) : $key; + $value = is_array($row) ? ($row[$property] ?? '') : ($row->$property ?? ''); + + if (in_array($key, EXPORT_BLANKABLE_COLUMNS, true) && $value === '-') { + $value = ''; + } + + $values[$key] = $value; + } + + return $values; +} + +function writeExportCsv(array $headers, iterable $rows, string $delimiter): string +{ + $stream = fopen('php://temp', 'r+'); + + fputcsv($stream, array_map(static fn ($header) => reset($header), $headers), $delimiter, '"', '\\'); + + foreach ($rows as $row) { + fputcsv($stream, array_values(exportRowValues($headers, $row)), $delimiter, '"', '\\'); + } + + rewind($stream); + $body = stream_get_contents($stream); + fclose($stream); + + return "\xEF\xBB\xBF" . $body; +} + +function writeExportJson(array $headers, iterable $rows): string +{ + $data = []; + + foreach ($rows as $row) { + $data[] = exportRowValues($headers, $row); + } + + return json_encode($data, JSON_PRETTY_PRINT); +} + +function writeExportXml(array $headers, iterable $rows): string +{ + $writer = new XMLWriter(); + $writer->openMemory(); + $writer->setIndent(true); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('rows'); + + foreach ($rows as $row) { + $writer->startElement('row'); + foreach (exportRowValues($headers, $row) as $key => $value) { + $writer->writeElement(sanitizeExportXmlTag($key), (string) $value); + } + $writer->endElement(); + } + + $writer->endElement(); + $writer->endDocument(); + + return $writer->outputMemory(); +} + +function sanitizeExportXmlTag(string $key): string +{ + return preg_replace('/[^a-zA-Z0-9_]/', '_', $key) ?: 'column'; +} + +function writeExportSql(array $headers, iterable $rows, string $tableName): string +{ + $db = db_connect(); + $columns = array_map(static fn ($header) => array_key_first($header), $headers); + $quotedColumns = implode(', ', array_map(static fn ($col) => '`' . $col . '`', $columns)); + + $lines = []; + + foreach ($rows as $row) { + $values = array_values(exportRowValues($headers, $row)); + $quotedValues = implode(', ', array_map(static fn ($value) => $db->escape($value), $values)); + $lines[] = "INSERT INTO `$tableName` ($quotedColumns) VALUES ($quotedValues);"; + } + + return implode("\n", $lines); +} + +/** + * Deletes export cache files left behind by a prior export that crashed or timed + * out before reaching its own cleanup (e.g. memory/timeout kills the request before + * the `finally` in writeExportXlsx() runs). Only sweeps files older than 10 + * minutes - far longer than any real export should take - so a concurrent, + * still-in-progress export (different tab/user) is never touched. + */ +function sweepStaleExportCache(): void +{ + $directory = WRITEPATH . 'cache/exports/'; + + if (!is_dir($directory)) { + return; + } + + $staleBefore = time() - 600; + + foreach (glob($directory . 'xlsx_*') as $file) { + if (is_file($file) && filemtime($file) < $staleBefore) { + unlink($file); + } + } +} + +/** + * Column index (0-based, matching exportRowValues()'s order) => Excel NumberFormat + * code to apply to every cell in that column, or null if the column should stay text. + */ +function exportXlsxColumnFormats(array $headers): array +{ + $quantityFormat = '0' . (quantity_decimals() > 0 ? '.' . str_repeat('0', quantity_decimals()) : ''); + $currencyFormat = '0' . (totals_decimals() > 0 ? '.' . str_repeat('0', totals_decimals()) : ''); + + $formats = []; + + foreach (array_values($headers) as $index => $header) { + $key = array_key_first($header); + + if (in_array($key, EXPORT_INTEGER_COLUMNS, true) || in_array($key, EXPORT_NUMERIC_IF_NUMERIC_COLUMNS, true)) { + $formats[$index] = '0'; + } elseif (in_array($key, EXPORT_QUANTITY_COLUMNS, true)) { + $formats[$index] = $quantityFormat; + } elseif (in_array($key, EXPORT_CURRENCY_COLUMNS, true)) { + $formats[$index] = $currencyFormat; + } elseif (($header['type'] ?? null) === DECIMAL) { + $formats[$index] = '0.00'; + } else { + $formats[$index] = null; + } + } + + return $formats; +} + +function writeExportXlsx(array $headers, iterable $rows): string +{ + sweepStaleExportCache(); + + $tempPath = WRITEPATH . 'cache/exports/xlsx_' . bin2hex(random_bytes(8)) . '.xlsx'; + + $columns = array_map(static fn ($header) => array_key_first($header), $headers); + $columnFormats = exportXlsxColumnFormats($headers); + $numericIfNumericIndexes = array_keys(array_intersect($columns, EXPORT_NUMERIC_IF_NUMERIC_COLUMNS)); + + $columnStyles = []; + foreach ($columnFormats as $index => $format) { + if ($format !== null) { + $columnStyles[$index] = new OpenSpout\Common\Entity\Style\Style(format: $format); + } + } + + $writer = new OpenSpout\Writer\XLSX\Writer(); + + try { + $writer->openToFile($tempPath); + + $writer->addRow(OpenSpout\Common\Entity\Row::fromValues(array_map(static fn ($header) => (string) reset($header), $headers))); + + foreach ($rows as $row) { + $values = array_values(exportRowValues($headers, $row)); + + $cellValues = []; + foreach ($values as $index => $value) { + $format = $columnFormats[$index] ?? null; + + if ($format !== null && in_array($index, $numericIfNumericIndexes, true) && !is_numeric($value)) { + $format = null; + } + + $cellValues[$index] = ($format !== null && $value !== '' && is_numeric($value)) ? (float) $value : (string) $value; + } + + $writer->addRow(OpenSpout\Common\Entity\Row::fromValuesWithStyles($cellValues, $columnStyles)); + } + + $writer->close(); + + return file_get_contents($tempPath); + } finally { + if (is_file($tempPath)) { + unlink($tempPath); + } + } +} + +const EXPORT_PDF_PAGE_MARGIN = 10.0; +const EXPORT_PDF_FONT_SIZE = 9; +const EXPORT_PDF_ROW_HEIGHT = 6.0; + +const EXPORT_PDF_AVG_CHAR_WIDTH_MM = 0.42 * EXPORT_PDF_FONT_SIZE * 25.4 / 72; + +function exportPdfColumnWidths(array $headers, float $usableWidth): array +{ + $labelLengths = array_map(static fn ($header) => max(mb_strlen((string) reset($header)), 6), $headers); + $totalLength = array_sum($labelLengths); + + return array_map(static fn ($length) => $usableWidth * $length / $totalLength, $labelLengths); +} + +function exportPdfTruncateCellText(string $text, float $columnWidth): string +{ + $maxChars = max(1, (int) floor($columnWidth / EXPORT_PDF_AVG_CHAR_WIDTH_MM)); + + if (mb_strlen($text) <= $maxChars) { + return $text; + } + + return mb_substr($text, 0, max(1, $maxChars - 1)) . '…'; +} + +function writeExportPdf(array $headers, iterable $rows): string +{ + $buildStart = microtime(true); + + set_time_limit(300); + define('K_PATH_FONTS', realpath(APPPATH . '../vendor/tecnickcom/tc-lib-pdf-font/target/fonts')); + + $pdf = new Com\Tecnick\Pdf\Tcpdf(unit: 'mm', subsetfont: true); + $font = $pdf->font->insert($pdf->pon, 'dejavusans', '', EXPORT_PDF_FONT_SIZE); + + $headerValues = array_values(array_map(static fn ($header) => (string) reset($header), $headers)); + + $startNewPage = function () use ($pdf, $font): array { + $page = $pdf->addPage(['format' => 'A4', 'orientation' => 'L']); + $pdf->page->addContent($font['out']); + + return $page; + }; + + $page = $startNewPage(); + $pageHeight = $page['height']; + $usableWidth = $page['width'] - (2 * EXPORT_PDF_PAGE_MARGIN); + $columnWidths = array_values(exportPdfColumnWidths($headers, $usableWidth)); + + $drawRow = function (array $values, float $posY) use ($pdf, $columnWidths): void { + $posX = EXPORT_PDF_PAGE_MARGIN; + foreach ($values as $index => $value) { + $text = exportPdfTruncateCellText((string) $value, $columnWidths[$index]); + + $out = $pdf->getTextCell( + txt: $text, + posx: $posX, + posy: $posY, + width: $columnWidths[$index], + height: EXPORT_PDF_ROW_HEIGHT, + halign: 'L', + valign: 'T', + ); + $pdf->page->addContent($out); + $posX += $columnWidths[$index]; + } + }; + + $drawHeaderRow = function (float $posY) use ($pdf, $font, $headerValues, $drawRow): void { + $boldFont = $pdf->font->cloneFont($pdf->pon, $font['idx'], 'B'); + $pdf->page->addContent($boldFont['out']); + $drawRow($headerValues, $posY); + + $regularFont = $pdf->font->cloneFont($pdf->pon, $boldFont['idx'], ''); + $pdf->page->addContent($regularFont['out']); + }; + + $posY = EXPORT_PDF_PAGE_MARGIN; + $drawHeaderRow($posY); + $posY += EXPORT_PDF_ROW_HEIGHT; + + $rowCount = 0; + foreach ($rows as $row) { + if ($posY + EXPORT_PDF_ROW_HEIGHT > $pageHeight - EXPORT_PDF_PAGE_MARGIN) { + $startNewPage(); + $posY = EXPORT_PDF_PAGE_MARGIN; + $drawHeaderRow($posY); + $posY += EXPORT_PDF_ROW_HEIGHT; + } + + $drawRow(array_values(exportRowValues($headers, $row)), $posY); + $posY += EXPORT_PDF_ROW_HEIGHT; + $rowCount++; + } + + $buildMs = (int) round((microtime(true) - $buildStart) * 1000); + + log_message('debug', "Export pdf write: build={$buildMs}ms rows=$rowCount"); + + return $pdf->getOutPDFString(); +} diff --git a/app/Helpers/tabular_helper.php b/app/Helpers/tabular_helper.php index 2670929c0..0f3f14d0e 100644 --- a/app/Helpers/tabular_helper.php +++ b/app/Helpers/tabular_helper.php @@ -452,6 +452,34 @@ function getItemsManageTableHeaders(): string return transform_headers($headers); } +/** + * Column set for exporting items: same data columns the manage table shows, + * minus the icon/action columns (image thumbnail, inventory/stock/edit icons) + * that only make sense in the live UI. + * + * @return array Array of single-key [column_key => label] pairs, see item_headers() + */ +function itemExportHeaders(): array +{ + $attribute = model(Attribute::class); + $config = config(OSPOS::class)->settings; + $definitionsWithTypes = $attribute->getDefinitionsByFlags($attribute::SHOW_IN_ITEMS, true); + + $headers = item_headers(); + + if ($config['use_destination_based_tax']) { + $headers[] = ['tax_percents' => lang('Items.tax_category')]; + } else { + $headers[] = ['tax_percents' => lang('Items.tax_percents')]; + } + + foreach ($definitionsWithTypes as $definitionId => $definitionInfo) { + $headers[] = [$definitionId => $definitionInfo['name'], 'type' => $definitionInfo['type']]; + } + + return $headers; +} + /** * Get the html data row for the item * @@ -528,7 +556,43 @@ function getItemDataRow(object $item, array $definitionNames, array $taxPercents ) ]; - return $columns + expand_attribute_values($definitionNames, (array) $item) + $icons; + return $columns + expandAttributeValues($definitionNames, (array) $item) + $icons; +} + +/** + * Export row for an item: raw (unformatted) values, matching itemExportHeaders()'s + * column set - no locale display formatting, no icons/HTML, since consumers (CSV/XLSX/etc + * writers) each apply their own number formatting as appropriate for the target format. + * + * @param object $item + * @param array $definitionNames Attribute definitions with types, keyed by definition_id (see Attribute::getDefinitionsByFlags(..., true)) + * @param array $taxPercentsByItemId Pre-computed tax percent strings, keyed by item_id (see Items::buildTaxPercentsByItem()) + * @return array + */ +function getItemExportRow(object $item, array $definitionNames, array $taxPercentsByItemId): array +{ + $config = config(OSPOS::class)->settings; + + $taxPercents = $taxPercentsByItemId[$item->item_id] ?? ''; + + $name = $item->name; + if ($config['multi_pack_enabled']) { + $name .= NAME_SEPARATOR . $item->pack_name; + } + + $columns = [ + 'items.item_id' => $item->item_id, + 'item_number' => $item->item_number, + 'name' => $name, + 'category' => $item->category, + 'company_name' => $item->company_name, + 'cost_price' => $item->cost_price, + 'unit_price' => $item->unit_price, + 'quantity' => $item->quantity ?? '', + 'tax_percents' => $taxPercents + ]; + + return $columns + expandAttributeValues($definitionNames, (array) $item, raw: true); } function giftcard_headers(): array @@ -639,39 +703,41 @@ function parse_attribute_values(array $columns, array $row): array } /** - * @param array $definition_names Array of definition_id => ['name' => name, 'type' => type] or definition_id => name + * @param array $definitionNames Array of definition_id => ['name' => name, 'type' => type] or definition_id => name * @param array $row + * @param bool $raw When true, skip locale formatting of DECIMAL attributes and return the raw value instead * @return array */ -function expand_attribute_values(array $definition_names, array $row): array +function expandAttributeValues(array $definitionNames, array $row, bool $raw = false): array { $values = parse_attribute_values(['attribute_values', 'attribute_dtvalues', 'attribute_dvalues'], $row); - $indexed_values = []; - foreach ($values as $attribute_value) { - $exploded_value = explode('_', $attribute_value); - if (sizeof($exploded_value) > 1) { - $indexed_values[$exploded_value[0]] = $exploded_value[1]; + $indexedValues = []; + foreach ($values as $attributeValue) { + $explodedValue = explode('_', $attributeValue); + if (sizeof($explodedValue) > 1) { + $indexedValues[$explodedValue[0]] = $explodedValue[1]; } } - $attribute_values = []; - foreach ($definition_names as $definition_id => $definitionInfo) { - if (isset($indexed_values[$definition_id])) { - $raw_value = $indexed_values[$definition_id]; + $attributeValues = []; + foreach ($definitionNames as $definitionId => $definitionInfo) { + if (isset($indexedValues[$definitionId])) { + $rawValue = $indexedValues[$definitionId]; + $isDecimal = is_array($definitionInfo) && isset($definitionInfo['type']) && $definitionInfo['type'] === DECIMAL; - // Format DECIMAL attributes according to locale - if (is_array($definitionInfo) && isset($definitionInfo['type']) && $definitionInfo['type'] === DECIMAL) { - $attribute_values["$definition_id"] = to_decimals($raw_value); + // Format DECIMAL attributes according to locale, unless the raw value was requested + if ($isDecimal && !$raw) { + $attributeValues["$definitionId"] = to_decimals($rawValue); } else { - $attribute_values["$definition_id"] = $raw_value; + $attributeValues["$definitionId"] = $rawValue; } } else { - $attribute_values["$definition_id"] = ""; + $attributeValues["$definitionId"] = ""; } } - return $attribute_values; + return $attributeValues; } function attribute_definition_headers(): array diff --git a/composer.json b/composer.json index f12384fa4..a2d631f54 100644 --- a/composer.json +++ b/composer.json @@ -31,9 +31,12 @@ "dompdf/dompdf": "^3.1.6", "ezyang/htmlpurifier": "^4.17", "laminas/laminas-escaper": "2.18.0", + "openspout/openspout": "^5.3", "paragonie/random_compat": "^2.0.21", "picqer/php-barcode-generator": "^2.4.0", - "tamtamchik/namecase": "^3.0.0" + "tamtamchik/namecase": "^3.0.0", + "tecnickcom/tc-lib-pdf": "^8.78", + "tecnickcom/tc-lib-pdf-font": "^4.5" }, "require-dev": { "codeigniter/coding-standard": "^1.8", @@ -71,7 +74,16 @@ "sort-packages": true }, "scripts": { - "test": "phpunit" + "test": "phpunit", + "tc-lib-pdf-fonts": [ + "@php vendor/tecnickcom/tc-lib-pdf-font/util/build_fonts.php" + ], + "post-install-cmd": [ + "@tc-lib-pdf-fonts" + ], + "post-update-cmd": [ + "@tc-lib-pdf-fonts" + ] }, "scripts-descriptions": { "test": "Run unit tests" diff --git a/composer.lock b/composer.lock index 661a34712..41ae2f33e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "caa6ba9519694ccf54b8a487640e87fd", + "content-hash": "2326859d839643b303d732699d17078e", "packages": [ { "name": "codeigniter4/framework", @@ -427,6 +427,98 @@ }, "time": "2026-06-23T18:43:15+00:00" }, + { + "name": "openspout/openspout", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/openspout/openspout.git", + "reference": "bc0d2cdefa3fa90c7caceb778192654b20f414f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/openspout/openspout/zipball/bc0d2cdefa3fa90c7caceb778192654b20f414f7", + "reference": "bc0d2cdefa3fa90c7caceb778192654b20f414f7", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-filter": "*", + "ext-libxml": "*", + "ext-xmlreader": "*", + "ext-zip": "*", + "php": "~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "require-dev": { + "ext-zlib": "*", + "friendsofphp/php-cs-fixer": "^3.93.1", + "infection/infection": "^0.32.3", + "phpbench/phpbench": "^1.4.3", + "phpstan/phpstan": "^2.1.38", + "phpstan/phpstan-phpunit": "^2.0.12", + "phpstan/phpstan-strict-rules": "^2.0.8", + "phpunit/phpunit": "^12.5.8" + }, + "suggest": { + "ext-iconv": "To handle non UTF-8 CSV files (if \"php-mbstring\" is not already installed or is too limited)", + "ext-mbstring": "To handle non UTF-8 CSV files (if \"iconv\" is not already installed)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenSpout\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Adrien Loison", + "email": "adrien@box.com" + } + ], + "description": "PHP Library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way", + "homepage": "https://github.com/openspout/openspout", + "keywords": [ + "OOXML", + "csv", + "excel", + "memory", + "odf", + "ods", + "office", + "open", + "php", + "read", + "scale", + "spreadsheet", + "stream", + "write", + "xlsx" + ], + "support": { + "issues": "https://github.com/openspout/openspout/issues", + "source": "https://github.com/openspout/openspout/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://paypal.me/filippotessarotto", + "type": "custom" + }, + { + "url": "https://github.com/Slamdunk", + "type": "github" + } + ], + "time": "2026-02-02T13:08:39+00:00" + }, { "name": "paragonie/random_compat", "version": "v2.0.21", @@ -712,6 +804,1017 @@ }, "time": "2023-01-26T15:07:18+00:00" }, + { + "name": "tecnickcom/tc-lib-barcode", + "version": "2.16.6", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-barcode.git", + "reference": "2ca97e587e28579dbd7335dc6e159f2de181f0e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-barcode/zipball/2ca97e587e28579dbd7335dc6e159f2de181f0e6", + "reference": "2ca97e587e28579dbd7335dc6e159f2de181f0e6", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-gd": "*", + "ext-pcre": "*", + "php": "^8.2", + "tecnickcom/tc-lib-color": "^3.0" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "suggest": { + "ext-bcmath": "Speeds up the arbitrary precision arithmetic used by the IMB and PDF417 barcode types (a pure-PHP implementation is used when missing)", + "ext-imagick": "Alternative PNG renderer used by getPngData() when the extension is loaded" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Barcode\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library to generate 73 types of linear, 2D and postal barcodes as SVG, PNG, HTML or GD images", + "homepage": "https://tcpdf.org", + "keywords": [ + "2D Barcode", + "AZTEC Rune", + "CODABAR", + "CODE 11", + "CODE 128", + "CODE 16K", + "CODE 32", + "CODE 39", + "CODE 49", + "CODE 93", + "DMRE", + "Deutsche Post", + "EAN 13", + "EAN 2", + "EAN 5", + "EAN 8", + "ECC200", + "GB T 21049", + "GS1 DataBar", + "GS1-14", + "HIBC", + "Han Xin Code", + "IATA 2 of 5", + "ISO IEC 15438", + "ISO IEC 16022", + "ISO IEC 18004", + "ISO IEC 20830", + "ISO IEC 21471", + "ISO IEC 24724", + "ISO IEC 24778", + "ITF-14", + "Intelligent Mail Barcode", + "Interleaved 2 of 5", + "Japan Post", + "KIX", + "LOGMARS", + "Linear barcode", + "MSI", + "Mailmark", + "Matrix 2 of 5", + "Micro QR Code", + "Onecode", + "PHARMACODE", + "PHARMACODE TWO-TRACKS", + "POSTNET", + "PZN", + "Plessey", + "RMS4CC", + "SSCC-18", + "Standard 2 of 5", + "Telepen", + "UPC-A", + "UPC-E", + "australia post", + "aztec", + "barcode", + "datamatrix", + "gs1", + "gs1-128", + "identcode", + "leitcode", + "matrix barcode", + "pdf417", + "planet", + "png", + "postal barcode", + "qr-code", + "royal mail", + "stacked barcode", + "svg", + "tc-lib-barcode", + "upc" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-barcode/issues", + "source": "https://github.com/tecnickcom/tc-lib-barcode" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T09:58:22+00:00" + }, + { + "name": "tecnickcom/tc-lib-color", + "version": "3.0.9", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-color.git", + "reference": "3d1a595f26d071a27cf11e869b2bf6abf87c6d45" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-color/zipball/3d1a595f26d071a27cf11e869b2bf6abf87c6d45", + "reference": "3d1a595f26d071a27cf11e869b2bf6abf87c6d45", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "php": "^8.2" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Color\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library to manipulate various color representations", + "homepage": "https://tcpdf.org", + "keywords": [ + "cielab", + "cmyk", + "color", + "colors", + "colour", + "colours", + "css", + "gray", + "grayscale", + "hsl", + "hsla", + "javascript", + "lab", + "pdf", + "rgb", + "rgba", + "separation", + "spot", + "tc-lib-color", + "web" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-color/issues", + "source": "https://github.com/tecnickcom/tc-lib-color" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T09:55:11+00:00" + }, + { + "name": "tecnickcom/tc-lib-file", + "version": "3.9.6", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-file.git", + "reference": "bfe19eb7cf7d344ce0dd71ed472ac4d0efc371e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-file/zipball/bfe19eb7cf7d344ce0dd71ed472ac4d0efc371e6", + "reference": "bfe19eb7cf7d344ce0dd71ed472ac4d0efc371e6", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-pcre": "*", + "php": "^8.2" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "suggest": { + "ext-intl": "Enables Unicode NFC normalization when matching paths against the allowlist" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\File\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library for safe low-level file access: byte-level reads, path allowlisting, file caching and directory lookup", + "homepage": "https://tcpdf.org", + "keywords": [ + "Double", + "bit", + "byte", + "file", + "long", + "read", + "short", + "tc-lib-file" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-file/issues", + "source": "https://github.com/tecnickcom/tc-lib-file" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T09:55:26+00:00" + }, + { + "name": "tecnickcom/tc-lib-pdf", + "version": "8.78.2", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-pdf.git", + "reference": "89b85b224a2b7b0d3e3f203987dddf9daf877d22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-pdf/zipball/89b85b224a2b7b0d3e3f203987dddf9daf877d22", + "reference": "89b85b224a2b7b0d3e3f203987dddf9daf877d22", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-pcre": "*", + "ext-xml": "*", + "ext-zlib": "*", + "php": "^8.2", + "tecnickcom/tc-lib-barcode": "^2.16", + "tecnickcom/tc-lib-color": "^3.0", + "tecnickcom/tc-lib-file": "^3.9", + "tecnickcom/tc-lib-pdf-encrypt": "^2.11", + "tecnickcom/tc-lib-pdf-font": "^4.5", + "tecnickcom/tc-lib-pdf-graph": "^2.18", + "tecnickcom/tc-lib-pdf-image": "^3.14", + "tecnickcom/tc-lib-pdf-page": "^4.16", + "tecnickcom/tc-lib-pdf-parser": "^3.16", + "tecnickcom/tc-lib-pdf-sign": "^2.0", + "tecnickcom/tc-lib-unicode": "^3.1", + "tecnickcom/tc-lib-unicode-data": "^3.0" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "suggest": { + "ext-curl": "Required to contact a Timestamp Authority (RFC 3161) when timestamping signatures", + "ext-intl": "Enables Unicode NFC normalization of the PDF file name" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Pdf\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP PDF Library", + "homepage": "https://tcpdf.org", + "keywords": [ + "PDFD32000-2008", + "TCPDF", + "document", + "pdf", + "tc-lib-pdf" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-pdf/issues", + "source": "https://github.com/tecnickcom/tc-lib-pdf" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T21:40:20+00:00" + }, + { + "name": "tecnickcom/tc-lib-pdf-encrypt", + "version": "2.11.6", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-pdf-encrypt.git", + "reference": "0d11e6dc9548f39eff857d8bf925e297f0430b0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-pdf-encrypt/zipball/0d11e6dc9548f39eff857d8bf925e297f0430b0b", + "reference": "0d11e6dc9548f39eff857d8bf925e297f0430b0b", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-hash": "*", + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^8.2" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Pdf\\Encrypt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library to encrypt and decrypt PDF data", + "homepage": "https://tcpdf.org", + "keywords": [ + "aes", + "encrypt", + "encryption", + "pdf", + "rc4", + "tc-lib-pdf-encrypt" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-pdf-encrypt/issues", + "source": "https://github.com/tecnickcom/tc-lib-pdf-encrypt" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T09:55:42+00:00" + }, + { + "name": "tecnickcom/tc-lib-pdf-filter", + "version": "2.11.6", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-pdf-filter.git", + "reference": "652e4efb438ba7b818efd96197c6291501ceafd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-pdf-filter/zipball/652e4efb438ba7b818efd96197c6291501ceafd2", + "reference": "652e4efb438ba7b818efd96197c6291501ceafd2", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "ext-zlib": "*", + "php": "^8.2" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "suggest": { + "ext-imagick": "Required for the JPXDecode (JPEG 2000) and CCITTFaxDecode filters", + "jbig2dec": "CLI tool required for the JBIG2Decode filter" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Pdf\\Filter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library to decode PDF compression and encryption filters", + "homepage": "https://tcpdf.org", + "keywords": [ + "compression", + "encryption", + "filter", + "pdf", + "tc-lib-pdf-filter" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-pdf-filter/issues", + "source": "https://github.com/tecnickcom/tc-lib-pdf-filter" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T09:55:58+00:00" + }, + { + "name": "tecnickcom/tc-lib-pdf-font", + "version": "4.5.1", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-pdf-font.git", + "reference": "cd9da3e82da10a32c29ff65e2d44a6416fa46f47" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-pdf-font/zipball/cd9da3e82da10a32c29ff65e2d44a6416fa46f47", + "reference": "cd9da3e82da10a32c29ff65e2d44a6416fa46f47", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-zlib": "*", + "php": "^8.2", + "tecnickcom/tc-lib-file": "^3.9", + "tecnickcom/tc-lib-pdf-encrypt": "^2.11", + "tecnickcom/tc-lib-unicode-data": "^3.0" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "suggest": { + "ext-iconv": "Decodes legacy Macintosh (MacRoman) TrueType name strings", + "ext-mbstring": "Decodes UTF-16BE and Windows-1252 TrueType name strings" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Pdf\\Font\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library containing PDF font methods and utilities", + "homepage": "https://tcpdf.org", + "keywords": [ + "PFB", + "afm", + "font", + "import", + "pdf", + "tc-lib-pdf-font", + "ttf" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-pdf-font/issues", + "source": "https://github.com/tecnickcom/tc-lib-pdf-font" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T21:35:29+00:00" + }, + { + "name": "tecnickcom/tc-lib-pdf-graph", + "version": "2.18.4", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-pdf-graph.git", + "reference": "33c9f1164094f013b8ef0a76e871d88d46a0a367" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-pdf-graph/zipball/33c9f1164094f013b8ef0a76e871d88d46a0a367", + "reference": "33c9f1164094f013b8ef0a76e871d88d46a0a367", + "shasum": "" + }, + "require": { + "ext-zlib": "*", + "php": "^8.2", + "tecnickcom/tc-lib-color": "^3.0", + "tecnickcom/tc-lib-pdf-encrypt": "^2.11" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Pdf\\Graph\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library containing PDF graphic and geometric methods", + "homepage": "https://tcpdf.org", + "keywords": [ + "geometry", + "graphic", + "pdf", + "tc-lib-pdf-graph", + "transformation" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-pdf-graph/issues", + "source": "https://github.com/tecnickcom/tc-lib-pdf-graph" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T10:01:01+00:00" + }, + { + "name": "tecnickcom/tc-lib-pdf-image", + "version": "3.14.6", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-pdf-image.git", + "reference": "57767049979617808b3bb85ab49bdeb6c87841da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-pdf-image/zipball/57767049979617808b3bb85ab49bdeb6c87841da", + "reference": "57767049979617808b3bb85ab49bdeb6c87841da", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-zlib": "*", + "php": "^8.2", + "tecnickcom/tc-lib-file": "^3.9", + "tecnickcom/tc-lib-pdf-encrypt": "^2.11" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Pdf\\Image\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library containing PDF Image methods", + "homepage": "https://tcpdf.org", + "keywords": [ + "image", + "jpeg", + "jpg", + "pdf", + "png", + "tc-lib-pdf-image" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-pdf-image/issues", + "source": "https://github.com/tecnickcom/tc-lib-pdf-image" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T10:01:17+00:00" + }, + { + "name": "tecnickcom/tc-lib-pdf-page", + "version": "4.16.7", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-pdf-page.git", + "reference": "437f27649a7bac898da0408140262381d61d016a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-pdf-page/zipball/437f27649a7bac898da0408140262381d61d016a", + "reference": "437f27649a7bac898da0408140262381d61d016a", + "shasum": "" + }, + "require": { + "ext-zlib": "*", + "php": "^8.2", + "tecnickcom/tc-lib-color": "^3.0", + "tecnickcom/tc-lib-pdf-encrypt": "^2.11" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Pdf\\Page\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library containing PDF page formats and definitions", + "homepage": "https://tcpdf.org", + "keywords": [ + "format", + "page", + "pdf", + "tc-lib-pdf-page" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-pdf-page/issues", + "source": "https://github.com/tecnickcom/tc-lib-pdf-page" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T10:00:45+00:00" + }, + { + "name": "tecnickcom/tc-lib-pdf-parser", + "version": "3.16.4", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-pdf-parser.git", + "reference": "99f8279ca5c8fa2e0729d82f1e7beb2a1c82be9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-pdf-parser/zipball/99f8279ca5c8fa2e0729d82f1e7beb2a1c82be9a", + "reference": "99f8279ca5c8fa2e0729d82f1e7beb2a1c82be9a", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "php": "^8.2", + "tecnickcom/tc-lib-pdf-filter": "^2.11" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Pdf\\Parser\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library to parse PDF documents", + "homepage": "https://tcpdf.org", + "keywords": [ + "document", + "parser", + "pdf", + "tc-lib-pdf-parser" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-pdf-parser/issues", + "source": "https://github.com/tecnickcom/tc-lib-pdf-parser" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T10:00:30+00:00" + }, + { + "name": "tecnickcom/tc-lib-pdf-sign", + "version": "2.0.7", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-pdf-sign.git", + "reference": "81b9abbf99ba4fefd28a5f468aa6796e77185fd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-pdf-sign/zipball/81b9abbf99ba4fefd28a5f468aa6796e77185fd4", + "reference": "81b9abbf99ba4fefd28a5f468aa6796e77185fd4", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-json": "*", + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^8.2" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Pdf\\Sign\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library to create and embed digital signatures (PKCS#7 / CAdES / PAdES) in PDF documents", + "homepage": "https://tcpdf.org", + "keywords": [ + "cades", + "digital-signature", + "pades", + "pdf", + "pkcs7", + "sign", + "signature", + "tc-lib-pdf-sign", + "timestamp" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-pdf-sign/issues", + "source": "https://github.com/tecnickcom/tc-lib-pdf-sign" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T09:56:16+00:00" + }, + { + "name": "tecnickcom/tc-lib-unicode", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-unicode.git", + "reference": "92d0fb83ec1a577474fc4491fbf87c21d60e59e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-unicode/zipball/92d0fb83ec1a577474fc4491fbf87c21d60e59e0", + "reference": "92d0fb83ec1a577474fc4491fbf87c21d60e59e0", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-mbstring": "*", + "ext-pcre": "*", + "php": "^8.2", + "tecnickcom/tc-lib-unicode-data": "^3.0" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Unicode\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library to process UTF-8 and Unicode text", + "homepage": "https://tcpdf.org", + "keywords": [ + "font", + "pdf", + "tc-lib-unicode", + "unicode", + "utf-8" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-unicode/issues", + "source": "https://github.com/tecnickcom/tc-lib-unicode" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T12:26:19+00:00" + }, + { + "name": "tecnickcom/tc-lib-unicode-data", + "version": "3.0.11", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/tc-lib-unicode-data.git", + "reference": "9d894cdcfe82b4143155ec63b110a78607aef763" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/tc-lib-unicode-data/zipball/9d894cdcfe82b4143155ec63b110a78607aef763", + "reference": "9d894cdcfe82b4143155ec63b110a78607aef763", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "require-dev": { + "carthage-software/mago": "1.51.2", + "pdepend/pdepend": "^2.16", + "phpunit/phpunit": "^11.5 || ^12.5 || ^13.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Com\\Tecnick\\Unicode\\Data\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "role": "lead" + } + ], + "description": "PHP library containing Unicode definitions", + "homepage": "https://tcpdf.org", + "keywords": [ + "font", + "pdf", + "tc-lib-unicode-data", + "unicode", + "utf-8" + ], + "support": { + "issues": "https://github.com/tecnickcom/tc-lib-unicode-data/issues", + "source": "https://github.com/tecnickcom/tc-lib-unicode-data" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tecnickcom", + "type": "github" + } + ], + "time": "2026-10-03T09:56:31+00:00" + }, { "name": "thecodingmachine/safe", "version": "v3.4.0", diff --git a/package.json b/package.json index 9ae950bf1..0f3848931 100644 --- a/package.json +++ b/package.json @@ -90,5 +90,8 @@ }, "publishConfig": { "registry": "https://npm.pkg.github.com/" + }, + "allowScripts": { + "core-js@3.37.0": true } } diff --git a/public/js/manage_tables.js b/public/js/manage_tables.js index 28a6d6496..5ae8daa5b 100644 --- a/public/js/manage_tables.js +++ b/public/js/manage_tables.js @@ -226,9 +226,11 @@ search: options.resource || false, showColumns: true, clickToSelect: true, + // showExport/exportTypes only render the button+dropdown; init_export_all_intercept() + // always wins the click and routes every format to the server, bypassing this plugin's own export. showExport: true, exportDataType: 'basic', - exportTypes: ['json', 'xml', 'csv', 'txt', 'sql', 'excel', 'pdf'], + exportTypes: ['json', 'xml', 'csv', 'txt', 'sql', 'xlsx', 'pdf'], exportOptions: { fileName: options.resource.replace(/.*\/(.*?)$/g, '$1') + "_" + export_suffix }, @@ -266,9 +268,30 @@ init_restore(); toggle_column_visibility(); init_export_all_toggle(); + init_export_all_intercept(); dialog_support.init("button.modal-dlg"); }; + let export_all_enabled = false; + + const init_export_all_intercept = function () { + document.addEventListener('click', function(event) { + const $exportItem = $(event.target).closest('.export [data-type], .export[data-type]'); + if (!$exportItem.length) { + return; + } + + const format = $exportItem.attr('data-type') || $exportItem.closest('.export').data('type'); + + event.preventDefault(); + event.stopImmediatePropagation(); + $exportItem.closest('.dropdown-menu').prev('[data-toggle="dropdown"]').dropdown('toggle'); + + const queryString = export_all_enabled ? export_all_query_string() : export_page_query_string(); + window.location = options.resource + '/exportAll/' + format + '?' + queryString; + }, true); + }; + const init_export_all_toggle = function () { const $toggleInput = $('', { type: 'checkbox', @@ -301,10 +324,30 @@ $toggleInput.on('change', function() { $toggleWrapper.attr('aria-checked', this.checked); - $('#table').bootstrapTable('refreshOptions', { exportDataType: this.checked ? 'all' : 'basic' }); + export_all_enabled = this.checked; }); }; + const export_all_query_string = function() { + const queryParamsFn = $('#table').bootstrapTable('getOptions').queryParams; + const params = queryParamsFn ? queryParamsFn({}) : {}; + delete params.limit; + delete params.offset; + + return $.param(params); + }; + + const export_page_query_string = function() { + const tableOptions = $('#table').bootstrapTable('getOptions'); + const queryParamsFn = tableOptions.queryParams; + const params = queryParamsFn ? queryParamsFn({ + limit: tableOptions.pageSize, + offset: (tableOptions.pageNumber - 1) * tableOptions.pageSize + }) : {}; + + return $.param(params); + }; + const init_delete = function (confirmMessage) { $("#delete").click(function(event) { do_action("delete")(); diff --git a/tests/Models/ItemSearchTest.php b/tests/Models/ItemSearchTest.php index fdf20bd10..24f60826b 100644 --- a/tests/Models/ItemSearchTest.php +++ b/tests/Models/ItemSearchTest.php @@ -18,7 +18,7 @@ class ItemSearchTest extends CIUnitTestCase protected $migrateOnce = true; protected $seed = ''; protected $seedOnce = true; - protected $refresh = true; + protected $refresh = false; protected $namespace = null; protected $item; diff --git a/tests/Models/ItemTaxesMultipleTest.php b/tests/Models/ItemTaxesMultipleTest.php index e820d005f..1c0372b67 100644 --- a/tests/Models/ItemTaxesMultipleTest.php +++ b/tests/Models/ItemTaxesMultipleTest.php @@ -17,7 +17,7 @@ class ItemTaxesMultipleTest extends CIUnitTestCase protected $migrateOnce = true; protected $seed = ''; protected $seedOnce = true; - protected $refresh = true; + protected $refresh = false; protected $namespace = null; protected $item_taxes; diff --git a/tests/Models/ReceivingTest.php b/tests/Models/ReceivingTest.php index 318727b09..34ff550d4 100644 --- a/tests/Models/ReceivingTest.php +++ b/tests/Models/ReceivingTest.php @@ -22,7 +22,7 @@ class ReceivingTest extends CIUnitTestCase protected $migrate = true; protected $migrateOnce = true; - protected $refresh = true; + protected $refresh = false; protected $namespace = null; private const LOCATION_ID = 1;