Completed
Push — master ( ff2b0b...f369f8 )
by Alexey
05:58
created

FileState::isEnabled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 1
c 1
b 0
f 1
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace common\components\maintenance\states;
4
5
use Yii;
6
use DateTime;
7
use Generator;
8
use Exception;
9
use RuntimeException;
10
use yii\base\BaseObject;
11
use yii\base\InvalidConfigException;
12
use common\components\maintenance\interfaces\StateInterface;
13
use common\components\maintenance\models\SubscribeForm;
14
use yii\helpers\ArrayHelper;
15
16
/**
17
 * Class FileState
18
 * @package common\components\maintenance\states
19
 *
20
 * @property bool|string $filePath
21
 * @property array $contentArray
22
 * @property array $maintenanceFileLinesParamsArray
23
 * @property bool $validDate
24
 */
25
class FileState extends BaseObject implements StateInterface
26
{
27
    const MAINTENANCE_PARAM_DATE = 'date';
28
    const MAINTENANCE_PARAM_TITLE = 'title';
29
    const MAINTENANCE_PARAM_CONTENT = 'text';
30
    const MAINTENANCE_PARAM_SUBSCRIBE = 'subscribe';
31
32
    const MAINTENANCE_SUBSCRIBE_ON = 'true';
33
    const MAINTENANCE_SUBSCRIBE_OFF = 'false';
34
35
    /**
36
     * @var string the filename that will determine if the maintenance mode is enabled
37
     */
38
    public $fileName = 'YII_MAINTENANCE_MODE_ENABLED';
39
40
    /**
41
     * Default title
42
     * @var string
43
     */
44
    public $defaultTitle = 'Maintenance';
45
46
    /**
47
     * Default content
48
     * @var string
49
     */
50
    public $defaultContent = 'The site is undergoing technical work. We apologize for any inconvenience caused.';
51
52
    /**
53
     * @var string name of the file where subscribers will be stored
54
     */
55
    public $fileSubscribe = 'YII_MAINTENANCE_MODE_SUBSCRIBE';
56
57
    /**
58
     * Set status subscribe
59
     * @var string
60
     */
61
    public $subscribe;
62
63
    /**
64
     * @var string the directory in that the file stated in $fileName above is residing
65
     */
66
    public $directory = '@runtime';
67
68
    /**
69
     * @var string the complete path of the file - populated in init
70
     */
71
    public $path;
72
73
    /**
74
     * @var string the complete path of the file subscribe - populated in init
75
     */
76
    public $subscribePath;
77
78
    /**
79
     * Enter Datetime format
80
     * @var string
81
     */
82
    public $dateFormat = 'd-m-Y H:i:s';
83
84
    /**
85
     * Initialization
86
     */
87
    public function init()
88
    {
89
        $this->path = $this->getFilePath($this->fileName);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getFilePath($this->fileName) can also be of type boolean. However, the property $path is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
90
        $this->subscribePath = $this->getFilePath($this->fileSubscribe);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getFilePath($this->fileSubscribe) can also be of type boolean. However, the property $subscribePath is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
91
        $this->subscribe = $this->subscribe ?: self::MAINTENANCE_SUBSCRIBE_ON;
92
    }
93
94
    /**
95
     * Turn on mode.
96
     *
97
     * @param string $datetime
98
     * @param string $title
99
     * @param string $content
100
     * @param string $subscribe
101
     * @return mixed|void
102
     * @throws Exception
103
     */
104
    public function enable($datetime = '', $title = '', $content = '', $subscribe = '')
105
    {
106
        $date = new DateTime(date($this->dateFormat));
107
        if ($this->validDate($datetime)) {
108
            $date = new DateTime($datetime);
109
        }
110
        $timestamp = $date->getTimestamp();
111
112
        $title = $title ?: Yii::t('app', $this->defaultTitle);
113
        $content = $content ?: Yii::t('app', $this->defaultContent);
114
        $subscribe = $subscribe ?: $this->subscribe;
115
116
        $data = $timestamp . PHP_EOL . $title . PHP_EOL . $content . PHP_EOL . $subscribe . PHP_EOL;
117
        $result = file_put_contents($this->path, $data);
118
        chmod($this->path, 0765);
119
120
        if ($result === false) {
121
            throw new RuntimeException(
122
                "Attention: the maintenance mode could not be enabled because {$this->path} could not be created."
123
            );
124
        }
125
        return true;
126
    }
127
128
    /**
129
     * Update param in maintenance file
130
     * @param string $param
131
     * @param string $value
132
     * @return bool
133
     * @throws Exception
134
     */
135
    public function update($param = '', $value = '')
136
    {
137
        switch ($param) {
138
            case self::MAINTENANCE_PARAM_DATE:
139
                if ($this->validDate($value)) {
140
                    $date = new DateTime($value);
141
                    $this->replace($date->getTimestamp(), $this->getLine(self::MAINTENANCE_PARAM_DATE));
142
                }
143
                break;
144
            case self::MAINTENANCE_PARAM_TITLE:
145
                $this->replace($value, $this->getLine(self::MAINTENANCE_PARAM_TITLE));
146
                break;
147
            case self::MAINTENANCE_PARAM_CONTENT:
148
                $this->replace($value, $this->getLine(self::MAINTENANCE_PARAM_CONTENT));
149
                break;
150
            case self::MAINTENANCE_PARAM_SUBSCRIBE:
151
                $this->replace($value, $this->getLine(self::MAINTENANCE_PARAM_SUBSCRIBE));
152
                break;
153
            default:
154
                return false;
155
        }
156
        return true;
157
    }
158
159
    /**
160
     * Turn off mode.
161
     *
162
     * @return int|mixed
163
     */
164
    public function disable()
165
    {
166
        $result = 0;
167
        try {
168
            if (file_exists($this->path)) {
169
                unlink($this->path);
170
                $subscribe = new SubscribeForm();
171
                $result = $subscribe->send($this->emails());
172
                if ($result > 0) {
173
                    unlink($this->subscribePath);
174
                }
175
            }
176
        } catch (RuntimeException $e) {
177
            throw new RuntimeException(
178
                "Attention: the maintenance mode could not be disabled because {$this->path} could not be removed."
179
            );
180
        }
181
        return $result;
182
    }
183
184
    /**
185
     * File Line Param
186
     * @param $param
187
     * @return mixed
188
     */
189
    public function getLine($param)
190
    {
191
        return ArrayHelper::getValue(array_flip($this->getMaintenanceFileLinesParamsArray()), $param);
192
    }
193
194
    /**
195
     * File Lines Params
196
     * @return array
197
     */
198
    protected function getMaintenanceFileLinesParamsArray()
199
    {
200
        return [
201
            1 => self::MAINTENANCE_PARAM_DATE,
202
            2 => self::MAINTENANCE_PARAM_TITLE,
203
            3 => self::MAINTENANCE_PARAM_CONTENT,
204
            4 => self::MAINTENANCE_PARAM_SUBSCRIBE
205
        ];
206
    }
207
208
    /**
209
     * Update text line to mode file
210
     *
211
     * @param string $replace
212
     * @param int $line
213
     * @return mixed|void
214
     * @throws Exception
215
     */
216
    public function replace($replace, $line = 1)
217
    {
218
        $result = false;
219
        if ($replace && file_exists($this->path)) {
220
            $file = file($this->path);
221
            $file[$line - 1] = $replace . PHP_EOL;
222
            $result = file_put_contents($this->path, implode('', $file));
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

222
            $result = file_put_contents($this->path, implode('', /** @scrutinizer ignore-type */ $file));
Loading history...
223
        }
224
        if ($result === false) {
225
            throw new RuntimeException(
226
                "Attention: failed to update the end date of the maintenance mode, because {$this->path} failed to update."
227
            );
228
        }
229
    }
230
231
    /**
232
     * Validate datetime
233
     *
234
     * @param $date
235
     * @return bool
236
     */
237
    public function validDate($date)
238
    {
239
        $d = DateTime::createFromFormat($this->dateFormat, $date);
240
        return $d && $d->format($this->dateFormat) === $date;
241
    }
242
243
    /**
244
     * Date ant Time
245
     *
246
     * @param string $format
247
     * @param string|integer $timestamp
248
     * @return string
249
     * @throws InvalidConfigException
250
     */
251
    public function datetime($timestamp = '', $format = '')
252
    {
253
        $format = $format ?: $this->dateFormat;
254
        $timestamp = $timestamp ?: $this->timestamp();
255
        return Yii::$app->formatter->asDatetime($timestamp, 'php:' . $format);
256
    }
257
258
    /**
259
     * Timestamp
260
     *
261
     * @return string
262
     */
263
    public function timestamp()
264
    {
265
        return $this->getParams(self::MAINTENANCE_PARAM_DATE);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getParams(...MAINTENANCE_PARAM_DATE) also could return the type array|false which is incompatible with the documented return type string.
Loading history...
266
    }
267
268
    /**
269
     * Save email in file
270
     *
271
     * @param string $str
272
     * @param string $file
273
     * @return bool
274
     */
275
    public function save($str, $file)
276
    {
277
        try {
278
            if ($str && $file) {
279
                $fp = fopen($file, 'ab');
280
                fwrite($fp, $str . PHP_EOL);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

280
                fwrite(/** @scrutinizer ignore-type */ $fp, $str . PHP_EOL);
Loading history...
281
                fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

281
                fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
282
                return chmod($file, 0765);
283
            }
284
            return false;
285
        } catch (RuntimeException $e) {
286
            throw new RuntimeException(
287
                "Attention: Subscriber cannot be added because {$file} could not be save."
288
            );
289
        }
290
    }
291
292
    /**
293
     * Get params this maintenance file
294
     * @param string $param
295
     * @return array|false|mixed|string
296
     */
297
    public function getParams($param = '')
298
    {
299
        $content = $this->getContentArray($this->path);
300
        if ($param) {
301
            switch ($param) {
302
                case self::MAINTENANCE_PARAM_DATE:
303
                    $value = isset($content[0]) ? $content[0] : time();
304
                    break;
305
                case self::MAINTENANCE_PARAM_TITLE:
306
                    $value = isset($content[1]) ? $content[1] : '';
307
                    break;
308
                case self::MAINTENANCE_PARAM_CONTENT:
309
                    $value = isset($content[2]) ? $content[2] : '';
310
                    break;
311
                case self::MAINTENANCE_PARAM_SUBSCRIBE:
312
                    $value = isset($content[3]) ? $content[3] : '';
313
                    break;
314
                default:
315
                    $value = '';
316
            }
317
            return $value;
318
        }
319
        return $content;
320
    }
321
322
    /**
323
     * Return emails to followers
324
     *
325
     * @return array
326
     */
327
    public function emails()
328
    {
329
        $contents = $this->getContentArray($this->subscribePath);
330
        sort($contents);
331
        return $contents;
332
    }
333
334
    /**
335
     * Return content to array this file
336
     *
337
     * @param $file string
338
     * @return array
339
     */
340
    protected function getContentArray($file)
341
    {
342
        $contents = $this->readTheFile($file);
343
        $items = [];
344
        foreach ($contents as $key => $item) {
345
            $items[] = $item;
346
        }
347
        return array_filter($items);
348
    }
349
350
    /**
351
     * Read file
352
     *
353
     * @param $file string
354
     * @return Generator
355
     */
356
    protected function readTheFile($file)
357
    {
358
        try {
359
            if (file_exists($file)) {
360
                $handle = fopen($file, 'rb');
361
                while (!feof($handle)) {
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of feof() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

361
                while (!feof(/** @scrutinizer ignore-type */ $handle)) {
Loading history...
362
                    yield trim(fgets($handle));
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of fgets() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

362
                    yield trim(fgets(/** @scrutinizer ignore-type */ $handle));
Loading history...
363
                }
364
                fclose($handle);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

364
                fclose(/** @scrutinizer ignore-type */ $handle);
Loading history...
365
            }
366
        } catch (RuntimeException $e) {
367
            throw new RuntimeException(
368
                "Failed to read $file file"
369
            );
370
        }
371
    }
372
373
    /**
374
     * Create file
375
     *
376
     * @param $file string
377
     */
378
    /*protected function createFile($file)
379
    {
380
        try {
381
            if ($file && !file_exists($file)) {
382
                file_put_contents($file, '');
383
                chmod($file, 0765);
384
            }
385
        } catch (RuntimeException $e) {
386
            throw new RuntimeException(
387
                "Failed to create $file file."
388
            );
389
        }
390
    }*/
391
392
    /**
393
     * @return bool will return true if on timer
394
     */
395
    public function isTimer()
396
    {
397
        return (($date = $this->getParams(self::MAINTENANCE_PARAM_DATE)) && $date > time());
398
    }
399
400
    /**
401
     * @return bool will return true if on subscribe
402
     */
403
    public function isSubscribe()
404
    {
405
        $param = $this->getParams(self::MAINTENANCE_PARAM_SUBSCRIBE);
406
        return $param === 'true';
407
    }
408
409
    /**
410
     * @return bool will return true if the file exists
411
     */
412
    public function isEnabled()
413
    {
414
        return file_exists($this->path);
415
    }
416
417
    /**
418
     * Return file path.
419
     *
420
     * @param $fileName string
421
     * @return bool|string
422
     */
423
    protected function getFilePath($fileName)
424
    {
425
        return Yii::getAlias($this->directory . '/' . $fileName);
426
    }
427
}
428