FrontendController::actionIndex()   B
last analyzed

Complexity

Conditions 5
Paths 7

Size

Total Lines 48

Duplication

Lines 12
Ratio 25 %

Importance

Changes 0
Metric Value
dl 12
loc 48
rs 8.8234
c 0
b 0
f 0
cc 5
nc 7
nop 0
1
<?php
2
3
namespace app\controllers;
4
5
use Yii;
6
use yii\helpers\Url;
7
use yii\web\NotFoundHttpException;
8
use yii\db\Expression;
9
use app\helpers\Alert;
10
use app\models\Screen;
11
use app\models\Device;
12
use app\models\Field;
13
use app\models\Flow;
14
use app\models\Content;
15
use app\models\ContentType;
16
17
/**
18
 * FrontendController implements the actions used by screens.
19
 */
20
class FrontendController extends BaseController
21
{
22
    const ID = 'device_id';
23
    const EXP_YEARS = 10;
24
    public $layout = 'frontend';
25
26
    /**
27
     * Index redirects to associated screen ID.
28
     * Checks authorization based on session & cookie
29
     * Else create a new screen and display auth.
30
     *
31
     * @return string
32
     */
33
    public function actionIndex()
34
    {
35
        $device = $this->getClientDevice();
36
37
        if ($device !== null) { // Associated device
38
            // Check session
39
            if (!$this->isClientAuth()) {
40
                $this->setClientAuth($device);
0 ignored issues
show
Documentation introduced by
$device is of type object<yii\db\ActiveRecordInterface>|array, but the function expects a object<app\models\Device>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
41
            }
42
43 View Code Duplication
            if (!$device->enabled) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
44
                // Render enable view
45
                return $this->render('err/authorize', [
46
                    'url' => Url::to(['device/view', 'id' => $device->id], true),
47
                ]);
48
            }
49
50
            $screen = $device->getNextScreen();
51 View Code Duplication
            if (!$screen) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
52
                // Render add screen view
53
                return $this->render('err/missing-screen', [
54
                    'url' => Url::to(['device/view', 'id' => $device->id], true),
55
                ]);
56
            }
57
58
            return $this->redirect(['screen', 'id' => $screen->id]);
59
        }
60
61
        // New device
62
        $cookies = Yii::$app->response->cookies;
63
64
        $device = new Device();
65
        $device->name = Yii::$app->request->getUserIP();
66
        $device->description = Yii::t('app', 'New unauthorized device');
67
        $device->save();
68
        $device->id = $device->lastId;
69
70
        $cookies->add(new \yii\web\Cookie([
71
            'name' => self::ID,
72
            'value' => $device->id,
73
            'expire' => time() + (self::EXP_YEARS * 365 * 24 * 60 * 60),
74
        ]));
75
76
        // Render enable view
77
        return $this->render('err/authorize', [
78
            'url' => Url::to(['device/view', 'id' => $device->id], true),
79
        ]);
80
    }
81
82
    /**
83
     * Initializes screen content html structure.
84
     *
85
     * @param int $id screen id
86
     *
87
     * @return \yii\web\Response|string redirect or render
88
     */
89
    public function actionScreen($id, $preview = false)
90
    {
91
        // Session auth
92
        if (!$this->isClientAuth() && !($preview && Yii::$app->user->can('previewScreen'))) {
93
            return $this->redirect(['index']);
94
        }
95
96
        $screen = Screen::find()->where([Screen::tableName().'.id' => $id])->joinWith(['template', 'template.fields', 'template.fields.contentTypes'])->one();
97
        if ($screen === null) {
98
            throw new NotFoundHttpException(Yii::t('app', 'The requested screen does not exist.'));
99
        }
100
        $content = [
101
            'name' => $screen->name,
102
            'screenCss' => $screen->template->css,
103
            'background' => $screen->template->background->uri,
104
            'fields' => $screen->template->fields,
105
            'updateUrl' => $preview ? null : Url::to(['frontend/update', 'id' => $id]),
106
            'nextUrl' => Url::to(['frontend/next', 'id' => $id, 'fieldid' => '']),
107
            'types' => ContentType::getAll(),
108
        ];
109
110
        return $this->render('default', $content);
111
    }
112
113
    /**
114
     * Sends last screen update timestamp, indicating if refresh is needed.
115
     *
116
     * @api
117
     *
118
     * @param int $id screen id
119
     *
120
     * @return string json last update
121
     */
122
    public function actionUpdate($id)
123
    {
124
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
125
        // Session auth
126
        if (!$this->isClientAuth()) { // Disable update if no device association
127
            return ['success' => false, 'message' => 'Unauthorized'];
128
        }
129
130
        $screen = Screen::find()->where([Screen::tableName().'.id' => $id])->one();
131
        if ($screen === null) {
132
            return ['success' => false, 'message' => 'Unknown screen'];
133
        }
134
135
        $device = $this->getClientDevice();
136
        $nextScreen = $device ? $device->getNextScreen($screen->id) : null;
137
138
        return ['success' => true, 'data' => [
139
            'lastChanges' => $screen->last_changes,
140
            'duration' => $screen->duration,
141
            'nextScreenUrl' => $nextScreen ? Url::to(['frontend/screen', 'id' => $nextScreen->id]) : null,
142
        ]];
143
    }
144
145
    /**
146
     * Sends all available content for a specific field.
147
     *
148
     * @param int $id      screen id
149
     * @param int $fieldid field id
150
     *
151
     * @return string json array
152
     */
153
    public function actionNext($id, $fieldid)
154
    {
155
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
156
        // Session auth
157
        if (!$this->isClientAuth() && !Yii::$app->user->can('previewScreen')) {
158
            return ['success' => false, 'message' => 'Unauthorized'];
159
        }
160
161
        // Get screen
162
        $screen = Screen::find()->where([Screen::tableName().'.id' => $id])->joinWith(['flows'])->one();
163
        if ($screen === null) {
164
            return ['success' => false, 'message' => 'Unknown screen'];
165
        }
166
167
        // Get field
168
        $field = Field::find()->where(['id' => $fieldid])->one();
169
        if ($field === null) {
170
            return ['success' => false, 'message' => 'Unknown field'];
171
        }
172
173
        // Get all flows for screen
174
        $flows = $screen->allFlows();
175
176
        // Get all flow ids
177
        $flowIds = array_map(function ($e) {
178
            return $e->id;
179
        }, $flows);
180
181
        // Get all content type ids
182
        $contentTypes = array_map(function ($e) {
183
            return $e->id;
184
        }, $field->contentTypes);
185
186
        // Get content for flows and field type
187
        $contents = Content::find()
188
            ->joinWith(['flow'])
189
            ->where(['type_id' => $contentTypes])
190
            ->andWhere([Flow::tableName().'.id' => $flowIds])
191
            ->andWhere(['enabled' => true])
192
            ->andWhere(['or', ['start_ts' => null], ['<', 'start_ts', new Expression('NOW()')]])
193
            ->andWhere(['or', ['end_ts' => null], ['>', 'end_ts', new Expression('NOW()')]])
194
            ->orderBy('duration ASC')
195
            ->all();
196
197
        $next = array_map(function ($c) use ($field) {
198
            return [
199
                'id' => $c->id,
200
                'data' => $field->mergeData($c->getData()),
201
                'duration' => $c->duration,
202
                'type' => $c->type_id,
203
            ];
204
        }, $contents);
205
206
        return ['success' => true, 'next' => $next];
207
    }
208
209
    /**
210
     * Send an screen reload order to device.
211
     *
212
     * @param int $id screen id
213
     *
214
     * @return \yii\web\Response
215
     */
216
    public function actionForceReload($id)
217
    {
218
        if (Yii::$app->user->can('setScreens')) {
219
            $screen = Screen::findOne($id);
220
            if ($screen !== null) {
221
                Alert::add('Screen will reload', Alert::SUCCESS);
222
                $screen->setModified();
223
224
                return $this->smartGoBack();
225
            }
226
        }
227
228
        Alert::add('Failed to force Screen reload', Alert::DANGER);
229
230
        return $this->smartGoBack();
231
    }
232
233
    /**
234
     * Checks client session for device ID.
235
     *
236
     * @return bool is authenticated
237
     */
238
    private function isClientAuth()
239
    {
240
        return Yii::$app->session->get(self::ID) !== null;
241
    }
242
243
    /**
244
     * Set session with device ID, also add to DB last auth timestamp.
245
     *
246
     * @param \app\models\Device $device
247
     */
248
    private function setClientAuth($device)
249
    {
250
        Yii::$app->session->set(self::ID, $device->id);
251
        $device->setAuthenticated();
252
    }
253
254
    /**
255
     * Look for client device ID from session & cookie.
256
     *
257
     * @return int|null device ID
258
     */
259
    private function getClientId()
260
    {
261
        $id = Yii::$app->session->get(self::ID);
262
        if ($id !== null) {
263
            return $id;
264
        }
265
266
        $cookies = Yii::$app->request->cookies;
267
        $id = $cookies->getValue(self::ID);
268
        if ($id !== null) {
269
            return $id;
270
        }
271
272
        return;
273
    }
274
275
    /**
276
     * Get client device based on session & cookie.
277
     *
278
     * @return \app\models\Device|null device
279
     */
280
    private function getClientDevice()
281
    {
282
        $id = $this->getClientId();
283
        if ($id === null) {
284
            return;
285
        }
286
287
        return Device::findOne($id);
288
    }
289
}
290