Addition of files
This commit is contained in:
717
includes/Gregwar/Captcha/CaptchaBuilder.php
Normal file
717
includes/Gregwar/Captcha/CaptchaBuilder.php
Normal file
@@ -0,0 +1,717 @@
|
||||
<?php
|
||||
|
||||
namespace Gregwar\Captcha;
|
||||
|
||||
use \Exception;
|
||||
|
||||
/**
|
||||
* Builds a new captcha image
|
||||
* Uses the fingerprint parameter, if one is passed, to generate the same image
|
||||
*
|
||||
* @author Gregwar <g.passault@gmail.com>
|
||||
* @author Jeremy Livingston <jeremy.j.livingston@gmail.com>
|
||||
*/
|
||||
class CaptchaBuilder implements CaptchaBuilderInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fingerprint = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $useFingerprint = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $textColor = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $backgroundColor = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $backgroundImages = array();
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $contents = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $phrase = null;
|
||||
|
||||
/**
|
||||
* @var PhraseBuilderInterface
|
||||
*/
|
||||
protected $builder;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $distortion = true;
|
||||
|
||||
/**
|
||||
* The maximum number of lines to draw in front of
|
||||
* the image. null - use default algorithm
|
||||
*/
|
||||
protected $maxFrontLines = null;
|
||||
|
||||
/**
|
||||
* The maximum number of lines to draw behind
|
||||
* the image. null - use default algorithm
|
||||
*/
|
||||
protected $maxBehindLines = null;
|
||||
|
||||
/**
|
||||
* The maximum angle of char
|
||||
*/
|
||||
protected $maxAngle = 8;
|
||||
|
||||
/**
|
||||
* The maximum offset of char
|
||||
*/
|
||||
protected $maxOffset = 5;
|
||||
|
||||
/**
|
||||
* Is the interpolation enabled ?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $interpolation = true;
|
||||
|
||||
/**
|
||||
* Ignore all effects
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $ignoreAllEffects = false;
|
||||
|
||||
/**
|
||||
* Allowed image types for the background images
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $allowedBackgroundImageTypes = array('image/png', 'image/jpeg', 'image/gif');
|
||||
|
||||
/**
|
||||
* The image contents
|
||||
*/
|
||||
public function getContents()
|
||||
{
|
||||
return $this->contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/Disables the interpolation
|
||||
*
|
||||
* @param $interpolate bool True to enable, false to disable
|
||||
*
|
||||
* @return CaptchaBuilder
|
||||
*/
|
||||
public function setInterpolation($interpolate = true)
|
||||
{
|
||||
$this->interpolation = $interpolate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary dir, for OCR check
|
||||
*/
|
||||
public $tempDir = 'temp/';
|
||||
|
||||
public function __construct($phrase = null, PhraseBuilderInterface $builder = null)
|
||||
{
|
||||
if ($builder === null) {
|
||||
$this->builder = new PhraseBuilder;
|
||||
} else {
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
$this->phrase = is_string($phrase) ? $phrase : $this->builder->build($phrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting the phrase
|
||||
*/
|
||||
public function setPhrase($phrase)
|
||||
{
|
||||
$this->phrase = (string) $phrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disable distortion
|
||||
*/
|
||||
public function setDistortion($distortion)
|
||||
{
|
||||
$this->distortion = (bool) $distortion;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMaxBehindLines($maxBehindLines)
|
||||
{
|
||||
$this->maxBehindLines = $maxBehindLines;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMaxFrontLines($maxFrontLines)
|
||||
{
|
||||
$this->maxFrontLines = $maxFrontLines;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMaxAngle($maxAngle)
|
||||
{
|
||||
$this->maxAngle = $maxAngle;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMaxOffset($maxOffset)
|
||||
{
|
||||
$this->maxOffset = $maxOffset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the captcha phrase
|
||||
*/
|
||||
public function getPhrase()
|
||||
{
|
||||
return $this->phrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given phrase is good
|
||||
*/
|
||||
public function testPhrase($phrase)
|
||||
{
|
||||
return ($this->builder->niceize($phrase) == $this->builder->niceize($this->getPhrase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiation
|
||||
*/
|
||||
public static function create($phrase = null)
|
||||
{
|
||||
return new self($phrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text color to use
|
||||
*/
|
||||
public function setTextColor($r, $g, $b)
|
||||
{
|
||||
$this->textColor = array($r, $g, $b);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the background color to use
|
||||
*/
|
||||
public function setBackgroundColor($r, $g, $b)
|
||||
{
|
||||
$this->backgroundColor = array($r, $g, $b);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ignoreAllEffects value
|
||||
*
|
||||
* @param bool $ignoreAllEffects
|
||||
* @return CaptchaBuilder
|
||||
*/
|
||||
public function setIgnoreAllEffects($ignoreAllEffects)
|
||||
{
|
||||
$this->ignoreAllEffects = $ignoreAllEffects;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of background images to use (one image is randomly selected)
|
||||
*/
|
||||
public function setBackgroundImages(array $backgroundImages)
|
||||
{
|
||||
$this->backgroundImages = $backgroundImages;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw lines over the image
|
||||
*/
|
||||
protected function drawLine($image, $width, $height, $tcol = null)
|
||||
{
|
||||
if ($tcol === null) {
|
||||
$tcol = imagecolorallocate($image, $this->rand(100, 255), $this->rand(100, 255), $this->rand(100, 255));
|
||||
}
|
||||
|
||||
if ($this->rand(0, 1)) { // Horizontal
|
||||
$Xa = $this->rand(0, $width/2);
|
||||
$Ya = $this->rand(0, $height);
|
||||
$Xb = $this->rand($width/2, $width);
|
||||
$Yb = $this->rand(0, $height);
|
||||
} else { // Vertical
|
||||
$Xa = $this->rand(0, $width);
|
||||
$Ya = $this->rand(0, $height/2);
|
||||
$Xb = $this->rand(0, $width);
|
||||
$Yb = $this->rand($height/2, $height);
|
||||
}
|
||||
imagesetthickness($image, $this->rand(1, 3));
|
||||
imageline($image, $Xa, $Ya, $Xb, $Yb, $tcol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply some post effects
|
||||
*/
|
||||
protected function postEffect($image)
|
||||
{
|
||||
if (!function_exists('imagefilter')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->backgroundColor != null || $this->textColor != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Negate ?
|
||||
if ($this->rand(0, 1) == 0) {
|
||||
imagefilter($image, IMG_FILTER_NEGATE);
|
||||
}
|
||||
|
||||
// Edge ?
|
||||
if ($this->rand(0, 10) == 0) {
|
||||
imagefilter($image, IMG_FILTER_EDGEDETECT);
|
||||
}
|
||||
|
||||
// Contrast
|
||||
imagefilter($image, IMG_FILTER_CONTRAST, $this->rand(-50, 10));
|
||||
|
||||
// Colorize
|
||||
if ($this->rand(0, 5) == 0) {
|
||||
imagefilter($image, IMG_FILTER_COLORIZE, $this->rand(-80, 50), $this->rand(-80, 50), $this->rand(-80, 50));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the phrase on the image
|
||||
*/
|
||||
protected function writePhrase($image, $phrase, $font, $width, $height)
|
||||
{
|
||||
$length = mb_strlen($phrase);
|
||||
if ($length === 0) {
|
||||
return \imagecolorallocate($image, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Gets the text size and start position
|
||||
$size = $width / $length - $this->rand(0, 3) - 1;
|
||||
$box = \imagettfbbox($size, 0, $font, $phrase);
|
||||
$textWidth = $box[2] - $box[0];
|
||||
$textHeight = $box[1] - $box[7];
|
||||
$x = ($width - $textWidth) / 2;
|
||||
$y = ($height - $textHeight) / 2 + $size;
|
||||
|
||||
if (!$this->textColor) {
|
||||
$textColor = array($this->rand(0, 150), $this->rand(0, 150), $this->rand(0, 150));
|
||||
} else {
|
||||
$textColor = $this->textColor;
|
||||
}
|
||||
$col = \imagecolorallocate($image, $textColor[0], $textColor[1], $textColor[2]);
|
||||
|
||||
// Write the letters one by one, with random angle
|
||||
for ($i=0; $i<$length; $i++) {
|
||||
$symbol = mb_substr($phrase, $i, 1);
|
||||
$box = \imagettfbbox($size, 0, $font, $symbol);
|
||||
$w = $box[2] - $box[0];
|
||||
$angle = $this->rand(-$this->maxAngle, $this->maxAngle);
|
||||
$offset = $this->rand(-$this->maxOffset, $this->maxOffset);
|
||||
\imagettftext($image, $size, $angle, $x, $y + $offset, $col, $font, $symbol);
|
||||
$x += $w;
|
||||
}
|
||||
|
||||
return $col;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to read the code against an OCR
|
||||
*/
|
||||
public function isOCRReadable()
|
||||
{
|
||||
if (!is_dir($this->tempDir)) {
|
||||
@mkdir($this->tempDir, 0755, true);
|
||||
}
|
||||
|
||||
$tempj = $this->tempDir . uniqid('captcha', true) . '.jpg';
|
||||
$tempp = $this->tempDir . uniqid('captcha', true) . '.pgm';
|
||||
|
||||
$this->save($tempj);
|
||||
shell_exec("convert $tempj $tempp");
|
||||
$value = trim(strtolower(shell_exec("ocrad $tempp")));
|
||||
|
||||
@unlink($tempj);
|
||||
@unlink($tempp);
|
||||
|
||||
return $this->testPhrase($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds while the code is readable against an OCR
|
||||
*/
|
||||
public function buildAgainstOCR($width = 150, $height = 40, $font = null, $fingerprint = null)
|
||||
{
|
||||
do {
|
||||
$this->build($width, $height, $font, $fingerprint);
|
||||
} while ($this->isOCRReadable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the image
|
||||
*/
|
||||
public function build($width = 150, $height = 40, $font = null, $fingerprint = null)
|
||||
{
|
||||
if (null !== $fingerprint) {
|
||||
$this->fingerprint = $fingerprint;
|
||||
$this->useFingerprint = true;
|
||||
} else {
|
||||
$this->fingerprint = array();
|
||||
$this->useFingerprint = false;
|
||||
}
|
||||
|
||||
if ($font === null) {
|
||||
$font = __DIR__ . '/Font/captcha'.$this->rand(0, 5).'.ttf';
|
||||
}
|
||||
|
||||
if (empty($this->backgroundImages)) {
|
||||
// if background images list is not set, use a color fill as a background
|
||||
$image = imagecreatetruecolor($width, $height);
|
||||
if ($this->backgroundColor == null) {
|
||||
$bg = imagecolorallocate($image, $this->rand(200, 255), $this->rand(200, 255), $this->rand(200, 255));
|
||||
} else {
|
||||
$color = $this->backgroundColor;
|
||||
$bg = imagecolorallocate($image, $color[0], $color[1], $color[2]);
|
||||
}
|
||||
$this->background = $bg;
|
||||
imagefill($image, 0, 0, $bg);
|
||||
} else {
|
||||
// use a random background image
|
||||
$randomBackgroundImage = $this->backgroundImages[rand(0, count($this->backgroundImages)-1)];
|
||||
|
||||
$imageType = $this->validateBackgroundImage($randomBackgroundImage);
|
||||
|
||||
$image = $this->createBackgroundImageFromType($randomBackgroundImage, $imageType);
|
||||
}
|
||||
|
||||
// Apply effects
|
||||
if (!$this->ignoreAllEffects) {
|
||||
$square = $width * $height;
|
||||
$effects = $this->rand($square/3000, $square/2000);
|
||||
|
||||
// set the maximum number of lines to draw in front of the text
|
||||
if ($this->maxBehindLines != null && $this->maxBehindLines > 0) {
|
||||
$effects = min($this->maxBehindLines, $effects);
|
||||
}
|
||||
|
||||
if ($this->maxBehindLines !== 0) {
|
||||
for ($e = 0; $e < $effects; $e++) {
|
||||
$this->drawLine($image, $width, $height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write CAPTCHA text
|
||||
$color = $this->writePhrase($image, $this->phrase, $font, $width, $height);
|
||||
|
||||
// Apply effects
|
||||
if (!$this->ignoreAllEffects) {
|
||||
$square = $width * $height;
|
||||
$effects = $this->rand($square/3000, $square/2000);
|
||||
|
||||
// set the maximum number of lines to draw in front of the text
|
||||
if ($this->maxFrontLines != null && $this->maxFrontLines > 0) {
|
||||
$effects = min($this->maxFrontLines, $effects);
|
||||
}
|
||||
|
||||
if ($this->maxFrontLines !== 0) {
|
||||
for ($e = 0; $e < $effects; $e++) {
|
||||
$this->drawLine($image, $width, $height, $color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Distort the image
|
||||
if ($this->distortion && !$this->ignoreAllEffects) {
|
||||
$image = $this->distort($image, $width, $height, $bg);
|
||||
}
|
||||
|
||||
// Post effects
|
||||
if (!$this->ignoreAllEffects) {
|
||||
$this->postEffect($image);
|
||||
}
|
||||
|
||||
$this->contents = $image;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Distorts the image
|
||||
*/
|
||||
public function distort($image, $width, $height, $bg)
|
||||
{
|
||||
$contents = imagecreatetruecolor($width, $height);
|
||||
$X = $this->rand(0, $width);
|
||||
$Y = $this->rand(0, $height);
|
||||
$phase = $this->rand(0, 10);
|
||||
$scale = 1.1 + $this->rand(0, 10000) / 30000;
|
||||
for ($x = 0; $x < $width; $x++) {
|
||||
for ($y = 0; $y < $height; $y++) {
|
||||
$Vx = $x - $X;
|
||||
$Vy = $y - $Y;
|
||||
$Vn = sqrt($Vx * $Vx + $Vy * $Vy);
|
||||
|
||||
if ($Vn != 0) {
|
||||
$Vn2 = $Vn + 4 * sin($Vn / 30);
|
||||
$nX = $X + ($Vx * $Vn2 / $Vn);
|
||||
$nY = $Y + ($Vy * $Vn2 / $Vn);
|
||||
} else {
|
||||
$nX = $X;
|
||||
$nY = $Y;
|
||||
}
|
||||
$nY = $nY + $scale * sin($phase + $nX * 0.2);
|
||||
|
||||
if ($this->interpolation) {
|
||||
$p = $this->interpolate(
|
||||
$nX - floor($nX),
|
||||
$nY - floor($nY),
|
||||
$this->getCol($image, floor($nX), floor($nY), $bg),
|
||||
$this->getCol($image, ceil($nX), floor($nY), $bg),
|
||||
$this->getCol($image, floor($nX), ceil($nY), $bg),
|
||||
$this->getCol($image, ceil($nX), ceil($nY), $bg)
|
||||
);
|
||||
} else {
|
||||
$p = $this->getCol($image, round($nX), round($nY), $bg);
|
||||
}
|
||||
|
||||
if ($p == 0) {
|
||||
$p = $bg;
|
||||
}
|
||||
|
||||
imagesetpixel($contents, $x, $y, $p);
|
||||
}
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Captcha to a jpeg file
|
||||
*/
|
||||
public function save($filename, $quality = 90)
|
||||
{
|
||||
imagejpeg($this->contents, $filename, $quality);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the image GD
|
||||
*/
|
||||
public function getGd()
|
||||
{
|
||||
return $this->contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the image contents
|
||||
*/
|
||||
public function get($quality = 90)
|
||||
{
|
||||
ob_start();
|
||||
$this->output($quality);
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTML inline base64
|
||||
*/
|
||||
public function inline($quality = 90)
|
||||
{
|
||||
return 'data:image/jpeg;base64,' . base64_encode($this->get($quality));
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the image
|
||||
*/
|
||||
public function output($quality = 90)
|
||||
{
|
||||
imagejpeg($this->contents, null, $quality);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFingerprint()
|
||||
{
|
||||
return $this->fingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random number or the next number in the
|
||||
* fingerprint
|
||||
*/
|
||||
protected function rand($min, $max)
|
||||
{
|
||||
if (!is_array($this->fingerprint)) {
|
||||
$this->fingerprint = array();
|
||||
}
|
||||
|
||||
if ($this->useFingerprint) {
|
||||
$value = current($this->fingerprint);
|
||||
next($this->fingerprint);
|
||||
} else {
|
||||
$value = mt_rand($min, $max);
|
||||
$this->fingerprint[] = $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $x
|
||||
* @param $y
|
||||
* @param $nw
|
||||
* @param $ne
|
||||
* @param $sw
|
||||
* @param $se
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function interpolate($x, $y, $nw, $ne, $sw, $se)
|
||||
{
|
||||
list($r0, $g0, $b0) = $this->getRGB($nw);
|
||||
list($r1, $g1, $b1) = $this->getRGB($ne);
|
||||
list($r2, $g2, $b2) = $this->getRGB($sw);
|
||||
list($r3, $g3, $b3) = $this->getRGB($se);
|
||||
|
||||
$cx = 1.0 - $x;
|
||||
$cy = 1.0 - $y;
|
||||
|
||||
$m0 = $cx * $r0 + $x * $r1;
|
||||
$m1 = $cx * $r2 + $x * $r3;
|
||||
$r = (int) ($cy * $m0 + $y * $m1);
|
||||
|
||||
$m0 = $cx * $g0 + $x * $g1;
|
||||
$m1 = $cx * $g2 + $x * $g3;
|
||||
$g = (int) ($cy * $m0 + $y * $m1);
|
||||
|
||||
$m0 = $cx * $b0 + $x * $b1;
|
||||
$m1 = $cx * $b2 + $x * $b3;
|
||||
$b = (int) ($cy * $m0 + $y * $m1);
|
||||
|
||||
return ($r << 16) | ($g << 8) | $b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $image
|
||||
* @param $x
|
||||
* @param $y
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getCol($image, $x, $y, $background)
|
||||
{
|
||||
$L = imagesx($image);
|
||||
$H = imagesy($image);
|
||||
if ($x < 0 || $x >= $L || $y < 0 || $y >= $H) {
|
||||
return $background;
|
||||
}
|
||||
|
||||
return imagecolorat($image, $x, $y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $col
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getRGB($col)
|
||||
{
|
||||
return array(
|
||||
(int) ($col >> 16) & 0xff,
|
||||
(int) ($col >> 8) & 0xff,
|
||||
(int) ($col) & 0xff,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the background image path. Return the image type if valid
|
||||
*
|
||||
* @param string $backgroundImage
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function validateBackgroundImage($backgroundImage)
|
||||
{
|
||||
// check if file exists
|
||||
if (!file_exists($backgroundImage)) {
|
||||
$backgroundImageExploded = explode('/', $backgroundImage);
|
||||
$imageFileName = count($backgroundImageExploded) > 1? $backgroundImageExploded[count($backgroundImageExploded)-1] : $backgroundImage;
|
||||
|
||||
throw new Exception('Invalid background image: ' . $imageFileName);
|
||||
}
|
||||
|
||||
// check image type
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
|
||||
$imageType = finfo_file($finfo, $backgroundImage);
|
||||
finfo_close($finfo);
|
||||
|
||||
if (!in_array($imageType, $this->allowedBackgroundImageTypes)) {
|
||||
throw new Exception('Invalid background image type! Allowed types are: ' . join(', ', $this->allowedBackgroundImageTypes));
|
||||
}
|
||||
|
||||
return $imageType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create background image from type
|
||||
*
|
||||
* @param string $backgroundImage
|
||||
* @param string $imageType
|
||||
* @return resource
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function createBackgroundImageFromType($backgroundImage, $imageType)
|
||||
{
|
||||
switch ($imageType) {
|
||||
case 'image/jpeg':
|
||||
$image = imagecreatefromjpeg($backgroundImage);
|
||||
break;
|
||||
case 'image/png':
|
||||
$image = imagecreatefrompng($backgroundImage);
|
||||
break;
|
||||
case 'image/gif':
|
||||
$image = imagecreatefromgif($backgroundImage);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Not supported file type for background image!');
|
||||
break;
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
29
includes/Gregwar/Captcha/CaptchaBuilderInterface.php
Normal file
29
includes/Gregwar/Captcha/CaptchaBuilderInterface.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Gregwar\Captcha;
|
||||
|
||||
/**
|
||||
* A Captcha builder
|
||||
*/
|
||||
interface CaptchaBuilderInterface
|
||||
{
|
||||
/**
|
||||
* Builds the code
|
||||
*/
|
||||
public function build($width, $height, $font, $fingerprint);
|
||||
|
||||
/**
|
||||
* Saves the code to a file
|
||||
*/
|
||||
public function save($filename, $quality);
|
||||
|
||||
/**
|
||||
* Gets the image contents
|
||||
*/
|
||||
public function get($quality);
|
||||
|
||||
/**
|
||||
* Outputs the image
|
||||
*/
|
||||
public function output($quality);
|
||||
}
|
||||
BIN
includes/Gregwar/Captcha/Font/captcha0.ttf
Normal file
BIN
includes/Gregwar/Captcha/Font/captcha0.ttf
Normal file
Binary file not shown.
BIN
includes/Gregwar/Captcha/Font/captcha1.ttf
Normal file
BIN
includes/Gregwar/Captcha/Font/captcha1.ttf
Normal file
Binary file not shown.
BIN
includes/Gregwar/Captcha/Font/captcha2.ttf
Normal file
BIN
includes/Gregwar/Captcha/Font/captcha2.ttf
Normal file
Binary file not shown.
BIN
includes/Gregwar/Captcha/Font/captcha3.ttf
Normal file
BIN
includes/Gregwar/Captcha/Font/captcha3.ttf
Normal file
Binary file not shown.
BIN
includes/Gregwar/Captcha/Font/captcha4.ttf
Normal file
BIN
includes/Gregwar/Captcha/Font/captcha4.ttf
Normal file
Binary file not shown.
BIN
includes/Gregwar/Captcha/Font/captcha5.ttf
Normal file
BIN
includes/Gregwar/Captcha/Font/captcha5.ttf
Normal file
Binary file not shown.
105
includes/Gregwar/Captcha/ImageFileHandler.php
Normal file
105
includes/Gregwar/Captcha/ImageFileHandler.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Gregwar\Captcha;
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
/**
|
||||
* Handles actions related to captcha image files including saving and garbage collection
|
||||
*
|
||||
* @author Gregwar <g.passault@gmail.com>
|
||||
* @author Jeremy Livingston <jeremy@quizzle.com>
|
||||
*/
|
||||
class ImageFileHandler
|
||||
{
|
||||
/**
|
||||
* Name of folder for captcha images
|
||||
* @var string
|
||||
*/
|
||||
protected $imageFolder;
|
||||
|
||||
/**
|
||||
* Absolute path to public web folder
|
||||
* @var string
|
||||
*/
|
||||
protected $webPath;
|
||||
|
||||
/**
|
||||
* Frequency of garbage collection in fractions of 1
|
||||
* @var int
|
||||
*/
|
||||
protected $gcFreq;
|
||||
|
||||
/**
|
||||
* Maximum age of images in minutes
|
||||
* @var int
|
||||
*/
|
||||
protected $expiration;
|
||||
|
||||
/**
|
||||
* @param $imageFolder
|
||||
* @param $webPath
|
||||
* @param $gcFreq
|
||||
* @param $expiration
|
||||
*/
|
||||
public function __construct($imageFolder, $webPath, $gcFreq, $expiration)
|
||||
{
|
||||
$this->imageFolder = $imageFolder;
|
||||
$this->webPath = $webPath;
|
||||
$this->gcFreq = $gcFreq;
|
||||
$this->expiration = $expiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the provided image content as a file
|
||||
*
|
||||
* @param string $contents
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function saveAsFile($contents)
|
||||
{
|
||||
$this->createFolderIfMissing();
|
||||
|
||||
$filename = md5(uniqid()) . '.jpg';
|
||||
$filePath = $this->webPath . '/' . $this->imageFolder . '/' . $filename;
|
||||
imagejpeg($contents, $filePath, 15);
|
||||
|
||||
return '/' . $this->imageFolder . '/' . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly runs garbage collection on the image directory
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function collectGarbage()
|
||||
{
|
||||
if (!mt_rand(1, $this->gcFreq) == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->createFolderIfMissing();
|
||||
|
||||
$finder = new Finder();
|
||||
$criteria = sprintf('<= now - %s minutes', $this->expiration);
|
||||
$finder->in($this->webPath . '/' . $this->imageFolder)
|
||||
->date($criteria);
|
||||
|
||||
foreach ($finder->files() as $file) {
|
||||
unlink($file->getPathname());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the folder if it doesn't exist
|
||||
*/
|
||||
protected function createFolderIfMissing()
|
||||
{
|
||||
if (!file_exists($this->webPath . '/' . $this->imageFolder)) {
|
||||
mkdir($this->webPath . '/' . $this->imageFolder, 0755);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
includes/Gregwar/Captcha/PhraseBuilder.php
Normal file
59
includes/Gregwar/Captcha/PhraseBuilder.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Gregwar\Captcha;
|
||||
|
||||
/**
|
||||
* Generates random phrase
|
||||
*
|
||||
* @author Gregwar <g.passault@gmail.com>
|
||||
*/
|
||||
class PhraseBuilder implements PhraseBuilderInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $length;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $charset;
|
||||
/**
|
||||
* Constructs a PhraseBuilder with given parameters
|
||||
*/
|
||||
public function __construct($length = 5, $charset = 'abcdefghijklmnpqrstuvwxyz123456789')
|
||||
{
|
||||
$this->length = $length;
|
||||
$this->charset = $charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates random phrase of given length with given charset
|
||||
*/
|
||||
public function build($length = null, $charset = null)
|
||||
{
|
||||
if ($length !== null) {
|
||||
$this->length = $length;
|
||||
}
|
||||
if ($charset !== null) {
|
||||
$this->charset = $charset;
|
||||
}
|
||||
|
||||
$phrase = '';
|
||||
$chars = str_split($this->charset);
|
||||
|
||||
for ($i = 0; $i < $this->length; $i++) {
|
||||
$phrase .= $chars[array_rand($chars)];
|
||||
}
|
||||
|
||||
return $phrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Niceize" a code
|
||||
*/
|
||||
public function niceize($str)
|
||||
{
|
||||
return strtr(strtolower($str), '01', 'ol');
|
||||
}
|
||||
}
|
||||
21
includes/Gregwar/Captcha/PhraseBuilderInterface.php
Normal file
21
includes/Gregwar/Captcha/PhraseBuilderInterface.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Gregwar\Captcha;
|
||||
|
||||
/**
|
||||
* Interface for the PhraseBuilder
|
||||
*
|
||||
* @author Gregwar <g.passault@gmail.com>
|
||||
*/
|
||||
interface PhraseBuilderInterface
|
||||
{
|
||||
/**
|
||||
* Generates random phrase of given length with given charset
|
||||
*/
|
||||
public function build();
|
||||
|
||||
/**
|
||||
* "Niceize" a code
|
||||
*/
|
||||
public function niceize($str);
|
||||
}
|
||||
250
includes/Identicon/Generator/BaseGenerator.php
Normal file
250
includes/Identicon/Generator/BaseGenerator.php
Normal file
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
namespace Identicon\Generator;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @author Benjamin Laugueux <benjamin@yzalis.com>
|
||||
*/
|
||||
class BaseGenerator
|
||||
{
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected $generatedImage;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $color;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $backgroundColor;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $size;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $pixelRatio;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $hash;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $arrayOfSquare = [];
|
||||
|
||||
/**
|
||||
* Set the image color.
|
||||
*
|
||||
* @param string|array $color The color in hexa (3 or 6 chars) or rgb array
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setColor($color)
|
||||
{
|
||||
if (null === $color) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->color = $this->convertColor($color);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the image background color.
|
||||
*
|
||||
* @param string|array $backgroundColor The color in hexa (3 or 6 chars) or rgb array
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBackgroundColor($backgroundColor)
|
||||
{
|
||||
if (null === $backgroundColor) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->backgroundColor = $this->convertColor($backgroundColor);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $color
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function convertColor($color)
|
||||
{
|
||||
if (is_array($color)) {
|
||||
return $color;
|
||||
}
|
||||
|
||||
if (preg_match('/^#?([a-z\d])([a-z\d])([a-z\d])$/i', $color, $matches)) {
|
||||
$color = $matches[1].$matches[1];
|
||||
$color .= $matches[2].$matches[2];
|
||||
$color .= $matches[3].$matches[3];
|
||||
}
|
||||
|
||||
preg_match('/#?([a-z\d]{2})([a-z\d]{2})([a-z\d]{2})$/i', $color, $matches);
|
||||
|
||||
return array_map(function ($value) {
|
||||
return hexdec($value);
|
||||
}, array_slice($matches, 1, 3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getColor()
|
||||
{
|
||||
return $this->color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the background color.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getBackgroundColor()
|
||||
{
|
||||
return $this->backgroundColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the hash into an multidimensional array of boolean.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
private function convertHashToArrayOfBoolean()
|
||||
{
|
||||
preg_match_all('/(\w)(\w)/', $this->hash, $chars);
|
||||
|
||||
foreach ($chars[1] as $i => $char) {
|
||||
$index = (int) ($i / 3);
|
||||
$data = $this->convertHexaToBoolean($char);
|
||||
|
||||
$items = [
|
||||
0 => [0, 4],
|
||||
1 => [1, 3],
|
||||
2 => [2],
|
||||
];
|
||||
|
||||
foreach ($items[$i % 3] as $item) {
|
||||
$this->arrayOfSquare[$index][$item] = $data;
|
||||
}
|
||||
|
||||
ksort($this->arrayOfSquare[$index]);
|
||||
}
|
||||
|
||||
$this->color = array_map(function ($data) {
|
||||
return hexdec($data) * 16;
|
||||
}, array_reverse($chars[1]));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an hexadecimal number into a boolean.
|
||||
*
|
||||
* @param string $hexa
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function convertHexaToBoolean($hexa)
|
||||
{
|
||||
return (bool) round(hexdec($hexa) / 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getArrayOfSquare()
|
||||
{
|
||||
return $this->arrayOfSquare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identicon string hash.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHash()
|
||||
{
|
||||
return $this->hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a hash from the original string.
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setString($string)
|
||||
{
|
||||
if (null === $string) {
|
||||
throw new Exception('The string cannot be null.');
|
||||
}
|
||||
|
||||
$this->hash = md5($string);
|
||||
|
||||
$this->convertHashToArrayOfBoolean();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the image size.
|
||||
*
|
||||
* @param int $size
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSize($size)
|
||||
{
|
||||
if (null === $size) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->size = $size;
|
||||
$this->pixelRatio = (int) round($size / 5);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image size.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pixel ratio.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPixelRatio()
|
||||
{
|
||||
return $this->pixelRatio;
|
||||
}
|
||||
}
|
||||
90
includes/Identicon/Generator/GdGenerator.php
Normal file
90
includes/Identicon/Generator/GdGenerator.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Identicon\Generator;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @author Benjamin Laugueux <benjamin@yzalis.com>
|
||||
*/
|
||||
class GdGenerator extends BaseGenerator implements GeneratorInterface
|
||||
{
|
||||
/**
|
||||
* GdGenerator constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (!extension_loaded('gd') && !extension_loaded('ext-gd')) {
|
||||
throw new Exception('GD does not appear to be available in your PHP installation. Please try another generator');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMimeType()
|
||||
{
|
||||
return 'image/png';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
private function generateImage()
|
||||
{
|
||||
// prepare image
|
||||
$this->generatedImage = imagecreatetruecolor($this->getPixelRatio() * 5, $this->getPixelRatio() * 5);
|
||||
|
||||
$rgbBackgroundColor = $this->getBackgroundColor();
|
||||
if (null === $rgbBackgroundColor) {
|
||||
$background = imagecolorallocate($this->generatedImage, 0, 0, 0);
|
||||
imagecolortransparent($this->generatedImage, $background);
|
||||
} else {
|
||||
$background = imagecolorallocate($this->generatedImage, $rgbBackgroundColor[0], $rgbBackgroundColor[1], $rgbBackgroundColor[2]);
|
||||
imagefill($this->generatedImage, 0, 0, $background);
|
||||
}
|
||||
|
||||
// prepare color
|
||||
$rgbColor = $this->getColor();
|
||||
$gdColor = imagecolorallocate($this->generatedImage, $rgbColor[0], $rgbColor[1], $rgbColor[2]);
|
||||
|
||||
// draw content
|
||||
foreach ($this->getArrayOfSquare() as $lineKey => $lineValue) {
|
||||
foreach ($lineValue as $colKey => $colValue) {
|
||||
if (true === $colValue) {
|
||||
imagefilledrectangle($this->generatedImage, $colKey * $this->getPixelRatio(), $lineKey * $this->getPixelRatio(), ($colKey + 1) * $this->getPixelRatio(), ($lineKey + 1) * $this->getPixelRatio(), $gdColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getImageBinaryData($string, $size = null, $color = null, $backgroundColor = null)
|
||||
{
|
||||
ob_start();
|
||||
imagepng($this->getImageResource($string, $size, $color, $backgroundColor));
|
||||
$imageData = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $imageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getImageResource($string, $size = null, $color = null, $backgroundColor = null)
|
||||
{
|
||||
$this
|
||||
->setString($string)
|
||||
->setSize($size)
|
||||
->setColor($color)
|
||||
->setBackgroundColor($backgroundColor)
|
||||
->generateImage();
|
||||
|
||||
return $this->generatedImage;
|
||||
}
|
||||
}
|
||||
43
includes/Identicon/Generator/GeneratorInterface.php
Normal file
43
includes/Identicon/Generator/GeneratorInterface.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Identicon\Generator;
|
||||
|
||||
/**
|
||||
* @author Benjamin Laugueux <benjamin@yzalis.com>
|
||||
*/
|
||||
interface GeneratorInterface
|
||||
{
|
||||
/**
|
||||
* @param string $string
|
||||
* @param int $size
|
||||
* @param array|string $color
|
||||
* @param array|string $backgroundColor
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getImageBinaryData($string, $size = null, $color = null, $backgroundColor = null);
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param int $size
|
||||
* @param array|string $color
|
||||
* @param array|string $backgroundColor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getImageResource($string, $size = null, $color = null, $backgroundColor = null);
|
||||
|
||||
/**
|
||||
* Return the mime-type of this identicon.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMimeType();
|
||||
|
||||
/**
|
||||
* Return the color of the created identicon.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getColor();
|
||||
}
|
||||
98
includes/Identicon/Generator/ImageMagickGenerator.php
Normal file
98
includes/Identicon/Generator/ImageMagickGenerator.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Identicon\Generator;
|
||||
|
||||
use Exception;
|
||||
use ImagickDraw;
|
||||
use ImagickPixel;
|
||||
|
||||
/**
|
||||
* @author Francis Chuang <francis.chuang@gmail.com>
|
||||
*/
|
||||
class ImageMagickGenerator extends BaseGenerator implements GeneratorInterface
|
||||
{
|
||||
/**
|
||||
* ImageMagickGenerator constructor.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (!extension_loaded('imagick')) {
|
||||
throw new Exception('ImageMagick does not appear to be avaliable in your PHP installation. Please try another generator');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMimeType()
|
||||
{
|
||||
return 'image/png';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
private function generateImage()
|
||||
{
|
||||
$this->generatedImage = new \Imagick();
|
||||
$rgbBackgroundColor = $this->getBackgroundColor();
|
||||
|
||||
if (null === $rgbBackgroundColor) {
|
||||
$background = 'none';
|
||||
} else {
|
||||
$background = new ImagickPixel("rgb($rgbBackgroundColor[0],$rgbBackgroundColor[1],$rgbBackgroundColor[2])");
|
||||
}
|
||||
|
||||
$this->generatedImage->newImage($this->pixelRatio * 5, $this->pixelRatio * 5, $background, 'png');
|
||||
|
||||
// prepare color
|
||||
$rgbColor = $this->getColor();
|
||||
$color = new ImagickPixel("rgb($rgbColor[0],$rgbColor[1],$rgbColor[2])");
|
||||
|
||||
$draw = new ImagickDraw();
|
||||
$draw->setFillColor($color);
|
||||
|
||||
// draw the content
|
||||
foreach ($this->getArrayOfSquare() as $lineKey => $lineValue) {
|
||||
foreach ($lineValue as $colKey => $colValue) {
|
||||
if (true === $colValue) {
|
||||
$draw->rectangle($colKey * $this->pixelRatio, $lineKey * $this->pixelRatio, ($colKey + 1) * $this->pixelRatio, ($lineKey + 1) * $this->pixelRatio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->generatedImage->drawImage($draw);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getImageBinaryData($string, $size = null, $color = null, $backgroundColor = null)
|
||||
{
|
||||
ob_start();
|
||||
echo $this->getImageResource($string, $size, $color, $backgroundColor);
|
||||
$imageData = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $imageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getImageResource($string, $size = null, $color = null, $backgroundColor = null)
|
||||
{
|
||||
$this
|
||||
->setString($string)
|
||||
->setSize($size)
|
||||
->setColor($color)
|
||||
->setBackgroundColor($backgroundColor)
|
||||
->generateImage();
|
||||
|
||||
return $this->generatedImage;
|
||||
}
|
||||
}
|
||||
88
includes/Identicon/Generator/SvgGenerator.php
Normal file
88
includes/Identicon/Generator/SvgGenerator.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Identicon\Generator;
|
||||
|
||||
/**
|
||||
* @author Grummfy <grummfy@gmail.com>
|
||||
*/
|
||||
class SvgGenerator extends BaseGenerator implements GeneratorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMimeType()
|
||||
{
|
||||
return 'image/svg+xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getImageBinaryData($string, $size = null, $color = null, $backgroundColor = null)
|
||||
{
|
||||
return $this->getImageResource($string, $size, $color, $backgroundColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getImageResource($string, $size = null, $color = null, $backgroundColor = null)
|
||||
{
|
||||
$this
|
||||
->setString($string)
|
||||
->setSize($size)
|
||||
->setColor($color)
|
||||
->setBackgroundColor($backgroundColor)
|
||||
->_generateImage();
|
||||
|
||||
return $this->generatedImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
protected function _generateImage()
|
||||
{
|
||||
// prepare image
|
||||
$w = $this->getPixelRatio() * 5;
|
||||
$h = $this->getPixelRatio() * 5;
|
||||
$svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'.$w.'" height="'.$h.'">';
|
||||
|
||||
$backgroundColor = '#FFFFFF';
|
||||
$rgbBackgroundColor = $this->getBackgroundColor();
|
||||
if (!is_null($rgbBackgroundColor)) {
|
||||
$backgroundColor = $this->_toUnderstandableColor($rgbBackgroundColor);
|
||||
}
|
||||
$svg .= '<rect width="'.$w.'" height="'.$h.'" style="fill:'.$backgroundColor.';stroke-width:1;stroke:'.$backgroundColor.'"/>';
|
||||
|
||||
$rgbColor = $this->_toUnderstandableColor($this->getColor());
|
||||
// draw content
|
||||
foreach ($this->getArrayOfSquare() as $lineKey => $lineValue) {
|
||||
foreach ($lineValue as $colKey => $colValue) {
|
||||
if (true === $colValue) {
|
||||
$svg .= '<rect x="'.$colKey * $this->getPixelRatio().'" y="'.$lineKey * $this->getPixelRatio().'" width="'.($this->getPixelRatio()).'" height="'.$this->getPixelRatio().'" style="fill:'.$rgbColor.';stroke-width:0;"/>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$svg .= '</svg>';
|
||||
|
||||
$this->generatedImage = $svg;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $color
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _toUnderstandableColor($color)
|
||||
{
|
||||
if (is_array($color)) {
|
||||
return 'rgb('.implode(', ', $color).')';
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
}
|
||||
123
includes/Identicon/Identicon.php
Normal file
123
includes/Identicon/Identicon.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace Identicon;
|
||||
|
||||
use Identicon\Generator\GdGenerator;
|
||||
use Identicon\Generator\GeneratorInterface;
|
||||
|
||||
/**
|
||||
* @author Benjamin Laugueux <benjamin@yzalis.com>
|
||||
*/
|
||||
class Identicon
|
||||
{
|
||||
/**
|
||||
* @var \Identicon\Generator\GeneratorInterface
|
||||
*/
|
||||
private $generator;
|
||||
|
||||
/**
|
||||
* Identicon constructor.
|
||||
*
|
||||
* @param \Identicon\Generator\GeneratorInterface|null $generator
|
||||
*/
|
||||
public function __construct($generator = null)
|
||||
{
|
||||
if (null === $generator) {
|
||||
$this->generator = new GdGenerator();
|
||||
} else {
|
||||
$this->generator = $generator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the image generator.
|
||||
*
|
||||
* @param \Identicon\Generator\GeneratorInterface $generator
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setGenerator(GeneratorInterface $generator)
|
||||
{
|
||||
$this->generator = $generator;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an Identicon image.
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $size
|
||||
* @param string $color
|
||||
* @param string $backgroundColor
|
||||
*/
|
||||
public function displayImage($string, $size = 64, $color = null, $backgroundColor = null)
|
||||
{
|
||||
header('Content-Type: '.$this->generator->getMimeType());
|
||||
echo $this->getImageData($string, $size, $color, $backgroundColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an Identicon PNG image data.
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $size
|
||||
* @param string $color
|
||||
* @param string $backgroundColor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getImageData($string, $size = 64, $color = null, $backgroundColor = null)
|
||||
{
|
||||
return $this->generator->getImageBinaryData($string, $size, $color, $backgroundColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an Identicon PNG image resource.
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $size
|
||||
* @param string $color
|
||||
* @param string $backgroundColor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getImageResource($string, $size = 64, $color = null, $backgroundColor = null)
|
||||
{
|
||||
return $this->generator->getImageResource($string, $size, $color, $backgroundColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an Identicon PNG image data as base 64 encoded.
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $size
|
||||
* @param string $color
|
||||
* @param string $backgroundColor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getImageDataUri($string, $size = 64, $color = null, $backgroundColor = null)
|
||||
{
|
||||
return sprintf('data:%s;base64,%s', $this->generator->getMimeType(), base64_encode($this->getImageData($string, $size, $color, $backgroundColor)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color of the Identicon
|
||||
*
|
||||
* Returns an array with RGB values of the Identicon's color. Colors may be NULL if no image has been generated
|
||||
* so far (e.g., when calling the method on a new Identicon()).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getColor()
|
||||
{
|
||||
$colors = $this->generator->getColor();
|
||||
|
||||
return [
|
||||
"r" => $colors[0],
|
||||
"g" => $colors[1],
|
||||
"b" => $colors[2]
|
||||
];
|
||||
}
|
||||
}
|
||||
244
includes/OWASP/PureCaptcha.php
Normal file
244
includes/OWASP/PureCaptcha.php
Normal file
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
/**
|
||||
* OWASP PureCaptcha
|
||||
* Generates simple CAPTCHAs without requiring any third party library.
|
||||
* @version 1.1
|
||||
*/
|
||||
|
||||
namespace OWASP;
|
||||
|
||||
class PureCaptcha
|
||||
{
|
||||
protected $charWidth=6;
|
||||
protected $charHeight=13;
|
||||
protected $chars="2346789ABDHKLMNPRTWXYZ"; //do not modify!
|
||||
protected $ascii="eNrtW0FuwyAQ/NIANjbOa3LMG6r8vWrrSonkkt0xDWDvIcGXEYHM7O6s4b
|
||||
p4v3zcFlyuiwu/T/Hn4ba4h49fx7COwzqO3+P964tF+i0kViRWJLaQ4RGJF3PiETnQyFGzzidklC
|
||||
A31znlkMjt7Zzb2+y/kjQ79MwEbEH/+kOftsjxLHKehK7cbYT/qu0KNBcHmosjzcVI63yi1znTyE
|
||||
RHCAd6oZX479uL/yIuBho5SFgs5z8kc0YaOUm4iJfxXxVbEr23Djy0Dv+D1T/iXzvSyKjhop7/r+
|
||||
M/LP5v83+w+qdM/aOP/6LKqXr9r0Lm+Z+H1uH/aPGf87/Y7X8t/jfA/2j8b43/MP/7Pv5P7frfbH
|
||||
YPtKOszn8VcqIrxPrxXwetw/+5n/pfz395/Y8L2//HG+sf1ZwzjUw0Utf/byH+J+N/bf7L47/xv/
|
||||
z7L7QnAFG+7NgAgDYAnRngHgog50wAXAcU9BtgaBqDEz3nTK/zVALw/QiAbwHxFvg/X4G53QJwuw
|
||||
VgR4CCZYBc9wh0BpD3gKDuAVkJVE4Aw5EEgGICcF0JgG+C4vQCGI/QBTqWCT5cCSSDVhJANAH0Kw
|
||||
AzwfsFMB3xHBzEJliFLHwPoMY5OBEy0cj8OWi0mAFmM8G1D0LwHgC0CYbaBIvaBB1mgHRuAajOEB
|
||||
W+CcNnANGcM408UwnkYQLoTwBWApUTgN0F7vAuDNRdILsLvymA+ycgmwSd";
|
||||
function __construct()
|
||||
{
|
||||
$this->ascii=unserialize(gzuncompress(base64_decode(
|
||||
preg_replace('/\s+/', '', $this->ascii))));
|
||||
$this->text=$this->randomText();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains the captcha text.
|
||||
* @var string
|
||||
*/
|
||||
public $text;
|
||||
|
||||
/**
|
||||
* Generates random text for use in captcha
|
||||
* @param integer $length
|
||||
* @return string
|
||||
*/
|
||||
protected function randomText($length=5)
|
||||
{
|
||||
$res="";
|
||||
for ($i=0;$i<$length;++$i)
|
||||
$res.=$this->chars[mt_rand(0,strlen($this->chars)-1)];
|
||||
return $res;
|
||||
}
|
||||
/**
|
||||
* returns the index of a char in $chars array
|
||||
*/
|
||||
protected function asciiEntry($char)
|
||||
{
|
||||
for ($i=0;$i<strlen($this->chars);++$i)
|
||||
if ($this->chars[$i]==$char) return $i;
|
||||
return -1;
|
||||
|
||||
}
|
||||
/**
|
||||
* converts a text to a bitmap
|
||||
* which is a 2D array of ones and zeroes denoting the text
|
||||
*/
|
||||
protected function textBitmap($text,$spacing=2)
|
||||
{
|
||||
$width=$this->charWidth;
|
||||
$height=$this->charHeight;
|
||||
$result=array();
|
||||
$baseY=$baseX=0;
|
||||
|
||||
for ($index=0;$index<strlen($text);++$index)
|
||||
{
|
||||
for ($j=0;$j<$height;++$j)
|
||||
{
|
||||
for ($i=0;$i<$width;++$i)
|
||||
$result[$baseY+$j][$baseX+$i]=
|
||||
1-$this->ascii[$this->asciiEntry($text[$index])][$j][$i];
|
||||
for ($i=0;$i<$spacing;++$i)
|
||||
$result[$baseY+$j][$baseX+$width+$i]=0;
|
||||
}
|
||||
$baseX+=$width+$spacing;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* displays a bitmap string on the browser screen
|
||||
*/
|
||||
protected function displayBitmap($bitmap)
|
||||
{
|
||||
header("Content-Type: image/bmp");
|
||||
echo $this->bitmap2bmp($bitmap);
|
||||
}
|
||||
|
||||
protected function inlineBitmap($bitmap)
|
||||
{
|
||||
return base64_encode($this->bitmap2bmp($bitmap));
|
||||
}
|
||||
|
||||
/**
|
||||
* generates a monochrome BMP file
|
||||
* a bitmap needs to be sent to this function
|
||||
* i.e a 2D array with every element being either 1 or 0
|
||||
* @param integer $width
|
||||
* @param integer $height
|
||||
* @param array $bitmap
|
||||
* @return string
|
||||
*/
|
||||
protected function bitmap2bmp($bitmap)
|
||||
{
|
||||
$width=count($bitmap[0]);
|
||||
$height=count($bitmap);
|
||||
$bytemap=$this->bitmap2bytemap($bitmap);
|
||||
|
||||
$rowSize=floor(($width+31)/32)*4;
|
||||
$size=$rowSize*$height + 62; //62 metadata size
|
||||
#bitmap header
|
||||
$data= "BM"; //header
|
||||
$data.= (pack('V',$size)); //bitmap size ,4 bytes unsigned little endian
|
||||
$data.= "RRRR";
|
||||
$data.= (pack('V',14+40+8)); //bitmap data start offset ,
|
||||
//4 bytes unsigned little endian, 14 forced, 40 header, 8 colors
|
||||
|
||||
#info header
|
||||
$data.= pack('V',40); //bitmap header size (min 40),
|
||||
//4 bytes unsigned little-endian
|
||||
$data.= (pack('V',$width)); //bitmap width , 4 bytes signed integer
|
||||
$data.= (pack('V',$height)); //bitmap height , 4 bytes signed integer
|
||||
$data.= (pack('v',1)); //number of colored plains , 2 bytes
|
||||
$data.= (pack('v',1)); //color depth , 2 bytes
|
||||
$data.= (pack('V',0)); //compression algorithm , 4 bytes (0=none, RGB)
|
||||
$data.= (pack('V',0)); //size of raw data, 0 is fine for no compression
|
||||
$data.= (pack('V',11808)); //horizontal resolution (dpi), 4 bytes
|
||||
$data.= (pack('V',11808)); //vertical resolution (dpi), 4 bytes
|
||||
$data.= (pack('V',0)); //number of colors in pallette (0 = all), 4 bytes
|
||||
$data.= (pack('V',0)); //number of important colors (0 = all), 4 bytes
|
||||
|
||||
#color palette
|
||||
$data.= (pack('V',0x00FFFFFF)); //next color, white
|
||||
$data.= (pack('V',0)); //first color, black
|
||||
|
||||
for ($j=$height-1;$j>=0;--$j)
|
||||
for ($i=0;$i<$rowSize/4;++$i)
|
||||
for ($k=0;$k<4;++$k)
|
||||
if (isset($bytemap[$j][$i*4+$k]))
|
||||
$data.= pack('C',$bytemap[$j][$i*4+$k]);
|
||||
else
|
||||
$data.= pack('C',0);
|
||||
return $data;
|
||||
}
|
||||
/**
|
||||
* Converts a bitmap to a bytemap, which is necessary for outputting it
|
||||
*
|
||||
*/
|
||||
protected function bitmap2bytemap($bitmap)
|
||||
{
|
||||
$width=count($bitmap[0]);
|
||||
$height=count($bitmap);
|
||||
$bytemap=array();
|
||||
for ($j=0;$j<$height;++$j)
|
||||
{
|
||||
for ($i=0;$i<$width/8;++$i)
|
||||
{
|
||||
$bitstring="";
|
||||
for ($k=0;$k<8;++$k)
|
||||
if (isset($bitmap[$j][$i*8+$k]))
|
||||
$bitstring.=$bitmap[$j][$i*8+$k];
|
||||
else
|
||||
$bitstring.="0";
|
||||
$bytemap[$j][]=bindec($bitstring);
|
||||
}
|
||||
}
|
||||
return $bytemap;
|
||||
}
|
||||
/**
|
||||
* rotates a bitmap, returning new dimensions with the bitmap
|
||||
* return bitmap
|
||||
*/
|
||||
protected function rotateBitmap($bitmap, $degree)
|
||||
{
|
||||
$c=cos(deg2rad($degree));
|
||||
$s=sin(deg2rad($degree));
|
||||
|
||||
$width=count($bitmap[0]);
|
||||
$height=count($bitmap);
|
||||
$newHeight=round(abs($width*$s)+abs($height*$c));
|
||||
$newWidth=round(abs($width*$c) + abs($height*$s))+1;
|
||||
$x0 = $width/2 - $c*$newWidth/2 - $s*$newHeight/2;
|
||||
$y0 = $height/2 - $c*$newHeight/2 + $s*$newWidth/2;
|
||||
$result=array_fill(0, $newHeight, array_fill(0, $newWidth, 0));
|
||||
for ($j=0;$j<$newHeight;++$j)
|
||||
for ($i=1;$i<$newWidth;++$i)
|
||||
{
|
||||
$y=(int)(-$s*$i+$c*$j+$y0);
|
||||
$x=(int)($c*$i+$s*$j+$x0);
|
||||
if (isset($bitmap[$y][$x]))
|
||||
$result[$j][$i]=$bitmap[$y][$x];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* scales a bitmap to be bigger
|
||||
*/
|
||||
protected function scaleBitmap($bitmap,$scaleX,$scaleY)
|
||||
{
|
||||
$width=count($bitmap[0]);
|
||||
$height=count($bitmap);
|
||||
$newHeight=$height*$scaleY;
|
||||
$newWidth=$width*$scaleX;
|
||||
$result=array_fill(0, $newHeight, array_fill(0, $newWidth, 0));
|
||||
for ($j=0;$j<$newHeight;++$j)
|
||||
for ($i=0;$i<$newWidth;++$i)
|
||||
$result[$j][$i]=$bitmap[(int)($j/$scaleY)]
|
||||
[(int)($i/$scaleX)];
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* adds random noise to the captcha
|
||||
*/
|
||||
protected function distort($bitmap,$noisePercent=5)
|
||||
{
|
||||
for ($j=0;$j<count($bitmap);++$j)
|
||||
for ($i=0;$i<count($bitmap[0]);++$i)
|
||||
if (isset($bitmap[$j][$i]) && mt_rand()%100<$noisePercent)
|
||||
$bitmap[$j][$i]=1;
|
||||
return $bitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* draw a captcha to the screen, returning its value
|
||||
*/
|
||||
public function show($distort=true,$scale=2.3)
|
||||
{
|
||||
$bitmap=$this->textBitmap($this->text);
|
||||
$degree=mt_rand(2,4);
|
||||
if (mt_rand()%100<50)
|
||||
$degree=-$degree;
|
||||
$bitmap=$this->rotateBitmap($bitmap,$degree);
|
||||
$bitmap=$this->scaleBitmap($bitmap,$scale,$scale);
|
||||
if ($distort) $bitmap=$this->distort($bitmap);
|
||||
return $this->inlineBitmap($bitmap);
|
||||
}
|
||||
|
||||
}
|
||||
516
includes/PIT/Zip.php
Normal file
516
includes/PIT/Zip.php
Normal file
@@ -0,0 +1,516 @@
|
||||
<?php
|
||||
/*
|
||||
| ZIP A ZipArchive and Plain PKZIP PHP Helper
|
||||
| @file ./PIT/ZIP.php
|
||||
| @author SamBrishes <sam@pytes.net>
|
||||
| @version 0.2.1 [0.2.1] - Beta
|
||||
|
|
||||
| @license X11 / MIT License
|
||||
| @copyright Copyright © 2015 - 2019 SamBrishes, pytesNET <info@pytes.net>
|
||||
*/
|
||||
/*
|
||||
| The following websites contains all required informations, which were unavoidable for the
|
||||
| creation of this class:
|
||||
|
|
||||
| - https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
|
||||
| - https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html
|
||||
| - https://php.net/manual/class.ziparchive.php
|
||||
*/
|
||||
|
||||
namespace PIT;
|
||||
|
||||
class Zip{
|
||||
const FLAGS = "\x00\x00";
|
||||
const VERSION = "\x14\x00";
|
||||
const SIGNATURE = "\x50\x4b";
|
||||
const COMPRESSION = "\x08\x00";
|
||||
|
||||
/*
|
||||
| SETTINGs
|
||||
*/
|
||||
public $zipArchive = false;
|
||||
public $compression = 6;
|
||||
|
||||
/*
|
||||
| ZIP ARCHIVE
|
||||
*/
|
||||
private $zipFilename;
|
||||
private $zipInstance;
|
||||
|
||||
/*
|
||||
| FALLBACK
|
||||
*/
|
||||
private $offset = 0;
|
||||
private $headers = array();
|
||||
private $central = array();
|
||||
private $counter = 0;
|
||||
|
||||
/*
|
||||
| CONSTRUCTOR
|
||||
| @since 0.2.0
|
||||
|
|
||||
| @param bool TRUE to check and use ZipArchive if available,
|
||||
| FALSE to use the PKZIP PHP compression per default.
|
||||
| @return int The compression level between -1 and 9.
|
||||
*/
|
||||
public function __construct($ziparchive = false, $compression = 6){
|
||||
if($ziparchive){
|
||||
$this->zipArchive = class_exists("ZipArchive", false);
|
||||
$this->zipInstance = ($this->zipArchive)? new ZipArchive(): false;
|
||||
}
|
||||
if($this->zipArchive){
|
||||
$this->zipFilename = tempnam(sys_get_temp_dir(), "tzp") . ".zip";
|
||||
$this->zipInstance->open($this->zipFilename, ZipArchive::CREATE);
|
||||
}
|
||||
$this->compression = ($compression >= -1 && $compression <= 9)? $compression: 6;
|
||||
}
|
||||
|
||||
/*
|
||||
| DESTRUCTOR
|
||||
| @since 0.2.0
|
||||
*/
|
||||
public function __destruct(){
|
||||
$this->clear(false);
|
||||
}
|
||||
|
||||
/*
|
||||
| HELPER :: CONVERT UNIX TO DOS TIME
|
||||
| @since 0.2.1
|
||||
|
|
||||
| @param int The respective timestamp as INTEGER.
|
||||
*/
|
||||
protected function msDOSTime($time){
|
||||
$array = getdate((is_int($time) && $time > 0)? $time: time());
|
||||
if($array["year"] < 1980 || $array["year"] > 2107){
|
||||
$array = getdate(time());
|
||||
}
|
||||
|
||||
// Return as DEC
|
||||
return (
|
||||
(($array["year"]-1980 << 25)) |
|
||||
(($array["mon"] << 21)) |
|
||||
(($array["mday"] << 16)) |
|
||||
(($array["hours"] << 11)) |
|
||||
(($array["minutes"] << 5)) |
|
||||
(($array["seconds"] >> 1))
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
| ADD A FILE
|
||||
| @since 0.1.0
|
||||
| @update 0.2.1
|
||||
|
|
||||
| @param string The relative or absolute filepath or the respective file content.
|
||||
| @param string The local path within the archive file.
|
||||
| @param int The timestamp to use.
|
||||
| @param string The optional file comment or just an empty string.
|
||||
|
|
||||
| @return bool TRUE on success, FALSE on failure.
|
||||
*/
|
||||
public function addFile($data, $path, $time = 0, $comment = ""){
|
||||
if((!is_string($data) && !is_numeric($data)) || !is_string($path)){
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sanitize Data
|
||||
if(is_string($data) && file_exists($data) && is_file($data)){
|
||||
$data = file_get_contents($data);
|
||||
}
|
||||
$path = trim(str_replace("\\", "/", $path), "/");
|
||||
$time = $this->msDOSTime($time);
|
||||
|
||||
// Zip Archive
|
||||
if($this->zipArchive){
|
||||
return $this->zipInstance->addFromString($path, $data);
|
||||
}
|
||||
|
||||
// Fallback
|
||||
$crcval = crc32($data);
|
||||
$length = strlen($data);
|
||||
if(version_compare(PHP_VERSION, "5.4.0", ">=")){
|
||||
$gzcval = gzcompress($data, $this->compression, ZLIB_ENCODING_DEFLATE);
|
||||
} else {
|
||||
$gzcval = gzcompress($data, $this->compression);
|
||||
}
|
||||
$gzcval = substr($gzcval, 2, strlen($gzcval) - 6); // Fix CRC-32 Bug
|
||||
$gzclen = strlen($gzcval);
|
||||
|
||||
/*
|
||||
| LOCAL FILE HEADER
|
||||
| 01 SIGNATURE
|
||||
| 02 Version needed to extract this archive.
|
||||
| 03 General purpose bit flag.
|
||||
| 04 Compression method.
|
||||
| 05 Last modification DOS datetime.
|
||||
| 06 CRC32 value.
|
||||
| 07 Compressed Filesize.
|
||||
| 08 Uncompressed Filesize.
|
||||
| 09 Length of the filename inside the archive.
|
||||
| 10 Length of the extra fields.
|
||||
| 11 The relative path / filename inside the archive.
|
||||
| 12 The main file data value.
|
||||
*/
|
||||
$this->headers[] =
|
||||
self::SIGNATURE . "\x03\x04" .
|
||||
self::VERSION .
|
||||
self::FLAGS .
|
||||
self::COMPRESSION .
|
||||
pack("V", $time) .
|
||||
pack("V", $crcval) .
|
||||
pack("V", $gzclen) .
|
||||
pack("V", $length) .
|
||||
pack("v", strlen($path)) .
|
||||
pack("v", 0) .
|
||||
$path .
|
||||
$gzcval;
|
||||
|
||||
/*
|
||||
| CENTRAL DIRECTORY RECORD
|
||||
| 01 SIGNATURE
|
||||
| 02 MadeBy Version numbers.
|
||||
| 03 Version needed to extract this archive.
|
||||
| 04 General purpose bit flag.
|
||||
| 05 Compression method.
|
||||
| 06 Last modification DOS datetime.
|
||||
| 07 CRC32 value.
|
||||
| 08 Compressed Filesize.
|
||||
| 09 Uncompressed Filesize.
|
||||
| 10 Length of the filename inside the archive.
|
||||
| 11 Length of the extra fields.
|
||||
| 12 Length of the file comment.
|
||||
| 13 The disk number where the file exists.
|
||||
| 14 Internal file attributes.
|
||||
| 15 External file attributes.
|
||||
| 16 Offset of the local file header.
|
||||
| 17 The relative path / filename inside the archive.
|
||||
| 18 The file comment.
|
||||
*/
|
||||
$this->central[] =
|
||||
self::SIGNATURE . "\x01\x02" .
|
||||
"\x00\x00" .
|
||||
self::VERSION .
|
||||
self::FLAGS .
|
||||
self::COMPRESSION .
|
||||
pack("V", $time) .
|
||||
pack("V", $crcval) .
|
||||
pack("V", $gzclen) .
|
||||
pack("V", $length) .
|
||||
pack("v", strlen($path)) .
|
||||
pack("v", 0) .
|
||||
pack("v", strlen($comment)) .
|
||||
pack("v", 0) .
|
||||
pack("v", 0) .
|
||||
pack("V", 32) .
|
||||
pack("V", $this->offset) .
|
||||
$path .
|
||||
$comment;
|
||||
|
||||
// Count Offset and Return
|
||||
$this->offset += strlen($this->headers[count($this->headers)-1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
| ADD MULTIPLE FILES
|
||||
| @since 0.2.0
|
||||
| @update 0.2.1
|
||||
|
|
||||
| @param array Multiple 'local/file/path' => "filepath/or/filecontent" ARRAY pairs.
|
||||
| @param int The timestamp to use for all files.
|
||||
| @param string The optional comment or just an empty string for alle respective files.
|
||||
|
|
||||
| @return int The number of successfully added elements / files.
|
||||
*/
|
||||
public function addFiles($array, $time = 0, $comment = ""){
|
||||
if(!is_array($array)){
|
||||
return false;
|
||||
}
|
||||
foreach($array AS $path => &$data){
|
||||
$data = $this->addFile($data, $path);
|
||||
}
|
||||
return array_filter(array_values($array));
|
||||
}
|
||||
|
||||
/*
|
||||
| ADD FOLDER
|
||||
| @since 0.2.0
|
||||
|
|
||||
| @param string The path to the folder, which should be zipped.
|
||||
| @param string The local path within the zip file.
|
||||
| @param bool TRUE to zip recursive and include all sub directories,
|
||||
| FALSE to just zip all files within the $path folder.
|
||||
| @param bool TRUE to include empty folders on recursive zips.
|
||||
| FALSE to skip empty folders.
|
||||
|
|
||||
| @return multi The number as INT of successfully added elements / files,
|
||||
| FALSE on failure.
|
||||
*/
|
||||
public function addFolder($path, $local = "/", $recursive = false, $empty = false){
|
||||
if(!file_exists($path) || !is_dir($path)){
|
||||
return false;
|
||||
}
|
||||
|
||||
// Chech Path
|
||||
$path = str_replace(array("/", "\\"), DIRECTORY_SEPARATOR, realpath($path));
|
||||
if(strpos($path, DIRECTORY_SEPARATOR) !== strlen($path)-1){
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
// Check Local
|
||||
if(!is_string($local)){
|
||||
$local = "";
|
||||
}
|
||||
$local = trim(str_replace("\\", "/", $local), "/") . "/";
|
||||
|
||||
// Start Flow
|
||||
$this->counter = 0;
|
||||
$this->addFolderFlow($path, "", $local, !!$recursive, !!$empty);
|
||||
return $this->counter;
|
||||
}
|
||||
|
||||
/*
|
||||
| HELPER :: ADD FOLDER LOOP
|
||||
| @since 0.2.0
|
||||
|
|
||||
| @param string The base path to the folder, which should be zipped.
|
||||
| @param string The further path, within the base path, on recursive calls.
|
||||
| @param string The local path within the zip file.
|
||||
| @param bool TRUE to zip recursive and include all sub directories,
|
||||
| FALSE to just zip all files within the $path folder.
|
||||
| @param bool TRUE to include empty folders on recursive zips.
|
||||
| FALSE to skip empty folders.
|
||||
|
|
||||
| @return int The number of successfully added elements / files.
|
||||
*/
|
||||
private function addFolderFlow($base, $path = "", $local = "", $recursive = false, $empty = false){
|
||||
$path = str_replace(array("/", "\\"), DIRECTORY_SEPARATOR, $path);
|
||||
$path = trim($path, DIRECTORY_SEPARATOR);
|
||||
if(!empty($path)){
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$handle = opendir($base . $path);
|
||||
while(($file = readdir($handle)) !== false){
|
||||
if(in_array($file, array(".", ".."))){
|
||||
continue;
|
||||
}
|
||||
if(is_dir($base . $path . $file)){
|
||||
if($recursive){
|
||||
$count = $this->addFolderFlow($base, $path . $file, $local, $recursive, $empty);
|
||||
if($count == 0 && $empty){
|
||||
$this->addEmptyFolder($local . $path . $file);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(is_file($base . $path . $file)){
|
||||
if($this->addFile($base . $path . $file, $local . $path . $file)){
|
||||
$count++;
|
||||
$this->counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
return $count;
|
||||
}
|
||||
|
||||
/*
|
||||
| ADD EMPTY FOLDER
|
||||
| @since 0.2.0
|
||||
| @update 0.2.1
|
||||
|
|
||||
| @param string The local path structure within the zip file.
|
||||
| @param int The timestamp to use.
|
||||
| @param string The optional file comment or just an empty string.
|
||||
|
|
||||
| @return bool TRUE on success, FALSE on failure.
|
||||
*/
|
||||
public function addEmptyFolder($path, $time = 0, $comment = ""){
|
||||
$path = trim(str_replace("\\", "/", $path), "/") . "/";
|
||||
$time = $this->msDOSTime($time);
|
||||
|
||||
// ZipArchive
|
||||
if($this->zipArchive){
|
||||
return $this->zipInstance->addEmptyDir($path);
|
||||
}
|
||||
|
||||
// Add Header
|
||||
$this->headers[] =
|
||||
self::SIGNATURE . "\x03\x04" .
|
||||
self::VERSION .
|
||||
self::FLAGS .
|
||||
"\x00\x00" .
|
||||
pack("V", $time) .
|
||||
pack("V", 0) .
|
||||
pack("V", 0) .
|
||||
pack("V", 0) .
|
||||
pack("v", strlen($path)) .
|
||||
pack("v", 0) .
|
||||
$path .
|
||||
"";
|
||||
|
||||
// Add Central
|
||||
$this->central[] =
|
||||
self::SIGNATURE . "\x01\x02" .
|
||||
"\x14\x03" .
|
||||
self::VERSION .
|
||||
self::FLAGS .
|
||||
"\x00\x00" .
|
||||
pack("V", $time) .
|
||||
pack("V", 0) .
|
||||
pack("V", 0) .
|
||||
pack("V", 0) .
|
||||
pack("v", strlen($path)) .
|
||||
pack("v", 0) .
|
||||
pack("v", strlen($comment)) .
|
||||
pack("v", 0) .
|
||||
pack("v", 0) .
|
||||
"\x00\x00\xFF\x41" .
|
||||
pack("V", $this->offset) .
|
||||
$path .
|
||||
$comment;
|
||||
|
||||
// Count Offset and Return
|
||||
$this->offset += strlen($this->headers[count($this->headers)-1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
| CLEAR DATA STRINGs
|
||||
| @since 0.1.0
|
||||
| @update 0.2.0
|
||||
*/
|
||||
public function clear($new = true){
|
||||
if($this->zipArchive){
|
||||
if(is_a($this->zipInstance, "ZipArchive")){
|
||||
$this->zipInstance->close();
|
||||
}
|
||||
if(strpos($this->zipFilename, sys_get_temp_dir()) === 0 && file_exists($this->zipFilename)){
|
||||
@unlink($this->zipFilename);
|
||||
}
|
||||
if($new){
|
||||
$this->zipFilename = "./temp-".time().".zip";
|
||||
$this->zipInstance = new ZipArchive();
|
||||
$this->zipInstance->open($this->zipFilename, ZipArchive::CREATE);
|
||||
}
|
||||
}
|
||||
$this->offset = 0;
|
||||
$this->headers = array();
|
||||
$this->central = array();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
| DUMBS OUT THE FILE
|
||||
| @since 0.1.0
|
||||
| @update 0.2.1
|
||||
*/
|
||||
public function file(){
|
||||
$comment = "PKZipped with https://github.com/SamBrishes/FoxCMS/tree/helpers/zip";
|
||||
|
||||
// ZipArchive
|
||||
if($this->zipArchive){
|
||||
$this->zipInstance->setArchiveComment($comment);
|
||||
$this->zipInstance->close();
|
||||
|
||||
$content = file_get_contents($this->zipFilename);
|
||||
|
||||
$this->zipInstance = new ZipArchive();
|
||||
$this->zipInstance->open($this->zipFilename);
|
||||
return $content;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
$headers = implode("", $this->headers);
|
||||
$central = implode("", $this->central);
|
||||
/*
|
||||
| RETURN
|
||||
| 01 The file header items.
|
||||
| 02 The central directory items.
|
||||
| 03 The signature for the end of the central directory record.
|
||||
| 04 The number of this disk / part.
|
||||
| 05 The number of the disk / part where the central directory starts.
|
||||
| 06 The number of central directoy entries on this disk.
|
||||
| 07 Total number of entries on this disk / part.
|
||||
| 08 Total number of entries in general.
|
||||
| 09 Length of the central directory.
|
||||
| 10 Offset where the central directory starts.
|
||||
| 11 The length of the following comment field.
|
||||
| 12 The archive comment.
|
||||
*/
|
||||
return $headers . $central .
|
||||
self::SIGNATURE . "\x05\x06" .
|
||||
"\x00" .
|
||||
"\x00" .
|
||||
"\x00" .
|
||||
"\x00" .
|
||||
pack("v", count($this->central)) .
|
||||
pack("v", count($this->central)) .
|
||||
pack("V", strlen($central)) .
|
||||
pack("V", strlen($headers)) .
|
||||
pack("v", strlen($comment)) .
|
||||
$comment;
|
||||
}
|
||||
|
||||
/*
|
||||
| STORE THE ZIP FILE
|
||||
| @since 0.1.0
|
||||
| @update 0.2.0
|
||||
|
|
||||
| @param string The filename with the respective path to store the archive.
|
||||
| @param bool TRUE to overwrite existing archives, FALSE to do it not.
|
||||
|
|
||||
| @return bool TRUE on success, FALSE on failure.
|
||||
*/
|
||||
public function save($filename = "archive.zip", $overwrite = false){
|
||||
if(file_exists($filename) && !$overwrite){
|
||||
return false;
|
||||
}
|
||||
|
||||
// Zip Archive
|
||||
if($this->zipArchive){
|
||||
if(is_a($this->zipInstance, "ZipArchive")){
|
||||
$this->zipInstance->close();
|
||||
}
|
||||
if(@file_put_contents($filename, file_get_contents($this->zipFilename))){
|
||||
@unlink($this->zipFilename);
|
||||
|
||||
$this->zipFilename = $filename;
|
||||
$this->zipInstance = new ZipArchive();
|
||||
return $this->zipInstance->open($this->zipFilename);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return @file_put_contents($filename, $this->file()) !== false;
|
||||
}
|
||||
|
||||
/*
|
||||
| DOWNLOAD THE FILE
|
||||
| @since 0.1.0
|
||||
| @update 0.2.0
|
||||
|
|
||||
| @param string The filename for the archiv.
|
||||
| @param bool TRUE to exit after the execution, FALSE to do it not.
|
||||
|
|
||||
| @return void
|
||||
*/
|
||||
public function download($filename = "archive.zip", $exit = false){
|
||||
$file = $this->file();
|
||||
header("Pragma: public");
|
||||
header("Expires: 0");
|
||||
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
|
||||
header("Cache-Control: private", false);
|
||||
header("Content-Type: application/zip");
|
||||
header("Content-Disposition: attachment; filename={$filename};" );
|
||||
header("Content-Transfer-Encoding: binary");
|
||||
header("Content-Length: " . strlen($file));
|
||||
print ($file);
|
||||
if($exit){
|
||||
die();
|
||||
}
|
||||
}
|
||||
}
|
||||
23
includes/autoload.php
Normal file
23
includes/autoload.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/*
|
||||
| Snicker The first native FlatFile Comment Plugin 4 Bludit
|
||||
| @file ./includes/autoload.php
|
||||
| @author SamBrishes <sam@pytes.net>
|
||||
| @version 0.1.2 [0.1.0] - Alpha
|
||||
|
|
||||
| @website https://github.com/pytesNET/snicker
|
||||
| @license X11 / MIT License
|
||||
| @copyright Copyright © 2019 SamBrishes, pytesNET <info@pytes.net>
|
||||
*/
|
||||
|
||||
spl_autoload_register(function($class){
|
||||
foreach(array("Gregwar", "Identicon", "PIT", "OWASP") AS $allowed){
|
||||
if(strpos($class, $allowed) !== 0){
|
||||
continue;
|
||||
}
|
||||
$path = dirname(__FILE__) . DIRECTORY_SEPARATOR;
|
||||
$class = str_replace("\\", DIRECTORY_SEPARATOR, $class);
|
||||
require_once $class . ".php";
|
||||
}
|
||||
return false;
|
||||
});
|
||||
BIN
includes/img/default-avatar.jpg
Normal file
BIN
includes/img/default-avatar.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
205
includes/js/identicon.js
Normal file
205
includes/js/identicon.js
Normal file
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Identicon.js 2.3.3
|
||||
* http://github.com/stewartlord/identicon.js
|
||||
*
|
||||
* PNGLib required for PNG output
|
||||
* http://www.xarg.org/download/pnglib.js
|
||||
*
|
||||
* Copyright 2018, Stewart Lord
|
||||
* Released under the BSD license
|
||||
* http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var PNGlib;
|
||||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
||||
PNGlib = require('./pnglib');
|
||||
} else {
|
||||
PNGlib = window.PNGlib;
|
||||
}
|
||||
|
||||
var Identicon = function(hash, options){
|
||||
if (typeof(hash) !== 'string' || hash.length < 15) {
|
||||
throw 'A hash of at least 15 characters is required.';
|
||||
}
|
||||
|
||||
this.defaults = {
|
||||
background: [240, 240, 240, 255],
|
||||
margin: 0.08,
|
||||
size: 64,
|
||||
saturation: 0.7,
|
||||
brightness: 0.5,
|
||||
format: 'png'
|
||||
};
|
||||
|
||||
this.options = typeof(options) === 'object' ? options : this.defaults;
|
||||
|
||||
// backward compatibility with old constructor (hash, size, margin)
|
||||
if (typeof(arguments[1]) === 'number') { this.options.size = arguments[1]; }
|
||||
if (arguments[2]) { this.options.margin = arguments[2]; }
|
||||
|
||||
this.hash = hash
|
||||
this.background = this.options.background || this.defaults.background;
|
||||
this.size = this.options.size || this.defaults.size;
|
||||
this.format = this.options.format || this.defaults.format;
|
||||
this.margin = this.options.margin !== undefined ? this.options.margin : this.defaults.margin;
|
||||
|
||||
// foreground defaults to last 7 chars as hue at 70% saturation, 50% brightness
|
||||
var hue = parseInt(this.hash.substr(-7), 16) / 0xfffffff;
|
||||
var saturation = this.options.saturation || this.defaults.saturation;
|
||||
var brightness = this.options.brightness || this.defaults.brightness;
|
||||
this.foreground = this.options.foreground || this.hsl2rgb(hue, saturation, brightness);
|
||||
};
|
||||
|
||||
Identicon.prototype = {
|
||||
background: null,
|
||||
foreground: null,
|
||||
hash: null,
|
||||
margin: null,
|
||||
size: null,
|
||||
format: null,
|
||||
|
||||
image: function(){
|
||||
return this.isSvg()
|
||||
? new Svg(this.size, this.foreground, this.background)
|
||||
: new PNGlib(this.size, this.size, 256);
|
||||
},
|
||||
|
||||
render: function(){
|
||||
var image = this.image(),
|
||||
size = this.size,
|
||||
baseMargin = Math.floor(size * this.margin),
|
||||
cell = Math.floor((size - (baseMargin * 2)) / 5),
|
||||
margin = Math.floor((size - cell * 5) / 2),
|
||||
bg = image.color.apply(image, this.background),
|
||||
fg = image.color.apply(image, this.foreground);
|
||||
|
||||
// the first 15 characters of the hash control the pixels (even/odd)
|
||||
// they are drawn down the middle first, then mirrored outwards
|
||||
var i, color;
|
||||
for (i = 0; i < 15; i++) {
|
||||
color = parseInt(this.hash.charAt(i), 16) % 2 ? bg : fg;
|
||||
if (i < 5) {
|
||||
this.rectangle(2 * cell + margin, i * cell + margin, cell, cell, color, image);
|
||||
} else if (i < 10) {
|
||||
this.rectangle(1 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);
|
||||
this.rectangle(3 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);
|
||||
} else if (i < 15) {
|
||||
this.rectangle(0 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);
|
||||
this.rectangle(4 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
},
|
||||
|
||||
rectangle: function(x, y, w, h, color, image){
|
||||
if (this.isSvg()) {
|
||||
image.rectangles.push({x: x, y: y, w: w, h: h, color: color});
|
||||
} else {
|
||||
var i, j;
|
||||
for (i = x; i < x + w; i++) {
|
||||
for (j = y; j < y + h; j++) {
|
||||
image.buffer[image.index(i, j)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// adapted from: https://gist.github.com/aemkei/1325937
|
||||
hsl2rgb: function(h, s, b){
|
||||
h *= 6;
|
||||
s = [
|
||||
b += s *= b < .5 ? b : 1 - b,
|
||||
b - h % 1 * s * 2,
|
||||
b -= s *= 2,
|
||||
b,
|
||||
b + h % 1 * s,
|
||||
b + s
|
||||
];
|
||||
|
||||
return[
|
||||
s[ ~~h % 6 ] * 255, // red
|
||||
s[ (h|16) % 6 ] * 255, // green
|
||||
s[ (h|8) % 6 ] * 255 // blue
|
||||
];
|
||||
},
|
||||
|
||||
toString: function(raw){
|
||||
// backward compatibility with old toString, default to base64
|
||||
if (raw) {
|
||||
return this.render().getDump();
|
||||
} else {
|
||||
return this.render().getBase64();
|
||||
}
|
||||
},
|
||||
|
||||
isSvg: function(){
|
||||
return this.format.match(/svg/i)
|
||||
}
|
||||
};
|
||||
|
||||
var Svg = function(size, foreground, background){
|
||||
this.size = size;
|
||||
this.foreground = this.color.apply(this, foreground);
|
||||
this.background = this.color.apply(this, background);
|
||||
this.rectangles = [];
|
||||
};
|
||||
|
||||
Svg.prototype = {
|
||||
size: null,
|
||||
foreground: null,
|
||||
background: null,
|
||||
rectangles: null,
|
||||
|
||||
color: function(r, g, b, a){
|
||||
var values = [r, g, b].map(Math.round);
|
||||
values.push((a >= 0) && (a <= 255) ? a/255 : 1);
|
||||
return 'rgba(' + values.join(',') + ')';
|
||||
},
|
||||
|
||||
getDump: function(){
|
||||
var i,
|
||||
xml,
|
||||
rect,
|
||||
fg = this.foreground,
|
||||
bg = this.background,
|
||||
stroke = this.size * 0.005;
|
||||
|
||||
xml = "<svg xmlns='http://www.w3.org/2000/svg'"
|
||||
+ " width='" + this.size + "' height='" + this.size + "'"
|
||||
+ " style='background-color:" + bg + ";'>"
|
||||
+ "<g style='fill:" + fg + "; stroke:" + fg + "; stroke-width:" + stroke + ";'>";
|
||||
|
||||
for (i = 0; i < this.rectangles.length; i++) {
|
||||
rect = this.rectangles[i];
|
||||
if (rect.color == bg) continue;
|
||||
xml += "<rect "
|
||||
+ " x='" + rect.x + "'"
|
||||
+ " y='" + rect.y + "'"
|
||||
+ " width='" + rect.w + "'"
|
||||
+ " height='" + rect.h + "'"
|
||||
+ "/>";
|
||||
}
|
||||
xml += "</g></svg>"
|
||||
|
||||
return xml;
|
||||
},
|
||||
|
||||
getBase64: function(){
|
||||
if ('function' === typeof btoa) {
|
||||
return btoa(this.getDump());
|
||||
} else if (Buffer) {
|
||||
return new Buffer(this.getDump(), 'binary').toString('base64');
|
||||
} else {
|
||||
throw 'Cannot generate base64 output';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
||||
module.exports = Identicon;
|
||||
} else {
|
||||
window.Identicon = Identicon;
|
||||
}
|
||||
})();
|
||||
214
includes/js/pnglib.js
Normal file
214
includes/js/pnglib.js
Normal file
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* A handy class to calculate color values.
|
||||
*
|
||||
* @version 1.0
|
||||
* @author Robert Eisele <robert@xarg.org>
|
||||
* @copyright Copyright (c) 2010, Robert Eisele
|
||||
* @link http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
// helper functions for that ctx
|
||||
function write(buffer, offs) {
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
for (var j = 0; j < arguments[i].length; j++) {
|
||||
buffer[offs++] = arguments[i].charAt(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function byte2(w) {
|
||||
return String.fromCharCode((w >> 8) & 255, w & 255);
|
||||
}
|
||||
|
||||
function byte4(w) {
|
||||
return String.fromCharCode((w >> 24) & 255, (w >> 16) & 255, (w >> 8) & 255, w & 255);
|
||||
}
|
||||
|
||||
function byte2lsb(w) {
|
||||
return String.fromCharCode(w & 255, (w >> 8) & 255);
|
||||
}
|
||||
|
||||
// modified from original source to support NPM
|
||||
var PNGlib = function(width,height,depth) {
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.depth = depth;
|
||||
|
||||
// pixel data and row filter identifier size
|
||||
this.pix_size = height * (width + 1);
|
||||
|
||||
// deflate header, pix_size, block headers, adler32 checksum
|
||||
this.data_size = 2 + this.pix_size + 5 * Math.floor((0xfffe + this.pix_size) / 0xffff) + 4;
|
||||
|
||||
// offsets and sizes of Png chunks
|
||||
this.ihdr_offs = 0; // IHDR offset and size
|
||||
this.ihdr_size = 4 + 4 + 13 + 4;
|
||||
this.plte_offs = this.ihdr_offs + this.ihdr_size; // PLTE offset and size
|
||||
this.plte_size = 4 + 4 + 3 * depth + 4;
|
||||
this.trns_offs = this.plte_offs + this.plte_size; // tRNS offset and size
|
||||
this.trns_size = 4 + 4 + depth + 4;
|
||||
this.idat_offs = this.trns_offs + this.trns_size; // IDAT offset and size
|
||||
this.idat_size = 4 + 4 + this.data_size + 4;
|
||||
this.iend_offs = this.idat_offs + this.idat_size; // IEND offset and size
|
||||
this.iend_size = 4 + 4 + 4;
|
||||
this.buffer_size = this.iend_offs + this.iend_size; // total PNG size
|
||||
|
||||
this.buffer = new Array();
|
||||
this.palette = new Object();
|
||||
this.pindex = 0;
|
||||
|
||||
var _crc32 = new Array();
|
||||
|
||||
// initialize buffer with zero bytes
|
||||
for (var i = 0; i < this.buffer_size; i++) {
|
||||
this.buffer[i] = "\x00";
|
||||
}
|
||||
|
||||
// initialize non-zero elements
|
||||
write(this.buffer, this.ihdr_offs, byte4(this.ihdr_size - 12), 'IHDR', byte4(width), byte4(height), "\x08\x03");
|
||||
write(this.buffer, this.plte_offs, byte4(this.plte_size - 12), 'PLTE');
|
||||
write(this.buffer, this.trns_offs, byte4(this.trns_size - 12), 'tRNS');
|
||||
write(this.buffer, this.idat_offs, byte4(this.idat_size - 12), 'IDAT');
|
||||
write(this.buffer, this.iend_offs, byte4(this.iend_size - 12), 'IEND');
|
||||
|
||||
// initialize deflate header
|
||||
var header = ((8 + (7 << 4)) << 8) | (3 << 6);
|
||||
header+= 31 - (header % 31);
|
||||
|
||||
write(this.buffer, this.idat_offs + 8, byte2(header));
|
||||
|
||||
// initialize deflate block headers
|
||||
for (var i = 0; (i << 16) - 1 < this.pix_size; i++) {
|
||||
var size, bits;
|
||||
if (i + 0xffff < this.pix_size) {
|
||||
size = 0xffff;
|
||||
bits = "\x00";
|
||||
} else {
|
||||
size = this.pix_size - (i << 16) - i;
|
||||
bits = "\x01";
|
||||
}
|
||||
write(this.buffer, this.idat_offs + 8 + 2 + (i << 16) + (i << 2), bits, byte2lsb(size), byte2lsb(~size));
|
||||
}
|
||||
|
||||
/* Create crc32 lookup table */
|
||||
for (var i = 0; i < 256; i++) {
|
||||
var c = i;
|
||||
for (var j = 0; j < 8; j++) {
|
||||
if (c & 1) {
|
||||
c = -306674912 ^ ((c >> 1) & 0x7fffffff);
|
||||
} else {
|
||||
c = (c >> 1) & 0x7fffffff;
|
||||
}
|
||||
}
|
||||
_crc32[i] = c;
|
||||
}
|
||||
|
||||
// compute the index into a png for a given pixel
|
||||
this.index = function(x,y) {
|
||||
var i = y * (this.width + 1) + x + 1;
|
||||
var j = this.idat_offs + 8 + 2 + 5 * Math.floor((i / 0xffff) + 1) + i;
|
||||
return j;
|
||||
}
|
||||
|
||||
// convert a color and build up the palette
|
||||
this.color = function(red, green, blue, alpha) {
|
||||
|
||||
alpha = alpha >= 0 ? alpha : 255;
|
||||
var color = (((((alpha << 8) | red) << 8) | green) << 8) | blue;
|
||||
|
||||
if (typeof this.palette[color] == "undefined") {
|
||||
if (this.pindex == this.depth) return "\x00";
|
||||
|
||||
var ndx = this.plte_offs + 8 + 3 * this.pindex;
|
||||
|
||||
this.buffer[ndx + 0] = String.fromCharCode(red);
|
||||
this.buffer[ndx + 1] = String.fromCharCode(green);
|
||||
this.buffer[ndx + 2] = String.fromCharCode(blue);
|
||||
this.buffer[this.trns_offs+8+this.pindex] = String.fromCharCode(alpha);
|
||||
|
||||
this.palette[color] = String.fromCharCode(this.pindex++);
|
||||
}
|
||||
return this.palette[color];
|
||||
}
|
||||
|
||||
// output a PNG string, Base64 encoded
|
||||
this.getBase64 = function() {
|
||||
|
||||
var s = this.getDump();
|
||||
|
||||
var ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
var c1, c2, c3, e1, e2, e3, e4;
|
||||
var l = s.length;
|
||||
var i = 0;
|
||||
var r = "";
|
||||
|
||||
do {
|
||||
c1 = s.charCodeAt(i);
|
||||
e1 = c1 >> 2;
|
||||
c2 = s.charCodeAt(i+1);
|
||||
e2 = ((c1 & 3) << 4) | (c2 >> 4);
|
||||
c3 = s.charCodeAt(i+2);
|
||||
if (l < i+2) { e3 = 64; } else { e3 = ((c2 & 0xf) << 2) | (c3 >> 6); }
|
||||
if (l < i+3) { e4 = 64; } else { e4 = c3 & 0x3f; }
|
||||
r+= ch.charAt(e1) + ch.charAt(e2) + ch.charAt(e3) + ch.charAt(e4);
|
||||
} while ((i+= 3) < l);
|
||||
return r;
|
||||
}
|
||||
|
||||
// output a PNG string
|
||||
this.getDump = function() {
|
||||
|
||||
// compute adler32 of output pixels + row filter bytes
|
||||
var BASE = 65521; /* largest prime smaller than 65536 */
|
||||
var NMAX = 5552; /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
|
||||
var s1 = 1;
|
||||
var s2 = 0;
|
||||
var n = NMAX;
|
||||
|
||||
for (var y = 0; y < this.height; y++) {
|
||||
for (var x = -1; x < this.width; x++) {
|
||||
s1+= this.buffer[this.index(x, y)].charCodeAt(0);
|
||||
s2+= s1;
|
||||
if ((n-= 1) == 0) {
|
||||
s1%= BASE;
|
||||
s2%= BASE;
|
||||
n = NMAX;
|
||||
}
|
||||
}
|
||||
}
|
||||
s1%= BASE;
|
||||
s2%= BASE;
|
||||
write(this.buffer, this.idat_offs + this.idat_size - 8, byte4((s2 << 16) | s1));
|
||||
|
||||
// compute crc32 of the PNG chunks
|
||||
function crc32(png, offs, size) {
|
||||
var crc = -1;
|
||||
for (var i = 4; i < size-4; i += 1) {
|
||||
crc = _crc32[(crc ^ png[offs+i].charCodeAt(0)) & 0xff] ^ ((crc >> 8) & 0x00ffffff);
|
||||
}
|
||||
write(png, offs+size-4, byte4(crc ^ -1));
|
||||
}
|
||||
|
||||
crc32(this.buffer, this.ihdr_offs, this.ihdr_size);
|
||||
crc32(this.buffer, this.plte_offs, this.plte_size);
|
||||
crc32(this.buffer, this.trns_offs, this.trns_size);
|
||||
crc32(this.buffer, this.idat_offs, this.idat_size);
|
||||
crc32(this.buffer, this.iend_offs, this.iend_size);
|
||||
|
||||
// convert PNG to string
|
||||
return "\x89PNG\r\n\x1a\n"+this.buffer.join('');
|
||||
}
|
||||
}
|
||||
|
||||
// modified from original source to support NPM
|
||||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
||||
module.exports = PNGlib;
|
||||
} else {
|
||||
window.PNGlib = PNGlib;
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user