<?php /** * @package RSForm! Pro * @copyright (C) 2007-2019 www.rsjoomla.com * @license GPL, http://www.gnu.org/copyleft/gpl.html */ defined('_JEXEC') or die; use Joomla\String\StringHelper; use Joomla\Archive\Zip; use Joomla\CMS\Factory; if (!class_exists('ZipArchive')) { // Attempt to emulate if ZipArchive does not exist (highly unlikely) class ZipArchive { const CREATE = 1; protected $filename; protected $files = array(); public function open($filename, $flag) { $this->filename = $filename; return is_writable(dirname($filename)); } public function addEmptyDir($dir) {} public function addFile($path, $filename) { $this->addFromString($filename, file_get_contents($path)); } public function addFromString($filename, $contents) { $this->files[] = array( 'name' => $filename, 'data' => $contents ); } public function close() { $zip = new Zip(); $zip->create($this->filename, $this->files); } } } class RSFormProXLSX { protected $cell_formats = array('GENERAL'); // Temporary filename protected $filename; // Submissions counter protected $start; // Max rows counter protected $rows; // Use headers flag public $useHeaders = false; // Form name public $name; // File pointer public $fp; public function open($filename, $mode, $start, $rows = null, $cols = null) { $this->filename = $filename; $this->start = $start; $this->fp = @fopen($filename, $mode); if (!is_resource($this->fp)) { throw new Exception("Could not open $filename for writing."); } if ($mode == 'w') { $xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n". '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'."\n". '<sheetPr filterMode="false">'."\n". '<pageSetUpPr fitToPage="false"/>'."\n". '</sheetPr>'."\n". '<dimension ref="A1:' . $this->getCellName($rows, $cols) . '"/>'."\n". '<sheetViews>'."\n". '<sheetView colorId="64" defaultGridColor="true" rightToLeft="false" showFormulas="false" showGridLines="true" showOutlineSymbols="true" showRowColHeaders="true" showZeros="true" tabSelected="true" topLeftCell="A1" view="normal" windowProtection="false" workbookViewId="0" zoomScale="100" zoomScaleNormal="100" zoomScalePageLayoutView="100">'."\n". '<selection activeCell="A1" activeCellId="0" pane="topLeft" sqref="A1"/>'."\n". '</sheetView>'."\n". '</sheetViews>'."\n". '<cols>'."\n". '<col collapsed="false" hidden="false" max="1025" min="1" style="0" width="11.5"/>'."\n". '</cols>'."\n". '<sheetData>'."\n"; if (!fwrite($this->fp, $xml)) { throw new Exception("Could not write sheet XML head data to $filename."); } } } public function write($data) { $this->rows = $this->start + count($data); foreach ($data as $num => $row) { $this->writeRow($num, $row); } } public function writeHeaders($data) { $xml = '<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="1">'."\n"; $column_number = 0; foreach ($data as $key => $value) { $xml .= $this->writeCell(0, $column_number, $value); $column_number++; } $xml .= '</row>'."\n"; fwrite($this->fp, $xml); } protected function writeRow($num, $row) { if ($this->useHeaders) { // Skip one row since we have headers in that one $num++; } $xml = '<row collapsed="false" customFormat="false" customHeight="false" hidden="false" ht="12.1" outlineLevel="0" r="' . ($this->start + $num + 1) . '">'."\n"; $column_number = 0; foreach ($row as $key => $value) { $xml .= $this->writeCell($this->start + $num, $column_number, $value); $column_number++; } $xml .= '</row>'."\n"; fwrite($this->fp, $xml); } protected function writeCell($row_number, $column_number, $value) { $cell_name = $this->getCellName($row_number, $column_number); return "\t".'<c r="'.$cell_name.'" s="0" t="inlineStr"><is><t>'.$this->escape($value).'</t></is></c>'."\n"; } public function close() { $zip = new ZipArchive(); if (!$zip->open($this->filename.'.zip', ZipArchive::CREATE)) { throw new Exception("Could not create archive {$this->filename}.zip."); } $zip->addEmptyDir("_rels/"); $zip->addEmptyDir("docProps/"); $zip->addEmptyDir("xl/_rels/"); $zip->addEmptyDir("xl/worksheets/"); $zip->addFromString("[Content_Types].xml", $this->buildContentTypesXML()); $zip->addFromString("docProps/app.xml", $this->buildAppXML()); $zip->addFromString("docProps/core.xml", $this->buildCoreXML()); $zip->addFromString("_rels/.rels", $this->buildRelationshipsXML()); $zip->addFromString("xl/_rels/workbook.xml.rels", $this->buildWorkbookRelsXML()); $zip->addFromString("xl/styles.xml", $this->buildStylesXML()); $zip->addFromString("xl/workbook.xml", $this->buildWorkbookXML()); // Finalize sheet $xml = '</sheetData>'."\n". '<printOptions headings="false" gridLines="false" gridLinesSet="true" horizontalCentered="false" verticalCentered="false"/>'."\n". '<pageMargins left="0.5" right="0.5" top="1.0" bottom="1.0" header="0.5" footer="0.5"/>'."\n". '<pageSetup blackAndWhite="false" cellComments="none" copies="1" draft="false" firstPageNumber="1" fitToHeight="1" fitToWidth="1" horizontalDpi="300" orientation="portrait" pageOrder="downThenOver" paperSize="1" scale="100" useFirstPageNumber="true" usePrinterDefaults="false" verticalDpi="300"/>'."\n". '<headerFooter differentFirst="false" differentOddEven="false">'."\n". '<oddHeader>&C&"Times New Roman,Regular"&12&A</oddHeader>'."\n". '<oddFooter>&C&"Times New Roman,Regular"&12Page &P</oddFooter>'."\n". '</headerFooter>'."\n". '</worksheet>'; fwrite($this->fp, $xml); $zip->addFile($this->filename, "xl/worksheets/sheet1.xml"); $zip->close(); } protected function buildAppXML() { return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n". '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"><TotalTime>0</TotalTime></Properties>'; } protected function buildCoreXML() { return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n". '<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'."\n". '<dcterms:created xsi:type="dcterms:W3CDTF">'.Factory::getDate()->format("Y-m-d\TH:i:s.00\Z").'</dcterms:created>'."\n". '<dc:creator>'.$this->escape(Factory::getUser()->name).'</dc:creator>'."\n". '<cp:revision>0</cp:revision>'."\n". '</cp:coreProperties>'; } protected function buildRelationshipsXML() { return '<?xml version="1.0" encoding="UTF-8"?>'."\n". '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'."\n". '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>'."\n". '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>'."\n". '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>'."\n". '</Relationships>'; } protected function buildWorkbookXML() { return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n". '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'."\n". '<fileVersion appName="Calc"/><workbookPr backupFile="false" showObjects="all" date1904="false"/><workbookProtection/>'."\n". '<bookViews><workbookView activeTab="0" firstSheet="0" showHorizontalScroll="true" showSheetTabs="true" showVerticalScroll="true" tabRatio="212" windowHeight="8192" windowWidth="16384" xWindow="0" yWindow="0"/></bookViews>'."\n". '<sheets>'."\n". '<sheet name="'.$this->escapeSheet($this->name).'" sheetId="1" state="visible" r:id="rId2"/>'."\n". '</sheets>'."\n". '<calcPr iterateCount="100" refMode="A1" iterate="false" iterateDelta="0.001"/>'."\n". '</workbook>'; } protected function escapeSheet($string) { // https://www.accountingweb.com/technology/excel/seven-characters-you-cant-use-in-worksheet-names $string = str_replace(array('\\', '/', '*', '[', ']', ':', '?'), '', $string); if (StringHelper::strlen($string) > 31) { $string = StringHelper::substr($string, 0, 31); } return $this->escape($string); } protected function buildWorkbookRelsXML() { return '<?xml version="1.0" encoding="UTF-8"?>'."\n". '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'."\n". '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>'."\n". '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>'."\n". '</Relationships>'; } protected function buildContentTypesXML() { return '<?xml version="1.0" encoding="UTF-8"?>'."\n". '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'."\n". '<Override PartName="/_rels/.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'."\n". '<Override PartName="/xl/_rels/workbook.xml.rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'."\n". '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'."\n". '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'."\n". '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>'."\n". '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>'."\n". '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>'."\n". '</Types>'; } protected function buildStylesXML() { $xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'."\n". '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'."\n". '<numFmts count="'.count($this->cell_formats).'">'."\n"; foreach ($this->cell_formats as $i => $format) { $xml .= '<numFmt numFmtId="'.(164 + $i).'" formatCode="'.$this->escape($format).'" />'."\n"; } $xml .= '</numFmts>'."\n". '<fonts count="4">'."\n". '<font><name val="Arial"/><charset val="1"/><family val="2"/><sz val="10"/></font>'."\n". '<font><name val="Arial"/><family val="0"/><sz val="10"/></font>'."\n". '<font><name val="Arial"/><family val="0"/><sz val="10"/></font>'."\n". '<font><name val="Arial"/><family val="0"/><sz val="10"/></font>'."\n". '</fonts>'."\n". '<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>'."\n". '<borders count="1"><border diagonalDown="false" diagonalUp="false"><left/><right/><top/><bottom/><diagonal/></border></borders>'."\n". '<cellStyleXfs count="20">'."\n". '<xf applyAlignment="true" applyBorder="true" applyFont="true" applyProtection="true" borderId="0" fillId="0" fontId="0" numFmtId="164">'."\n". '<alignment horizontal="general" indent="0" shrinkToFit="false" textRotation="0" vertical="bottom" wrapText="false"/>'."\n". '<protection hidden="false" locked="true"/>'."\n". '</xf>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="2" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="0"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="43"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="41"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="44"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="42"/>'."\n". '<xf applyAlignment="false" applyBorder="false" applyFont="true" applyProtection="false" borderId="0" fillId="0" fontId="1" numFmtId="9"/>'."\n". '</cellStyleXfs>'."\n"; $xml .= '<cellXfs count="'.count($this->cell_formats).'">'."\n"; foreach ($this->cell_formats as $i => $format) { $xml .= '<xf applyAlignment="false" applyBorder="false" applyFont="false" applyProtection="false" borderId="0" fillId="0" fontId="0" numFmtId="'.(164 + $i).'" xfId="0"/>'."\n"; } $xml .= '</cellXfs>'."\n". '<cellStyles count="6">'."\n". '<cellStyle builtinId="0" customBuiltin="false" name="Normal" xfId="0"/>'."\n". '<cellStyle builtinId="3" customBuiltin="false" name="Comma" xfId="15"/>'."\n". '<cellStyle builtinId="6" customBuiltin="false" name="Comma [0]" xfId="16"/>'."\n". '<cellStyle builtinId="4" customBuiltin="false" name="Currency" xfId="17"/>'."\n". '<cellStyle builtinId="7" customBuiltin="false" name="Currency [0]" xfId="18"/>'."\n". '<cellStyle builtinId="5" customBuiltin="false" name="Percent" xfId="19"/>'."\n". '</cellStyles>'."\n". '</styleSheet>'."\n"; return $xml; } public function getCellName($row_number, $column_number) { $n = $column_number; for($r = ""; $n >= 0; $n = intval($n / 26) - 1) { $r = chr($n%26 + 0x41) . $r; } return $r . ($row_number+1); } protected function escape($string) { return htmlspecialchars($string, ENT_QUOTES, 'utf-8'); } }