From 869dd2c799500ebc5faf4a2e094cd79461611202 Mon Sep 17 00:00:00 2001 From: Shine Date: Tue, 31 Dec 2024 17:29:31 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Global=20switch=20for=20the=20QR?= =?UTF-8?q?=20code=20generator=20(#129)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 1 + src/config/environment.php | 2 + src/forms/qrcode_options.php | 12 +- src/lib/DynamicQrcode/DynamicQrcode.php | 9 +- src/lib/Qrcode/Qrcode-intchil.php | 447 ++++++++++++++++++++++++ src/lib/Qrcode/Qrcode.php | 229 +----------- src/lib/StaticQrcode/StaticQrcode.php | 9 +- 7 files changed, 494 insertions(+), 215 deletions(-) create mode 100644 src/lib/Qrcode/Qrcode-intchil.php diff --git a/docker-compose.yml b/docker-compose.yml index cb379b1..ba133c5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,7 @@ services: restart: "unless-stopped" environment: TYPE: "docker" + QRCODE_GENERATOR: "internal-chillerlan.qrcode" BASE_URL: "https://mydomain.com" DATABASE_HOST: "php-dynamic-qrcode-db" DATABASE_PORT: "3306" diff --git a/src/config/environment.php b/src/config/environment.php index 609fb56..add1fc3 100644 --- a/src/config/environment.php +++ b/src/config/environment.php @@ -15,6 +15,7 @@ if(is_string(Getenv('TYPE')) && Getenv('TYPE') == "docker") { define('DATABASE_CHARSET', Getenv('DATABASE_CHARSET')); define('TYPE', Getenv('TYPE')); define('BASE_URL', Getenv('BASE_URL')); + define("QRCODE_GENERATOR", Getenv('QRCODE_GENERATOR')); } else { define('DATABASE_HOST', "localhost"); define('DATABASE_PORT', "3306"); @@ -23,6 +24,7 @@ if(is_string(Getenv('TYPE')) && Getenv('TYPE') == "docker") { define('DATABASE_PASSWORD', "root"); define('DATABASE_PREFIX', "qr_"); define('DATABASE_CHARSET', "utf8"); + define("QRCODE_GENERATOR", "external-api.qrserver.com"); // external-api.qrserver.com => https://api.qrserver.com/v1/create-qr-code/?data= // internal-chillerlan.qrcode => https://github.com/chillerlan/php-qrcode } /* |-------------------------------------------------------------------------- diff --git a/src/forms/qrcode_options.php b/src/forms/qrcode_options.php index b7f3b34..219d86c 100644 --- a/src/forms/qrcode_options.php +++ b/src/forms/qrcode_options.php @@ -45,7 +45,11 @@ - +2000'; +} +?> @@ -76,7 +80,11 @@ - +SVG (BW)'; +} +?> diff --git a/src/lib/DynamicQrcode/DynamicQrcode.php b/src/lib/DynamicQrcode/DynamicQrcode.php index 75c5cb1..c484b70 100644 --- a/src/lib/DynamicQrcode/DynamicQrcode.php +++ b/src/lib/DynamicQrcode/DynamicQrcode.php @@ -1,6 +1,13 @@ table = "static_qrcodes"; + $this->redirect_url = "static_qrcodes.php"; + } else if($type === "dynamic") { + $this->table = "dynamic_qrcodes"; + $this->redirect_url = "dynamic_qrcodes.php"; + } else { + $this->redirect_url = "index.php"; + $this->failure("Type not allowed"); + } + } + + /** + * + */ + public function __destruct() + { + } + + public function getQrcode($id) { + $db = getDbInstance(); + + $db->where('id', $id); + $result = $db->getOne($this->table); + + if($result !== NULL) + return $result; + else + $this->failure("Qrcode not found"); + } + + /** + * Set option for qr code like: + * Error Correction Level, size (default = 100), foreground, background + * return array of values + */ + private function setOptions($input_data) { + $errorCorrectionLevel = 'L'; + + if (isset($input_data['level']) && in_array($input_data['level'], array('L','M','Q','H'))) + $errorCorrectionLevel = $input_data['level']; + + $size = 100; + if (isset($input_data['size'])) + $size = min(max((int)$input_data['size'], 100), 2000); + + //character # deleted + $foreground = substr($input_data['foreground'], 1); + $background = substr($input_data['background'], 1); + + //$logo = $_POST['optionlogo']; + + return array( + "errorCorrectionLevel" => $errorCorrectionLevel, + "size" => $size, + "foreground" => $foreground, + "background" => $background, + //"optionlogo" => $logo, + ); + } + + /** + * Add qr code + * Check out http://goqr.me/api/ for more information + * We save the file obtained with the chosen name and in the selected folder + * We save into db the url of qrcode image + */ + public function addQrcode($input_data, $data_to_db, $data_to_qrcode) { + $options = $this->setOptions($input_data); + + $outputInterface = QRGdImagePNG::class; + $imageFormat = strtolower($data_to_db['format']); + $fileExt = $imageFormat; + $forceBlackWhite = false; + + + switch ($imageFormat) + { + case 'png': + $outputInterface = QRGdImagePNG::class; + $imageFormat = 'png'; + break; + case 'gif': + $outputInterface = QRImagick::class; + $imageFormat = 'gif'; + break; + case 'jpg': + $outputInterface = QRImagick::class; + $imageFormat = 'jpg'; + break; + case 'jpeg': + $outputInterface = QRImagick::class; + $imageFormat = 'jpeg'; + break; + case 'svg': + # $outputInterface = QRImagick::class; + $outputInterface = QRMarkupSVG::class; + $imageFormat = 'svg'; + break; + case 'svgbw': + $outputInterface = QRMarkupSVG::class; + $imageFormat = 'svg'; + $fileExt = 'svg'; + $forceBlackWhite = true; + $data_to_db['format'] = $fileExt; + $data_to_db['qrcode'] = str_replace('.svgbw', '.svg', $data_to_db['qrcode']); + break; + case 'eps': + $outputInterface = QREps::class; + $imageFormat = 'eps'; + break; + } + + + if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.' . $fileExt)){ + $eccLevel = constant(EccLevel::class.'::'.strtoupper($options['errorCorrectionLevel'])); + + $qroptions = new QROptions; + $qroptions->outputInterface = $outputInterface; + $qroptions->outputBase64 = false; + $qroptions->eccLevel = $eccLevel; + $qroptions->quietzoneSize = 2; + + $moduleValues = [ + // finder + QRMatrix::M_FINDER_DARK => $options['foreground'], + QRMatrix::M_FINDER_DOT => $options['foreground'], + QRMatrix::M_FINDER => $options['background'], + // alignment + QRMatrix::M_ALIGNMENT_DARK => $options['foreground'], + QRMatrix::M_ALIGNMENT => $options['background'], + // timing + QRMatrix::M_TIMING_DARK => $options['foreground'], + QRMatrix::M_TIMING => $options['background'], + // format + QRMatrix::M_FORMAT_DARK => $options['foreground'], + QRMatrix::M_FORMAT => $options['background'], + // version + QRMatrix::M_VERSION_DARK => $options['foreground'], + QRMatrix::M_VERSION => $options['background'], + // data + QRMatrix::M_DATA_DARK => $options['foreground'], + QRMatrix::M_DATA => $options['background'], + // darkmodule + QRMatrix::M_DARKMODULE => $options['foreground'], + // separator + QRMatrix::M_SEPARATOR => $options['background'], + // quietzone + QRMatrix::M_QUIETZONE => $options['background'], + ]; + + if(in_array($data_to_db['format'], ['png', 'jpg', 'gif'], true)) + { + $moduleValues = array_map(function($v) + { + if(preg_match('/[a-f\d]{6}/i', $v) === 1) + { + return array_map('hexdec', str_split($v, 2)); + } + + return null; + }, $moduleValues); + + $qroptions->moduleValues = $moduleValues; + } + else + { + $moduleValues = array_map(function($v) + { + if(preg_match('/[a-f\d]{6}/i', $v) === 1) + { + return '#' . $v ; + } + + return null; + }, $moduleValues); + + $qroptions->moduleValues = $moduleValues; + } + + if ($outputInterface === QRMarkupSVG::class) + { + $qroptions->version = Version::AUTO; + // if set to false, the light modules won't be rendered + $qroptions->drawLightModules = true; + $qroptions->svgUseFillAttributes = false; + // draw the modules as circles isntead of squares + $qroptions->drawCircularModules = true; + $qroptions->circleRadius = 0.4; + // connect paths + $qroptions->connectPaths = true; + // keep modules of these types as square + $qroptions->keepAsSquare = [ + QRMatrix::M_FINDER_DARK, + QRMatrix::M_FINDER_DOT, + QRMatrix::M_ALIGNMENT_DARK, + ]; + + if($forceBlackWhite) + { + $qroptions->drawLightModules = false; + $qroptions->drawCircularModules = false; + // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient + $qroptions->svgDefs = ' + + + + + '; + } + else + { + // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient + $qroptions->svgDefs = ' + + + + + + + + + '; + } + } + else + { + $qroptions->version = Version::AUTO; + $qroptions->scale = 20; + $qroptions->quality = 83; + } + + if ($outputInterface === QRImagick::class) + { + $qroptions->imagickFormat = $imageFormat; + $qroptions->returnResource = true; + $imagick = (new QRCodeEx($qroptions))->render(urldecode($data_to_qrcode)); + $imagick->scaleImage($options['size'], $options['size'], true); + $content = $imagick->getImageBlob(); + $imagick->destroy(); + } + else + { + $content = (new QRCodeEx($qroptions))->render(urldecode($data_to_qrcode)); + } + + $filename = SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.' . $fileExt; + + try + { + file_put_contents($filename, $content); + if ($outputInterface !== QRImagick::class && + $outputInterface !== QRMarkupSVG::class) + { + $imagick = new \Imagick(realpath($filename)); + $imagick->resizeImage($options['size'], $options['size'], imagick::FILTER_LANCZOS, 1, false); + $imagick->writeImage($filename); + $imagick->destroy(); + } + } + catch(Exception $e) + { + $this->failure($e->getMessage()); + } + + // If you want you can customiaddLogo($data_to_db['qrcode'], $options['optionlogo']); + + $db = getDbInstance(); + $last_id = $db->insert($this->table, $data_to_db); + } + else + $this->failure('You cannot create a new qr code with an existing name on the server!'); + + if ($last_id){ + $this->success('Qr code added successfully!'); + } + else { + $this->failure('Insert failed: ' . $db->getLastError()); + } + } + + /** + * Edit qr code + * + */ + public function editQrcode($input_data, $data_to_db) { + $db = getDbInstance(); + $old_qrcode = $this->getQrcode($input_data["id"]); + + $data_to_db['qrcode'] = $data_to_db['filename'].'.'.$old_qrcode["format"]; + + if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$old_qrcode["format"]) || $data_to_db['filename'] == $input_data["old_filename"]){ + $db->where('id', $input_data["id"]); + $stat = $db->update($this->table, $data_to_db); + + try{ + rename(SAVED_QRCODE_DIRECTORY.$old_qrcode["qrcode"], SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$old_qrcode["format"]); + } + catch(Exception $e){ + $this->failure($e->getMessage()); + } + } + else + $this->failure('You cannot edit a qr code with an existing name on the server!'); + + if ($stat){ + $this->success('Qr code updated successfully!'); + } + else { + $this->failure('Insert failed: ' . $db->getLastError()); + } + } + + + /** + * Delete qr code + * + */ + public function deleteQrcode($id, $async = false) { + $db = getDbInstance(); + + $qrcode = $this->getQrcode($id); + + $db->where('id', $id); + $status = $db->delete($this->table); + + try{ + unlink(SAVED_QRCODE_DIRECTORY.$qrcode["filename"].'.'.$qrcode["format"]); + } + catch(Exception $e){ + $this->failure($e->getMessage()); + } + + if ($status) + if (!$async) { + $this->info('Qr code deleted successfully!'); + } + else + if (!$async) { + $this->failure('Unable to delete qr code'); + } + } + + /** + * Add logo + * IMPORTANT: I do not recommend to use this option because there may be problems with the scanning of the qr code as some readers may not recognize the code + */ + private function addLogo($src, $logo = 'none') { + try + { + if($logo != 'none') + { + $logo = imagecreatefrompng($_SERVER['HTTP_HOST'].'/admin'.$logo); + $QR = imagecreatefrompng(BASE_PATH.$src); + + $QR_width = imagesx($QR); + $QR_height = imagesy($QR); + + $logo_width = imagesx($logo); + $logo_height = imagesy($logo); + + // Scale logo to fit in the QR Code + $logo_qr_width = $QR_width/3; + $scale = $logo_width/$logo_qr_width; + $logo_qr_height = $logo_height/$scale; + + // You can try also with imagecopymerge() with same arguments + imagecopyresampled($QR, $logo, $QR_width/3, $QR_height/3, 0, 0, $logo_qr_width, $logo_qr_height, $logo_width, $logo_height); + + //$output = Set directory for saving image; + header('Content-Type: image/png'); + imagepng($QR /*, $output*/); + imagedestroy($QR); + } + } + catch(Exception $e) + { + $this->failure($e->getMessage()); + } + } + + /** + * Flash message Failure process + */ + private function failure($message) { + $_SESSION['failure'] = $message; + header('Location: ' . $this->redirect_url); + exit(); + } + + /** + * Flash message Success process + */ + private function success($message) { + $_SESSION['success'] = $message; + header('Location: ' . $this->redirect_url); + exit(); + } + + /** + * Flash message Info process + */ + private function info($message) { + $_SESSION['info'] = $message; + header('Location: ' . $this->redirect_url); + exit(); + } + + public function debug($data) { + echo '
' . var_export($data, true) . '
'; + exit(); + } +} +?> diff --git a/src/lib/Qrcode/Qrcode.php b/src/lib/Qrcode/Qrcode.php index 0043258..8686d09 100644 --- a/src/lib/Qrcode/Qrcode.php +++ b/src/lib/Qrcode/Qrcode.php @@ -1,19 +1,5 @@ setOptions($input_data); - $outputInterface = QRGdImagePNG::class; - $imageFormat = strtolower($data_to_db['format']); - $fileExt = $imageFormat; - $forceBlackWhite = false; + if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$data_to_db['format'])){ + $url = + 'https://api.qrserver.com/v1/create-qr-code/?data='. + $data_to_qrcode. + '&&size='.$options['size'].'x'.$options['size']. + '&ecc='.$options['errorCorrectionLevel']. + '&margin=0&color='.$options['foreground']. + '&bgcolor='.$options['background']. + '&qzone=2'. + '&format='.$data_to_db['format']; - - switch ($imageFormat) - { - case 'png': - $outputInterface = QRGdImagePNG::class; - $imageFormat = 'png'; - break; - case 'gif': - $outputInterface = QRImagick::class; - $imageFormat = 'gif'; - break; - case 'jpg': - $outputInterface = QRImagick::class; - $imageFormat = 'jpg'; - break; - case 'jpeg': - $outputInterface = QRImagick::class; - $imageFormat = 'jpeg'; - break; - case 'svg': - # $outputInterface = QRImagick::class; - $outputInterface = QRMarkupSVG::class; - $imageFormat = 'svg'; - break; - case 'svgbw': - $outputInterface = QRMarkupSVG::class; - $imageFormat = 'svg'; - $fileExt = 'svg'; - $forceBlackWhite = true; - $data_to_db['format'] = $fileExt; - $data_to_db['qrcode'] = str_replace('.svgbw', '.svg', $data_to_db['qrcode']); - break; - case 'eps': - $outputInterface = QREps::class; - $imageFormat = 'eps'; - break; - } - - - if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.' . $fileExt)){ - $eccLevel = constant(EccLevel::class.'::'.strtoupper($options['errorCorrectionLevel'])); - - $qroptions = new QROptions; - $qroptions->outputInterface = $outputInterface; - $qroptions->outputBase64 = false; - $qroptions->eccLevel = $eccLevel; - $qroptions->quietzoneSize = 2; - - $moduleValues = [ - // finder - QRMatrix::M_FINDER_DARK => $options['foreground'], - QRMatrix::M_FINDER_DOT => $options['foreground'], - QRMatrix::M_FINDER => $options['background'], - // alignment - QRMatrix::M_ALIGNMENT_DARK => $options['foreground'], - QRMatrix::M_ALIGNMENT => $options['background'], - // timing - QRMatrix::M_TIMING_DARK => $options['foreground'], - QRMatrix::M_TIMING => $options['background'], - // format - QRMatrix::M_FORMAT_DARK => $options['foreground'], - QRMatrix::M_FORMAT => $options['background'], - // version - QRMatrix::M_VERSION_DARK => $options['foreground'], - QRMatrix::M_VERSION => $options['background'], - // data - QRMatrix::M_DATA_DARK => $options['foreground'], - QRMatrix::M_DATA => $options['background'], - // darkmodule - QRMatrix::M_DARKMODULE => $options['foreground'], - // separator - QRMatrix::M_SEPARATOR => $options['background'], - // quietzone - QRMatrix::M_QUIETZONE => $options['background'], - ]; - - if(in_array($data_to_db['format'], ['png', 'jpg', 'gif'], true)) - { - $moduleValues = array_map(function($v) - { - if(preg_match('/[a-f\d]{6}/i', $v) === 1) - { - return array_map('hexdec', str_split($v, 2)); - } - - return null; - }, $moduleValues); - - $qroptions->moduleValues = $moduleValues; - } - else - { - $moduleValues = array_map(function($v) - { - if(preg_match('/[a-f\d]{6}/i', $v) === 1) - { - return '#' . $v ; - } - - return null; - }, $moduleValues); - - $qroptions->moduleValues = $moduleValues; - } - - if ($outputInterface === QRMarkupSVG::class) - { - $qroptions->version = 7; - // if set to false, the light modules won't be rendered - $qroptions->drawLightModules = true; - $qroptions->svgUseFillAttributes = false; - // draw the modules as circles isntead of squares - $qroptions->drawCircularModules = true; - $qroptions->circleRadius = 0.4; - // connect paths - $qroptions->connectPaths = true; - // keep modules of these types as square - $qroptions->keepAsSquare = [ - QRMatrix::M_FINDER_DARK, - QRMatrix::M_FINDER_DOT, - QRMatrix::M_ALIGNMENT_DARK, - ]; - - if($forceBlackWhite) - { - $qroptions->drawLightModules = false; - $qroptions->drawCircularModules = false; - // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient - $qroptions->svgDefs = ' - - - - - '; - } - else - { - // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient - $qroptions->svgDefs = ' - - - - - - - - - '; - } - } - else - { - $qroptions->version = -1; - $qroptions->scale = 20; - $qroptions->quality = 83; - } - - if ($outputInterface === QRImagick::class) - { - $qroptions->imagickFormat = $imageFormat; - $qroptions->returnResource = true; - $imagick = (new QRCodeEx($qroptions))->render(urldecode($data_to_qrcode)); - $imagick->scaleImage($options['size'], $options['size'], true); - $content = $imagick->getImageBlob(); - $imagick->destroy(); - } - else - { - $content = (new QRCodeEx($qroptions))->render(urldecode($data_to_qrcode)); - } - - $filename = SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.' . $fileExt; - - try - { + $content = file_get_contents($url); + + $filename = SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$data_to_db['format']; + + try{ file_put_contents($filename, $content); - if ($outputInterface !== QRImagick::class && - $outputInterface !== QRMarkupSVG::class) - { - $imagick = new \Imagick(realpath($filename)); - $imagick->resizeImage($options['size'], $options['size'], imagick::FILTER_LANCZOS, 1, false); - $imagick->writeImage($filename); - $imagick->destroy(); - } } - catch(Exception $e) - { + catch(Exception $e){ $this->failure($e->getMessage()); } - + // If you want you can customiaddLogo($data_to_db['qrcode'], $options['optionlogo']); diff --git a/src/lib/StaticQrcode/StaticQrcode.php b/src/lib/StaticQrcode/StaticQrcode.php index da8ca9a..2323750 100644 --- a/src/lib/StaticQrcode/StaticQrcode.php +++ b/src/lib/StaticQrcode/StaticQrcode.php @@ -2,7 +2,14 @@ require_once 'config/config.php'; require_once BASE_PATH . '/lib/ICS/ICS.php'; require_once BASE_PATH . '/lib/vCard/vCard.php'; -require_once BASE_PATH . '/lib/Qrcode/Qrcode.php'; + +if (QRCODE_GENERATOR === "external-api.qrserver.com") { + require_once BASE_PATH . '/lib/Qrcode/Qrcode.php'; +} + +if (QRCODE_GENERATOR === "internal-chillerlan.qrcode") { + require_once BASE_PATH . '/lib/Qrcode/Qrcode-intchil.php'; +} class StaticQrcode { private $sData; // Data for the qr code