PageRenderTime 55ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/fuel/core/classes/image/driver.php

https://github.com/tilur/taranoelle
PHP | 803 lines | 522 code | 50 blank | 231 comment | 73 complexity | 9c1b7e7ed6776e1b64369c5b87880133 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /**
  3. * Fuel is a fast, lightweight, community driven PHP5 framework.
  4. *
  5. * Image manipulation class.
  6. *
  7. * @package Fuel
  8. * @version 1.0
  9. * @license MIT License
  10. * @copyright 2010 - 2011 Fuel Development Team
  11. * @link http://fuelphp.com
  12. */
  13. namespace Fuel\Core;
  14. abstract class Image_Driver {
  15. protected $image_fullpath = null;
  16. protected $image_directory = null;
  17. protected $image_filename = null;
  18. protected $image_extension = null;
  19. protected $new_extension = null;
  20. protected $config = array();
  21. protected $queued_actions = array();
  22. protected $accepted_extensions;
  23. public function __construct($config)
  24. {
  25. Config::load('image', 'image');
  26. if (is_array($config))
  27. $this->config = array_merge(Config::get('image'), $config);
  28. else
  29. $this->config = Config::get('image');
  30. $this->debug("Image Class was initalized using the " . $this->config['driver'] . " driver.");
  31. }
  32. /**
  33. * Accepts configuration in either an array (as $index) or a pairing using $index and $value
  34. *
  35. * @param string $index The index to be set, or an array of configuration options.
  36. * @param mixed $value The value to be set if $index is not an array.
  37. */
  38. public function config($index = null, $value = null)
  39. {
  40. if (is_array($index))
  41. {
  42. if (isset($index['driver']))
  43. throw new \Fuel_Exception("The driver cannot be changed after initalization!");
  44. $this->config = array_merge($this->config, $index);
  45. }
  46. elseif ($index != null)
  47. {
  48. if ($index == 'driver')
  49. throw new \Fuel_Exception("The driver cannot be changed after initalization!");
  50. $this->config[$index] = $value;
  51. }
  52. return $this;
  53. }
  54. /**
  55. * Exectues the presets set in the config. Additional parameters replace the $1, $2, ect.
  56. *
  57. * @param string $name The name of the preset.
  58. */
  59. public function preset($name)
  60. {
  61. $vars = func_get_args();
  62. if (isset($this->config['presets'][$name]))
  63. {
  64. $old_config = $this->config;
  65. $this->config = array_merge($this->config, $this->config['presets'][$name]);
  66. foreach ($this->config['actions'] AS $action)
  67. {
  68. $func = $action[0];
  69. array_shift($action);
  70. for ($i = 0; $i < count($action); $i++)
  71. {
  72. for ($x = count($vars) - 1; $x >= 0; $x--)
  73. {
  74. $action[$i] = preg_replace('#\$' . $x . '#', $vars[$x], $action[$i]);
  75. }
  76. }
  77. call_user_func_array(array($this, $func), $action);
  78. }
  79. $this->config = $old_config;
  80. }
  81. else
  82. {
  83. throw new \Fuel_Exception("Could not load preset $name, you sure it exists?");
  84. }
  85. return $this;
  86. }
  87. /**
  88. * Loads the image and checks if its compatable.
  89. *
  90. * @param string $filename The file to load
  91. * @param string $return_data Decides if it should return the images data, or just "$this".
  92. * @return Image_Driver
  93. */
  94. public function load($filename, $return_data = false)
  95. {
  96. // First check if the filename exists
  97. $filename = realpath($filename);
  98. $return = array(
  99. 'filename' => $filename,
  100. 'return_data' => $return_data
  101. );
  102. if (file_exists($filename))
  103. {
  104. // Check the extension
  105. $ext = $this->check_extension($filename);;
  106. if ($ext !== false)
  107. {
  108. $return = array_merge($return, array(
  109. 'image_fullpath' => $filename,
  110. 'image_directory' => dirname($filename),
  111. 'image_filename' => basename($filename),
  112. 'image_extension' => $ext
  113. ));
  114. if ( ! $return_data)
  115. {
  116. $this->image_fullpath = $filename;
  117. $this->image_directory = dirname($filename);
  118. $this->image_filename = basename($filename);
  119. $this->image_extension = $ext;
  120. }
  121. }
  122. else
  123. {
  124. throw new \Fuel_Exception("The library does not support this filetype for <i>$filename</i>.");
  125. }
  126. }
  127. else
  128. {
  129. throw new \Fuel_Exception("Image file <i>$filename</i> does not exist.");
  130. }
  131. return $return;
  132. }
  133. /**
  134. * Crops the image using coordinates or percentages.
  135. *
  136. * Positive whole numbers or percentages are coordinates from the top left.
  137. *
  138. * Negative whole numbers or percentages are coordinates from the bottom right.
  139. *
  140. * @param integer $x1 X-Coordinate for first set.
  141. * @param integer $y1 Y-Coordinate for first set.
  142. * @param integer $x2 X-Coordinate for second set.
  143. * @param integer $y2 Y-Coordinate for second set.
  144. * @return Image_Driver
  145. */
  146. public function crop($x1, $y1, $x2, $y2)
  147. {
  148. $this->queue('crop', $x1, $y1, $x2, $y2);
  149. return $this;
  150. }
  151. /**
  152. * Executes the crop event when the queue is ran.
  153. *
  154. * Formats the crop method input for use with driver specific methods
  155. *
  156. * @param integer $x1 X-Coordinate for first set.
  157. * @param integer $y1 Y-Coordinate for first set.
  158. * @param integer $x2 X-Coordinate for second set.
  159. * @param integer $y2 Y-Coordinate for second set.
  160. * @return Array An array of variables for the specific driver.
  161. */
  162. protected function _crop($x1, $y1, $x2, $y2)
  163. {
  164. $y1 === null and $y1 = $x1;
  165. $x2 === null and $x2 = "-" . $x1;
  166. $y2 === null and $y2 = "-" . $y1;
  167. $x1 = $this->convert_number($x1, true);
  168. $y1 = $this->convert_number($y1, false);
  169. $x2 = $this->convert_number($x2, true);
  170. $y2 = $this->convert_number($y2, false);
  171. return array(
  172. 'x1' => $x1,
  173. 'y1' => $y1,
  174. 'x2' => $x2,
  175. 'y2' => $y2
  176. );
  177. }
  178. /**
  179. * Resizes the image. If the width or height is null, it will resize retaining the original aspect ratio.
  180. *
  181. * @param integer $width The new width of the image.
  182. * @param integer $height The new height of the image.
  183. * @param boolean $keepar If false, allows stretching of the image.
  184. * @param boolean $pad Adds padding to the image when resizing.
  185. * @return Image_Driver
  186. */
  187. public function resize($width, $height = null, $keepar = true, $pad = false)
  188. {
  189. $this->queue('resize', $width, $height, $keepar, $pad);
  190. return $this;
  191. }
  192. /**
  193. * Executes the resize event when the queue is ran.
  194. *
  195. * Formats the resize method input for use with driver specific methods.
  196. *
  197. * @param integer $width The new width of the image.
  198. * @param integer $height The new height of the image.
  199. * @param boolean $keepar If false, allows stretching of the image.
  200. * @param boolean $pad Adds padding to the image when resizing.
  201. * @return Array An array of variables for the specific driver.
  202. */
  203. protected function _resize($width, $height = null, $keepar = true, $pad = true)
  204. {
  205. if ($height == null or $width == null)
  206. {
  207. if ($height == null and substr($width, -1) == '%')
  208. {
  209. $height = $width;
  210. }
  211. elseif (substr($height, -1) == '%' and $width == null)
  212. {
  213. $width = $height;
  214. }
  215. else
  216. {
  217. $sizes = $this->sizes();
  218. if ($height == null and $width != null)
  219. {
  220. $height = $width * ($sizes->height / $sizes->width);
  221. }
  222. elseif ($height != null and $width == null)
  223. {
  224. $width = $height * ($sizes->width / $sizes->height);
  225. }
  226. else
  227. {
  228. throw new \Fuel_Exception("Width and height cannot be null.");
  229. }
  230. }
  231. }
  232. $origwidth = $this->convert_number($width, true);
  233. $origheight = $this->convert_number($height, false);
  234. $width = $origwidth;
  235. $height = $origheight;
  236. $sizes = $this->sizes();
  237. $x = 0;
  238. $y = 0;
  239. if ($keepar)
  240. {
  241. // See which is the biggest ratio
  242. if (function_exists('bcdiv'))
  243. {
  244. $width_ratio = bcdiv($width, $sizes->width, 10);
  245. $height_ratio = bcdiv($height, $sizes->height, 10);
  246. $compare = bccomp($width_ratio, $height_ratio, 10);
  247. if ($compare > -1)
  248. {
  249. $height = ceil((real) bcmul($sizes->height, $height_ratio, 10));
  250. $width = ceil((real) bcmul($sizes->width, $height_ratio, 10));
  251. }
  252. else
  253. {
  254. $height = ceil((real) bcmul($sizes->height, $width_ratio, 10));
  255. $width = ceil((real) bcmul($sizes->width, $width_ratio, 10));
  256. }
  257. }
  258. else
  259. {
  260. $width_ratio = $width / $sizes->width;
  261. $height_ratio = $height / $sizes->height;
  262. if ($width_ratio >= $height_ratio)
  263. {
  264. $height = ceil($sizes->height * $height_ratio);
  265. $width = ceil($sizes->width * $height_ratio);
  266. }
  267. else
  268. {
  269. $height = ceil($sizes->height * $width_ratio);
  270. $width = ceil($sizes->width * $width_ratio);
  271. }
  272. }
  273. }
  274. if ($pad)
  275. {
  276. $x = floor(($origwidth - $width) / 2);
  277. $y = floor(($origheight - $height) / 2);
  278. } else {
  279. $origwidth = $width;
  280. $origheight = $height;
  281. }
  282. return array(
  283. 'width' => $width,
  284. 'height' => $height,
  285. 'cwidth' => $origwidth,
  286. 'cheight' => $origheight,
  287. 'x' => $x,
  288. 'y' => $y
  289. );
  290. }
  291. public function crop_resize($width, $height = null)
  292. {
  293. $this->queue('crop_resize', $width, $height);
  294. return $this;
  295. }
  296. protected function _crop_resize($width, $height)
  297. {
  298. // Determine the crop size
  299. $sizes = $this->sizes();
  300. $width = $this->convert_number($width, true);
  301. $height = $this->convert_number($height, false);
  302. $x = $y = 0;
  303. if (function_exists('bcdiv'))
  304. {
  305. $widthr = bcdiv($sizes->width, $width, 10);
  306. $heightr = bcdiv($sizes->height, $height, 10);
  307. $compare = bccomp($widthr, $heightr, 10);
  308. if ($compare < 1)
  309. {
  310. $t_height = ceil((float) bcmul($height, $widthr, 10));
  311. $this->_resize($width, $t_height, true, false);
  312. }
  313. else
  314. {
  315. $t_width = ceil((float) bcmul($width, $heightr, 10));
  316. $this->_resize($t_width, $height, true, false);
  317. }
  318. }
  319. else
  320. {
  321. $widthr = $sizes->width / $width;
  322. $heightr = $sizes->height / $height;
  323. if ($widthr < $heightr)
  324. {
  325. $t_height = ceil($height * $widthr);
  326. $this->_resize($width, $t_height, true, false);
  327. }
  328. else
  329. {
  330. $t_width = ceil($width * $heightr);
  331. $this->_resize($t_width, $height, true, false);
  332. }
  333. }
  334. $sizes = $this->sizes();
  335. $y = floor(($sizes->height - $height) / 2);
  336. $x = floor(($sizes->width - $width) / 2);
  337. $this->_crop($x, $y, $x + $width, $y + $height);
  338. }
  339. /**
  340. * Rotates the image
  341. *
  342. * @param integer $degrees The degrees to rotate, negatives integers allowed.
  343. * @param Image_Driver
  344. */
  345. public function rotate($degrees)
  346. {
  347. $this->queue('rotate', $degrees);
  348. return $this;
  349. }
  350. /**
  351. * Executes the rotate event when the queue is ran.
  352. *
  353. * Formats the rotate method input for use with driver specific methods
  354. *
  355. * @param integer $degrees The degrees to rotate, negatives integers allowed.
  356. * @return Array An array of variables for the specific driver.
  357. */
  358. protected function _rotate($degrees)
  359. {
  360. $degrees %= 360;
  361. if ($degrees < 0)
  362. {
  363. $degrees = 360 + $degrees;
  364. }
  365. return array(
  366. 'degrees' => $degrees
  367. );
  368. }
  369. /**
  370. * Adds a watermark to the image.
  371. *
  372. * @param string $filename The filename of the watermark file to use.
  373. * @param string $position The position of the watermark, ex: "bottom right", "center center", "top left"
  374. * @param integer $padding The amount of padding (in pixels) from the position.
  375. * @param Image_Driver
  376. */
  377. public function watermark($filename, $position, $padding = 5)
  378. {
  379. $this->queue('watermark', $filename, $position, $padding);
  380. return $this;
  381. }
  382. /**
  383. * Executes the watermark event when the queue is ran.
  384. *
  385. * Formats the watermark method input for use with driver specific methods
  386. *
  387. * @param string $filename The filename of the watermark file to use.
  388. * @param string $position The position of the watermark, ex: "bottom right", "center center", "top left"
  389. * @param integer $padding The amount of padding (in pixels) from the position.
  390. * @return Array An array of variables for the specific driver.
  391. */
  392. protected function _watermark($filename, $position, $padding = 5)
  393. {
  394. $filename = realpath($filename);
  395. $return = false;
  396. if (file_exists($filename) and $this->check_extension($filename, false))
  397. {
  398. $x = 0;
  399. $y = 0;
  400. $wsizes = $this->sizes($filename);
  401. $sizes = $this->sizes();
  402. // Get the x and y positions.
  403. list($ypos, $xpos) = explode(' ', $position);
  404. switch ($xpos)
  405. {
  406. case 'left':
  407. $x = $padding;
  408. break;
  409. case 'middle':
  410. case 'center':
  411. $x = ($sizes->width / 2) - ($wsizes->width / 2);
  412. break;
  413. case 'right':
  414. $x = $sizes->width - $wsizes->width - $padding;
  415. break;
  416. }
  417. switch ($ypos)
  418. {
  419. case 'top':
  420. $y = $padding;
  421. break;
  422. case 'middle':
  423. case 'center':
  424. $y = ($sizes->height / 2) - ($wsizes->height / 2);
  425. break;
  426. case 'bottom':
  427. $y = $sizes->height - $wsizes->height - $padding;
  428. break;
  429. }
  430. $this->debug("Watermark being placed at $x,$y");
  431. $return = array(
  432. 'filename' => $filename,
  433. 'x' => $x,
  434. 'y' => $y,
  435. 'padding' => $padding
  436. );
  437. }
  438. return $return;
  439. }
  440. /**
  441. * Adds a border to the image.
  442. *
  443. * @param integer $size The side of the border, in pixels.
  444. * @param string $color A hexidecimal color.
  445. * @param Image_Driver
  446. */
  447. public function border($size, $color = null)
  448. {
  449. $this->queue('border', $size, $color);
  450. return $this;
  451. }
  452. /**
  453. * Executes the border event when the queue is ran.
  454. *
  455. * Formats the border method input for use with driver specific methods
  456. *
  457. * @param integer $size The side of the border, in pixels.
  458. * @param string $color A hexidecimal color.
  459. * @return Array An array of variables for the specific driver.
  460. */
  461. protected function _border($size, $color = null)
  462. {
  463. empty($color) and $color = $this->config['bgcolor'];
  464. return array(
  465. 'size' => $size,
  466. 'color' => $color
  467. );
  468. }
  469. /**
  470. * Masks the image using the alpha channel of the image input.
  471. *
  472. * @param string $maskimage The location of the image to use as the mask
  473. * @return Image_Driver
  474. */
  475. public function mask($maskimage)
  476. {
  477. $this->queue('mask', $maskimage);
  478. return $this;
  479. }
  480. /**
  481. * Executes the mask event when the queue is ran.
  482. *
  483. * Formats the mask method input for use with driver specific methods
  484. *
  485. * @param string $maskimage The location of the image to use as the mask
  486. * @return Array An array of variables for the specific driver.
  487. */
  488. protected function _mask($maskimage)
  489. {
  490. return array(
  491. 'maskimage' => $maskimage
  492. );
  493. }
  494. /**
  495. * Adds rounded corners to the image.
  496. *
  497. * @param integer $radius
  498. * @param integer $sides Accepts any combination of "tl tr bl br" seperated by spaces, or null for all sides
  499. * @param integer $antialias Sets the antialias range.
  500. * @return Image_Driver
  501. */
  502. public function rounded($radius, $sides = null, $antialias = null)
  503. {
  504. $this->queue('rounded', $radius, $sides, $antialias);
  505. return $this;
  506. }
  507. /**
  508. * Executes the rounded event when the queue is ran.
  509. *
  510. * Formats the rounded method input for use with driver specific methods
  511. *
  512. * @param integer $radius
  513. * @param integer $sides Accepts any combination of "tl tr bl br" seperated by spaces, or null for all sides
  514. * @param integer $antialias Sets the antialias range.
  515. * @return Array An array of variables for the specific driver.
  516. */
  517. protected function _rounded($radius, $sides, $antialias)
  518. {
  519. $radius < 0 and $radius = 0;
  520. $tl = $tr = $bl = $br = $sides == null;
  521. if ($sides != null)
  522. {
  523. $sides = explode(' ', $sides);
  524. foreach ($sides as $side)
  525. {
  526. if ($side == 'tl' || $side == 'tr' || $side == 'bl' || $side == 'br')
  527. {
  528. $$side = true;
  529. }
  530. }
  531. }
  532. $antialias == null and $antialias = 1;
  533. return array(
  534. 'radius' => $radius,
  535. 'tl' => $tl,
  536. 'tr' => $tr,
  537. 'bl' => $bl,
  538. 'br' => $br,
  539. 'antialias' => $antialias
  540. );
  541. }
  542. /**
  543. * Turns the image into a grayscale version
  544. *
  545. * @return Image_Driver
  546. */
  547. public function grayscale()
  548. {
  549. $this->queue('grayscale');
  550. return $this;
  551. }
  552. /**
  553. * Executes the grayscale event when the queue is ran.
  554. */
  555. protected function _grayscale()
  556. {
  557. }
  558. /**
  559. * Saves the image, and optionally attempts to set permissions
  560. *
  561. * @param string $filename The location where to save the image.
  562. * @param string $permissions Allows unix style permissions
  563. */
  564. public function save($filename, $permissions = null)
  565. {
  566. $directory = dirname($filename);
  567. if ( ! is_dir($directory))
  568. {
  569. throw new \Fuel_Exception("Could not find directory \"$directory\"");
  570. }
  571. if ( ! $this->check_extension($filename, true))
  572. {
  573. $filename .= "." . $this->image_extension;
  574. }
  575. // Touch the file
  576. if ( ! touch($filename))
  577. {
  578. throw new \Fuel_Exception("Do not have permission to write to \"$filename\"");
  579. }
  580. // Set the new permissions
  581. if ($permissions != null and ! chmod($filename, $permissions))
  582. {
  583. throw new \Fuel_Exception("Could not set permissions on the file.");
  584. }
  585. $this->debug("", "Saving image as <code>$filename</code>");
  586. return array(
  587. 'filename' => $filename
  588. );
  589. }
  590. /**
  591. * Saves the file in the original location, adding the append and prepend to the filename.
  592. *
  593. * @param string $append The string to append to the filename
  594. * @param string $prepend The string to prepend to the filename
  595. * @param string $extension The extension to save the image as, null defaults to the loaded images extension.
  596. * @param integer $permissions The permissions to attempt to set on the file.
  597. */
  598. public function save_pa($append, $prepend = null, $extension = null, $permissions = null)
  599. {
  600. $filename = substr($this->image_filename, 0, -(strlen($this->image_extension) + 1));
  601. $fullpath = $this->image_directory.'/'.$append.$filename.$prepend.'.'.($extension !== null ? $extension : $this->image_extension);
  602. $this->save($fullpath, $permissions);
  603. return $this;
  604. }
  605. /**
  606. * Outputs the file directly to the user.
  607. *
  608. * @param string $filetype The extension type to use. Ex: png, jpg, gif
  609. */
  610. public function output($filetype = null)
  611. {
  612. if ($filetype == null)
  613. {
  614. $filetype = $this->config['filetype'] == null ? $this->image_extension : $this->config['filetype'];
  615. }
  616. if ($this->check_extension($filetype, false))
  617. {
  618. if ( ! $this->config['debug'])
  619. {
  620. header('Content-Type: image/' . $filetype);
  621. }
  622. $this->new_extension = $filetype;
  623. }
  624. else
  625. {
  626. throw new \Fuel_Exception("Image extension $filetype is unsupported.");
  627. }
  628. $this->debug('', "Outputting image as $filetype");
  629. return array(
  630. 'filetype' => $filetype
  631. );
  632. }
  633. /**
  634. * Returns sizes for the currently loaded image, or the image given in the $filename.
  635. *
  636. * @param string $filename The location of the file to get sizes for.
  637. * @return object An object containing width and height variables.
  638. */
  639. abstract public function sizes($filename = null);
  640. /**
  641. * Adds a background to the image using the 'bgcolor' config option.
  642. */
  643. abstract protected function add_background();
  644. /**
  645. * Checks if the extension is accepted by this library, and if its valid sets the $this->image_extension variable.
  646. *
  647. * @param string $filename
  648. * @param boolean $writevar Decides if the extension should be written to $this->image_extension
  649. * @return boolean
  650. */
  651. protected function check_extension($filename, $writevar = true)
  652. {
  653. $return = false;
  654. foreach ($this->accepted_extensions AS $ext)
  655. {
  656. if (strtolower(substr($filename, strlen($ext) * -1)) == strtolower($ext))
  657. {
  658. $writevar and $this->image_extension = $ext;
  659. $return = $ext;
  660. }
  661. }
  662. return $return;
  663. }
  664. /**
  665. * Converts percentages, negatives, and other values to absolute integers.
  666. *
  667. * @param string $input
  668. * @param boolean $x Determines if the number relates to the x-axis or y-axis.
  669. * @return integer The converted number, useable with the image being edited.
  670. */
  671. protected function convert_number($input, $x = null)
  672. {
  673. // Sanatize double negatives
  674. $input = str_replace('--', '', $input);
  675. $orig = $input;
  676. $sizes = $this->sizes();
  677. $size = $x ? $sizes->width : $sizes->height;
  678. // Convert percentages to absolutes
  679. if (substr($input, -1) == '%')
  680. {
  681. $input = floor((substr($input, 0, -1) / 100) * $size);
  682. }
  683. // Negatives are based off the bottom right
  684. if ($x !== null && $input < 0)
  685. {
  686. $input = $size + $input;
  687. }
  688. return $input;
  689. }
  690. /**
  691. * Queues a function to run at a later time.
  692. *
  693. * @param string $function The name of the function to be ran, without the leading _
  694. */
  695. protected function queue($function)
  696. {
  697. $func = func_get_args();
  698. $tmpfunc = array();
  699. for ($i = 0; $i < count($func); $i++)
  700. {
  701. $tmpfunc[$i] = var_export($func[$i], true);
  702. }
  703. $this->debug("Queued <code>" . implode(", ", $tmpfunc) . "</code>");
  704. $this->queued_actions[] = $func;
  705. }
  706. /**
  707. * Runs all queued actions on the loaded image.
  708. *
  709. * @param boolean $clear Decides if the queue should be cleared once completed.
  710. */
  711. public function run_queue($clear = null)
  712. {
  713. foreach ($this->queued_actions AS $action)
  714. {
  715. $tmpfunc = array();
  716. for ($i = 0; $i < count($action); $i++)
  717. {
  718. $tmpfunc[$i] = var_export($action[$i], true);
  719. }
  720. $this->debug('', "<b>Executing <code>" . implode(", ", $tmpfunc) . "</code></b>");
  721. call_user_func_array(array(&$this, '_' . $action[0]), array_slice($action, 1));
  722. }
  723. if (($clear === null && $this->config['clear_queue']) || $clear === true)
  724. {
  725. $this->queued_actions = array();
  726. }
  727. }
  728. /**
  729. * Reloads the image.
  730. */
  731. public function reload()
  732. {
  733. $this->debug("Reloading was called!");
  734. $this->load($this->image_fullpath);
  735. return $this;
  736. }
  737. /**
  738. * Used for debugging image output.
  739. *
  740. * @param string $message
  741. */
  742. protected function debug()
  743. {
  744. if ($this->config['debug'])
  745. {
  746. $messages = func_get_args();
  747. foreach ($messages as $message)
  748. {
  749. echo '<div>' . $message . '&nbsp;</div>';
  750. }
  751. }
  752. }
  753. }