Completed
Pull Request — master (#351)
by Stefano
01:30
created

Installer::createDotEnvConfig()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 9
Ratio 100 %

Importance

Changes 0
Metric Value
dl 9
loc 9
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11
 * @link      https://cakephp.org CakePHP(tm) Project
12
 * @since     3.0.0
13
 * @license   https://opensource.org/licenses/mit-license.php MIT License
14
 */
15
namespace App\Console;
16
17
if (!defined('STDIN')) {
18
    define('STDIN', fopen('php://stdin', 'r'));
19
}
20
21
use Cake\Utility\Security;
22
use Composer\Script\Event;
23
use Exception;
24
25
/**
26
 * Provides installation hooks for when this application is installed via
27
 * composer. Customize this class to suit your needs.
28
 */
29
class Installer
30
{
31
32
    /**
33
     * An array of directories to be made writable
34
     */
35
    const WRITABLE_DIRS = [
36
        'logs',
37
        'tmp',
38
        'tmp/cache',
39
        'tmp/cache/models',
40
        'tmp/cache/persistent',
41
        'tmp/cache/views',
42
        'tmp/sessions',
43
        'tmp/tests'
44
    ];
45
46
    /**
47
     * Does some routine installation tasks so people don't have to.
48
     *
49
     * @param \Composer\Script\Event $event The composer event object.
50
     * @throws \Exception Exception raised by validator.
51
     * @return void
52
     */
53
    public static function postInstall(Event $event)
54
    {
55
        $io = $event->getIO();
56
57
        $rootDir = dirname(dirname(__DIR__));
58
59
        static::createAppConfig($rootDir, $io);
60
        static::createDotEnvConfig($rootDir, $io);
61
        static::createWritableDirectories($rootDir, $io);
62
63
        // ask if the permissions should be changed
64
        if ($io->isInteractive()) {
65
            $validator = function ($arg) {
66
                if (in_array($arg, ['Y', 'y', 'N', 'n'])) {
67
                    return $arg;
68
                }
69
                throw new Exception('This is not a valid answer. Please choose Y or n.');
70
            };
71
            $setFolderPermissions = $io->askAndValidate(
72
                '<info>Set Folder Permissions ? (Default to Y)</info> [<comment>Y,n</comment>]? ',
73
                $validator,
74
                10,
75
                'Y'
76
            );
77
78
            if (in_array($setFolderPermissions, ['Y', 'y'])) {
79
                static::setFolderPermissions($rootDir, $io);
80
            }
81
        } else {
82
            static::setFolderPermissions($rootDir, $io);
83
        }
84
85
        static::setSecuritySalt($rootDir, $io);
86
87
        if (class_exists('\Cake\Codeception\Console\Installer')) {
88
            \Cake\Codeception\Console\Installer::customizeCodeceptionBinary($event);
89
        }
90
    }
91
92
    /**
93
     * Create the config/app.php file if it does not exist.
94
     *
95
     * @param string $dir The application's root directory.
96
     * @param \Composer\IO\IOInterface $io IO interface to write to console.
97
     * @return void
98
     */
99 View Code Duplication
    public static function createAppConfig($dir, $io)
100
    {
101
        $appConfig = $dir . '/config/app.php';
102
        $defaultConfig = $dir . '/config/app.default.php';
103
        if (!file_exists($appConfig)) {
104
            copy($defaultConfig, $appConfig);
105
            $io->write('Created `config/app.php` file');
106
        }
107
    }
108
109
    /**
110
     * Create the config/.env file if it does not exist.
111
     *
112
     * @param string $dir The application's root directory.
113
     * @param \Composer\IO\IOInterface $io IO interface to write to console.
114
     * @return void
115
     */
116 View Code Duplication
    public static function createDotEnvConfig($dir, $io)
117
    {
118
        $appConfig = $dir . '/config/.env';
119
        $defaultConfig = $dir . '/config/.env.default';
120
        if (!file_exists($appConfig)) {
121
            copy($defaultConfig, $appConfig);
122
            $io->write('Created `config/.env` file');
123
        }
124
    }
125
126
    /**
127
     * Create the `logs` and `tmp` directories.
128
     *
129
     * @param string $dir The application's root directory.
130
     * @param \Composer\IO\IOInterface $io IO interface to write to console.
131
     * @return void
132
     */
133
    public static function createWritableDirectories($dir, $io)
134
    {
135
        foreach (static::WRITABLE_DIRS as $path) {
136
            $path = $dir . '/' . $path;
137
            if (!file_exists($path)) {
138
                mkdir($path);
139
                $io->write('Created `' . $path . '` directory');
140
            }
141
        }
142
    }
143
144
    /**
145
     * Set globally writable permissions on the "tmp" and "logs" directory.
146
     *
147
     * This is not the most secure default, but it gets people up and running quickly.
148
     *
149
     * @param string $dir The application's root directory.
150
     * @param \Composer\IO\IOInterface $io IO interface to write to console.
151
     * @return void
152
     */
153
    public static function setFolderPermissions($dir, $io)
154
    {
155
        // Change the permissions on a path and output the results.
156
        $changePerms = function ($path, $perms, $io) {
157
            // Get permission bits from stat(2) result.
158
            $currentPerms = fileperms($path) & 0777;
159
            if (($currentPerms & $perms) == $perms) {
160
                return;
161
            }
162
163
            $res = chmod($path, $currentPerms | $perms);
164
            if ($res) {
165
                $io->write('Permissions set on ' . $path);
166
            } else {
167
                $io->write('Failed to set permissions on ' . $path);
168
            }
169
        };
170
171
        $walker = function ($dir, $perms, $io) use (&$walker, $changePerms) {
172
            $files = array_diff(scandir($dir), ['.', '..']);
173
            foreach ($files as $file) {
174
                $path = $dir . '/' . $file;
175
176
                if (!is_dir($path)) {
177
                    continue;
178
                }
179
180
                $changePerms($path, $perms, $io);
181
                $walker($path, $perms, $io);
182
            }
183
        };
184
185
        $worldWritable = bindec('0000000111');
186
        $walker($dir . '/tmp', $worldWritable, $io);
187
        $changePerms($dir . '/tmp', $worldWritable, $io);
188
        $changePerms($dir . '/logs', $worldWritable, $io);
189
    }
190
191
    /**
192
     * Set the security.salt value in the application's config file.
193
     *
194
     * @param string $dir The application's root directory.
195
     * @param \Composer\IO\IOInterface $io IO interface to write to console.
196
     * @return void
197
     */
198
    public static function setSecuritySalt($dir, $io)
199
    {
200
        $newKey = hash('sha256', Security::randomBytes(64));
201
        static::setSecuritySaltInFile($dir, $io, $newKey, 'app.php');
202
    }
203
204
    /**
205
     * Set the security.salt value in a given file
206
     *
207
     * @param string $dir The application's root directory.
208
     * @param \Composer\IO\IOInterface $io IO interface to write to console.
209
     * @param string $newKey key to set in the file
210
     * @param string $file A path to a file relative to the application's root
211
     * @return void
212
     */
213 View Code Duplication
    public static function setSecuritySaltInFile($dir, $io, $newKey, $file)
214
    {
215
        $config = $dir . '/config/' . $file;
216
        $content = file_get_contents($config);
217
218
        $content = str_replace('__SALT__', $newKey, $content, $count);
219
220
        if ($count == 0) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $count of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
221
            $io->write('No Security.salt placeholder to replace.');
222
223
            return;
224
        }
225
226
        $result = file_put_contents($config, $content);
227
        if ($result) {
228
            $io->write('Updated Security.salt value in config/' . $file);
229
230
            return;
231
        }
232
        $io->write('Unable to update Security.salt value.');
233
    }
234
235
    /**
236
     * Set the APP_NAME value in a given file
237
     *
238
     * @param string $dir The application's root directory.
239
     * @param \Composer\IO\IOInterface $io IO interface to write to console.
240
     * @param string $appName app name to set in the file
241
     * @param string $file A path to a file relative to the application's root
242
     * @return void
243
     */
244 View Code Duplication
    public static function setAppNameInFile($dir, $io, $appName, $file)
245
    {
246
        $config = $dir . '/config/' . $file;
247
        $content = file_get_contents($config);
248
        $content = str_replace('__APP_NAME__', $appName, $content, $count);
249
250
        if ($count == 0) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $count of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
251
            $io->write('No __APP_NAME__ placeholder to replace.');
252
253
            return;
254
        }
255
256
        $result = file_put_contents($config, $content);
257
        if ($result) {
258
            $io->write('Updated __APP_NAME__ value in config/' . $file);
259
260
            return;
261
        }
262
        $io->write('Unable to update __APP_NAME__ value.');
263
    }
264
}
265