sam002/yii2-otp

View on GitHub
src/widgets/OtpInit.php

Summary

Maintainability
B
6 hrs
Test Coverage
<?php
/**
 * Author: Semen Dubina
 * Date: 20.01.16
 * Time: 4:22
 */

namespace sam002\otp\widgets;

use BaconQrCode\Common\CharacterSetEci;
use Da\QrCode\Contracts\ErrorCorrectionLevelInterface;
use Da\QrCode\Contracts\WriterInterface;
use Da\QrCode\QrCode;
use Da\QrCode\Writer\EpsWriter;
use Da\QrCode\Writer\JpgWriter;
use Da\QrCode\Writer\PngWriter;
use Da\QrCode\Writer\SvgWriter;
use sam002\otp\Otp;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\FileHelper;
use yii\helpers\Html;
use yii\widgets\InputWidget;

class OtpInit extends InputWidget
{

    /**
     * @var string
     */
    public $component = 'otp';

    /**
     * @var bool|string
     */
    public $link = true;

    /**
     * @var array
     */
    public $QrParams;

    /**
     * @var array
     */
    private $defaultQrParams = [
        'logo' => null,
        'logoWidth' => null,
        'foregroundColor' => [0,0,0],
        'backgroundColor' => [255,255,255],
        'encoding' => 'UTF-8',
        'label' => null,
        'outfile' => false,
        'level' => ErrorCorrectionLevelInterface::HIGH,
        'size' => 300,
        'margin' => 10,
        'save' => false,
        'type' => PngWriter::class
    ];

    /**
     * @var \OTPHP\OTP
     */
    private $otp;

    public function init()
    {
        /** @var Otp $componentOtp */
        $componentOtp = Yii::$app->get($this->component);
        parent::init();

        $secret = $this->model->{$this->attribute};
        if (!empty($secret)) {
            $componentOtp->setSecret($secret);
        }

        $this->otp = $componentOtp->getOtp();
        $this->QrParams = array_merge($this->defaultQrParams, $this->QrParams);
    }

    public function run()
    {
        parent::run();
        return $this->renderWidget();
    }


    /**
     * Render Image and link block
     */
    private function renderWidget()
    {
        $img = $this->getImageSource();

        echo "<div>$img</div>";

        if ($this->link || is_string($this->link)) {
            echo Html::a($this->link, $this->otp->getProvisioningUri());
        }

        if ($this->hasModel()
            && $this->model->hasProperty($this->attribute)
            && empty($this->model->{$this->attribute})
        ) {
            $this->model->setAttributes([$this->attribute => $this->otp->getSecret()]);
        }
        echo Html::activeHiddenInput($this->model, $this->attribute, $this->options);
    }

    private function getImageSource()
    {
        switch ($this->QrParams['type']) {
            case JpgWriter::class :
                $imgSrc = "data:image/jpeg;base64,";
                $img = '<img src=' . $imgSrc . base64_encode($this->generateQr($this->otp->getProvisioningUri())) . ' />';
                break;
            case PngWriter::class :
                $imgSrc = "data:image/png;base64,";
                $img = '<img src=' . $imgSrc . base64_encode($this->generateQr($this->otp->getProvisioningUri())) . ' />';
                break;
            case SvgWriter::class :
            case EpsWriter::class :
            default:
                $img = $this->generateQr($this->otp->getProvisioningUri());
        }

        return $img;
    }

    /**
     * @param string $text
     * @return array|int
     * @throws InvalidConfigException
     */
    private function generateQr($text = '')
    {
        $qrCode = $this->initQr($text);
        $qrCode = $this->decorateQr($qrCode);
        $qrCode = $this->initLogoQr($qrCode);


        $image = $qrCode->writeString();

        if(
            isset($this->QrParams['save'])
            && $this->checkParamSave($this->QrParams['save'])
        ) {
            $outfile = $this->checkParamOutfile($this->QrParams['outfile']);
            file_put_contents($outfile, $image);
        }

        return $image;
    }

    private function initQr($text)
    {

        $level = ErrorCorrectionLevelInterface::QUARTILE;
        $type = PngWriter::class;
        $encoding = 'UTF-8';
        $label = null;

        foreach ($this->QrParams as $name => $param) {
            switch ($name) {
                case 'level':
                    $level = $this->checkParamLevel($param);
                    break;
                case 'type':
                    $type = $this->checkParamType($param);
                    break;
                case 'encoding':
                    $encoding = $this->checkParamEncoding($param);
                    break;
                case 'label':
                    $label = is_string($param) ? $param : null;
                    break;
                default:
                    break;
            }
        }

        $writer = new $type();
        /** @var QrCode $qrCode */
        return (new QrCode($text, $level, $writer))
            ->useEncoding($encoding)
            ->setLabel($label);
    }

    private function initLogoQr($qrCode)
    {
        if (empty($this->QrParams['logo'])) {
            return $qrCode;
        }
        $with = isset($this->QrParams['logoWith']) ? $this->QrParams['logoWith'] : null;
        $logo = $this->checkParamLogo($this->QrParams['logo']);
        return $qrCode->useLogo($logo)
            ->setLogoWidth($with);
    }

    /**
     * @param QrCode $qrCode
     * @return QrCode
     * @throws InvalidConfigException
     */
    private function decorateQr(QrCode $qrCode)
    {

        $foreColor = [0,0,0];
        $backColor = [255,255,255];
        $size = 300;
        $margin = 10;

        foreach ($this->QrParams as $name => $param) {
            switch ($name) {
                case 'foregroundColor':
                    $foreColor = $this->checkParamColor($param);
                    break;
                case 'backgroundColor':
                    $backColor = $this->checkParamColor($param);
                    break;
                case 'margin':
                    $margin = $this->checkParamMargin($param);
                    break;
                case 'size':
                    $size = $this->checkParamSize($param);
                    break;
                default:
                    break;
            }
        }
        return $qrCode->useForegroundColor($foreColor[0], $foreColor[1], $foreColor[2])
            ->useBackgroundColor($backColor[0], $backColor[1], $backColor[2])
            ->setMargin($margin)
            ->setSize($size);
    }

    private function checkParamOutfile($outfile)
    {
        if(is_string($outfile) && !is_dir(dirname($outfile))) {
            throw new InvalidConfigException('OtpInit::$qrParams[\'outfile\'] error ' . dirname($outfile) . ' is not dir');
        } elseif (!is_string($outfile) && $outfile !== false) {
            throw new InvalidConfigException('OtpInit::$qrParams[\'outfile\'] is not false or file path');
        }
        if($outfile === false) {
            $outfile = Yii::$app->runtimePath . DIRECTORY_SEPARATOR .
                'yii2otp' . DIRECTORY_SEPARATOR . 'savedQr' . uniqid('qr_', true);
            $dir = dirname($outfile);
            if (!is_dir($dir)) {
                FileHelper::createDirectory($dir);
            }
        }
        return $outfile;
    }

    private function checkParamLevel($level)
    {
        $qrCodeLevels = [
            ErrorCorrectionLevelInterface::LOW,
            ErrorCorrectionLevelInterface::MEDIUM,
            ErrorCorrectionLevelInterface::QUARTILE,
            ErrorCorrectionLevelInterface::HIGH
        ];
        if (!in_array($level, $qrCodeLevels, true)) {
            throw new InvalidConfigException('OtpInit::$qrParams[\'level\'] is not ErrorCorrectionLevelInterface*');
        }
        return $level;
    }

    private function checkParamSize($size)
    {
        if (!is_int($size)) {
            throw new InvalidConfigException('OtpInit::$qrParams[\'size\'] is not integer');
        }
        return $size;
    }

    private function checkParamMargin($margin)
    {
        if (!is_int($margin)) {
            throw new InvalidConfigException('OtpInit::$qrParams[\'margin\'] is not integer');
        }
        return $margin;
    }


    private function checkParamSave($saveAndPrint)
    {
        if (!is_bool($saveAndPrint)) {
            throw new InvalidConfigException('OtpInit::$qrParams[\'save\'] is not boolean');
        }
        return $saveAndPrint;
    }

    private function checkParamType($type)
    {
        if(!is_subclass_of($type, WriterInterface::class)) {
            throw new InvalidConfigException('OtpInit::$qrParams[\'type\'] is not extend ' . WriterInterface::class);
        }
        return $type;
    }

    private function checkParamLogo($logo)
    {
        if (empty($logo)) {
            return null;
        }

        //Try process URL
        if (!is_file($logo)) {
            $img = file_get_contents($logo);

            $tmpFile = Yii::$app->runtimePath . DIRECTORY_SEPARATOR .
                    'yii2otp' . DIRECTORY_SEPARATOR . 'proxylogo'
                    . md5($logo);

            $dir = dirname($tmpFile);
            if (!is_dir($dir)) {
                FileHelper::createDirectory($dir);
            }
            $logo = file_put_contents($tmpFile, $img);
        }

        if (!is_file($logo)) {
            throw new InvalidConfigException('OtpInit::$qrParams[\'logo\'] '.$logo.' is not exist');
        }
        return $logo;
    }

    private function checkParamEncoding($encoding)
    {
        if (CharacterSetEci::getCharacterSetECIByName($encoding) === null) {
            throw new InvalidConfigException('OtpInit::$qrParams[\'encoding\'] Unknown '.$encoding.' encoding');
        }
        return $encoding;
    }

    private function checkParamColor($color)
    {

        $max = max($color);
        $min = min($color);
        if (!is_array($color)
            || count($color) != 3
            || 255 < $max
            || 0 > $min
        ) {
            throw new InvalidConfigException('OtpInit::$qrParams[\'encoding\'] Not correct color');
        }
        return $color;
    }
}