Passed
Push — master ( 530886...6bdffc )
by Josh
02:33
created

Blender::runInstallMigration()   C

Complexity

Conditions 13
Paths 60

Size

Total Lines 70
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 45
dl 0
loc 70
rs 6.6166
c 0
b 0
f 0
cc 13
nc 60
nop 5

3 Methods

Rating   Name   Duplication   Size   Complexity  
A Blender::update() 0 4 1
A Blender::install() 0 36 4
A Blender::uninstall() 0 3 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: jgulledge
5
 * Date: 9/29/2017
6
 * Time: 3:33 PM
7
 */
8
9
namespace LCI\Blend;
10
11
use LCI\Blend\Console\Blend;
12
use LCI\Blend\Exception\MigratorException;
13
use LCI\Blend\Helpers\Format;
14
use LCI\Blend\Helpers\BlendableLoader;
15
use LCI\Blend\Migrations\MigrationsCreator;
16
use LCI\Blend\Migrations\Migrator;
17
use LCI\MODX\Console\Helpers\UserInteractionHandler;
18
use modX;
0 ignored issues
show
Bug introduced by
The type modX was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
use Symfony\Component\Console\Output\OutputInterface;
20
21
class Blender
22
{
23
    /** @var string ~ version number of the project */
24
    private $version = '1.0.0 beta13';
25
26
    /** @var  \modx */
0 ignored issues
show
Bug introduced by
The type modx was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
    protected $modx;
28
29
    /** @var array  */
30
    protected $modx_version_info = [];
31
32
    /** @var \LCI\MODX\Console\Helpers\UserInteractionHandler */
33
    protected $userInteractionHandler;
34
35
    /** @var array  */
36
    protected $config = [];
37
38
    /** @var boolean|array  */
39
    protected $blendMigrations = false;
40
41
    /** @var BlendableLoader */
42
    protected $blendableLoader;
43
44
    /** @var string  */
45
    protected $project = 'local';
46
47
    /** @var  \Tagger */
0 ignored issues
show
Bug introduced by
The type Tagger was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
48
    protected $tagger;
49
50
    protected $resource_id_map = [];
51
52
    protected $resource_seek_key_map = [];
53
54
    protected $category_map = [];
55
56
    /** @var string date('Y_m_d_His') */
57
    protected $seeds_dir = '';
58
59
    /** @var int  */
60
    protected $xpdo_version = 3;
61
62
    protected $blend_class_object = 'BlendMigrations';
63
64
    protected $blend_package = 'blend';
65
66
    const VERBOSITY_QUIET = OutputInterface::VERBOSITY_QUIET;
67
    const VERBOSITY_NORMAL = OutputInterface::VERBOSITY_NORMAL;
68
    const VERBOSITY_VERBOSE = OutputInterface::VERBOSITY_VERBOSE;
69
    const VERBOSITY_VERY_VERBOSE = OutputInterface::VERBOSITY_VERY_VERBOSE;
70
    const VERBOSITY_DEBUG = OutputInterface::VERBOSITY_DEBUG;
71
72
    /**
73
     * Blender constructor.
74
     *
75
     * @param \modX $modx
76
     * @param UserInteractionHandler $userInteractionHandler
77
     * @param array $config
78
     */
79
    public function __construct(modX $modx, UserInteractionHandler $userInteractionHandler, $config = [])
80
    {
81
        $this->modx = $modx;
82
83
        $this->modx_version_info = $this->modx->getVersionData();
84
85
        $this->userInteractionHandler = $userInteractionHandler;
86
87
        if (version_compare($this->modx_version_info['full_version'], '3.0') >= 0) {
88
            $this->xpdo_version = 3;
89
            $this->blend_class_object = 'LCI\\Blend\\Model\\xPDO\\BlendMigrations';
90
            $this->blend_package = 'LCI\\Blend\\Model\\xPDO';
91
92
        } else {
93
            $this->xpdo_version = 2;
94
        }
95
96
        $blend_modx_migration_dir = dirname(__DIR__);
97
        if (isset($config['blend_modx_migration_dir'])) {
98
            $blend_modx_migration_dir = $config['blend_modx_migration_dir'];
99
        }
100
101
        $this->config = [
102
            'migration_templates_path' => __DIR__.'/Migrations/templates/',
103
            'migrations_path' => $blend_modx_migration_dir.'database/migrations/',
104
            'seeds_path' => $blend_modx_migration_dir.'database/seeds/',
105
            'model_dir' => __DIR__.($this->xpdo_version >= 3 ? '/' : '/xpdo2/'),
106
            'extras' => [
107
                'tagger' => false
108
            ]
109
        ];
110
111
        $this->setVerbose();
112
113
        $this->config = array_merge($this->config, $config);
114
115
        $this->seeds_dir = date('Y_m_d_His');
116
117
        $tagger_path = $this->modx->getOption('tagger.core_path', null, $this->modx->getOption('core_path').'components/tagger/').'model/tagger/';
118
        if (is_dir($tagger_path)) {
119
            $this->config['extras']['tagger'] = true;
120
            /** @var \Tagger $tagger */
121
            $this->tagger = $this->modx->getService('tagger', 'Tagger', $tagger_path, []);
122
        }
123
124
        if ($this->xpdo_version >= 3) {
125
            $this->modx->setPackage($this->blend_package, $this->config['model_dir']);
126
127
        } else {
128
            $this->modx->addPackage($this->blend_package, $this->config['model_dir']);
129
        }
130
131
        $this->blendableLoader = new BlendableLoader($this, $this->modx, $this->userInteractionHandler);
132
    }
133
134
    /**
135
     * @deprecated
136
     * @param $name
137
     * @param $arguments
138
     * @return mixed
139
     * @throws MigratorException
140
     */
141
    public function __call($name, $arguments)
142
    {
143
        // How to mark as deprecated?
144
        if (method_exists($this->blendableLoader, $name)) {
145
146
            $message = get_class($this) . '->'.$name.'() has been deprecated, please use ' . get_class($this) . '->getBlendableLoader()->' . $name;
147
            trigger_error($message, E_USER_WARNING);
148
            $this->modx->log(\modX::LOG_LEVEL_ERROR, $message);
149
150
            return call_user_func_array(array($this->blendableLoader, $name), $arguments);
151
152
        } else {
153
            throw new MigratorException('Call to undefined Method ' . get_class($this) . '->' . $name);
154
        }
155
    }
156
157
    /**
158
     * @return BlendableLoader
159
     */
160
    public function getBlendableLoader(): BlendableLoader
161
    {
162
        return $this->blendableLoader;
163
    }
164
165
    /**
166
     * @return string
167
     */
168
    public function getBlendClassObject()
169
    {
170
        return $this->blend_class_object;
171
    }
172
173
    /**
174
     * @return array
175
     */
176
    public function getConfig(): array
177
    {
178
        return $this->config;
179
    }
180
181
    /**
182
     * @return int
183
     */
184
    public function getVerbose(): int
185
    {
186
        return $this->config['verbose'];
187
    }
188
189
    /**
190
     * @param int $verbose
191
     * @see https://symfony.com/doc/current/console/verbosity.html
192
     * @return $this
193
     */
194
    public function setVerbose(int $verbose=self::VERBOSITY_NORMAL)
195
    {
196
        $this->config['verbose'] = $verbose;
197
        return $this;
198
    }
199
200
    /**
201
     * @return UserInteractionHandler
202
     */
203
    public function getUserInteractionHandler()
204
    {
205
        return $this->userInteractionHandler;
206
    }
207
208
    /**
209
     * @return string
210
     */
211
    public function getVersion()
212
    {
213
        return $this->version;
214
    }
215
216
    /**
217
     * @return string
218
     */
219
    public function getSeedsDir()
220
    {
221
        return $this->seeds_dir;
222
    }
223
224
    /**
225
     * @param string $seeds_dir ~ local folder
226
     *
227
     * @return Blender
228
     */
229
    public function setSeedsDir($seeds_dir)
230
    {
231
        $this->seeds_dir = $seeds_dir;
232
        return $this;
233
    }
234
235
    /**
236
     * @param null $directory_key
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $directory_key is correct as it would always require null to be passed?
Loading history...
237
     * @return string
238
     */
239
    public function getSeedsPath($directory_key = null)
240
    {
241
        $seed_path = $this->config['seeds_path'];
242
        if (!empty($directory_key)) {
243
            $seed_path .= trim($directory_key, '/').DIRECTORY_SEPARATOR;
244
        }
245
        return $seed_path;
246
    }
247
248
    /**
249
     * @return string
250
     */
251
    public function getMigrationPath()
252
    {
253
        return $this->config['migrations_path'];
254
    }
255
256
    /**
257
     * @return \Tagger
258
     */
259
    public function getTagger()
260
    {
261
        return $this->tagger;
262
    }
263
264
    /**
265
     * @param bool $refresh
266
     * @return array
267
     */
268
    public function getCategoryMap($refresh = false)
269
    {
270
        if (count($this->category_map) == 0 || $refresh) {
271
            $this->category_map = [
272
                'ids' => [],
273
                'names' => [],
274
                'lineage' => []
275
            ];
276
            $query = $this->modx->newQuery('modCategory');
277
            $query->sortBy('parent');
278
            $query->sortBy('rank');
279
            $categories = $this->modx->getCollection('modCategory', $query);
280
            foreach ($categories as $category) {
281
                $category_data = $category->toArray();
282
283
                $this->category_map['ids'][$category->get('id')] = $category_data;
284
285
                $key = trim($category->get('category'));
286
                // This is not unique!
287
                $this->category_map['names'][$key] = $category_data;
288
289
                // Get the lineage: Parent=>Child=>Grand Child as key
290
                $lineage = $key;
291
                if ($category_data['parent'] > 0 && isset($this->category_map['ids'][$category_data['parent']]) && isset($this->category_map['ids'][$category_data['parent']]['lineage'])) {
292
                    $lineage = $this->category_map['ids'][$category_data['parent']]['lineage'].'=>'.$key;
293
                } elseif ($category_data['parent'] > 0) {
294
                    //$this->out('DID NOT FIND PARENT?? '. print_r($category_data, true), true);
295
                }
296
297
                $this->category_map['ids'][$category->get('id')]['lineage'] = $lineage;
298
299
                $this->category_map['lineage'][$lineage] = $category->toArray();
300
            }
301
        }
302
        return $this->category_map;
303
    }
304
305
    /**
306
     * @param string $message
307
     * @param int $verbose
308
     * @param bool $error
309
     */
310
    public function out($message, $verbose=Blender::VERBOSITY_NORMAL, $error = false)
311
    {
312
        if ($this->getVerbose() >= $verbose) {
313
            if ($error) {
314
                $this->userInteractionHandler->tellUser($message, userInteractionHandler::MASSAGE_ERROR);
315
316
            } else {
317
                $this->userInteractionHandler->tellUser($message, userInteractionHandler::MASSAGE_STRING);
318
            }
319
        }
320
    }
321
322
    /**
323
     * @param string $message
324
     * @param int $verbose
325
     */
326
    public function outError($message, $verbose=Blender::VERBOSITY_NORMAL)
327
    {
328
        if ($this->getVerbose() >= $verbose) {
329
            $this->userInteractionHandler->tellUser($message, userInteractionHandler::MASSAGE_ERROR);
330
        }
331
    }
332
333
    /**
334
     * @param string $message
335
     * @param int $verbose
336
     */
337
    public function outSuccess($message, $verbose=Blender::VERBOSITY_NORMAL)
338
    {
339
        if ($this->getVerbose() >= $verbose) {
340
            $this->userInteractionHandler->tellUser($message, userInteractionHandler::MASSAGE_SUCCESS);
341
        }
342
    }
343
344
    /**
345
     * @param string $name
346
     * @param string $server_type
347
     * @param string|null $migration_path
348
     *
349
     * @return bool
350
     */
351
    public function createBlankMigrationClassFile($name, $server_type = 'master', $migration_path = null)
352
    {
353
        $migrationCreator = new MigrationsCreator($this->userInteractionHandler);
354
355
        if (empty($migration_path)) {
356
            $migration_path = $this->getMigrationPath();
357
        }
358
359
        $success = $migrationCreator
360
            ->setPathTimeStamp($this->getSeedsDir())
361
            ->setVerbose($this->getVerbose())
362
            ->setName($name)
363
            ->setDescription('')
364
            ->setServerType($server_type)
365
            ->setMigrationsPath($migration_path)
366
            ->createBlankMigrationClassFile();
367
368
        $this->logCreatedMigration($migrationCreator->getLogData());
369
        return $success;
370
    }
371
372
    /**
373
     * @return SeedMaker
374
     */
375
    public function getSeedMaker()
376
    {
377
        return new SeedMaker($this->modx, $this);
378
    }
379
380
    /**
381
     * @param string $method
382
     * @param bool $prompt
383
     * @throws MigratorException
384
     */
385
    /**
386
     * @param string $method
387
     * @param bool $prompt
388
     * @throws MigratorException
389
     */
390
    public function install($method = 'up', $prompt = false)
391
    {
392
        $config = $this->config;
393
394
        $config['migrations_path'] = __DIR__.'/Migrations/Blend/';
395
396
        $blender = new Blender($this->modx, $this->getUserInteractionHandler(), $config);
397
        $blender->setProject('lci\blend');
398
399
        $migrator = new Migrator($blender, $this->modx, 'lci\blend');
400
        $migrator->runMigration($method);
401
402
        // does the migration directory exist?
403
        if (!file_exists($this->getMigrationPath())) {
404
            $this->out('MKDIR', 0);
405
            $create = true;
406
            if ($this->getVerbose() >= Blender::VERBOSITY_NORMAL) {
407
                $create = $this->getUserInteractionHandler()->promptConfirm('Create the following directory for migration files?'.PHP_EOL
408
                    .$this->getMigrationPath(), true);
409
            }
410
411
            if ($create) {
412
                mkdir($this->getMigrationPath(), 0700, true);
413
                $this->outSuccess('Created migration directory: '.$this->getMigrationPath());
414
            }
415
        }
416
417
        /** @var \LCI\Blend\Blendable\SystemSetting $systemSetting */
418
        $systemSetting = $this->getBlendableLoader()->getBlendableSystemSetting('blend.version');
419
        $systemSetting
420
            ->setSeedsDir($this->getSeedsDir())
421
            ->setFieldValue($this->getVersion())
422
            ->setFieldArea('Blend')
423
            ->blend(true);
424
425
        $this->modx->cacheManager->refresh();
426
    }
427
428
    /**
429
     * @throws MigratorException
430
     */
431
    public function uninstall()
432
    {
433
        $this->install('down');
434
    }
435
436
    /**
437
     * @param string $method
438
     * @throws MigratorException
439
     */
440
    public function update($method = 'up')
441
    {
442
        $current_version = $this->modx->getOption('blend.version');
0 ignored issues
show
Unused Code introduced by
The assignment to $current_version is dead and can be removed.
Loading history...
443
        $this->install($method);
444
    }
445
446
    /**
447
     * @return bool
448
     */
449
    public function requireUpdate()
450
    {
451
        $upgrade = false;
452
453
        $current_version = $this->modx->getOption('blend.version');
454
        //                                      FILE version,        DB Version
455
        if ($this->isBlendInstalledInModx() && (!$current_version || version_compare($this->getVersion(), $current_version) === 1)) {
456
            $this->outError('MODX System Setting Version: '. $current_version.' Code version: '.$this->getVersion(), Blender::VERBOSITY_DEBUG);
457
            $upgrade = true;
458
        }
459
460
        return $upgrade;
461
    }
462
463
    /**
464
     * @return bool
465
     */
466
    public function isBlendInstalledInModx()
467
    {
468
        try {
469
            $table = $this->modx->getTableName($this->blend_class_object);
470
            if ($this->modx->query("SELECT 1 FROM {$table} LIMIT 0") === false) {
471
                return false;
472
            }
473
        } catch (Exception $exception) {
474
            // We got an exception == table not found
475
            return false;
476
        }
477
478
        return true;
479
480
        /** @var \xPDOQuery $query */
481
        $query = $this->modx->newQuery($this->blend_class_object);
0 ignored issues
show
Unused Code introduced by
$query = $this->modx->ne...is->blend_class_object) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
482
        $query->select('id');
483
        $query->where([
484
            'name' => 'install_blender',
485
            'status' => 'up_complete'
486
        ]);
487
        $query->sortBy('name');
488
489
        $installMigration = $this->modx->getObject($this->blend_class_object, $query);
490
        if ($installMigration instanceof \BlendMigrations || $installMigration instanceof \LCI\Blend\Model\xPDO\BlendMigrations) {
491
            return true;
492
        }
493
494
        return false;
495
    }
496
497
    /**
498
     * @param string $method
499
     * @param string $type
500
     * @param int $count
501
     * @param int $id
502
     * @param null|string $name
503
     * @throws MigratorException
504
     */
505
    public function runMigration($method = 'up', $type = 'master', $count = 0, $id = 0, $name = null)
506
    {
507
        /** @var Migrator $migrator */
508
        $migrator = new Migrator($this, $this->modx, $this->project);
509
        $migrator->runMigration($method, $type, $count, $id, $name);
510
    }
511
512
    /**
513
     * @param $type
514
     * @param null $name
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $name is correct as it would always require null to be passed?
Loading history...
515
     * @return string
516
     */
517
    public function getMigrationName($type, $name = null)
518
    {
519
        $format = new Format($this->seeds_dir);
520
        return $format->getMigrationName($type, $name);
521
    }
522
523
    /**
524
     * @param string $name
525
     * @param string $type ~ chunk, plugin, resource, snippet, systemSettings, template, site
526
     *
527
     * @return bool
528
     */
529
    public function removeMigrationFile($name, $type)
530
    {
531
        $class_name = $this->getMigrationName($type, $name);
532
533
        $removed = false;
534
        $migration_file = $this->getMigrationPath().$class_name.'.php';
535
        if (file_exists($migration_file)) {
536
            if (unlink($migration_file)) {
537
                $removed = true;
538
                $migration = $this->modx->getObject($this->blend_class_object, ['name' => $class_name]);
539
                if (is_object($migration) && $migration->remove()) {
540
                    $this->out($class_name.' migration has been removed from the blend_migrations table', Blender::VERBOSITY_VERY_VERBOSE);
541
542
                }
543
            } else {
544
                $this->outError($class_name.' migration has not been removed from the blend_migrations table');
545
            }
546
547
        } else {
548
            $this->outError($this->getMigrationPath().$class_name.'.php migration could not be found to remove');
549
        }
550
551
        return $removed;
552
    }
553
    /**
554
     * @param int $id
555
     *
556
     * @return bool|array
557
     */
558
    public function getResourceSeedKeyFromID($id)
559
    {
560
        if (!isset($this->resource_id_map[$id])) {
561
            $seed_key = $context = false;
562
            $resource = $this->modx->getObject('modResource', $id);
563
            if ($resource) {
564
                $context = $resource->get('context_key');
565
                if (!isset($this->resource_seek_key_map[$context])) {
566
                    $this->resource_seek_key_map[$context] = [];
567
                }
568
                $seed_key = $this->getSeedKeyFromAlias($resource->get('alias'));
569
                $this->resource_seek_key_map[$context][$seed_key] = $id;
570
            }
571
            $this->resource_id_map[$id] = [
572
                'context' => $context,
573
                'seed_key' => $seed_key
574
            ];
575
        }
576
577
        return $this->resource_id_map[$id];
578
    }
579
580
    /**
581
     * @param string $seed_key
582
     * @param string $context
583
     *
584
     * @return bool|int
585
     */
586
    public function getResourceIDFromSeedKey($seed_key, $context = 'web')
587
    {
588
        if (!isset($this->resource_seek_key_map[$context])) {
589
            $this->resource_seek_key_map[$context] = [];
590
        }
591
        if (!isset($this->resource_seek_key_map[$context][$seed_key])) {
592
            $id = false;
593
            $alias = $this->getAliasFromSeedKey($seed_key);
594
            $resource = $this->modx->getObject('modResource', ['alias' => $alias, 'context_key' => $context]);
595
            if ($resource) {
596
                $id = $resource->get('id');
597
                $this->resource_seek_key_map[$context][$seed_key] = $id;
598
                $this->resource_id_map[$id] = [
599
                    'context' => $context,
600
                    'seed_key' => $seed_key
601
                ];
602
            }
603
            $this->resource_seek_key_map[$context][$seed_key] = $id;
604
        }
605
606
        return $this->resource_seek_key_map[$context][$seed_key];
607
    }
608
609
    /**
610
     * @param string $alias
611
     *
612
     * @return string
613
     */
614
    public function getSeedKeyFromAlias($alias)
615
    {
616
        return str_replace('/', '#', $alias);
617
    }
618
619
    /**
620
     * @param string $seed_key
621
     *
622
     * @return string
623
     */
624
    public function getAliasFromSeedKey($seed_key)
625
    {
626
        return str_replace('#', '/', $seed_key);
627
    }
628
629
    /**
630
     * @deprecated
631
     * @param string $name
632
     * @param string $type ~ template, template-variable, chunk, snippet or plugin
633
     * @return string
634
     */
635
    public function getElementSeedKeyFromName($name, $type)
636
    {
637
        return $type.'_'.$this->getSeedKeyFromName($name);
638
    }
639
640
    /**
641
     * @param string $name
642
     * @return string
643
     */
644
    public function getSeedKeyFromName($name)
645
    {
646
        // @TODO review
647
        return str_replace('/', '#', $name);
648
    }
649
650
    /**
651
     * @param string $seed_key
652
     * @return string
653
     */
654
    public function getNameFromSeedKey($seed_key)
655
    {
656
        return str_replace('#', '/', $seed_key);
657
    }
658
659
    /**
660
     * @param string $project
661
     * @return Blender
662
     */
663
    public function setProject(string $project): Blender
664
    {
665
        $this->project = $project;
666
        return $this;
667
    }
668
669
    /**
670
     * @param array $data
671
     */
672
    protected function logCreatedMigration(array $data)
673
    {
674
        $migrator = new Migrator($this, $this->modx, 'local');
675
        $migrator->logMigration($data);
676
    }
677
}
678