-
Notifications
You must be signed in to change notification settings - Fork 8
Export and import widget templates #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 7 commits
c684b7f
14ffdf1
0c5e1a4
0d2a2de
c31c252
c9c0c3b
131e9b6
702afa3
52262a1
a456cc5
919d2f1
753a34d
d285722
798db66
10a0cbc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,11 +3,15 @@ | |
| namespace hrzg\widget\controllers\crud; | ||
|
|
||
| use hrzg\widget\assets\WidgetAsset; | ||
| use hrzg\widget\models\crud\WidgetTemplate; | ||
| use yii\helpers\Url; | ||
| use hrzg\widget\helpers\WidgetTemplateExport; | ||
| use hrzg\widget\models\WidgetTemplateImport; | ||
| use yii\web\HttpException; | ||
| use yii\web\Response; | ||
| use yii\web\UploadedFile; | ||
|
|
||
| /** | ||
| * Class WidgetTemplateController | ||
| * | ||
| * @package hrzg\widget\controllers\crud | ||
| * @author Christopher Stebe <[email protected]> | ||
| */ | ||
|
|
@@ -18,4 +22,56 @@ public function beforeAction($action) | |
| WidgetAsset::register($this->view); | ||
| return parent::beforeAction($action); | ||
| } | ||
|
|
||
| /** | ||
| * Export given widget template | ||
| * | ||
| * @param string $id | ||
| * @return void | ||
| * @throws \yii\base\ErrorException | ||
| * @throws \yii\base\ExitException | ||
| * @throws \yii\web\HttpException | ||
| */ | ||
| public function actionExport($id) | ||
| { | ||
| $model = $this->findModel($id); | ||
|
|
||
| $export = new WidgetTemplateExport([ | ||
| 'widgetTemplate' => $model | ||
| ]); | ||
|
|
||
| if ($export->generateTar() === false) { | ||
| throw new HttpException(500, \Yii::t('widgets', 'Error while exporting widget template')); | ||
| } | ||
|
|
||
| if (\Yii::$app->getResponse()->sendFile($export->getTarFilePath()) instanceof Response) { | ||
| unlink($export->getTarFilePath()); | ||
| } else { | ||
| throw new HttpException(500, \Yii::t('widgets', 'Error while downloading widget template')); | ||
| } | ||
| \Yii::$app->end(); | ||
| } | ||
|
|
||
| public function actionImport() | ||
| { | ||
|
|
||
| $model = new WidgetTemplateImport(); | ||
| if (\Yii::$app->request->isPost) { | ||
| $model->tarFiles = UploadedFile::getInstances($model, 'tarFiles'); | ||
| if ($model->upload() && $model->extractFiles() && $model->import()) { | ||
| \Yii::$app->getSession()->addFlash('success', \Yii::t('widgets', 'Import was successful')); | ||
| return $this->refresh(); | ||
| } | ||
| } | ||
|
|
||
| $this->view->title = \Yii::t('widgets','Import'); | ||
| $this->view->params['breadcrumbs'][]= [ | ||
| 'label' => \Yii::t('widgets','Widget Templates'), | ||
| 'url' => ['index'] | ||
| ]; | ||
| $this->view->params['breadcrumbs'][]= $this->view->title; | ||
| return $this->render('import', [ | ||
| 'model' => $model | ||
| ]); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,250 @@ | ||
| <?php | ||
|
|
||
| namespace hrzg\widget\helpers; | ||
|
|
||
| use hrzg\widget\models\crud\base\WidgetTemplate; | ||
| use yii\base\BaseObject; | ||
| use yii\base\ErrorException; | ||
| use yii\base\InvalidConfigException; | ||
| use yii\helpers\FileHelper; | ||
| use yii\helpers\Inflector; | ||
| use yii\helpers\Json; | ||
|
|
||
| /** | ||
| * --- PUBLIC PROPERTIES --- | ||
| * | ||
| * @property string $exportDirectory | ||
| * @property string $templateFilename | ||
| * @property string $schemaFilename | ||
| * @property-write WidgetTemplate $widgetTemplate | ||
| * @property string $tarFileName | ||
| * | ||
| * --- MAGIC GETTERS --- | ||
| * | ||
| * @property-read string $tarFilePath | ||
| * | ||
| * @author Elias Luhr | ||
| */ | ||
| class WidgetTemplateExport extends BaseObject | ||
| { | ||
| public const META_FILE = 'meta.json'; | ||
| public const TEMPLATE_FILE = 'template.twig'; | ||
| public const SCHEMA_FILE = 'schema.json'; | ||
|
|
||
| /** | ||
| * Export directory of the generated tar file | ||
| * If this is set with an alias, it will be automatically resolved later on | ||
| * | ||
| * @var string | ||
| */ | ||
| public $exportDirectory = '@runtime/tmp/dmstr/widgets/templates'; | ||
|
|
||
| /** | ||
| * Name of the file which is generated from the content of the attribute $twig_template from the widget template | ||
| * The name must contain the file extension | ||
| * | ||
| * @var string | ||
| */ | ||
| public $templateFilename = self::TEMPLATE_FILE; | ||
|
|
||
| /** | ||
| * Name of the file which is generated from the content of the attribute $json_schema from the widget template | ||
| * The name must not contain the file extension | ||
| * | ||
| * @var string | ||
| */ | ||
| public $schemaFilename = self::SCHEMA_FILE; | ||
|
|
||
| /** | ||
| * Optional name for the tar file. If not set, the value of the $name attribute from the widget template is used. | ||
| * The name must contain the file extension | ||
| * | ||
| * @var string | ||
| */ | ||
| public $tarFileName; | ||
|
|
||
| /** | ||
| * Instance of the to be exported widget template instance | ||
| * | ||
| * @var WidgetTemplate | ||
| */ | ||
| protected $_widgetTemplate; | ||
|
|
||
| /** | ||
| * @return WidgetTemplate | ||
| */ | ||
| protected function getWidgetTemplate(): WidgetTemplate | ||
| { | ||
| return $this->_widgetTemplate; | ||
| } | ||
|
|
||
| /** | ||
| * @param WidgetTemplate $widgetTemplate | ||
| */ | ||
| public function setWidgetTemplate(WidgetTemplate $widgetTemplate): void | ||
| { | ||
| $this->_widgetTemplate = $widgetTemplate; | ||
| } | ||
|
|
||
| /** | ||
| * @return void | ||
| * @throws \yii\base\Exception if the export directory could not be created due to an php error | ||
| * @throws \yii\base\InvalidConfigException if either the export directory is not set or the export directory cannot | ||
| * be created due to permission errors or the widget template is configured incorrectly | ||
| */ | ||
| public function init() | ||
| { | ||
| parent::init(); | ||
|
|
||
| // Ensure that the export directory for the generated files is set | ||
| if (empty($this->exportDirectory)) { | ||
| throw new InvalidConfigException('$exportDirectory must be set'); | ||
| } | ||
|
|
||
| // Check if instance of widget template is not a new record or a new instance | ||
| if ($this->widgetTemplate instanceof WidgetTemplate && $this->widgetTemplate->getIsNewRecord() === true) { | ||
| throw new InvalidConfigException('Widget template must be saved at least once'); | ||
| } | ||
|
|
||
| // Set tar name to widget template name if not set | ||
| if (empty($this->tarFileName)) { | ||
| $this->tarFileName = Inflector::slug($this->getWidgetTemplate()->name) . '.tar'; | ||
| } | ||
|
|
||
| // Make sure that if an alias is set, it is resolved correctly | ||
| $this->exportDirectory = \Yii::getAlias($this->exportDirectory); | ||
|
|
||
| // Create export directory if not exists | ||
| if (FileHelper::createDirectory($this->exportDirectory) === false) { | ||
| throw new InvalidConfigException("Error while creating directory at: $this->exportDirectory"); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Absolute file path to the tar file | ||
| * | ||
| * @return string | ||
| */ | ||
| public function getTarFilePath(): string | ||
| { | ||
| return $this->exportDirectory . DIRECTORY_SEPARATOR . $this->tarFileName; | ||
| } | ||
|
|
||
| /** | ||
| * Absolute file path to the template file | ||
| * | ||
| * @return string | ||
| */ | ||
| protected function getTemplateFilePath(): string | ||
| { | ||
| return $this->exportDirectory . DIRECTORY_SEPARATOR . $this->templateFilename; | ||
| } | ||
|
|
||
| /** | ||
| * Absolute file path to the twig file | ||
| * | ||
| * @return string | ||
| */ | ||
| protected function getSchemaFilePath(): string | ||
| { | ||
| return $this->exportDirectory . DIRECTORY_SEPARATOR . $this->schemaFilename; | ||
| } | ||
|
|
||
| /** | ||
| * Absolute file path to the meta file | ||
| * | ||
| * @return string | ||
| */ | ||
| protected function getMetaFilePath(): string | ||
| { | ||
| return $this->exportDirectory . DIRECTORY_SEPARATOR . self::META_FILE; | ||
| } | ||
|
|
||
| /** | ||
| * Generate a tar file with the following contents | ||
| * - template file (twig): the content from the twig_template attribute of the given widget template | ||
| * - schema file (json): the content from the twig_template attribute of the given widget template | ||
| * - meta file (json): this contains some information about the export | ||
| * | ||
| * @return bool | ||
| * @throws \yii\base\ErrorException | ||
| */ | ||
| public function generateTar(): bool | ||
| { | ||
| // Remove existing tar file | ||
| if (is_file($this->getTarFilePath()) && unlink($this->getTarFilePath()) === false) { | ||
| return false; | ||
| } | ||
|
|
||
| // Generate needed files | ||
| if ($this->generateTemplateFile() === false) { | ||
| throw new ErrorException('Error while creating template file'); | ||
| } | ||
| if ($this->generateSchemaFile() === false) { | ||
| throw new ErrorException('Error while creating schema file'); | ||
| } | ||
| if ($this->generateMetaFile() === false) { | ||
| throw new ErrorException('Error while creating schema file'); | ||
| } | ||
|
|
||
| // Create the tar archive | ||
| $phar = new \PharData($this->getTarFilePath()); | ||
| // Add generated files | ||
| $phar->addFile($this->getTemplateFilePath(), $this->templateFilename); | ||
|
||
| $phar->addFile($this->getSchemaFilePath(), $this->schemaFilename); | ||
| $phar->addFile($this->getMetaFilePath(), self::META_FILE); | ||
|
|
||
| // Remove generated files | ||
| if (is_file($this->getTemplateFilePath()) && unlink($this->getTemplateFilePath()) === false) { | ||
| return false; | ||
| } | ||
| if (is_file($this->getSchemaFilePath()) && unlink($this->getSchemaFilePath()) === false) { | ||
| return false; | ||
| } | ||
| if (is_file($this->getMetaFilePath()) && unlink($this->getMetaFilePath()) === false) { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Generate a twig file based on the value of the widget template $twig_template property | ||
| * | ||
| * @return bool | ||
| */ | ||
| protected function generateTemplateFile(): bool | ||
| { | ||
| return file_put_contents($this->getTemplateFilePath(), $this->widgetTemplate->twig_template) !== false; | ||
| } | ||
|
|
||
| /** | ||
| * Generate a json file based on the value of the widget template $json_schema property | ||
| * | ||
| * @return bool | ||
| */ | ||
| protected function generateSchemaFile(): bool | ||
| { | ||
| return file_put_contents($this->getSchemaFilePath(), $this->widgetTemplate->json_schema) !== false; | ||
| } | ||
|
|
||
| /** | ||
| * Generate a json file based on the value of the widget template $json_schema property | ||
| * | ||
| * @return bool | ||
| */ | ||
| protected function generateMetaFile(): bool | ||
| { | ||
| $data = [ | ||
| 'id' => $this->getWidgetTemplate()->id, | ||
| 'name' => $this->getWidgetTemplate()->name, | ||
| 'php_class' => $this->getWidgetTemplate()->php_class, | ||
| 'created_at' => $this->getWidgetTemplate()->created_at, | ||
| 'updated_at' => $this->getWidgetTemplate()->updated_at, | ||
| 'exported_at' => date('Y-m-d H:i:s'), | ||
| 'download_url' => \Yii::$app->getRequest()->getAbsoluteUrl() | ||
| ]; | ||
| return file_put_contents($this->getMetaFilePath(), Json::encode($data)) !== false; | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
directories (and/or filenames) used for such exports should always have some randomness to prevent race conditions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now a protected property and is set to a random directory in /tmp (via sys_get_temp_dir)