Download */ namespace qfq; //use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException; require_once(__DIR__ . '/../Constants.php'); require_once(__DIR__ . '/../store/Session.php'); require_once(__DIR__ . '/../store/Store.php'); require_once(__DIR__ . '/../helper/OnArray.php'); require_once(__DIR__ . '/../helper/Logger.php'); require_once(__DIR__ . '/../helper/Sanitize.php'); require_once(__DIR__ . '/../helper/HelperFile.php'); require_once(__DIR__ . '/../report/Html2Pdf.php'); require_once(__DIR__ . '/Thumbnail.php'); //require_once(__DIR__ . '/Sendmail.php'); require_once(__DIR__ . '/../exceptions/DownloadException.php'); //require_once(__DIR__ . '/../Evaluate.php'); //require_once(__DIR__ . '/../helper/KeyValueStringParser.php'); // /** * Class Download * * Documentation: PROTOCOL.md >> Download * * Param: i=1..n * _mode=direct | html2pdf * _id= * _= * * @package qfq */ class Download { /** * @var Store */ private $store = null; /** * @var Session */ private $session = null; /** * @var Database */ private $db = null; /** * @var Html2Pdf */ private $html2pdf = null; /** * @var string Filename where to write download Information */ private $downloadDebugLog = ''; /** * @param bool|false $phpUnit */ public function __construct($phpUnit = false) { $this->session = Session::getInstance($phpUnit); $this->store = Store::getInstance('', $phpUnit); $this->db = new Database(); $this->html2pdf = new Html2Pdf($this->store->getStore(STORE_SYSTEM), $phpUnit); if (Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_DOWNLOAD, $this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM))) { $this->downloadDebugLog = $this->store->getVar(SYSTEM_SQL_LOG, STORE_SYSTEM); } } /** * Concatenate all named files to one PDF file. Return name of new full PDF. * * @param array $files * * @return string - fileName of concatenated file * @throws DownloadException */ private function concatPdfFiles(array $files) { switch (count($files)) { case 0: return ''; case 1: return $files[0]; default: break; } $concatFile = tempnam(sys_get_temp_dir(), TMP_FILE_PREFIX); if (false === $concatFile) { throw new DownloadException('Error creating output file.', ERROR_DOWNLOAD_CREATE_NEW_FILE); } // Check that all files are of type 'application/pdf' foreach ($files AS $filename) { $mimetype = mime_content_type($filename); if ($mimetype != 'application/pdf') { throw new downloadException("Error concat file $filename. Mimetype 'application/pdf' expected, got: $mimetype", ERROR_DOWNLOAD_UNEXPECTED_MIMETYPE); } } $files = OnArray::arrayEscapeshellarg($files); $inputFiles = implode(' ', $files); if (trim($inputFiles) == '') { throw new DownloadException('No files to concatenate.', ERROR_DOWNLOAD_NO_FILES); } $cmd = "pdftk $inputFiles cat output $concatFile"; if ($this->downloadDebugLog != '') { Logger::logMessage("Download: $cmd", $this->downloadDebugLog); } exec($cmd, $output, $rc); if ($rc != 0) { throw new DownloadException ("

Failed: RC=$rc $cmd

" . implode("
", $output)); } return $concatFile; } /** * Get the mimetype of $filename and store them in $rcMimetype. * Checks if the extension of $outputFilename fit's to the mimetype. If not, append the mimetype extension. * * @param string $filename * @param string $outputFilename * @param string $rcMimetype * * @return string possible updated $outputFilename, according the mimetype. */ private function targetFilenameExtension($filename, $outputFilename, &$rcMimetype) { $rcMimetype = mime_content_type($filename); return $outputFilename; } /** * Set header type and output $filename. Be careful not to send any additional characters. * * @param $file * @param $outputFilename */ private function outputFile($file, $outputFilename) { $length = filesize($file); $outputFilename = $this->targetFilenameExtension($file, $outputFilename, $mimetype); $outputFilename = Sanitize::safeFilename($outputFilename); // be sure that there are no problematic chars in the filename. E.g. MacOS X don't like spaces for downloads. header("Content-type: $mimetype"); header("Content-Length: $length"); // If defined as 'attachment': PDFs are not shown inside the browser (if user configured that). Instead, always a 'save as'-dialog appears (Chrome, FF) // header("Content-Disposition: attachment; filename=$outputFilename"); header("Content-Disposition: inline; filename=\"$outputFilename\"; name=\"$outputFilename\""); header("Pragma: no-cache"); header("Expires: 0"); print file_get_contents($file); } /** * Interprets $element and fetches corresponding content as file. * * @param string $element - U:id=myExport&r=12, u:http://www.nzz.ch/issue?nr=21, f:fileadmin/sample.pdf * * @return string filename - already ready or fresh exported. Fresh exported needs to be deleted later. * @throws DownloadException * @throws \exception */ private function getElement($element) { $arr = explode(':', $element, 2); if (count($arr) != 2) { throw new DownloadException('Missing parameter for "' . $element . '"', ERROR_MISSING_REQUIRED_PARAMETER); } $token = $arr[0]; $value = $arr[1]; switch ($token) { case TOKEN_URL: case TOKEN_URL_PARAM: case TOKEN_PAGE: $filename = $this->html2pdf->page2pdf($token, $value); break; case TOKEN_FILE: case TOKEN_FILE_DEPRECATED: $filename = $value; break; default: throw new DownloadException('Unknown token: "' . $token . '"', ERROR_UNKNOWN_TOKEN); break; } return $filename; } /** * Creates a ZIP Files of all given $files * * @param array $files * * @return string ZIP filename - has to be deleted later. * @throws DownloadException */ private function zipFiles(array $files) { $zipFile = tempnam(sys_get_temp_dir(), TMP_FILE_PREFIX); if (false === $zipFile) { throw new DownloadException("Error creating output file.", ERROR_DOWNLOAD_CREATE_NEW_FILE); } $zip = new \ZipArchive(); if ($zip->open($zipFile, \ZipArchive::CREATE) !== true) { throw new DownloadException("Error creating/opening new empty zip file: $zipFile", ERROR_IO_OPEN); } $len = strlen(TMP_FILE_PREFIX); $ii = 1; foreach ($files AS $filename) { $localname = substr($filename, strrpos($filename, '/') + 1); if (substr($localname, 0, $len) == TMP_FILE_PREFIX) { $localname = 'file-' . $ii; $ii++; } // $localname = $this->targetFilenameExtension($filename, $localname, $mimetype); $zip->addFile($filename, $localname); } $zip->close(); return $zipFile; } /** * exportFilename= * mode=file | pdf | excel - default is 'file' in case of only one or 'pdf' in case of multiple sources. * HTML to PDF | Excel * _id= * _= * Direct * _file= * * @param array $vars [ DOWNLOAD_EXPORT_FILENAME, DOWNLOAD_MODE, SIP_DOWNLOAD_PARAMETER ] * * @param string $outputMode OUTPUT_MODE_DIRECT | OUTPUT_MODE_FILE * @return string Filename of the generated file. The filename only points to a real existing filename with $outputMode=OUTPUT_MODE_FILE * @throws CodeException * @throws DownloadException */ private function doElements(array $vars, $outputMode) { $tmpFiles = array(); $workDir = $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM); if (!chdir($workDir)) { throw new DownloadException ("Error chdir($workDir)", ERROR_IO_CHDIR); } $downloadMode = $vars[DOWNLOAD_MODE]; if ($downloadMode == DOWNLOAD_MODE_THUMBNAIL) { // Fake $vars control array. $pathFilenameThumbnail = $this->doThumbnail($vars[SIP_DOWNLOAD_PARAMETER]); $downloadMode = DOWNLOAD_MODE_FILE; $vars[SIP_DOWNLOAD_PARAMETER] = TOKEN_FILE . ':' . $pathFilenameThumbnail; } $elements = explode(PARAM_DELIMITER, $vars[SIP_DOWNLOAD_PARAMETER]); // Get all files foreach ($elements as $element) { $tmpFiles[] = $this->getElement($element); } // Export, Concat File(s) switch ($downloadMode) { case DOWNLOAD_MODE_ZIP: $filename = $this->zipFiles($tmpFiles); if (empty($vars[DOWNLOAD_EXPORT_FILENAME])) { $vars[DOWNLOAD_EXPORT_FILENAME] = basename($filename); } break; case DOWNLOAD_MODE_EXCEL: throw new DownloadException("Not implemented: $downloadMode", ERROR_NOT_IMPLEMENTED); break; case DOWNLOAD_MODE_FILE: $filename = $tmpFiles[0]; if (empty($vars[DOWNLOAD_EXPORT_FILENAME])) { $vars[DOWNLOAD_EXPORT_FILENAME] = basename($filename); } break; case DOWNLOAD_MODE_PDF: $filename = $this->concatPdfFiles($tmpFiles); // try to find a meaningful filename if (empty($vars[DOWNLOAD_EXPORT_FILENAME])) { if (count($tmpFiles) > 1) { $vars[DOWNLOAD_EXPORT_FILENAME] = DOWNLOAD_OUTPUT_FILENAME . ".pdf"; } else { if (HelperFile::isQfqTemp($filename)) { $vars[DOWNLOAD_EXPORT_FILENAME] = DOWNLOAD_OUTPUT_FILENAME . ".pdf"; } else { $vars[DOWNLOAD_EXPORT_FILENAME] = basename($filename); } } } break; default: throw new DownloadException("Unknown downloadMode: $downloadMode", ERROR_UNKNOWN_MODE); break; } switch ($outputMode) { case OUTPUT_MODE_FILE: break; case OUTPUT_MODE_DIRECT: $this->outputFile($filename, $vars[DOWNLOAD_EXPORT_FILENAME]); HelperFile::cleanTempFiles([$filename]); break; default: throw new CodeException('Unkown mode: ' . $outputMode, ERROR_UNKNOWN_MODE); } return $filename; } /** * @param string $urlParam * @return string * @throws UserReportException */ private function doThumbnail($urlParam) { $thumbnail = new Thumbnail(); $pathFilenameThumbnail = $thumbnail->process($urlParam, THUMBNAIL_VIA_DOWNLOAD); return $pathFilenameThumbnail; } /** * Process download as requested in $vars. Output is either directly send to the browser, or a file which has to be deleted later. * * @param string|array $vars If $config is not an array, get values from STORE_SIP. If $config is an array, take it. * @param string $outputMode OUTPUT_MODE_DIRECT | OUTPUT_MODE_FILE * * @return string * @throws CodeException * @throws DownloadException * @throws UserFormException */ public function process($vars, $outputMode = OUTPUT_MODE_DIRECT) { if (!is_array($vars)) { $vars = $this->store->getStore(STORE_SIP); } return $this->doElements($vars, $outputMode); } }