Completed
Push — develop ( f6b543...55cbc4 )
by Tom
04:03
created

MagentoHelper::getEdition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace N98\Util\Console\Helper;
4
5
use ArrayIterator;
6
use CallbackFilterIterator;
7
use N98\Magento\Application;
8
use RuntimeException;
9
use Symfony\Component\Console\Helper\Helper as AbstractHelper;
10
use Symfony\Component\Console\Input\ArgvInput;
11
use Symfony\Component\Console\Input\InputInterface;
12
use Symfony\Component\Console\Output\ConsoleOutput;
13
use Symfony\Component\Console\Output\OutputInterface;
14
use Symfony\Component\Finder\Finder;
15
use UnexpectedValueException;
16
17
/**
18
 * Class MagentoHelper
19
 *
20
 * @package N98\Util\Console\Helper
21
 */
22
class MagentoHelper extends AbstractHelper
23
{
24
    /**
25
     * @var string
26
     */
27
    protected $_magentoRootFolder = null;
28
29
    /**
30
     * @var int
31
     */
32
    protected $_magentoMajorVersion = \N98\Magento\Application::MAGENTO_MAJOR_VERSION_1;
33
34
    /**
35
     * @var bool
36
     */
37
    protected $_magentoEnterprise = false;
38
39
    /**
40
     * @var bool
41
     */
42
    protected $_magerunStopFileFound = false;
43
44
    /**
45
     * @var string
46
     */
47
    protected $_magerunStopFileFolder = null;
48
49
    /**
50
     * @var InputInterface
51
     */
52
    protected $input;
53
54
    /**
55
     * @var OutputInterface
56
     */
57
    protected $output;
58
59
    /**
60
     * @var array
61
     */
62
    protected $baseConfig = array();
63
64
    /**
65
     * @var string
66
     */
67
    protected $_customConfigFilename = 'n98-magerun2.yaml';
68
69
    /**
70
     * Returns the canonical name of this helper.
71
     *
72
     * @return string The canonical name
73
     *
74
     * @api
75
     */
76
    public function getName()
77
    {
78
        return 'magento';
79
    }
80
81
    /**
82
     * @param InputInterface $input
83
     * @param OutputInterface $output
0 ignored issues
show
Documentation introduced by
Should the type for parameter $output not be null|OutputInterface?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
84
     */
85
    public function __construct(InputInterface $input = null, OutputInterface $output = null)
86
    {
87
        if (null === $input) {
88
            $input = new ArgvInput();
89
        }
90
91
        if (null === $output) {
92
            $output = new ConsoleOutput();
93
        }
94
95
        $this->input = $input;
96
        $this->output = $output;
97
    }
98
99
    /**
100
     * Start Magento detection
101
     *
102
     * @param string $folder
103
     * @param array $subFolders [optional] sub-folders to check
104
     * @return bool
105
     */
106
    public function detect($folder, array $subFolders = array())
107
    {
108
        $folders = $this->splitPathFolders($folder);
109
        $folders = $this->checkMagerunFile($folders);
110
        $folders = $this->checkModman($folders);
111
        $folders = array_merge($folders, $subFolders);
112
113
        foreach (array_reverse($folders) as $searchFolder) {
114
            if (!is_dir($searchFolder) || !is_readable($searchFolder)) {
115
                continue;
116
            }
117
118
            $found = $this->_search($searchFolder);
119
            if ($found) {
120
                return true;
121
            }
122
        }
123
124
        return false;
125
    }
126
127
    /**
128
     * @return string
129
     */
130
    public function getRootFolder()
131
    {
132
        return $this->_magentoRootFolder;
133
    }
134
135
    /**
136
     * @return bool
137
     */
138
    public function isEnterpriseEdition()
139
    {
140
        return $this->_magentoEnterprise;
141
    }
142
143
    /**
144
     * @return int
145
     */
146
    public function getMajorVersion()
147
    {
148
        return $this->_magentoMajorVersion;
149
    }
150
151
    /**
152
     * @return boolean
153
     */
154
    public function isMagerunStopFileFound()
155
    {
156
        return $this->_magerunStopFileFound;
157
    }
158
159
    /**
160
     * @return string
161
     */
162
    public function getMagerunStopFileFolder()
163
    {
164
        return $this->_magerunStopFileFolder;
165
    }
166
167
    /**
168
     * @param string $folder
169
     *
170
     * @return array
171
     */
172
    protected function splitPathFolders($folder)
173
    {
174
        $folders = array();
175
176
        $folderParts = explode('/', $folder);
177
        foreach ($folderParts as $key => $part) {
178
            $explodedFolder = implode('/', array_slice($folderParts, 0, $key + 1));
179
            if ($explodedFolder !== '') {
180
                $folders[] = $explodedFolder;
181
            }
182
        }
183
184
        return $folders;
185
    }
186
187
    /**
188
     * Check for modman file and .basedir
189
     *
190
     * @param array $folders
191
     *
192
     * @return array
193
     */
194
    protected function checkModman(array $folders)
195
    {
196
        foreach ($this->searchFolders($folders) as $searchFolder) {
197
            $finder = Finder::create();
198
            $finder
199
                ->files()
200
                ->ignoreUnreadableDirs(true)
201
                ->depth(0)
202
                ->followLinks()
203
                ->ignoreDotFiles(false)
204
                ->name('.basedir')
205
                ->in($searchFolder);
206
207
            $count = $finder->count();
208
            if ($count > 0) {
209
                $baseFolderContent = trim(file_get_contents($searchFolder . '/.basedir'));
210
                $this->writeDebug('Found modman .basedir file with content <info>' . $baseFolderContent . '</info>');
211
212
                if (!empty($baseFolderContent)) {
213
                    array_push($folders, $searchFolder . '/../' . $baseFolderContent);
214
                }
215
            }
216
        }
217
218
        return $folders;
219
    }
220
221
    /**
222
     * Check for magerun stop-file
223
     *
224
     * @param array $folders
225
     *
226
     * @return array
227
     */
228
    protected function checkMagerunFile(array $folders)
229
    {
230
        $stopFile = '.' . pathinfo($this->_customConfigFilename, PATHINFO_FILENAME);
231
232
        foreach ($this->searchFolders($folders) as $searchFolder) {
233
            $magerunFilePath = $searchFolder . '/' . $stopFile;
234
            if (is_link($magerunFilePath) && !file_exists($magerunFilePath)) {
235
                throw new \RuntimeException(
236
                    sprintf("Stopfile is broken symlink: '%s'", $magerunFilePath),
237
                    2
238
                );
239
            }
240
            if (!is_readable($magerunFilePath) || !is_file($magerunFilePath)) {
241
                continue;
242
            }
243
            $this->_magerunStopFileFound = true;
244
            $this->_magerunStopFileFolder = $searchFolder;
245
            $magerunFileContent = trim(file_get_contents($magerunFilePath));
246
            $message = sprintf(
247
                'Found stopfile \'%s\' file with content <info>%s</info> from \'%s\'',
248
                $stopFile,
249
                $magerunFileContent,
250
                $searchFolder
251
            );
252
            $this->writeDebug($message);
253
254
            array_push($folders, $searchFolder . '/' . $magerunFileContent);
255
            break;
256
        }
257
258
        return $folders;
259
    }
260
261
    /**
262
     * Turn an array of folders into a Traversable of readable paths.
263
     *
264
     * @param array $folders
265
     * @return CallbackFilterIterator Traversable of strings that are readable paths
266
     */
267
    private function searchFolders(array $folders)
268
    {
269
        $that = $this;
270
271
        $callback = function ($searchFolder) use ($that) {
272
            if (!is_readable($searchFolder)) {
273
                $that->writeDebug('Folder <info>' . $searchFolder . '</info> is not readable. Skip.');
274
275
                return false;
276
            }
277
278
            return true;
279
        };
280
281
        return new CallbackFilterIterator(
282
            new ArrayIterator(array_reverse($folders)),
283
            $callback
284
        );
285
    }
286
287
    /**
288
     * @param string $searchFolder
289
     *
290
     * @return bool
291
     */
292
    protected function _search($searchFolder)
293
    {
294
        $this->writeDebug('Search for Magento in folder <info>' . $searchFolder . '</info>');
295
296
        if (!is_dir($searchFolder . '/app')) {
297
            return false;
298
        }
299
300
        $finder = Finder::create();
301
        $finder
302
            ->ignoreUnreadableDirs(true)
303
            ->depth(0)
304
            ->followLinks()
305
            ->name('Mage.php')
306
            ->name('bootstrap.php')
307
            ->name('autoload.php')
308
            ->in($searchFolder . '/app');
309
310
        if ($finder->count() > 0) {
311
            $files = iterator_to_array($finder, false);
312
            /* @var $file \SplFileInfo */
313
314
            $hasMageFile = false;
315
            foreach ($files as $file) {
316
                if ($file->getFilename() == 'Mage.php') {
317
                    $hasMageFile = true;
318
                }
319
            }
320
321
            $this->_magentoRootFolder = $searchFolder;
322
323
            // Magento 2 does not have a god class and thus if this file is not there it is version 2
324
            if ($hasMageFile == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
325
                $this->_magentoMajorVersion = Application::MAGENTO_MAJOR_VERSION_2;
326
            } else {
327
                $this->_magentoMajorVersion = Application::MAGENTO_MAJOR_VERSION_1;
328
            }
329
330
            $this->writeDebug('Found Magento in folder <info>' . $this->_magentoRootFolder . '</info>');
331
332
            return true;
333
        }
334
335
        return false;
336
    }
337
338
    /**
339
     * @return array
340
     * @throws RuntimeException
341
     */
342
    public function getBaseConfig()
343
    {
344
        if (!$this->baseConfig) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->baseConfig of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
345
            $this->initBaseConfig();
346
        }
347
348
        return $this->baseConfig;
349
    }
350
351
    private function initBaseConfig()
352
    {
353
        $this->baseConfig = [];
354
355
        $application = $this->getApplication();
356
357
        $configFiles = [
358
            'app/etc/config.php',
359
            'app/etc/env.php',
360
        ];
361
362
        foreach ($configFiles as $configFileName) {
363
            $this->addBaseConfig($application->getMagentoRootFolder(), $configFileName);
364
        }
365
    }
366
367
    /**
368
     * private getter for application that has magento detected
369
     *
370
     * @return Application
371
     */
372
    private function getApplication()
373
    {
374
        $command = $this->getHelperSet()->getCommand();
375
376
        $application = $command ? $command->getApplication() : new Application();
377
378
        // verify type because of detectMagento() call below
379
        if (!$application instanceof Application) {
380
            throw new UnexpectedValueException(
381
                sprintf('Expected magerun application got %s', get_class($application))
382
            );
383
        }
384
385
        $application->detectMagento();
386
387
        return $application;
388
    }
389
390
    /**
391
     * @param string $root
392
     * @param string $configFileName
393
     */
394
    private function addBaseConfig($root, $configFileName)
395
    {
396
        $configFile = $root . '/' . $configFileName;
397
        if (!(is_file($configFile) && is_readable($configFile))) {
398
            throw new RuntimeException(sprintf('%s is not readable', $configFileName));
399
        }
400
401
        $config = @include $configFile;
402
403
        if (!is_array($config)) {
404
            throw new RuntimeException(sprintf('%s is corrupted. Please check it.', $configFileName));
405
        }
406
407
        $this->baseConfig = array_merge($this->baseConfig, $config);
408
    }
409
410
    /**
411
     * @param string $message
412
     * @return void
413
     */
414
    private function writeDebug($message)
415
    {
416
        if (OutputInterface::VERBOSITY_DEBUG <= $this->output->getVerbosity()) {
417
            $this->output->writeln(
418
                '<debug>' . $message . '</debug>'
419
            );
420
        }
421
    }
422
}
423