SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Helpers/FileConvert.php

  1. <?php
  2. /*
  3.  * Copyright (C) 2017 Karmabunny Pty Ltd.
  4.  *
  5.  * This file is a part of SproutCMS.
  6.  *
  7.  * SproutCMS is free software: you can redistribute it and/or modify it under the terms
  8.  * of the GNU General Public License as published by the Free Software Foundation, either
  9.  * version 2 of the License, or (at your option) any later version.
  10.  *
  11.  * For more information, visit <http://getsproutcms.com>.
  12.  */
  13.  
  14. namespace Sprout\Helpers;
  15.  
  16. use RuntimeException;
  17. use InvalidArgumentException;
  18. use Sprout\Exceptions\FileConversionException;
  19.  
  20. /**
  21.  * Converter between various file types
  22.  *
  23.  * For all features to work, the following programs need to be installed:
  24.  * - convert (e.g. from the graphicsmagick-imagemagick-compat Debian package)
  25.  * - exiftool (e.g. from the libimage-exiftool-perl Debian package)
  26.  * - libreoffice (e.g. from the libreoffice-common Debian package)
  27.  */
  28. class FileConvert
  29. {
  30. /**
  31.   * Validates that an output file extension is of a valid format
  32.   *
  33.   * @param string $ext The output extension
  34.   * @throws InvalidArgumentException IF the extension is invalid
  35.   */
  36. private static function validateExtension($ext)
  37. {
  38. if (!preg_match('/^[a-z]{3,4}$/i', $ext)) {
  39. throw new InvalidArgumentException('Output extension must be 3-4 alphabetic characters');
  40. }
  41. }
  42.  
  43. /**
  44.   * Convert file using LibreOffice
  45.   *
  46.   * @param string $in_file Input filename, with full path
  47.   * @param string $out_ext Extension to convert file to, e.g. "pdf", "jpg", "txt".
  48.   * Note that spreadsheets cannot be directly converted to images, but can be converted to PDFs which can
  49.   * then be converted using {@see FileConvert::imagemagick}
  50.   * @return string Destination file in temp dir
  51.   * @throws InvalidArgumentException The $out_ext argument has an invalid format
  52.   * @throws RuntimeException LibreOffice isn't installed/accessible to PHP
  53.   * @throws FileConversionException LibreOffice failed to convert the file
  54.   */
  55. public static function libreoffice($in_file, $out_ext)
  56. {
  57. static::validateExtension($out_ext);
  58.  
  59. $out_arg = escapeshellarg(APPPATH . 'temp/');
  60. $tmp_arg = escapeshellarg($in_file);
  61. $cmd = "libreoffice --headless --convert-to {$out_ext} --outdir {$out_arg} {$tmp_arg} 2>&1";
  62.  
  63. $output = [];
  64. $return_code = null;
  65. exec($cmd, $output, $return_code);
  66.  
  67. if ($return_code !== 0) {
  68. if (!self::installed('libreoffice')) {
  69. throw new RuntimeException("Program 'libreoffice' not installed - try the 'libreoffice-common' package");
  70. }
  71. throw new FileConversionException('Libreoffice converting to ' . $out_ext . ' failed - exec() error');
  72. }
  73.  
  74. $dest_file = APPPATH . 'temp/' . File::getNoext(basename($in_file)) . '.' . $out_ext;
  75. if (!file_exists($dest_file)) {
  76. throw new FileConversionException('Libreoffice converting to ' . $out_ext . ' failed - destination file "' . $dest_file . '" not found');
  77. }
  78.  
  79. return $dest_file;
  80. }
  81.  
  82.  
  83. /**
  84.   * Convert file using ImageMagick
  85.   *
  86.   * @throws Exception Conversion failure
  87.   * @param string $in_file Input filename, with full path
  88.   * @param string $out_ext Extension to convert file to, e.g. "png", "jpg".
  89.   * @param int $page_index Page number of document, 0-based (applies to PDFs and other page-based documents)
  90.   * @param int $density DPI
  91.   * @return string Destination file in temp dir
  92.   * @throws InvalidArgumentException The $out_ext argument has an invalid format
  93.   * @throws RuntimeException ImageMagick isn't installed/accessible to PHP
  94.   * @throws FileConversionException ImageMagick failed to convert the file
  95.   */
  96. public static function imagemagick($in_file, $out_ext, $page_index = 0, $density = 400) {
  97. $page_index = (int) $page_index;
  98. $density = (int) $density;
  99.  
  100. static::validateExtension($out_ext);
  101.  
  102. $out_file = APPPATH . 'temp/' . File::getNoext(basename($in_file)) . '_' . Sprout::randStr(4) . '.' . $out_ext;
  103.  
  104. $in_arg = escapeshellarg($in_file . '[' . $page_index . ']');
  105. $out_arg = escapeshellarg($out_file);
  106.  
  107. $cmd = "convert -background white -alpha background -alpha off -colorspace RGB -quality 100% -density {$density} -resize 25% {$in_arg} {$out_arg} 2>&1";
  108.  
  109. $output = [];
  110. $return_code = null;
  111. exec($cmd, $output, $return_code);
  112.  
  113. if ($return_code !== 0) {
  114. if (!self::installed('convert')) {
  115. throw new RuntimeException("Program 'convert' not installed - try the 'graphicsmagick-imagemagick-compat' package");
  116. }
  117. throw new FileConversionException('Imagemagick converting to ' . $out_ext . ' failed - exec() error');
  118. }
  119.  
  120. if (!file_exists($out_file)) {
  121. throw new FileConversionException('Imagemagick converting to ' . $out_ext . ' failed - destination file not found');
  122. }
  123.  
  124. return $out_file;
  125. }
  126.  
  127.  
  128. /**
  129.   * Use 'exiftool' to determine the number of pages in a file
  130.   *
  131.   * @throws Exception
  132.   * @param string $filename Server filename
  133.   * @return int Number of pages
  134.   * @throws RuntimeException exiftool isn't installed/accessible to PHP
  135.   * @throws FileConversionException exiftool was unable to determine the page count
  136.   */
  137. public static function getPageCount($filename) {
  138. $cmd = 'exiftool -json ' . escapeshellarg($filename);
  139.  
  140. $output = [];
  141. $return_code = null;
  142. exec($cmd, $output, $return_code);
  143.  
  144. if ($return_code !== 0) {
  145. if (!self::installed('exiftool')) {
  146. throw new RuntimeException("Program 'exiftool' not installed - try the 'libimage-exiftool-perl' package");
  147. }
  148. throw new FileConversionException('Exiftool failed - exec() error');
  149. }
  150.  
  151. $data = json_decode(implode('', $output));
  152. if (isset($data[0]->PageCount)) {
  153. return $data[0]->PageCount;
  154. }
  155. if (isset($data[0]->Pages)) {
  156. return $data[0]->Pages;
  157. }
  158.  
  159. if (strtolower(File::getExt($filename)) === 'pdf') {
  160. throw new FileConversionException('Unable to determine page count');
  161. }
  162.  
  163. // Exiftool couldn't process this file. Convert to PDF and try again.
  164. $dest_file_pdf = self::libreoffice('pdf', 'pdf', $filename);
  165. $count = self::getPageCount($dest_file_pdf);
  166. unlink($dest_file_pdf);
  167. return $count;
  168. }
  169.  
  170.  
  171. /**
  172.   * Checks to see that a conversion program is installed
  173.   *
  174.   * @param string $program The program name; 'libreoffice', 'convert' (i.e. ImageMagick), 'exiftool'
  175.   * @return bool True if the program is installed
  176.   */
  177. public static function installed($program)
  178. {
  179. $cmd = 'which ' . escapeshellarg($program);
  180.  
  181. $out = [];
  182. $return_code = null;
  183. exec($cmd, $out, $return_code);
  184.  
  185. if ($return_code !== 0) {
  186. return false;
  187. }
  188. return true;
  189. }
  190. }
  191.