Your IP : 216.73.216.79


Current Path : /var/www/html/masjid/backend/modules/mmngr/controllers/
Upload File :
Current File : /var/www/html/masjid/backend/modules/mmngr/controllers/ModuleImportController.php

<?php
namespace backend\modules\mmngr\controllers;

use Yii;
use yii\web\Controller;
use backend\modules\mmngr\models\ModuleImportSearch;
use backend\modules\mmngr\models\ModuleImport;
use yii\web\UploadedFile;
use ZipArchive;
use Exception;

/**
 * Module for import external module trough zip file.
 */
class ModuleImportController extends Controller
{

    public $container;

    public $modulePath = '/../../backend/modules';

    public $moduleClass = 'backend\modules';

    public $moduleClassName = 'Module';

    public $metaFileName = 'meta.json';

    public $moduleIdName = 'module_id';

    public $metaModuleName = 'Module.php';

    public $imageDir = 'images/modules';

    public function actionIndex()
    {
        $searchModel = new ModuleImportSearch();
        $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

        return $this->render('index', [
            'searchModel' => $searchModel,
            'dataProvider' => $dataProvider,
            'imageDir' => $this->getImgDir()
        ]);
    }

    public function actionImport()
    {
        $m = new ModuleImport();
        $meta = null;
        if (Yii::$app->request->isPost) {
            try {
                $m->file = UploadedFile::getInstance($m, 'file');
                $zip = new ZipArchive();
                $status = $zip->open($m->file->tempName);
                if ($status !== TRUE) {
                    throw new Exception($this->ZipStatusString($status));
                }
                $filearr = null;
                if ($zip->numFiles > 0) {
                    for ($i = 0; $i < $zip->numFiles; $i ++) {
                        // get container name
                        if ($i === 0) {
							// fix problem with zip without container
							list($zipname) = explode('/', $zip->getNameIndex($i));
							$this->container = $zipname . '/';
                        }
                        $filename = $zip->getNameIndex($i);
                        $stat = $zip->statName($filename);
                        if (trim(strtolower($filename)) === trim(strtolower($this->container . $this->metaFileName))) {
                            $content = $zip->getFromName($filename);
                            $meta = $this->checkMetaContent($content);
                        }
                        // get an array of filename
                        if (! empty($filename) && $stat['size'] > 0) {
                            $filearr[] = trim(strtolower($filename));
                        }
                    }
                    if (! empty($filearr)) {
                        $this->checkRequiredFile($filearr);
                    }
                    if (! empty($meta)) {
                       $version = $this->saveMeta($meta, $zip);
                    }
                    $zip->extractTo(Yii::getAlias('@webroot') . $this->modulePath);
                    $zip->close();
                } else {
                    throw new Exception('Empty Zip file');
                }

                if ($version) {
                    Yii::$app->session->setFlash('success', 'Module Has been successfully Upgrade to ' . $version);
                } else {
                    Yii::$app->session->setFlash('success', 'Module Has been successfully imported!. You may check it in listing below.');
                }
                return $this->asJson([
                    'success' => true,
                    'message' => 'ok!'
                ]);
            } catch (Exception $e) {
                Yii::$app->response->statusCode = 500;
                return $this->asJson([
                    'success' => false,
                    'message' => $e->getMessage()
                ]);
            }
        }
    }

    public function actionRemove(int $id)
    {
        $m = ModuleImport::findOne($id);
        //run down sql
        $data = json_decode($m->data);
        
        if (!empty($data->sql->down)) {
            $this->downSql($data->sql->down);
        }
        
        //remove folder
        try {
            $this->deleteDir( Yii::getAlias('@webroot') . $this->modulePath . '/' . $m->module_id);
            $m->delete();
            Yii::$app->session->setFlash('success', 'Module Has Been removed');
            return $this->redirect(['/mmngr/module-import/']);
        } catch (\Exception $e) {
            Yii::$app->session->setFlash('danger', $e->getMessage());
            return $this->redirect(['/mmngr/module-import/']);
        }
    }

    private function checkMetaContent($content)
    {
        $meta = json_decode($content);

        if (json_last_error()) {
            throw new Exception('Error When Parsing Json! ' . json_last_error_msg());
        }
        if (! property_exists($meta, $this->moduleIdName)) {
            throw new Exception('Missing required meta attribute id');
        }
        if (! property_exists($meta, 'version')) {
            throw new Exception('Missing required meta attribute version');
        }
        if (trim(strtolower($meta->{$this->moduleIdName})) !== trim(strtolower($this->container), '/')) {
            throw new Exception('Module id must be the same with module container name');
        }
        if (preg_match('/[^a-zA-Z0-9\']/', trim(strtolower($meta->{$this->moduleIdName})))) {
            throw new Exception('Invalide Module Id, module id must alphanumeric not symbol allow');
        }

        $m = ModuleImport::checkIdExist(trim(strtolower($meta->module_id)), trim(strtolower($meta->version)));
        if (! $m) {
            throw new Exception('This version less than the current version');
        }
        
        return $meta;
    }

    /**
     * Process meta from meta file
     * @param string $meta Json string for meta
     * @param object $zip
     * @throws Exception
     * @return string | boolean return version on upgrade
     */
    private function saveMeta($meta, $zip)
    {
        $upgrade = $this->replaceMeta($meta->{$this->moduleIdName});
        $m = new ModuleImport();
        foreach (array_keys($m->attributeLabels()) as $key) {
            if (property_exists($meta, $key)) {
                $item = $meta->{$key};
                if (is_string($item)) {
                    $m->{$key} = $item;
                } else if (is_array($item)) {
                    $m->{$key} = json_encode($item);
                } else if (is_object($item)) {
                    $m->{$key} = json_encode($item);
                }
            }
        }
        
        if (property_exists($meta, 'image_path')) {
            if (is_array($meta->image_path)) {
                $img = $this->saveImage($meta->image_path, $zip);
                if (! empty($img)) {
                    $m->image_path = json_encode($img);
                }
            }
        }

        $m->class = "{$this->moduleClass}\\{$meta->module_id}\\{$this->moduleClassName}";

        if (property_exists($meta, 'data') && property_exists($meta->data, 'sql')) {
            $sql = $meta->data->sql;
            if (! empty($sql->up)) {
                $query = $this->upSql($sql->up, $zip);
            } else {
                throw new Exception('Up properties missing for run SQL up on Sql Meta');
            }
        }
        
        Yii::$app->db->transaction(function($db) use ($query, $m) {
            try {
                $db->createCommand($query)->execute();
            } catch(\Throwable $t) {
                throw new Exception('Error Saving Module From Meta :-\n' . $t->getMessage());
            }
            
            if (! $m->save()) {
                throw new Exception('Error Saving Module From Meta' . json_encode($m->errors));
            }
        });
        
        if ($upgrade) {
            return $meta->version;   
        } else {
            return false;
        }
    }
    
    /***
     * Replace Meta for module import
     * @param string $moduleId
     * @return boolean true if module is upgrade
     */
    private function replaceMeta($moduleId){
        $m = ModuleImport::find()->where(['module_id' => $moduleId])->one();
        if (!empty($m)) {
            $data = json_decode($m->data);
            if (!empty($data->sql->down)) {
                $this->downSql($data->sql->down);
            }
            $m->delete();
            return true;
        }
        
        return false;
    }
    
    private function upSql($filename, $zip)
    {
        $sql = $zip->getFromName($filename);
        if (! empty($sql)) {
            return $sql;
        } else {
            throw new \Exception('Cannot read sql file');
        }
    }
    
    private function downSql($data){
        try {
            $down = file_get_contents(Yii::getAlias('@webroot') . $this->modulePath . '/' . $data);
            Yii::$app->db->createCommand($down)->execute();
        } catch (\Exception $e){
            Yii::$app->session->setFlash('danger', $e->getMessage());
            return $this->redirect(['/mmngr/module-import/']);
        }
    }

    private function saveImage($images, $zip)
    {
        $dir = Yii::getAlias('@webroot') . '/' . $this->imageDir;
        if (! is_dir($dir)) {
            mkdir($dir, 0777, true);
        }

        if (is_array($images)) {
            $imgarr = [];
            foreach ($images as $image) {
                $img = $zip->getFromName($image);
                $im = imagecreatefromstring($img);
                $imgrand = preg_replace('/[^a-zA-Z0-9\']/', '_', $this->container . microtime());
                $imgarr[] = $imgrand . '.jpg';
                imagejpeg($im, $dir . '/' . $imgrand . '.jpg');
            }
            return $imgarr;
        }
    }

    private function checkRequiredFile(array $filearr)
    {
        if (! in_array(trim(strtolower($this->container . $this->metaFileName)), $filearr)) {
            throw new Exception('Missing meta.json file!');
        }

        if (! in_array(trim(strtolower($this->container . $this->metaModuleName)), $filearr)) {
            throw new Exception('Missing Module.php file!');
        }
    }

    private function ZipStatusString($status)
    {
        switch ((int) $status) {
            case ZipArchive::ER_OK:
                return 'N No error';
            case ZipArchive::ER_MULTIDISK:
                return 'N Multi-disk zip archives not supported';
            case ZipArchive::ER_RENAME:
                return 'S Renaming temporary file failed';
            case ZipArchive::ER_CLOSE:
                return 'S Closing zip archive failed';
            case ZipArchive::ER_SEEK:
                return 'S Seek error';
            case ZipArchive::ER_READ:
                return 'S Read error';
            case ZipArchive::ER_WRITE:
                return 'S Write error';
            case ZipArchive::ER_CRC:
                return 'N CRC error';
            case ZipArchive::ER_ZIPCLOSED:
                return 'N Containing zip archive was closed';
            case ZipArchive::ER_NOENT:
                return 'N No such file';
            case ZipArchive::ER_EXISTS:
                return 'N File already exists';
            case ZipArchive::ER_OPEN:
                return 'S Can\'t open file';
            case ZipArchive::ER_TMPOPEN:
                return 'S Failure to create temporary file';
            case ZipArchive::ER_ZLIB:
                return 'Z Zlib error';
            case ZipArchive::ER_MEMORY:
                return 'N Malloc failure';
            case ZipArchive::ER_CHANGED:
                return 'N Entry has been changed';
            case ZipArchive::ER_COMPNOTSUPP:
                return 'N Compression method not supported';
            case ZipArchive::ER_EOF:
                return 'N Premature EOF';
            case ZipArchive::ER_INVAL:
                return 'N Invalid argument';
            case ZipArchive::ER_NOZIP:
                return 'N Not a zip archive';
            case ZipArchive::ER_INTERNAL:
                return 'N Internal error';
            case ZipArchive::ER_INCONS:
                return 'N Zip archive inconsistent';
            case ZipArchive::ER_REMOVE:
                return 'S Can\'t remove file';
            case ZipArchive::ER_DELETED:
                return 'N Entry has been deleted';

            default:
                return sprintf('Unknown status %s', $status);
        }
    }

    private function deleteDir($dirPath)
    {
        if (! is_dir($dirPath)) {
            throw new Exception("$dirPath must be a directory");
        }
        if (substr($dirPath, strlen($dirPath) - 1, 1) != '/') {
            $dirPath .= '/';
        }
        $files = scandir($dirPath);
        foreach ($files as $file) {
            if ($file === '.' || $file === '..')
                continue;
            if (is_dir($dirPath . $file)) {
                $this->deleteDir($dirPath . $file);
            } else {
                if ($dirPath . $file !== __FILE__) {
                    unlink($dirPath . $file);
                }
            }
        }
        rmdir($dirPath);
    }
    
    public function getImgDir() {
        return Yii::getAlias('@web') . '/' . $this->imageDir;
    }
}