1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/webtrees_ynh.git synced 2024-09-03 18:26:37 +02:00
webtrees_ynh/sources/mediafirewall.php

436 lines
12 KiB
PHP
Raw Normal View History

<?php
/**
* webtrees: online genealogy
* Copyright (C) 2016 webtrees development team
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Fisharebest\Webtrees;
/**
* Defined in session.php
*
* @global Tree $WT_TREE
*/
global $WT_TREE;
use Fisharebest\Webtrees\Functions\FunctionsMedia;
define('WT_SCRIPT_NAME', 'mediafirewall.php');
require './includes/session.php';
$mid = Filter::get('mid', WT_REGEX_XREF);
$thumb = Filter::getBool('thumb');
$media = Media::getInstance($mid, $WT_TREE);
/**
* Send a “Not found” error as an image
*/
function send404AsImage() {
$error = I18N::translate('The media file was not found in this family tree.');
$width = mb_strlen($error) * 6.5 + 50;
$height = 60;
$im = imagecreatetruecolor($width, $height); /* Create a black image */
$bgc = imagecolorallocate($im, 255, 255, 255); /* set background color */
imagefilledrectangle($im, 2, 2, $width - 4, $height - 4, $bgc); /* create a rectangle, leaving 2 px border */
embedText($im, $error, 100, '255, 0, 0', WT_ROOT . Config::FONT_DEJAVU_SANS_TTF, 'top', 'left');
http_response_code(404);
header('Content-Type: image/png');
imagepng($im);
imagedestroy($im);
}
/**
* The media firewall passes in an image
* this function can manipulate the image however it wants
* before returning it back to the media firewall
*
* @param resource $im
* @param Tree $tree
*
* @return resource
*/
function applyWatermark($im, Tree $tree) {
// text to watermark with
$word1_text = $tree->getTitle();
// maximum font size for “word1” ; will be automaticaly reduced to fit in the image
$word1_maxsize = 100;
// rgb color codes for text
$word1_color = '0,0,0';
// ttf font file to use
$word1_font = WT_ROOT . Config::FONT_DEJAVU_SANS_TTF;
// vertical position for the text to past; possible values are: top, middle or bottom, across
$word1_vpos = 'across';
// horizontal position for the text to past in media file; possible values are: left, right, top2bottom, bottom2top
// this value is used only if $word1_vpos=across
$word1_hpos = 'left';
$word2_text = $_SERVER['HTTP_HOST'];
$word2_maxsize = 20;
$word2_color = '0,0,0';
$word2_font = WT_ROOT . Config::FONT_DEJAVU_SANS_TTF;
$word2_vpos = 'top';
$word2_hpos = 'top2bottom';
embedText($im, $word1_text, $word1_maxsize, $word1_color, $word1_font, $word1_vpos, $word1_hpos);
embedText($im, $word2_text, $word2_maxsize, $word2_color, $word2_font, $word2_vpos, $word2_hpos);
return $im;
}
/**
* Embed text into an image.
*
* @param resource $im
* @param string $text
* @param int $maxsize
* @param string $color
* @param string $font
* @param string $vpos
* @param string $hpos
*/
function embedText($im, $text, $maxsize, $color, $font, $vpos, $hpos) {
global $useTTF;
// there are two ways to embed text with PHP
// (preferred) using GD and FreeType you can embed text using any True Type font
// (fall back) if that is not available, you can insert basic monospaced text
$col = explode(',', $color);
$textcolor = imagecolorallocate($im, $col[0], $col[1], $col[2]);
// make adjustments to settings that imagestring and imagestringup cant handle
if (!$useTTF) {
// imagestringup only writes up, cant use top2bottom
if ($hpos === 'top2bottom') {
$hpos = 'bottom2top';
}
}
$text = I18N::reverseText($text);
$height = imagesy($im);
$width = imagesx($im);
$calc_angle = rad2deg(atan($height / $width));
$hypoth = $height / sin(deg2rad($calc_angle));
// vertical and horizontal position of the text
switch ($vpos) {
default:
case 'top':
$taille = textlength($maxsize, $width, $text);
$pos_y = $height * 0.15 + $taille;
$pos_x = $width * 0.15;
$rotation = 0;
break;
case 'middle':
$taille = textlength($maxsize, $width, $text);
$pos_y = ($height + $taille) / 2;
$pos_x = $width * 0.15;
$rotation = 0;
break;
case 'bottom':
$taille = textlength($maxsize, $width, $text);
$pos_y = ($height * .85 - $taille);
$pos_x = $width * 0.15;
$rotation = 0;
break;
case 'across':
switch ($hpos) {
default:
case 'left':
$taille = textlength($maxsize, $hypoth, $text);
$pos_y = ($height * .85 - $taille);
$pos_x = $width * 0.15;
$rotation = $calc_angle;
break;
case 'right':
$taille = textlength($maxsize, $hypoth, $text);
$pos_y = ($height * .15 - $taille);
$pos_x = $width * 0.85;
$rotation = $calc_angle + 180;
break;
case 'top2bottom':
$taille = textlength($maxsize, $height, $text);
$pos_y = ($height * .15 - $taille);
$pos_x = ($width * .90 - $taille);
$rotation = -90;
break;
case 'bottom2top':
$taille = textlength($maxsize, $height, $text);
$pos_y = $height * 0.85;
$pos_x = $width * 0.15;
$rotation = 90;
break;
}
break;
}
// apply the text
if ($useTTF) {
// if imagettftext throws errors, catch them with a custom error handler
set_error_handler('\Fisharebest\Webtrees\\imagettftextErrorHandler');
imagettftext($im, $taille, $rotation, $pos_x, $pos_y, $textcolor, $font, $text);
restore_error_handler();
}
// Dont use an else here since imagettftextErrorHandler may have changed the value of $useTTF from true to false
if (!$useTTF) {
if ($rotation !== 90) {
imagestring($im, 5, $pos_x, $pos_y, $text, $textcolor);
} else {
imagestringup($im, 5, $pos_x, $pos_y, $text, $textcolor);
}
}
}
/**
* Generate an approximate length of text, in pixels.
*
* @param int $t
* @param int $mxl
* @param string $text
*
* @return int
*/
function textlength($t, $mxl, $text) {
$taille_c = $t;
$len = mb_strlen($text);
while (($taille_c - 2) * $len > $mxl) {
$taille_c--;
if ($taille_c == 2) {
break;
}
}
return $taille_c;
}
/**
* imagettftext is the function that is most likely to throw an error
* use this custom error handler to catch and log it
*
* @param int $errno
* @param string $errstr
*
* @return bool
*/
function imagettftextErrorHandler($errno, $errstr) {
global $useTTF, $serverFilename;
// log the error
Log::addErrorLog('Media Firewall error: >' . $errno . '/' . $errstr . '< while processing file >' . $serverFilename . '<');
// change value of useTTF to false so the fallback watermarking can be used.
$useTTF = false;
return true;
}
/**
* Determine if the system supports editing of that image type
*
* @param string $reqtype
*
* @return string|false
*/
function isImageTypeSupported($reqtype) {
$supportByGD = array('jpg' => 'jpeg', 'jpeg' => 'jpeg', 'gif' => 'gif', 'png' => 'png');
$reqtype = strtolower($reqtype);
if (empty($supportByGD[$reqtype])) {
return false;
}
$type = $supportByGD[$reqtype];
if (function_exists('imagecreatefrom' . $type) && function_exists('image' . $type)) {
return $type;
}
return false;
}
// this needs to be a global variable so imagettftextErrorHandler can set it
$useTTF = function_exists('imagettftext');
// Media object missing/private?
if (!$media || !$media->canShow()) {
send404AsImage();
return;
}
// media file somewhere else?
if ($media->isExternal()) {
header('Location: ' . $media->getFilename());
return;
}
$which = $thumb ? 'thumb' : 'main';
$serverFilename = $media->getServerFilename($which);
if (!file_exists($serverFilename)) {
send404AsImage();
return;
}
$mimetype = $media->mimeType();
$imgsize = $media->getImageAttributes($which);
$filetime = $media->getFiletime($which);
$filetimeHeader = gmdate('D, d M Y H:i:s', $filetime) . ' GMT';
$expireOffset = 3600 * 24; // tell browser to cache this image for 24 hours
if (Filter::get('cb')) {
$expireOffset = $expireOffset * 7;
} // if cb parameter was sent, cache for 7 days
$expireHeader = gmdate('D, d M Y H:i:s', WT_TIMESTAMP + $expireOffset) . ' GMT';
$type = isImageTypeSupported($imgsize['ext']);
$usewatermark = false;
// if this image supports watermarks and the watermark module is intalled...
if ($type) {
// if this is not a thumbnail, or WATERMARK_THUMB is true
if (($which === 'main') || $WT_TREE->getPreference('WATERMARK_THUMB')) {
// if the users privs justify it...
if (Auth::accessLevel($WT_TREE) > $WT_TREE->getPreference('SHOW_NO_WATERMARK')) {
// add a watermark
$usewatermark = true;
}
}
}
// determine whether we have enough memory to watermark this image
if ($usewatermark) {
if (!FunctionsMedia::hasMemoryForImage($serverFilename)) {
// not enough memory to watermark this file
$usewatermark = false;
}
}
$watermarkfile = '';
$generatewatermark = false;
if ($usewatermark) {
if ($which === 'thumb') {
$watermarkfile = WT_DATA_DIR . $WT_TREE->getPreference('MEDIA_DIRECTORY') . 'watermark/' . $WT_TREE->getName() . '/thumb/' . $media->getFilename();
} else {
$watermarkfile = WT_DATA_DIR . $WT_TREE->getPreference('MEDIA_DIRECTORY') . 'watermark/' . $WT_TREE->getName() . '/' . $media->getFilename();
}
if (!file_exists($watermarkfile)) {
// no saved watermark file exists
// generate the watermark file
$generatewatermark = true;
} else {
$watermarktime = filemtime($watermarkfile);
if ($filetime > $watermarktime) {
// if the original image was updated after the saved file was created
// generate the watermark file
$generatewatermark = true;
}
}
}
$etag = $media->getEtag($which);
// parse IF_MODIFIED_SINCE header from client
$if_modified_since = 'x';
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$if_modified_since = preg_replace('/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE']);
}
// parse IF_NONE_MATCH header from client
$if_none_match = 'x';
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
$if_none_match = str_replace('"', '', $_SERVER['HTTP_IF_NONE_MATCH']);
}
// add caching headers. allow browser to cache file, but not proxy
header('Last-Modified: ' . $filetimeHeader);
header('ETag: "' . $etag . '"');
header('Expires: ' . $expireHeader);
header('Cache-Control: max-age=' . $expireOffset . ', s-maxage=0, proxy-revalidate');
// if this file is already in the users cache, dont resend it
// first check if the if_modified_since param matches
if ($if_modified_since === $filetimeHeader) {
// then check if the etag matches
if ($if_none_match === $etag) {
http_response_code(304);
return;
}
}
// send headers for the image
header('Content-Type: ' . $mimetype);
header('Content-Disposition: filename="' . addslashes(basename($media->getFilename())) . '"');
if ($generatewatermark) {
// generate the watermarked image
$imCreateFunc = 'imagecreatefrom' . $type;
$imSendFunc = 'image' . $type;
if (function_exists($imCreateFunc) && function_exists($imSendFunc)) {
$im = $imCreateFunc($serverFilename);
$im = applyWatermark($im, $WT_TREE);
// save the image, if preferences allow
if ($which === 'thumb' && $WT_TREE->getPreference('SAVE_WATERMARK_THUMB') || $which === 'main' && $WT_TREE->getPreference('SAVE_WATERMARK_IMAGE')) {
// make sure the folder exists
File::mkdir(dirname($watermarkfile));
// save the image
$imSendFunc($im, $watermarkfile);
}
// send the image
$imSendFunc($im);
imagedestroy($im);
return;
} else {
// this image is defective. log it
Log::addMediaLog('Media Firewall error: >' . I18N::translate('This media file is broken and cannot be watermarked.') . '< in file >' . $serverFilename . '< memory used: ' . memory_get_usage());
// set usewatermark to false so image will simply be passed through below
$usewatermark = false;
}
}
// pass the image through without manipulating it
if ($usewatermark) {
// the stored watermarked image is good, lets use it
$serverFilename = $watermarkfile;
}
// determine filesize of image (could be original or watermarked version)
$filesize = filesize($serverFilename);
// set content-length header, send file
header('Content-Length: ' . $filesize);
// Some servers disable fpassthru() and readfile()
if (function_exists('readfile')) {
readfile($serverFilename);
} else {
$fp = fopen($serverFilename, 'rb');
if (function_exists('fpassthru')) {
fpassthru($fp);
} else {
while (!feof($fp)) {
echo fread($fp, 65536);
}
}
fclose($fp);
}