PageRenderTime 65ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 0ms

/core/src/plugins/editor.imagick/class.IMagickPreviewer.php

https://gitlab.com/KasaiDot/pydio-core
PHP | 382 lines | 317 code | 26 blank | 39 comment | 91 complexity | 444768609afd292af9bdf050b1c3147d MD5 | raw file
  1. <?php
  2. /*
  3. * Copyright 2007-2013 Charles du Jeu - Abstrium SAS <team (at) pyd.io>
  4. * This file is part of Pydio.
  5. *
  6. * Pydio is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Pydio is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with Pydio. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. * The latest code can be found at <http://pyd.io/>.
  20. */
  21. defined('AJXP_EXEC') or die( 'Access not allowed');
  22. /**
  23. * Encapsulates calls to Image Magick to extract JPG previews of PDF, PSD, TIFF, etc.
  24. * @package AjaXplorer_Plugins
  25. * @subpackage Editor
  26. */
  27. class IMagickPreviewer extends AJXP_Plugin
  28. {
  29. protected $extractAll = false;
  30. protected $onTheFly = false;
  31. protected $useOnTheFly = false;
  32. protected $imagickExtensions = array("pdf", "svg", "tif", "tiff", "psd", "xcf", "eps", "cr2","ai");
  33. protected $unoconvExtensios = array("xls", "xlt", "xlsx", "xltx", "ods", "doc", "dot", "docx", "dotx", "odt", "ppt", "pptx", "odp", "rtf");
  34. public function loadConfigs($configsData)
  35. {
  36. parent::loadConfigs($configsData);
  37. if (isSet($configsData["UNOCONV"]) && !empty($configsData["UNOCONV"])) {
  38. // APPEND THE UNOCONV SUPPORTED EXTENSIONS
  39. $this->manifestDoc->documentElement->setAttribute("mimes", implode(",", array_merge($this->imagickExtensions,$this->unoconvExtensios)));
  40. } else {
  41. $this->manifestDoc->documentElement->setAttribute("mimes", implode(",", $this->imagickExtensions));
  42. }
  43. }
  44. public function switchAction($action, $httpVars, $filesVars)
  45. {
  46. $repository = ConfService::getRepository();
  47. if (!$repository->detectStreamWrapper(true)) {
  48. return false;
  49. }
  50. $convert = $this->getFilteredOption("IMAGE_MAGICK_CONVERT");
  51. if (empty($convert)) {
  52. return false;
  53. }
  54. $flyThreshold = 1024*1024*intval($this->getFilteredOption("ONTHEFLY_THRESHOLD", $repository->getId()));
  55. $selection = new UserSelection($repository, $httpVars);
  56. $destStreamURL = $selection->currentBaseUrl();
  57. if ($action == "imagick_data_proxy") {
  58. $this->extractAll = false;
  59. $file = $selection->getUniqueNode()->getUrl();
  60. if(!file_exists($file) || !is_readable($file)){
  61. throw new Exception("Cannot find file");
  62. }
  63. if(isSet($httpVars["all"])) {
  64. $this->logInfo('Preview', 'Preview content of '.$file, array("files" => $file));
  65. $this->extractAll = true;
  66. }
  67. if (($size = filesize($file)) === false) {
  68. return false;
  69. } else {
  70. if($size > $flyThreshold) $this->useOnTheFly = true;
  71. else $this->useOnTheFly = false;
  72. }
  73. if ($this->extractAll) {
  74. $node = new AJXP_Node($file);
  75. AJXP_Controller::applyHook("node.read", array($node));
  76. }
  77. $cache = AJXP_Cache::getItem("imagick_".($this->extractAll?"full":"thumb"), $file, array($this, "generateJpegsCallback"));
  78. $cacheData = $cache->getData();
  79. if (!$this->useOnTheFly && $this->extractAll) { // extract all on first view
  80. $ext = pathinfo($file, PATHINFO_EXTENSION);
  81. $prefix = str_replace(".$ext", "", $cache->getId());
  82. $files = $this->listExtractedJpg($file, $prefix);
  83. header("Content-Type: application/json");
  84. print(json_encode($files));
  85. return false;
  86. } else if ($this->extractAll) { // on the fly extract mode
  87. $ext = pathinfo($file, PATHINFO_EXTENSION);
  88. $prefix = str_replace(".$ext", "", $cache->getId());
  89. $files = $this->listPreviewFiles($file, $prefix);
  90. header("Content-Type: application/json");
  91. print(json_encode($files));
  92. return false;
  93. } else {
  94. header("Content-Type: image/jpeg; name=\"".basename($file)."\"");
  95. header("Content-Length: ".strlen($cacheData));
  96. header('Cache-Control: public');
  97. header("Pragma:");
  98. header("Last-Modified: " . gmdate("D, d M Y H:i:s", time()-10000) . " GMT");
  99. header("Expires: " . gmdate("D, d M Y H:i:s", time()+5*24*3600) . " GMT");
  100. print($cacheData);
  101. return false;
  102. }
  103. } else if ($action == "get_extracted_page" && isSet($httpVars["file"])) {
  104. $file = (defined('AJXP_SHARED_CACHE_DIR')?AJXP_SHARED_CACHE_DIR:AJXP_CACHE_DIR)."/imagick_full/".AJXP_Utils::decodeSecureMagic($httpVars["file"]);
  105. if (!is_file($file)) {
  106. $srcfile = AJXP_Utils::decodeSecureMagic($httpVars["src_file"]);
  107. if($repository->hasContentFilter()){
  108. $contentFilter = $repository->getContentFilter();
  109. $srcfile = $contentFilter->filterExternalPath($srcfile);
  110. }
  111. $size = filesize($destStreamURL."/".$srcfile);
  112. if($size > $flyThreshold) $this->useOnTheFly = true;
  113. else $this->useOnTheFly = false;
  114. if($this->useOnTheFly) $this->onTheFly = true;
  115. $this->generateJpegsCallback($destStreamURL.$srcfile, $file);
  116. }
  117. if(!is_file($file)) return false;
  118. header("Content-Type: image/jpeg; name=\"".basename($file)."\"");
  119. header("Content-Length: ".filesize($file));
  120. header('Cache-Control: public');
  121. readfile($file);
  122. exit(1);
  123. } else if ($action == "delete_imagick_data" && !$selection->isEmpty()) {
  124. /*
  125. $files = $this->listExtractedJpg(AJXP_CACHE_DIR."/".$httpVars["file"]);
  126. foreach ($files as $file) {
  127. if(is_file(AJXP_CACHE_DIR."/".$file["file"])) unlink(AJXP_CACHE_DIR."/".$file["file"]);
  128. }
  129. */
  130. }
  131. }
  132. /**
  133. *
  134. * @param AJXP_Node $oldNode
  135. * @param AJXP_Node $newNode
  136. * @param Boolean $copy
  137. */
  138. public function deleteImagickCache($oldNode, $newNode = null, $copy = false)
  139. {
  140. if($oldNode == null) return;
  141. $oldFile = $oldNode->getUrl();
  142. // Should remove imagick cache file
  143. if(!$this->handleMime($oldFile)) return;
  144. if ($newNode == null || $copy == false) {
  145. AJXP_Cache::clearItem("imagick_thumb", $oldFile);
  146. $cache = AJXP_Cache::getItem("imagick_full", $oldFile, false);
  147. $prefix = str_replace(".".pathinfo($cache->getId(), PATHINFO_EXTENSION), "", $cache->getId());
  148. $files = $this->listExtractedJpg($oldFile, $prefix);
  149. foreach ($files as $file) {
  150. if(is_file((defined('AJXP_SHARED_CACHE_DIR')?AJXP_SHARED_CACHE_DIR:AJXP_CACHE_DIR)."/".$file["file"])) unlink(AJXP_CACHE_DIR."/".$file["file"]);
  151. }
  152. }
  153. }
  154. protected function listExtractedJpg($file, $prefix)
  155. {
  156. $n = new AJXP_Node($file);
  157. $path = $n->getPath();
  158. $files = array();
  159. $index = 0;
  160. while (is_file($prefix."-".$index.".jpg")) {
  161. $extract = $prefix."-".$index.".jpg";
  162. list($width, $height, $type, $attr) = @getimagesize($extract);
  163. $files[] = array(
  164. "file" => basename($extract),
  165. "width"=>$width,
  166. "height"=>$height,
  167. "rest" => "/get_extracted_page/".basename($extract).str_replace("%2F", "/", urlencode($path))
  168. );
  169. $index ++;
  170. }
  171. if (is_file($prefix.".jpg")) {
  172. $extract = $prefix.".jpg";
  173. list($width, $height, $type, $attr) = @getimagesize($extract);
  174. $files[] = array(
  175. "file" => basename($extract),
  176. "width"=>$width,
  177. "height"=>$height,
  178. "rest" => "/get_extracted_page/".basename($extract).str_replace("%2F", "/", urlencode($path))
  179. );
  180. }
  181. return $files;
  182. }
  183. protected function listPreviewFiles($file, $prefix)
  184. {
  185. $files = array();
  186. $index = 0;
  187. $unoconv = $this->getFilteredOption("UNOCONV");
  188. if (!empty($unoconv)) {
  189. $officeExt = array('xls', 'xlsx', 'ods', 'doc', 'docx', 'odt', 'ppt', 'pptx', 'odp', 'rtf');
  190. $extension = pathinfo($file, PATHINFO_EXTENSION);
  191. if (in_array(strtolower($extension), $officeExt)) {
  192. $unoDoc = $prefix."_unoconv.pdf";
  193. if(is_file($unoDoc)) $file = $unoDoc;
  194. }
  195. }
  196. $count = $this->countPages($file);
  197. $n = new AJXP_Node($file);
  198. $path = $n->getPath();
  199. while ($index < $count) {
  200. $extract = $prefix."-".$index.".jpg";
  201. list($width, $height, $type, $attr) = @getimagesize($extract);
  202. $files[] = array(
  203. "file" => basename($extract),
  204. "width"=>$width,
  205. "height"=>$height,
  206. "rest" => "/get_extracted_page/".basename($extract).str_replace("%2F", "/", urlencode($path))
  207. );
  208. $index ++;
  209. }
  210. if (is_file($prefix.".jpg")) {
  211. $extract = $prefix.".jpg";
  212. list($width, $height, $type, $attr) = @getimagesize($extract);
  213. $files[] = array(
  214. "file" => basename($extract),
  215. "width"=>$width,
  216. "height"=>$height,
  217. "rest" => "/get_extracted_page/".basename($extract).str_replace("%2F", "/", urlencode($path))
  218. );
  219. }
  220. return $files;
  221. }
  222. public function generateJpegsCallback($masterFile, $targetFile)
  223. {
  224. $unoconv = $this->getFilteredOption("UNOCONV");
  225. if (!empty($unoconv)) {
  226. $officeExt = array('xls', 'xlsx', 'ods', 'doc', 'docx', 'odt', 'ppt', 'pptx', 'odp', 'rtf');
  227. } else {
  228. $unoconv = false;
  229. }
  230. $extension = pathinfo($masterFile, PATHINFO_EXTENSION);
  231. $node = new AJXP_Node($masterFile);
  232. $masterFile = $node->getRealFile();
  233. if (DIRECTORY_SEPARATOR == "\\") {
  234. $masterFile = str_replace("/", "\\", $masterFile);
  235. }
  236. $wrappers = stream_get_wrappers();
  237. $wrappers_re = '(' . join('|', $wrappers) . ')';
  238. $isStream = (preg_match( "!^$wrappers_re://!", $targetFile ) === 1);
  239. if ($isStream) {
  240. $backToStreamTarget = $targetFile;
  241. $targetFile = tempnam(AJXP_Utils::getAjxpTmpDir(), "imagick_").".pdf";
  242. }
  243. $workingDir = dirname($targetFile);
  244. $out = array();
  245. $return = 0;
  246. $tmpFileThumb = str_replace(".$extension", ".jpg", $targetFile);
  247. if (DIRECTORY_SEPARATOR == "\\") {
  248. $tmpFileThumb = str_replace("/", "\\", $tmpFileThumb);
  249. }
  250. if (!$this->extractAll) {
  251. //register_shutdown_function("unlink", $tmpFileThumb);
  252. } else {
  253. @set_time_limit(90);
  254. }
  255. chdir($workingDir);
  256. if ($unoconv !== false && in_array(strtolower($extension), $officeExt)) {
  257. $unoDoc = preg_replace("/(-[0-9]+)?\\.jpg/", "_unoconv.pdf", $tmpFileThumb);
  258. if (!is_file($unoDoc) || (is_file($unoDoc) && (filesize($unoDoc) == 0))) {
  259. if (stripos(PHP_OS, "win") === 0) {
  260. $unoconv = $this->pluginConf["UNOCONV"]." -o ".escapeshellarg(basename($unoDoc))." -f pdf ".escapeshellarg($masterFile);
  261. } else {
  262. $unoconv = "HOME=/tmp ".$unoconv." --stdout -f pdf ".escapeshellarg($masterFile)." > ".escapeshellarg(basename($unoDoc));
  263. }
  264. putenv('LC_CTYPE='.AJXP_LOCALE);
  265. exec($unoconv, $out, $return);
  266. }
  267. if (is_file($unoDoc)) {
  268. $masterFile = basename($unoDoc);
  269. }
  270. }
  271. if ($this->onTheFly) {
  272. $pageNumber = strrchr($targetFile, "-");
  273. $pageNumber = str_replace(array(".jpg","-"), "", $pageNumber);
  274. $pageLimit = "[".$pageNumber."]";
  275. $this->extractAll = true;
  276. } else {
  277. if (!$this->useOnTheFly) {
  278. $pageLimit = ($this->extractAll?"":"[0]");
  279. } else {
  280. $pageLimit = "[0]";
  281. if($this->extractAll) $tmpFileThumb = str_replace(".jpg", "-0.jpg", $tmpFileThumb);
  282. }
  283. }
  284. $customOptions = $this->getFilteredOption("IM_CUSTOM_OPTIONS");
  285. $customEnvPath = $this->getFilteredOption("ADDITIONAL_ENV_PATH");
  286. $viewerQuality = $this->getFilteredOption("IM_VIEWER_QUALITY");
  287. $thumbQuality = $this->getFilteredOption("IM_THUMB_QUALITY");
  288. if (empty($customOptions)) {
  289. $customOptions = "";
  290. }
  291. if (!empty($customEnvPath)) {
  292. putenv("PATH=".getenv("PATH").":".$customEnvPath);
  293. }
  294. $params = $customOptions." ".( $this->extractAll? $viewerQuality : $thumbQuality );
  295. $cmd = $this->getFilteredOption("IMAGE_MAGICK_CONVERT")." ".escapeshellarg(($masterFile).$pageLimit)." ".$params." ".escapeshellarg($tmpFileThumb);
  296. $this->logDebug("IMagick Command : $cmd");
  297. session_write_close(); // Be sure to give the hand back
  298. exec($cmd, $out, $return);
  299. if (is_array($out) && count($out)) {
  300. throw new AJXP_Exception(implode("\n", $out));
  301. }
  302. if (!$this->extractAll) {
  303. rename($tmpFileThumb, $targetFile);
  304. if ($isStream) {
  305. $this->logDebug("Copy preview file to remote", $backToStreamTarget);
  306. copy($targetFile, $backToStreamTarget);
  307. unlink($targetFile);
  308. }
  309. } else {
  310. if ($isStream) {
  311. if (is_file(str_replace(".$extension", "", $targetFile))) {
  312. $targetFile = str_replace(".$extension", "", $targetFile);
  313. }
  314. if (is_file($targetFile)) {
  315. $this->logDebug("Copy preview file to remote", $backToStreamTarget);
  316. copy($targetFile, $backToStreamTarget);
  317. unlink($targetFile);
  318. }
  319. $this->logDebug("Searching for ", str_replace(".jpg", "-0.jpg", $tmpFileThumb));
  320. $i = 0;
  321. while (file_exists(str_replace(".jpg", "-$i.jpg", $tmpFileThumb))) {
  322. $page = str_replace(".jpg", "-$i.jpg", $tmpFileThumb);
  323. $remote_page = str_replace(".$extension", "-$i.jpg", $backToStreamTarget);
  324. $this->logDebug("Copy preview file to remote", $remote_page);
  325. copy($page, $remote_page);
  326. unlink($page);
  327. $i++;
  328. }
  329. }
  330. }
  331. return true;
  332. }
  333. protected function handleMime($filename)
  334. {
  335. $mimesAtt = explode(",", $this->getXPath()->query("@mimes")->item(0)->nodeValue);
  336. $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
  337. return in_array($ext, $mimesAtt);
  338. }
  339. protected function countPages($file)
  340. {
  341. $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
  342. if($ext != "pdf") return 20;
  343. if(!file_exists($file))return null;
  344. if (!$fp = @fopen($file,"r"))return null;
  345. $max=0;
  346. while (!feof($fp)) {
  347. $line = fgets($fp, 255);
  348. if (preg_match('/\/Count [0-9]+/', $line, $matches)) {
  349. preg_match('/[0-9]+/',$matches[0], $matches2);
  350. if ($max<$matches2[0]) $max=$matches2[0];
  351. }
  352. }
  353. fclose($fp);
  354. return (int) $max;
  355. }
  356. }