added support for dynamic content controllers

Also implemented a placeholder generator to showcase it
This commit is contained in:
Chris
2023-08-26 22:39:08 +02:00
parent c90a716d45
commit 19d7cb060e
10 changed files with 346 additions and 3 deletions

View File

@@ -56,6 +56,7 @@ Then open http://localhost:8080 in your browser
## New Features in v2
- Generate placeholder images by specifying the size in the URL. [example](https://pictshare.net/placeholder/555x250/color-white-blue)
- Added support for external storage
- [Encryption of files in external storage](/rtfm/ENCRYPTION.md)
- Added text hosting (like pastebin)
@@ -105,13 +106,13 @@ Read [here](/rtfm/CONFIG.md) what those options do
- [x] MASTER_DELETE_CODE
- [x] MASTER_DELETE_IP
- [x] UPLOAD_FORM_LOCATION
- [x] S3 Backend
- [ ] UPLOAD_QUOTA
- [ ] UPLOAD_CODE
- [ ] LOW_PROFILE
- [ ] IMAGE_CHANGE_CODE
- [ ] MAX_RESIZED_IMAGES
- [ ] ALLOW_BLOATING
- [ ] BACKBLAZE
### Image hosting
- [x] Resizing

View File

@@ -10,6 +10,7 @@
class ImageController implements ContentController
{
public const ctype = 'static';
//returns all extensions registered by this type of content
public function getRegisteredExtensions(){return array('png','bmp','gif','jpg','jpeg','x-png','webp');}

View File

@@ -0,0 +1,52 @@
<?php
class PlaceholderController implements ContentController
{
public const ctype = 'dynamic';
//returns all extensions registered by this type of content
public function getRegisteredExtensions(){return array('placeholder');}
public function handleHash($hash,$url)
{
$path = ROOT.DS.'data'.DS.$hash.DS.$hash;
include_once(dirname(__FILE__).DS.'placeholdergenerator.php');
$pg = new PlaceholderGenerator();
foreach($url as $u)
{
if(isSize($u))
$modifiers['size'] = $u;
if(startsWith($u,'color-'))
{
$u = substr($u,6);
$colors = explode('-',$u);
foreach($colors as $c)
if(isColor($c))
$modifiers['colors'][] = (ctype_xdigit($c)?$c:color_name_to_hex($c));
if(count($modifiers['colors'])>4)
$modifiers['colors'] = array_slice($modifiers['colors'],0,4);
}
}
$img = $pg->generateImage($modifiers);
$img = $pg->gradient($img, $modifiers['colors']);
$img = $pg->addSizeText($img,$modifiers);
header ("Content-type: image/jpeg");
header ("ETag: $hash");
header('Cache-control: public, max-age=31536000');
imagejpeg($img,null,(defined('JPEG_COMPRESSION')?JPEG_COMPRESSION:90));
}
public function handleUpload($tmpfile,$hash=false)
{
return array('status'=>'err','hash'=>$hash,'reason'=>'Cannot upload to placeholder image');
}
}

View File

@@ -0,0 +1,88 @@
<?php
class PlaceholderGenerator {
function generateImage($modifiers)
{
$size = ($modifiers['size']?:'800x600');
$sd = sizeStringToWidthHeight($size);
$width = $sd['width'];
$height = $sd['height'];
$im = imagecreatetruecolor($width, $height);
return $im;
}
function addSizeText($im,$modifiers)
{
$size = imagesx($im).'x'.imagesy($im);
$text = $size;
//add the size as text in the center of the image
$textcolor = imagecolorallocate($im, 0, 0, 0);
$font = dirname(__FILE__).DS.'fonts/RonysiswadiArchitect5-1GErv.ttf';
//calculate the size of the text to make sure it will alway be visible
$fontsize = 20;
$textsize = imagettfbbox($fontsize, 0, $font, $text);
$scaleX = imagesx($im) / ($textsize[2] - $textsize[0] + 25);
$scaleY = imagesy($im) / ($textsize[1] - $textsize[7] + 25);
$scale = min($scaleX,$scaleY);
$fontsize = 20 * $scale;
$textsize = imagettfbbox($fontsize, 0, $font, $text);
$textwidth = $textsize[2] - $textsize[0];
$textheight = $textsize[1] - $textsize[7];
if($textwidth > imagesx($im) || $textheight > imagesy($im))
return $im;
$x = (imagesx($im) - $textwidth) / 2;
$y = (imagesy($im) - $textheight) / 2 + $textheight;
imagettftext($im, $fontsize, 0, $x, $y, $textcolor, $font, $text);
return $im;
}
function gradient($im, $c) {
$w = imagesx($im);
$h = imagesy($im);
if(!$c[0]) $c = ['ffffff','ffffff','ffffff','ffffff'];
else if(!$c[1]) $c = [$c[0],$c[0],$c[0],$c[0]];
else if(!$c[2]) $c = [$c[0],$c[0],$c[1],$c[1]];
else if(!$c[3]) $c = [$c[0],$c[1],$c[2],$c[0]];
for($i=0;$i<=3;$i++) {
$c[$i]=$this->hex2rgb($c[$i]);
}
$rgb=$c[0]; // start with top left color
for($x=0;$x<=$w;$x++) { // loop columns
for($y=0;$y<=$h;$y++) { // loop rows
// set pixel color
$col=imagecolorallocate($im,$rgb[0],$rgb[1],$rgb[2]);
imagesetpixel($im,$x-1,$y-1,$col);
// calculate new color
for($i=0;$i<=2;$i++) {
$rgb[$i]=
$c[0][$i]*(($w-$x)*($h-$y)/($w*$h)) +
$c[1][$i]*($x *($h-$y)/($w*$h)) +
$c[2][$i]*(($w-$x)*$y /($w*$h)) +
$c[3][$i]*($x *$y /($w*$h));
}
}
}
return $im;
}
function hex2rgb($hex)
{
$rgb[0]=hexdec(substr($hex,0,2));
$rgb[1]=hexdec(substr($hex,2,2));
$rgb[2]=hexdec(substr($hex,4,2));
return($rgb);
}
}

View File

@@ -2,6 +2,8 @@
class TextController implements ContentController
{
public const ctype = 'static';
//returns all extensions registered by this type of content
public function getRegisteredExtensions(){return array('txt','text','csv');}

View File

@@ -2,6 +2,8 @@
class UrlController implements ContentController
{
public const ctype = 'static';
//returns all extensions registered by this type of content
public function getRegisteredExtensions(){return array('url');}
public function handleHash($hash,$url){}

View File

@@ -2,6 +2,8 @@
class VideoController implements ContentController
{
public const ctype = 'static';
//returns all extensions registered by this type of content
public function getRegisteredExtensions(){return array('mp4');}

View File

@@ -76,7 +76,21 @@ function architect($url)
break; // we break here because we already have the file. no need to check other storage controllers
}
}
}
}
// if it's still false, we only have one hope: Maybe it's from a dynamic controller and the cache hasn't been created yet
else if($hash===false)
{
foreach(loadAllContentControllers(true) as $cc)
{
if((new $cc)::ctype=='dynamic' && in_array((new $cc)->getRegisteredExtensions()[0],$u) )
{
$hash = true;
break;
}
}
}
}
@@ -112,7 +126,10 @@ function architect($url)
foreach(loadAllContentControllers(true) as $cc)
{
if(in_array($extension,(new $cc)->getRegisteredExtensions()))
if(
((new $cc)::ctype=='dynamic' && in_array((new $cc)->getRegisteredExtensions()[0],$u)) ||
((new $cc)::ctype=='static' && in_array($extension,(new $cc)->getRegisteredExtensions()))
)
{
(new $cc())->handleHash($hash,$u);
return;
@@ -464,6 +481,176 @@ function isSize($var)
return true;
}
function isColor($var)
{
if(strlen($var)==6 && ctype_xdigit($var)) return true;
else
{
$col = color_name_to_hex($var);
if($col) return true;
else return false;
}
}
function color_name_to_hex($color_name)
{
// standard 147 HTML color names
$colors = array(
'aliceblue'=>'F0F8FF',
'antiquewhite'=>'FAEBD7',
'aqua'=>'00FFFF',
'aquamarine'=>'7FFFD4',
'azure'=>'F0FFFF',
'beige'=>'F5F5DC',
'bisque'=>'FFE4C4',
'black'=>'000000',
'blanchedalmond '=>'FFEBCD',
'blue'=>'0000FF',
'blueviolet'=>'8A2BE2',
'brown'=>'A52A2A',
'burlywood'=>'DEB887',
'cadetblue'=>'5F9EA0',
'chartreuse'=>'7FFF00',
'chocolate'=>'D2691E',
'coral'=>'FF7F50',
'cornflowerblue'=>'6495ED',
'cornsilk'=>'FFF8DC',
'crimson'=>'DC143C',
'cyan'=>'00FFFF',
'darkblue'=>'00008B',
'darkcyan'=>'008B8B',
'darkgoldenrod'=>'B8860B',
'darkgray'=>'A9A9A9',
'darkgreen'=>'006400',
'darkgrey'=>'A9A9A9',
'darkkhaki'=>'BDB76B',
'darkmagenta'=>'8B008B',
'darkolivegreen'=>'556B2F',
'darkorange'=>'FF8C00',
'darkorchid'=>'9932CC',
'darkred'=>'8B0000',
'darksalmon'=>'E9967A',
'darkseagreen'=>'8FBC8F',
'darkslateblue'=>'483D8B',
'darkslategray'=>'2F4F4F',
'darkslategrey'=>'2F4F4F',
'darkturquoise'=>'00CED1',
'darkviolet'=>'9400D3',
'deeppink'=>'FF1493',
'deepskyblue'=>'00BFFF',
'dimgray'=>'696969',
'dimgrey'=>'696969',
'dodgerblue'=>'1E90FF',
'firebrick'=>'B22222',
'floralwhite'=>'FFFAF0',
'forestgreen'=>'228B22',
'fuchsia'=>'FF00FF',
'gainsboro'=>'DCDCDC',
'ghostwhite'=>'F8F8FF',
'gold'=>'FFD700',
'goldenrod'=>'DAA520',
'gray'=>'808080',
'green'=>'008000',
'greenyellow'=>'ADFF2F',
'grey'=>'808080',
'honeydew'=>'F0FFF0',
'hotpink'=>'FF69B4',
'indianred'=>'CD5C5C',
'indigo'=>'4B0082',
'ivory'=>'FFFFF0',
'khaki'=>'F0E68C',
'lavender'=>'E6E6FA',
'lavenderblush'=>'FFF0F5',
'lawngreen'=>'7CFC00',
'lemonchiffon'=>'FFFACD',
'lightblue'=>'ADD8E6',
'lightcoral'=>'F08080',
'lightcyan'=>'E0FFFF',
'lightgoldenrodyellow'=>'FAFAD2',
'lightgray'=>'D3D3D3',
'lightgreen'=>'90EE90',
'lightgrey'=>'D3D3D3',
'lightpink'=>'FFB6C1',
'lightsalmon'=>'FFA07A',
'lightseagreen'=>'20B2AA',
'lightskyblue'=>'87CEFA',
'lightslategray'=>'778899',
'lightslategrey'=>'778899',
'lightsteelblue'=>'B0C4DE',
'lightyellow'=>'FFFFE0',
'lime'=>'00FF00',
'limegreen'=>'32CD32',
'linen'=>'FAF0E6',
'magenta'=>'FF00FF',
'maroon'=>'800000',
'mediumaquamarine'=>'66CDAA',
'mediumblue'=>'0000CD',
'mediumorchid'=>'BA55D3',
'mediumpurple'=>'9370D0',
'mediumseagreen'=>'3CB371',
'mediumslateblue'=>'7B68EE',
'mediumspringgreen'=>'00FA9A',
'mediumturquoise'=>'48D1CC',
'mediumvioletred'=>'C71585',
'midnightblue'=>'191970',
'mintcream'=>'F5FFFA',
'mistyrose'=>'FFE4E1',
'moccasin'=>'FFE4B5',
'navajowhite'=>'FFDEAD',
'navy'=>'000080',
'oldlace'=>'FDF5E6',
'olive'=>'808000',
'olivedrab'=>'6B8E23',
'orange'=>'FFA500',
'orangered'=>'FF4500',
'orchid'=>'DA70D6',
'palegoldenrod'=>'EEE8AA',
'palegreen'=>'98FB98',
'paleturquoise'=>'AFEEEE',
'palevioletred'=>'DB7093',
'papayawhip'=>'FFEFD5',
'peachpuff'=>'FFDAB9',
'peru'=>'CD853F',
'pink'=>'FFC0CB',
'plum'=>'DDA0DD',
'powderblue'=>'B0E0E6',
'purple'=>'800080',
'red'=>'FF0000',
'rosybrown'=>'BC8F8F',
'royalblue'=>'4169E1',
'saddlebrown'=>'8B4513',
'salmon'=>'FA8072',
'sandybrown'=>'F4A460',
'seagreen'=>'2E8B57',
'seashell'=>'FFF5EE',
'sienna'=>'A0522D',
'silver'=>'C0C0C0',
'skyblue'=>'87CEEB',
'slateblue'=>'6A5ACD',
'slategray'=>'708090',
'slategrey'=>'708090',
'snow'=>'FFFAFA',
'springgreen'=>'00FF7F',
'steelblue'=>'4682B4',
'tan'=>'D2B48C',
'teal'=>'008080',
'thistle'=>'D8BFD8',
'tomato'=>'FF6347',
'turquoise'=>'40E0D0',
'violet'=>'EE82EE',
'wheat'=>'F5DEB3',
'white'=>'FFFFFF',
'whitesmoke'=>'F5F5F5',
'yellow'=>'FFFF00',
'yellowgreen'=>'9ACD32');
$color_name = strtolower($color_name);
if (isset($colors[$color_name]))
return $colors[$color_name];
else
return false;
}
function getStorageControllers()
{
$controllers = array();

View File

@@ -6,6 +6,14 @@
interface ContentController
{
/**
* When implementing a new content controller, you must specify a constant with the name of "ctype"
* This constant will be used to distinguish static controllers (which have files on disk) from dynamic ones (which don't, like the placeholder controller)
*
* Possible values: 'static' or 'dynamic'
*/
//const ctype = 'static';
/** This method will return all file extensions that will be associated with this content type
* for example 'pdf' but it could be anything really. You just need a way later to confirm that a type is what it says it is
*