writesdown/app-cms

View on GitHub
backend/controllers/WidgetController.php

Summary

Maintainability
C
1 day
Test Coverage
<?php
/**
 * @link http://www.writesdown.com/
 * @copyright Copyright (c) 2015 WritesDown
 * @license http://www.writesdown.com/license/
 */

namespace backend\controllers;

use common\components\Json;
use common\models\Widget;
use Yii;
use yii\filters\AccessControl;
use yii\filters\VerbFilter;
use yii\helpers\ArrayHelper;
use yii\helpers\FileHelper;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\web\UploadedFile;

/**
 * WidgetController, controlling the actions for Widget model.
 *
 * @author Agiel K. Saputra <13nightevil@gmail.com>
 * @since 0.2.0
 */
class WidgetController extends Controller
{
    /**
     * @var string Path to widget directory.
     */
    private $_dir;

    /**
     * @var string Path to temporary directory of widget.
     */
    private $_tmp;

    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'rules' => [
                    [
                        'actions' => [
                            'index',
                            'create',
                            'delete',
                            'ajax-activate',
                            'ajax-update',
                            'ajax-delete',
                            'ajax-save-order',
                        ],
                        'allow'   => true,
                        'roles'   => ['administrator'],
                    ],
                ],
            ],
            'verbs'  => [
                'class'   => VerbFilter::className(),
                'actions' => [
                    'delete' => ['post'],
                    'ajax-activate' => ['post'],
                    'ajax-update'   => ['post'],
                    'ajax-delete'   => ['post'],
                ],
            ],
        ];
    }

    /**
     * Scan widget directory to get list of all available widgets and list all active widgets.
     *
     * @return mixed
     */
    public function actionIndex()
    {
        $config = [];
        $active = [];
        $available = [];
        $spaces = ArrayHelper::getValue(Yii::$app->params, 'widget', []);

        if (!is_dir($this->_dir)) {
            FileHelper::createDirectory($this->_dir, 0755);
        }

        foreach (scandir($this->_dir) as $widget) {
            if (is_dir($this->_dir . $widget) && $widget !== '.' && $widget !== '..') {
                $configPath = $this->_dir . $widget . '/config/main.php';
                if (is_file($configPath)) {
                    $config = require($configPath);
                    $config['directory'] = $widget;
                }
                $available[$widget] = $config;
            }
        }

        foreach ($spaces as $space) {
            $model = Widget::find()
                ->where(['location' => $space['location']])
                ->orderBy(['order' => SORT_ASC])
                ->all();
            $active[$space['location']] = $model;
        }

        return $this->render('index', [
            'active' => $active,
            'available' => $available,
            'spaces' => $spaces,
        ]);
    }

    /**
     * Register new widget.
     * Widget zip uploaded to temporary directory and extracted there.
     * Check the widget directory and move the first directory of the extracted widget.
     * If the widget configuration is valid, save the widget, if not remove the widget.
     * If registration is successful, the browser will be redirected to the 'index' page.
     *
     * @return mixed
     */
    public function actionCreate()
    {
        $errors = [];
        $model = new Widget(['scenario' => 'upload']);

        if (!is_dir($this->_tmp)) {
            FileHelper::createDirectory($this->_tmp, 0755);
        }

        if (!is_dir($this->_dir)) {
            FileHelper::createDirectory($this->_dir, 0755);
        }

        if (($model->file = UploadedFile::getInstance($model, 'file')) && $model->validate(['file'])) {
            $tmpPath = $this->_tmp . $model->file->name;

            if (!$model->file->saveAs($tmpPath)) {
                return $this->render('create', [
                    'model' => $model,
                    'errors' => [Yii::t('writesdown', 'Failed to move uploaded file')],
                ]);
            }

            $zipArchive = new \ZipArchive();
            $zipArchive->open($tmpPath);

            if (!$zipArchive->extractTo($this->_tmp)) {
                $zipArchive->close();
                FileHelper::removeDirectory($this->_tmp);

                return $this->render('create', [
                    'model' => $model,
                    'errors' => [Yii::t('writesdown', 'Failed to extract file.')],
                ]);
            }

            $baseDir = substr($zipArchive->getNameIndex(0), 0, strpos($zipArchive->getNameIndex(0), '/'));
            $zipArchive->close();
            $configPath = $this->_tmp . $baseDir . '/config/main.php';

            if (!is_file($configPath)) {
                FileHelper::removeDirectory($this->_tmp);

                return $this->render('create', [
                    'model' => $model,
                    'errors' => [Yii::t('writesdown', 'File configuration does not exist.')],
                ]);
            }

            $config = require($configPath);

            if (is_dir($this->_dir . $baseDir)) {
                $errors['dirExist'] = Yii::t('writesdown', 'Widget with the same directory already exist.');
            } else {
                rename($this->_tmp . $baseDir, $this->_dir . $baseDir);
            }

            FileHelper::removeDirectory($this->_tmp);

            if (!isset($config['title'])
                || !(isset($config['config']['class']) && class_exists($config['config']['class']))
            ) {
                $errors[] = Yii::t('writesdown', 'Invalid configuration.');
            }

            if (!$errors) {
                Yii::$app->getSession()->setFlash('success', Yii::t('writesdown', 'Widget successfully installed.'));

                return $this->redirect(['index']);
            } else {
                if (!ArrayHelper::getValue($errors, 'dirExist')) {
                    FileHelper::removeDirectory($this->_dir . $baseDir);
                }

                $errors = ArrayHelper::merge($errors, $model->getFirstErrors());
            }
        }

        return $this->render('create', [
            'model' => $model,
            'errors' => $errors,
        ]);
    }

    /**
     * Delete widget and activated widgets.
     *
     * @param $id string
     *
     * @return \yii\web\Response
     */
    public function actionDelete($id)
    {
        FileHelper::removeDirectory($this->_dir . $id);
        Widget::deleteAll(['directory' => $id]);

        return $this->redirect(['index']);
    }

    /**
     * Activated widget via ajax
     *
     * @param $id string
     *
     * @return null|string
     */
    public function actionAjaxActivate($id)
    {
        $model = new Widget(['scenario' => 'activate']);
        if ($model->load(Yii::$app->request->post())) {
            $configPath = $this->_dir . $id . '/config/main.php';
            $config = require($configPath);

            // Set attribute of model
            $model->setAttributes($config);
            $model->setAttributes([
                'directory' => $id,
                'config' => Json::encode($model->config),
                'order' => Widget::find()->where(['location' => $model->location])->count(),
            ]);

            if ($model->save()) {
                return $this->renderPartial('_active', [
                    'active' => $model,
                    'available' => [$model->directory => $config],
                ]);
            }
        }

        return null;
    }

    /**
     * Update activated widget via ajax.
     *
     * @param $id integer
     */
    public function actionAjaxUpdate($id)
    {
        $model = $this->findModel($id);

        if ($model->load(Yii::$app->request->post())) {
            $model->config = Json::encode($model->config);
            $model->save();
        }
    }

    /**
     * Delete active widget via ajax.
     *
     * @param $id integer
     */
    public function actionAjaxDelete($id)
    {
        $this->findModel($id)->delete();
    }

    /**
     * Save order for widget.
     */
    public function actionAjaxSaveOrder()
    {
        if ($ids = Yii::$app->request->post('ids')) {
            foreach ($ids as $order => $id) {
                $this->findModel($id)->updateAttributes(['order' => $order]);
            }
        }
    }

    /**
     * @inheritdoc
     */
    public function beforeAction($action)
    {
        if (in_array($this->action->id, ['ajax-activate', 'ajax-update', 'ajax-delete', 'ajax-save-order'])) {
            $this->enableCsrfValidation = false;
        }

        if (parent::beforeAction($action)) {
            $this->_dir = Yii::getAlias('@widgets/');
            $this->_tmp = Yii::getAlias('@common/tmp/widgets/');

            return true;
        }

        return false;
    }

    /**
     * Finds the Widget model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     *
     * @param integer $id
     *
     * @return Widget the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Widget::findOne($id)) !== null) {
            return $model;
        }

        throw new NotFoundHttpException('The requested page does not exist.');
    }
}