Passed
Push — master ( 361368...f78f12 )
by Josh
02:46
created

Blender::getResourceIDFromLocalAlias()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
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
401
            ->setDelayLogging(true)
402
            ->setCheckInstallLog($method == 'up' ? false : true)
403
            ->runMigration($method);
404
405
        // does the migration directory exist?
406
        if (!file_exists($this->getMigrationPath())) {
407
            $create = true;
408
            if ($this->getVerbose() >= Blender::VERBOSITY_NORMAL || $prompt) {
409
                $create = $this->getUserInteractionHandler()->promptConfirm('Create the following directory for migration files?'.PHP_EOL
410
                    .$this->getMigrationPath(), true);
411
            }
412
413
            if ($create) {
414
                mkdir($this->getMigrationPath(), 0700, true);
415
                $this->outSuccess('Created migration directory: '.$this->getMigrationPath());
416
            }
417
        }
418
419
        /** @var \LCI\Blend\Blendable\SystemSetting $systemSetting */
420
        $systemSetting = $this->getBlendableLoader()->getBlendableSystemSetting('blend.version');
421
        $systemSetting
422
            ->setSeedsDir($this->getSeedsDir())
423
            ->setFieldValue($this->getVersion())
424
            ->setFieldArea('Blend')
425
            ->blend(true);
426
427
        $this->modx->cacheManager->refresh();
428
    }
429
430
    /**
431
     * @throws MigratorException
432
     */
433
    public function uninstall()
434
    {
435
        $this->install('down');
436
    }
437
438
    /**
439
     * @param string $method
440
     * @throws MigratorException
441
     */
442
    public function update($method = 'up')
443
    {
444
        $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...
445
        $this->install($method);
446
    }
447
448
    /**
449
     * @return bool
450
     */
451
    public function requireUpdate()
452
    {
453
        $upgrade = false;
454
455
        $current_version = $this->modx->getOption('blend.version');
456
        //                                      FILE version,        DB Version
457
        if ($this->isBlendInstalledInModx() && (!$current_version || version_compare($this->getVersion(), $current_version) === 1)) {
458
            $this->outError('MODX System Setting Version: '. $current_version.' Code version: '.$this->getVersion(), Blender::VERBOSITY_DEBUG);
459
            $upgrade = true;
460
        }
461
462
        return $upgrade;
463
    }
464
465
    /**
466
     * @param bool $check_install_log
467
     * @return bool
468
     */
469
    public function isBlendInstalledInModx($check_install_log=true)
470
    {
471
        $installed = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $installed is dead and can be removed.
Loading history...
472
        try {
473
            $table = $this->modx->getTableName($this->blend_class_object);
474
            if ($rs = $this->modx->query("SELECT 1 FROM {$table} LIMIT 0") === false) {
0 ignored issues
show
Unused Code introduced by
The assignment to $rs is dead and can be removed.
Loading history...
475
                $this->out(__METHOD__.' table not found ', Blender::VERBOSITY_DEBUG);
476
                return false;
477
            }
478
            $installed = true;
479
480
        } catch (\Exception $exception) {
481
            $this->out(__METHOD__ . ' Exception: '. $exception->getMessage(), Blender::VERBOSITY_DEBUG);
482
            // We got an exception == table not found
483
            return false;
484
        }
485
486
        if ($check_install_log) {
487
            $installed = false;
488
            /** @var \xPDOQuery $query */
489
            $query = $this->modx->newQuery($this->blend_class_object);
490
            $query->select('id');
491
            $query->where([
492
                'name' => 'install_blender',
493
                'status' => 'up_complete'
494
            ]);
495
            $query->sortBy('name');
496
497
            $installMigration = $this->modx->getObject($this->blend_class_object, $query);
498
            if ($installMigration instanceof \BlendMigrations || $installMigration instanceof \LCI\Blend\Model\xPDO\BlendMigrations) {
499
                $this->out(print_r($installMigration->toArray(), true), Blender::VERBOSITY_DEBUG);
500
                $installed = true;
501
            }
502
            $this->out(__METHOD__ . ' Blend install log not found', Blender::VERBOSITY_DEBUG);
503
        }
504
505
        return $installed;
506
    }
507
508
    /**
509
     * @param string $method
510
     * @param string $type
511
     * @param int $count
512
     * @param int $id
513
     * @param null|string $name
514
     * @throws MigratorException
515
     */
516
    public function runMigration($method = 'up', $type = 'master', $count = 0, $id = 0, $name = null)
517
    {
518
        /** @var Migrator $migrator */
519
        $migrator = new Migrator($this, $this->modx, $this->project);
520
        $migrator->runMigration($method, $type, $count, $id, $name);
521
    }
522
523
    /**
524
     * @param $type
525
     * @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...
526
     * @return string
527
     */
528
    public function getMigrationName($type, $name = null)
529
    {
530
        $format = new Format($this->seeds_dir);
531
        return $format->getMigrationName($type, $name);
532
    }
533
534
    /**
535
     * @param string $name
536
     * @param string $type ~ chunk, plugin, resource, snippet, systemSettings, template, site
537
     *
538
     * @return bool
539
     */
540
    public function removeMigrationFile($name, $type)
541
    {
542
        $class_name = $this->getMigrationName($type, $name);
543
544
        $removed = false;
545
        $migration_file = $this->getMigrationPath().$class_name.'.php';
546
        if (file_exists($migration_file)) {
547
            if (unlink($migration_file)) {
548
                $removed = true;
549
                $migration = $this->modx->getObject($this->blend_class_object, ['name' => $class_name]);
550
                if (is_object($migration) && $migration->remove()) {
551
                    $this->out($class_name.' migration has been removed from the blend_migrations table', Blender::VERBOSITY_VERY_VERBOSE);
552
553
                }
554
            } else {
555
                $this->outError($class_name.' migration has not been removed from the blend_migrations table');
556
            }
557
558
        } else {
559
            $this->outError($this->getMigrationPath().$class_name.'.php migration could not be found to remove');
560
        }
561
562
        return $removed;
563
    }
564
    /**
565
     * @param int $id
566
     *
567
     * @return bool|array
568
     */
569
    public function getResourceSeedKeyFromID($id)
570
    {
571
        if (!isset($this->resource_id_map[$id])) {
572
            $seed_key = $context = false;
573
            $resource = $this->modx->getObject('modResource', $id);
574
            if ($resource) {
575
                $context = $resource->get('context_key');
576
                if (!isset($this->resource_seek_key_map[$context])) {
577
                    $this->resource_seek_key_map[$context] = [];
578
                }
579
                $seed_key = $this->getSeedKeyFromAlias($resource->get('alias'));
580
                $this->resource_seek_key_map[$context][$seed_key] = $id;
581
            }
582
            $this->resource_id_map[$id] = [
583
                'context' => $context,
584
                'seed_key' => $seed_key
585
            ];
586
        }
587
588
        return $this->resource_id_map[$id];
589
    }
590
591
    /**
592
     * @param string $alias
593
     * @param string $context
594
     * @return bool|int
595
     */
596
    public function getResourceIDFromLocalAlias($alias, $context='web')
597
    {
598
        $seed_key = $this->getSeedKeyFromAlias($alias);
599
        return $this->getResourceIDFromSeedKey($seed_key, $context);
600
    }
601
602
    /**
603
     * @param string $seed_key
604
     * @param string $context
605
     *
606
     * @return bool|int
607
     */
608
    public function getResourceIDFromSeedKey($seed_key, $context = 'web')
609
    {
610
        if (!isset($this->resource_seek_key_map[$context])) {
611
            $this->resource_seek_key_map[$context] = [];
612
        }
613
        if (!isset($this->resource_seek_key_map[$context][$seed_key])) {
614
            $id = false;
615
            $alias = $this->getAliasFromSeedKey($seed_key);
616
            $resource = $this->modx->getObject('modResource', ['alias' => $alias, 'context_key' => $context]);
617
            if ($resource) {
618
                $id = $resource->get('id');
619
                $this->resource_seek_key_map[$context][$seed_key] = $id;
620
                $this->resource_id_map[$id] = [
621
                    'context' => $context,
622
                    'seed_key' => $seed_key
623
                ];
624
            }
625
            $this->resource_seek_key_map[$context][$seed_key] = $id;
626
        }
627
628
        return $this->resource_seek_key_map[$context][$seed_key];
629
    }
630
631
    /**
632
     * @param string $alias
633
     *
634
     * @return string
635
     */
636
    public function getSeedKeyFromAlias($alias)
637
    {
638
        return str_replace('/', '#', $alias);
639
    }
640
641
    /**
642
     * @param string $seed_key
643
     *
644
     * @return string
645
     */
646
    public function getAliasFromSeedKey($seed_key)
647
    {
648
        return str_replace('#', '/', $seed_key);
649
    }
650
651
    /**
652
     * @deprecated
653
     * @param string $name
654
     * @param string $type ~ template, template-variable, chunk, snippet or plugin
655
     * @return string
656
     */
657
    public function getElementSeedKeyFromName($name, $type)
658
    {
659
        return $type.'_'.$this->getSeedKeyFromName($name);
660
    }
661
662
    /**
663
     * @param string $name
664
     * @return string
665
     */
666
    public function getSeedKeyFromName($name)
667
    {
668
        // @TODO review
669
        return str_replace('/', '#', $name);
670
    }
671
672
    /**
673
     * @param string $seed_key
674
     * @return string
675
     */
676
    public function getNameFromSeedKey($seed_key)
677
    {
678
        return str_replace('#', '/', $seed_key);
679
    }
680
681
    /**
682
     * @param string $project
683
     * @return Blender
684
     */
685
    public function setProject(string $project): Blender
686
    {
687
        $this->project = $project;
688
        return $this;
689
    }
690
691
    /**
692
     * @param array $data
693
     */
694
    protected function logCreatedMigration(array $data)
695
    {
696
        $migrator = new Migrator($this, $this->modx, 'local');
697
        $migrator->logMigration($data);
698
    }
699
}
700