imageapi_optimize.module 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <?php
  2. /**
  3. * @file
  4. * Image optimize functionalities
  5. */
  6. /**
  7. * Implements hook_init().
  8. *
  9. * Abstract layer to all methods implemented by base toolkit.
  10. */
  11. function imageapi_optimize_init() {
  12. if (! $cache = cache_get('imageapi_optimize:methods')) {
  13. $methods = _imageapi_optimize_get_methods();
  14. }
  15. else {
  16. $methods = $cache->data;
  17. }
  18. foreach ($methods as $method) {
  19. eval('function image_imageapi_optimize_'.$method.'($image) {
  20. $params = array_slice(func_get_args(), 1);
  21. return _imageapi_optimize_invoke("'. $method .'", $image, $params);
  22. }');
  23. }
  24. }
  25. /**
  26. * Implements hook_image_toolkits().
  27. */
  28. function imageapi_optimize_image_toolkits() {
  29. return array(
  30. 'imageapi_optimize' => array(
  31. 'title' => 'ImageAPI Optimize',
  32. 'available' => TRUE,
  33. ),
  34. );
  35. }
  36. /**
  37. * Settings form for the toolkit.
  38. */
  39. function image_imageapi_optimize_settings() {
  40. $toolkits = array();
  41. foreach (image_get_available_toolkits() as $toolkit => $info) {
  42. if ($toolkit != 'imageapi_optimize') {
  43. $toolkits[$toolkit] = $info;
  44. }
  45. }
  46. $base_toolkit = variable_get('imageapi_optimize_toolkit', 'gd');
  47. $form['imageapi_optimize_base_toolkit'] = array(
  48. '#type' => 'fieldset',
  49. '#title' => t('ImageAPI Optimize Base Toolkit'),
  50. );
  51. $form['imageapi_optimize_base_toolkit']['imageapi_optimize_toolkit'] = array(
  52. '#type' => 'radios',
  53. '#title' => t('Base toolkit'),
  54. '#default_value' => $base_toolkit,
  55. '#options' => $toolkits,
  56. '#element_validate' => array('imageapi_optimize_toolkit_element_validate'),
  57. );
  58. $base_toolkit_settings_function = 'image_' . $base_toolkit . '_settings';
  59. if (function_exists($base_toolkit_settings_function)) {
  60. $base_toolkit_settings_form = $base_toolkit_settings_function();
  61. }
  62. $base_toolkit_settings_fieldset = array(
  63. '#type' => 'fieldset',
  64. '#title' => t('@toolkit Settings', array('@toolkit' => $toolkits[$base_toolkit])),
  65. '#collapsible' => TRUE,
  66. '#collapsed' => TRUE,
  67. );
  68. $form['imageapi_optimize_base_toolkit']['imageapi_optimize_toolkit_settings'] = array_merge($base_toolkit_settings_fieldset, $base_toolkit_settings_form);
  69. $form['imageapi_optimize_service'] = array(
  70. '#type' => 'fieldset',
  71. '#title' => t('ImageAPI Optimize Service'),
  72. );
  73. $services = array(
  74. 'internal' => 'Internal tools (configurable below)',
  75. 'smushit' => 'Yahoo! Smush.It',
  76. );
  77. $form['imageapi_optimize_service']['imageapi_optimize_service'] = array(
  78. '#type' => 'radios',
  79. '#title' => t('Choose which optimization service to use'),
  80. '#default_value' => variable_get('imageapi_optimize_service', 'internal'),
  81. '#options' => $services,
  82. );
  83. $form['imageapi_optimize_service']['imageapi_optimize_internal'] = array(
  84. '#type' => 'fieldset',
  85. '#title' => t('Paths to internal tools'),
  86. '#collapsible' => TRUE,
  87. '#collapsed' => TRUE,
  88. );
  89. $form['imageapi_optimize_service']['imageapi_optimize_internal']['imageapi_optimize_advpng'] = array(
  90. '#type' => 'textfield',
  91. '#title' => t('Path to advpng'),
  92. '#default_value' => variable_get('imageapi_optimize_advpng', ''),
  93. '#element_validate' => array('imageapi_optimize_validate_path'),
  94. '#description' => t('Leave empty to skip this command. You can download it <a href="!link">here</a> (part of AdvanceCOMP).', array('!link' => 'http://advancemame.sourceforge.net/comp-download.html')),
  95. );
  96. $form['imageapi_optimize_service']['imageapi_optimize_internal']['imageapi_optimize_optipng'] = array(
  97. '#type' => 'textfield',
  98. '#title' => t('Path to optipng'),
  99. '#default_value' => variable_get('imageapi_optimize_optipng', ''),
  100. '#element_validate' => array('imageapi_optimize_validate_path'),
  101. '#description' => t('Leave empty to skip this command. You can download it <a href="!link">here</a>.', array('!link' => 'http://optipng.sourceforge.net/')),
  102. );
  103. $form['imageapi_optimize_service']['imageapi_optimize_internal']['imageapi_optimize_pngcrush'] = array(
  104. '#type' => 'textfield',
  105. '#title' => t('Path to pngcrush'),
  106. '#default_value' => variable_get('imageapi_optimize_pngcrush', ''),
  107. '#element_validate' => array('imageapi_optimize_validate_path'),
  108. '#description' => t('Leave empty to skip this command. You can download it <a href="!link">here</a>.', array('!link' => 'http://pmt.sourceforge.net/pngcrush/')),
  109. );
  110. $form['imageapi_optimize_service']['imageapi_optimize_internal']['imageapi_optimize_jpegtran'] = array(
  111. '#type' => 'textfield',
  112. '#title' => t('Path to jpegtran'),
  113. '#default_value' => variable_get('imageapi_optimize_jpegtran', '/usr/bin/jpegtran'),
  114. '#element_validate' => array('imageapi_optimize_validate_path'),
  115. '#description' => t('Leave empty to skip this command. This is a part of <a href="!link">libjpeg</a> and could probably on your system.', array('!link' => 'http://ijg.org/')),
  116. );
  117. $form['imageapi_optimize_service']['imageapi_optimize_internal']['imageapi_optimize_jfifremove'] = array(
  118. '#type' => 'textfield',
  119. '#title' => t('Path to jfifremove'),
  120. '#default_value' => variable_get('imageapi_optimize_jfifremove', ''),
  121. '#element_validate' => array('imageapi_optimize_validate_path'),
  122. '#description' => t('Leave empty to skip this command. You can download it <a href="!link">here</a>.', array('!link' => 'http://lyncd.com/files/imgopt/jfifremove.c')),
  123. );
  124. // Reloads methods because user may change toolkit
  125. $form['#submit'][] = '_imageapi_optimize_get_methods';
  126. return $form;
  127. }
  128. function imageapi_optimize_toolkit_element_validate($element) {
  129. if ($element['#value'] == 'imageapi_optimize') {
  130. form_set_error($element['#name'], t('You must select a different toolkit from ImageAPI Optimize itself.'));
  131. }
  132. }
  133. function imageapi_optimize_validate_path($element) {
  134. if ($errors = _imageapi_optimize_check_path($element['#value'])) {
  135. form_set_error($element['#parents'][0], implode('<br />', $errors));
  136. return FALSE;
  137. }
  138. return TRUE;
  139. }
  140. /**
  141. * All ImageAPI functions call their base functions
  142. */
  143. function image_imageapi_optimize_load($image) {
  144. return _imageapi_optimize_invoke('load', $image);
  145. }
  146. function image_imageapi_optimize_save($image, $dst) {
  147. if (_imageapi_optimize_invoke('save', $image, array($dst))) {
  148. return _imageapi_optimize_optimize($image, $dst);
  149. }
  150. return FALSE;
  151. }
  152. /**
  153. * Helper. Based on imageapi_invoke()
  154. */
  155. function _imageapi_optimize_invoke($method, $image, array $params = array()) {
  156. $function = 'image_' . variable_get('imageapi_optimize_toolkit', '') . '_' . $method;
  157. if (function_exists($function)) {
  158. array_unshift($params, $image);
  159. return call_user_func_array($function, $params);
  160. }
  161. return FALSE;
  162. }
  163. /**
  164. * Checks if a path exists and is executable
  165. *
  166. * Warning: it does not check if the command is harmful.
  167. * You should keep an eye on users with "administer imageapi" permission
  168. */
  169. function _imageapi_optimize_check_path($path) {
  170. $errors = array();
  171. if (!empty($path)) {
  172. if (!is_file($path)) {
  173. $errors[] = t('The specified path %file does not exist.', array('%file' => $path));
  174. }
  175. if (!$errors && !is_executable($path)) {
  176. $errors[] = t('The specified path %file is not executable.', array('%file' => $path));
  177. }
  178. if ($errors && $open_basedir = ini_get('open_basedir')) {
  179. $errors[] = t('PHP\'s <a href="!open-basedir">open_basedir</a> security restriction is set to %open-basedir, which may be interfering with attempts to locate %file.', array('%file' => $path, '%open-basedir' => $open_basedir, '!info-link' => url('http://php.net/features.safe-mode#ini.open-basedir')));
  180. }
  181. }
  182. return $errors;
  183. }
  184. /**
  185. * Optimizes image with external commands
  186. */
  187. function _imageapi_optimize_optimize($image, $dst) {
  188. $optimize_service = '_imageapi_optimize_service_'. variable_get('imageapi_optimize_service', 'internal');
  189. $optimize_service($image, $dst);
  190. // If optimize service fails, there is no problem. Original image is saved.
  191. return TRUE;
  192. }
  193. /**
  194. * Uses internal tools to optimize
  195. */
  196. function _imageapi_optimize_service_internal($image, $dst) {
  197. $dst = drupal_realpath($dst);
  198. switch ($image->info['mime_type']) {
  199. case 'image/png':
  200. if ($cmd = variable_get('imageapi_optimize_optipng', '')) {
  201. exec("$cmd -o5 -quiet ". escapeshellarg($dst));
  202. }
  203. if ($cmd = variable_get('imageapi_optimize_pngcrush', '')) {
  204. $temp = drupal_realpath(drupal_tempnam('temporary://', 'file'));
  205. exec("$cmd -rem alla -reduce -brute -q ". escapeshellarg($dst) ." ". escapeshellarg($temp) ." && mv ". escapeshellarg($temp) ." ". escapeshellarg($dst));
  206. }
  207. if ($cmd = variable_get('imageapi_optimize_advpng', '')) {
  208. exec("$cmd -z4q ". escapeshellarg($dst), $return, $output);
  209. }
  210. break;
  211. case 'image/jpeg':
  212. if ($cmd = variable_get('imageapi_optimize_jpegtran', '')) {
  213. _imageapi_optimize_exec("$cmd -copy none -optimize ". escapeshellarg($dst), $dst);
  214. }
  215. if ($cmd = variable_get('imageapi_optimize_jfifremove', '')) {
  216. _imageapi_optimize_exec("$cmd < ". escapeshellarg($dst), $dst);
  217. }
  218. break;
  219. }
  220. return TRUE;
  221. }
  222. /**
  223. * Use smushit.com to optimize
  224. */
  225. function _imageapi_optimize_service_smushit($image, $dst) {
  226. if (! function_exists('json_decode')) {
  227. drupal_set_message(t('Required function, json_decode(), is not available.'), 'error');
  228. return FALSE;
  229. }
  230. $dst = drupal_realpath($dst);
  231. $url = 'http://www.smushit.com/ysmush.it/ws.php';
  232. $ch = curl_init();
  233. curl_setopt($ch, CURLOPT_URL, $url);
  234. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  235. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
  236. curl_setopt($ch, CURLOPT_POST, true);
  237. curl_setopt($ch, CURLOPT_POSTFIELDS, array('files' => '@' . $dst));
  238. $data = curl_exec($ch);
  239. curl_close($ch);
  240. $json = json_decode($data);
  241. // SmushIt returns an error if it cannot optimize the image. Otherwise, it
  242. // returns an object, with 'dest' (temporary file) and 'percent' (savings)
  243. // among other properties.
  244. if (!isset($json->error)) {
  245. $result = drupal_http_request($json->dest);
  246. if (!isset($result->error)) {
  247. file_unmanaged_save_data($result->data, $dst, FILE_EXISTS_REPLACE);
  248. return TRUE;
  249. }
  250. }
  251. }
  252. /**
  253. * Saves result of a command into file
  254. */
  255. function _imageapi_optimize_exec($cmd, $dst) {
  256. ob_start();
  257. passthru($cmd);
  258. $output = ob_get_contents();
  259. ob_end_clean();
  260. file_unmanaged_save_data($output, $dst, FILE_EXISTS_REPLACE);
  261. }
  262. /**
  263. * Gets all implemented methods by ImageAPI and contrib modules.
  264. * This function takes a dozens of miliseconds CPU times.
  265. */
  266. function _imageapi_optimize_get_methods() {
  267. // The list of toolkits might not loaded yet. We call this function to get
  268. // toolkits in separate .inc files eventually included.
  269. image_get_available_toolkits();
  270. $funcs = get_defined_functions();
  271. $methods = array();
  272. $prefix = 'image_' . variable_get('imageapi_optimize_toolkit', '') .'_';
  273. foreach ($funcs['user'] as $func) {
  274. if (strpos($func, $prefix) === 0) {
  275. $method = substr($func, strlen($prefix));
  276. if (!in_array($method, array('load', 'save', 'settings'))) {
  277. $methods[] = $method;
  278. }
  279. }
  280. }
  281. cache_set('imageapi_optimize:methods', $methods);
  282. watchdog('imageapi', 'Refresh ImageAPI methods');
  283. return $methods;
  284. }