| Current Path : /var/www/html/ukas/backend/modules/mmngr/controllers/ |
| Current File : /var/www/html/ukas/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;
use backend\modules\mmngr\models\ModuleRepositoryInstall;
/**
* 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()
]);
}
/**
* Proccess zip file upload
* @return \yii\web\Response
*/
public function actionImport()
{
$m = new ModuleImport();
if (Yii::$app->request->isPost) {
try {
$m->file = UploadedFile::getInstance($m, 'file');
$this->unpack($m->file->tempName);
return $this->asJson([
'success' => true,
'message' => 'ok!'
]);
} catch (Exception $e) {
Yii::$app->response->statusCode = 500;
return $this->asJson([
'success' => false,
'message' => $e->getMessage()
]);
}
}
}
/**
* Unpack zip and process all the content
* @param string $file path to the zip file
* @throws Exception
*/
private function unpack(string $file)
{
$zip = new ZipArchive();
$meta = null;
$status = $zip->open($file);
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)) {
$module = $this->saveMeta($meta, $zip);
}
$zip->extractTo(Yii::getAlias('@webroot') . $this->modulePath);
$zip->close();
} else {
throw new Exception('Empty Zip file');
}
Yii::$app->session->setFlash('success', 'Module Has been successfully install - Version ' . $module['version']);
return $module;
}
/**
* Remove file by module id
* @param int $id
* @return \yii\web\Response
*/
public function actionRemove(int $id)
{
$m = ModuleImport::findOne($id);
//run down sql
if(!empty($m->data)) {
$data = json_decode($m->data);
if (!empty($data->sql->down)) {
$this->downSql($data->sql->down);
}
}
$repo = ModuleRepositoryInstall::find()->where([
'module_id' => $id
])->one();
//remove folder
try {
//delete the folder
$this->deleteDir( Yii::getAlias('@webroot') . $this->modulePath . '/' . $m->module_id);
//delete the data
$m->delete();
if(!empty($repo)) {
$repo->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/']);
}
}
/**
* Check meta json validity
* @param string $content
* @throws Exception
* @return mixed
*/
private function checkMetaContent(string $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('Invalid Module Id, module id must alphanumeric no symbol is allowed');
}
$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)
{
$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;
$upgradeQuery = null; //for upgrade
if (! empty($sql->up)) {
$query = $this->upSql($sql->up, $zip);
} else {
throw new Exception('Up properties missing for run SQL up on Sql Meta');
}
if(property_exists($sql, 'upgrade')) {
if (property_exists($sql->upgrade, 'up') && ! empty($sql->upgrade->up)) {
$upgradeQuery = $this->upSql($sql->upgrade->up, $zip);
}
}
}
Yii::$app->db->transaction(function($db) use ($query, $upgradeQuery, $m) {
try {
$db->createCommand($query)->execute();
if (! empty($upgradeQuery)) {
$db->createCommand($upgradeQuery)->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));
}
});
return [
'version' => $meta->version,
'id' => $m->id
];
}
/***
* 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)) {
$m->delete();
return true;
}
return false;
}
/***
* Run sql querys
* @param string $moduleId
* @return boolean true if module is upgrade
*/
private function upSql($filename, $zip)
{
$sql = $zip->getFromName($filename);
if (! empty($sql)) {
return $sql;
} else {
throw new \Exception('Cannot read sql file');
}
}
/**
* Run sql on module uninstallation
* @param string $data
* @return \yii\web\Response
*/
private function downSql(string $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;
}
}
/**
* Check if the required file is present
* @param array $filearr
* @throws Exception
*/
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!');
}
}
/**
* Send zip status base on error code
* @param string $status
* @return string
*/
private function ZipStatusString(string $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);
}
}
/**
* Delete the provided path
* @param string $dirPath delete path
* @throws Exception
*/
private function deleteDir(string $dirPath)
{
try {
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);
} catch (\Throwable $t) {
//silent error folder not exist on module remove
}
}
/**
* Return image path
* @return string
*/
public function getImgDir() {
return Yii::getAlias('@web') . '/' . $this->imageDir;
}
/**
* External install provider
* @param string $path Path to file
**/
public function actionRepositoryInstall($path=null) {
$post = Yii::$app->request->post();
if (empty($path))
$path = $post['path'];
if (! empty($post)) {
try {
// unpack and processing
$module = $this->unpack($path);
$repo = new ModuleRepositoryInstall();
$repo->repo_module_id = $post['module_id'];
$repo->name = $post['name'];
$repo->version = $post['version'];
$repo->url = $post['url'];
$repo->module_id = $module['id'];
if (! $repo->save()) {
throw new Exception('Can\'t Save repository installation data');
}
return $this->asJson([
'success' => true,
'message' => 'ok!'
]);
} catch (Exception $e) {
Yii::$app->response->statusCode = 500;
return $this->asJson([
'success' => false,
'message' => $e->getMessage()
]);
}
}
}
}