Blender::getResourceIDFromLocalAlias()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 4
rs 10
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
    const VERSION = '1.3.1';
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
            'history_path' => $blend_modx_migration_dir.'database/history/',
106
            'model_dir' => __DIR__.($this->xpdo_version >= 3 ? '/' : '/xpdo2/'),
107
            'extras' => [
108
                'tagger' => false
109
            ]
110
        ];
111
112
        $this->setVerbose();
113
114
        $this->config = array_merge($this->config, $config);
115
116
        $this->seeds_dir = date('Y_m_d_His');
117
118
        $tagger_path = $this->modx->getOption('tagger.core_path', null, $this->modx->getOption('core_path').'components/tagger/').'model/tagger/';
119
        if (is_dir($tagger_path)) {
120
            $this->config['extras']['tagger'] = true;
121
            /** @var \Tagger $tagger */
122
            $this->tagger = $this->modx->getService('tagger', 'Tagger', $tagger_path, []);
123
        }
124
125
        if ($this->xpdo_version >= 3) {
126
            $this->modx->setPackage($this->blend_package, $this->config['model_dir']);
127
128
        } else {
129
            $this->modx->addPackage($this->blend_package, $this->config['model_dir']);
130
        }
131
132
        $this->blendableLoader = new BlendableLoader($this, $this->modx, $this->userInteractionHandler);
133
    }
134
135
    /**
136
     * @deprecated
137
     * @param $name
138
     * @param $arguments
139
     * @return mixed
140
     * @throws MigratorException
141
     */
142
    public function __call($name, $arguments)
143
    {
144
        // How to mark as deprecated?
145
        if (method_exists($this->blendableLoader, $name)) {
146
147
            $message = get_class($this) . '->'.$name.'() has been deprecated, please use ' . get_class($this) . '->getBlendableLoader()->' . $name;
148
            trigger_error($message, E_USER_WARNING);
149
            $this->modx->log(\modX::LOG_LEVEL_ERROR, $message);
150
151
            return call_user_func_array(array($this->blendableLoader, $name), $arguments);
152
153
        } else {
154
            throw new MigratorException('Call to undefined Method ' . get_class($this) . '->' . $name);
155
        }
156
    }
157
158
    /**
159
     * @return BlendableLoader
160
     */
161
    public function getBlendableLoader(): BlendableLoader
162
    {
163
        return $this->blendableLoader;
164
    }
165
166
    /**
167
     * @return string
168
     */
169
    public function getBlendClassObject()
170
    {
171
        return $this->blend_class_object;
172
    }
173
174
    /**
175
     * @return array
176
     */
177
    public function getConfig(): array
178
    {
179
        return $this->config;
180
    }
181
182
    /**
183
     * @return int
184
     */
185
    public function getVerbose(): int
186
    {
187
        return $this->config['verbose'];
188
    }
189
190
    /**
191
     * @param int $verbose
192
     * @see https://symfony.com/doc/current/console/verbosity.html
193
     * @return $this
194
     */
195
    public function setVerbose(int $verbose=self::VERBOSITY_NORMAL)
196
    {
197
        $this->config['verbose'] = $verbose;
198
        return $this;
199
    }
200
201
    /**
202
     * @return UserInteractionHandler
203
     */
204
    public function getUserInteractionHandler()
205
    {
206
        return $this->userInteractionHandler;
207
    }
208
209
    /**
210
     * @return string
211
     */
212
    public function getVersion()
213
    {
214
        return static::VERSION;
215
    }
216
217
    /**
218
     * @return string
219
     */
220
    public function getSeedsDir()
221
    {
222
        return $this->seeds_dir;
223
    }
224
225
    /**
226
     * @param string $seeds_dir ~ local folder
227
     *
228
     * @return Blender
229
     */
230
    public function setSeedsDir($seeds_dir)
231
    {
232
        $this->seeds_dir = $seeds_dir;
233
        return $this;
234
    }
235
236
    /**
237
     * @param string|null $directory_key
238
     * @return string
239
     */
240
    public function getSeedsPath($directory_key = null)
241
    {
242
        $seed_path = $this->config['seeds_path'];
243
        if (!empty($directory_key)) {
244
            $seed_path .= trim($directory_key, '/').DIRECTORY_SEPARATOR;
245
        }
246
        return $seed_path;
247
    }
248
249
    /**
250
     * @param string|null $directory_key
251
     * @return string
252
     */
253
    public function getHistoryPath($directory_key = null)
254
    {
255
        $seed_path = $this->config['history_path'];
256
        if (!empty($directory_key)) {
257
            $seed_path .= trim($directory_key, '/').DIRECTORY_SEPARATOR;
258
        }
259
        return $seed_path;
260
    }
261
262
    /**
263
     * @return string
264
     */
265
    public function getMigrationPath()
266
    {
267
        return $this->config['migrations_path'];
268
    }
269
270
    /**
271
     * @return \Tagger
272
     */
273
    public function getTagger()
274
    {
275
        return $this->tagger;
276
    }
277
278
    /**
279
     * @param bool $refresh
280
     * @return array
281
     */
282
    public function getCategoryMap($refresh = false)
283
    {
284
        if (count($this->category_map) == 0 || $refresh) {
285
            $this->category_map = [
286
                'ids' => [],
287
                'names' => [],
288
                'lineage' => []
289
            ];
290
            $query = $this->modx->newQuery('modCategory');
291
            $query->sortBy('parent');
292
            $query->sortBy('rank');
293
            $categories = $this->modx->getCollection('modCategory', $query);
294
            foreach ($categories as $category) {
295
                $category_data = $category->toArray();
296
297
                $this->category_map['ids'][$category->get('id')] = $category_data;
298
299
                $key = trim($category->get('category'));
300
                // This is not unique!
301
                $this->category_map['names'][$key] = $category_data;
302
303
                // Get the lineage: Parent=>Child=>Grand Child as key
304
                $lineage = $key;
305
                if ($category_data['parent'] > 0 && isset($this->category_map['ids'][$category_data['parent']]) && isset($this->category_map['ids'][$category_data['parent']]['lineage'])) {
306
                    $lineage = $this->category_map['ids'][$category_data['parent']]['lineage'].'=>'.$key;
307
                } elseif ($category_data['parent'] > 0) {
308
                    //$this->out('DID NOT FIND PARENT?? '. print_r($category_data, true), true);
309
                }
310
311
                $this->category_map['ids'][$category->get('id')]['lineage'] = $lineage;
312
313
                $this->category_map['lineage'][$lineage] = $category->toArray();
314
            }
315
        }
316
        return $this->category_map;
317
    }
318
319
    /**
320
     * @param string $message
321
     * @param int $verbose
322
     * @param bool $error
323
     */
324
    public function out($message, $verbose=Blender::VERBOSITY_NORMAL, $error = false)
325
    {
326
        if ($this->getVerbose() >= $verbose) {
327
            if ($error) {
328
                $this->userInteractionHandler->tellUser($message, userInteractionHandler::MASSAGE_ERROR);
329
330
            } else {
331
                $this->userInteractionHandler->tellUser($message, userInteractionHandler::MASSAGE_STRING);
332
            }
333
        }
334
    }
335
336
    /**
337
     * @param string $message
338
     * @param int $verbose
339
     */
340
    public function outError($message, $verbose=Blender::VERBOSITY_NORMAL)
341
    {
342
        if ($this->getVerbose() >= $verbose) {
343
            $this->userInteractionHandler->tellUser($message, userInteractionHandler::MASSAGE_ERROR);
344
        }
345
    }
346
347
    /**
348
     * @param string $message
349
     * @param int $verbose
350
     */
351
    public function outSuccess($message, $verbose=Blender::VERBOSITY_NORMAL)
352
    {
353
        if ($this->getVerbose() >= $verbose) {
354
            $this->userInteractionHandler->tellUser($message, userInteractionHandler::MASSAGE_SUCCESS);
355
        }
356
    }
357
358
    /**
359
     * @param string $name
360
     * @param string $server_type
361
     * @param string|null $migration_path
362
     *
363
     * @return bool
364
     */
365
    public function createBlankMigrationClassFile($name, $server_type = 'master', $migration_path = null)
366
    {
367
        $migrationCreator = new MigrationsCreator($this->userInteractionHandler);
368
369
        if (empty($migration_path)) {
370
            $migration_path = $this->getMigrationPath();
371
        }
372
373
        $success = $migrationCreator
374
            ->setPathTimeStamp($this->getSeedsDir())
375
            ->setVerbose($this->getVerbose())
376
            ->setName($name)
377
            ->setDescription('')
378
            ->setServerType($server_type)
379
            ->setMigrationsPath($migration_path)
380
            ->createBlankMigrationClassFile();
381
382
        $this->logCreatedMigration($migrationCreator->getLogData());
383
        return $success;
384
    }
385
386
    /**
387
     * @return SeedMaker
388
     */
389
    public function getSeedMaker()
390
    {
391
        return new SeedMaker($this->modx, $this);
392
    }
393
394
    /**
395
     * @param string $method
396
     * @param bool $prompt
397
     * @throws MigratorException
398
     */
399
    /**
400
     * @param string $method
401
     * @param bool $prompt
402
     * @throws MigratorException
403
     */
404
    public function install($method = 'up', $prompt = false)
405
    {
406
        $config = $this->config;
407
408
        $config['migrations_path'] = __DIR__.'/database/migrations/';
409
410
        $blender = new Blender($this->modx, $this->getUserInteractionHandler(), $config);
411
        $blender->setProject('lci/blend');
412
413
        $migrator = new Migrator($blender, $this->modx, 'lci/blend');
414
        $migrator
415
            ->setDelayLogging(true)
416
            ->setCheckInstallLog($method == 'up' ? false : true)
417
            ->runMigration($method);
418
419
        // does the migration directory exist?
420
        if (!file_exists($this->getMigrationPath())) {
421
            $create = true;
422
            if ($this->getVerbose() >= Blender::VERBOSITY_NORMAL || $prompt) {
423
                $create = $this->getUserInteractionHandler()->promptConfirm('Create the following directory for migration files?'.PHP_EOL
424
                    .$this->getMigrationPath(), true);
425
            }
426
427
            if ($create) {
428
                mkdir($this->getMigrationPath(), 0700, true);
429
                $this->outSuccess('Created migration directory: '.$this->getMigrationPath());
430
            }
431
        }
432
433
        /** @var \LCI\Blend\Blendable\SystemSetting $systemSetting */
434
        $systemSetting = $this->getBlendableLoader()->getBlendableSystemSetting('blend.version');
435
        $systemSetting
436
            ->setSeedsDir($this->getSeedsDir())
437
            ->setFieldValue($this->getVersion())
438
            ->setFieldArea('Blend')
439
            ->blend(true);
440
441
        $this->modx->cacheManager->refresh();
442
    }
443
444
    /**
445
     * @throws MigratorException
446
     */
447
    public function uninstall()
448
    {
449
        $this->install('down');
450
    }
451
452
    /**
453
     * @param string $method
454
     * @throws MigratorException
455
     */
456
    public function update($method = 'up')
457
    {
458
        $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...
459
        $this->install($method);
460
    }
461
462
    /**
463
     * @return bool
464
     */
465
    public function requireUpdate()
466
    {
467
        $upgrade = false;
468
469
        $current_version = $this->modx->getOption('blend.version');
470
        //                                      FILE version,        DB Version
471
        if ($this->isBlendInstalledInModx() && (!$current_version || version_compare($this->getVersion(), $current_version) === 1)) {
472
            $this->outError('MODX System Setting Version: '. $current_version.' Code version: '.$this->getVersion(), Blender::VERBOSITY_DEBUG);
473
            $upgrade = true;
474
        }
475
476
        return $upgrade;
477
    }
478
479
    /**
480
     * @param bool $check_install_log
481
     * @return bool
482
     */
483
    public function isBlendInstalledInModx($check_install_log=true)
484
    {
485
        $installed = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $installed is dead and can be removed.
Loading history...
486
        try {
487
            $table = $this->modx->getTableName($this->blend_class_object);
488
            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...
489
                $this->out(__METHOD__.' table not found ', Blender::VERBOSITY_DEBUG);
490
                return false;
491
            }
492
            $installed = true;
493
494
        } catch (\Exception $exception) {
495
            $this->out(__METHOD__ . ' Exception: '. $exception->getMessage(), Blender::VERBOSITY_DEBUG);
496
            // We got an exception == table not found
497
            return false;
498
        }
499
500
        if ($check_install_log) {
501
            $installed = false;
502
            /** @var \xPDOQuery $query */
503
            $query = $this->modx->newQuery($this->blend_class_object);
504
            $query->select('id');
505
            $query->where([
506
                'name:IN' => ['InstallBlender', 'install_blender'],
507
                'status' => 'up_complete'
508
            ]);
509
            $query->sortBy('name');
510
511
            $installMigration = $this->modx->getObject($this->blend_class_object, $query);
512
            if ($installMigration instanceof \BlendMigrations || $installMigration instanceof \LCI\Blend\Model\xPDO\BlendMigrations) {
513
                $this->out(print_r($installMigration->toArray(), true), Blender::VERBOSITY_DEBUG);
514
                $installed = true;
515
            }
516
            $this->out(__METHOD__ . ' Blend install log not found', Blender::VERBOSITY_DEBUG);
517
        }
518
519
        return $installed;
520
    }
521
522
    /**
523
     * @param string $method
524
     * @param string $type
525
     * @param int $count
526
     * @param int $id
527
     * @param null|string $name
528
     * @throws MigratorException
529
     */
530
    public function runMigration($method = 'up', $type = 'master', $count = 0, $id = 0, $name = null)
531
    {
532
        /** @var Migrator $migrator */
533
        $migrator = new Migrator($this, $this->modx, $this->project);
534
        $migrator->runMigration($method, $type, $count, $id, $name);
535
    }
536
537
    /**
538
     * @param $type
539
     * @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...
540
     * @return string
541
     */
542
    public function getMigrationName($type, $name = null)
543
    {
544
        $format = new Format($this->seeds_dir);
545
        return $format->getMigrationName($type, $name);
546
    }
547
548
    /**
549
     * @param string $name
550
     * @param string $type ~ chunk, plugin, resource, snippet, systemSettings, template, site
551
     *
552
     * @return bool
553
     */
554
    public function removeMigrationFile($name, $type)
555
    {
556
        $class_name = $this->getMigrationName($type, $name);
557
558
        $removed = false;
559
        $migration_file = $this->getMigrationPath().$class_name.'.php';
560
        if (file_exists($migration_file)) {
561
            if (unlink($migration_file)) {
562
                $removed = true;
563
                $migration = $this->modx->getObject($this->blend_class_object, ['name' => $class_name]);
564
                if (is_object($migration) && $migration->remove()) {
565
                    $this->out($class_name.' migration has been removed from the blend_migrations table', Blender::VERBOSITY_VERY_VERBOSE);
566
567
                }
568
            } else {
569
                $this->outError($class_name.' migration has not been removed from the blend_migrations table');
570
            }
571
572
        } else {
573
            $this->outError($this->getMigrationPath().$class_name.'.php migration could not be found to remove');
574
        }
575
576
        return $removed;
577
    }
578
    /**
579
     * @param int $id
580
     *
581
     * @return bool|array
582
     */
583
    public function getResourceSeedKeyFromID($id)
584
    {
585
        if (!isset($this->resource_id_map[$id])) {
586
            $seed_key = $context = false;
587
            $resource = $this->modx->getObject('modResource', $id);
588
            if ($resource) {
589
                $context = $resource->get('context_key');
590
                if (!isset($this->resource_seek_key_map[$context])) {
591
                    $this->resource_seek_key_map[$context] = [];
592
                }
593
                $seed_key = $this->getSeedKeyFromAlias($resource->get('alias'));
594
                $this->resource_seek_key_map[$context][$seed_key] = $id;
595
            }
596
            $this->resource_id_map[$id] = [
597
                'context' => $context,
598
                'seed_key' => $seed_key
599
            ];
600
        }
601
602
        return $this->resource_id_map[$id];
603
    }
604
605
    /**
606
     * @param string $alias
607
     * @param string $context
608
     * @return bool|int
609
     */
610
    public function getResourceIDFromLocalAlias($alias, $context='web')
611
    {
612
        $seed_key = $this->getSeedKeyFromAlias($alias);
613
        return $this->getResourceIDFromSeedKey($seed_key, $context);
614
    }
615
616
    /**
617
     * @param string $seed_key
618
     * @param string $context
619
     *
620
     * @return bool|int
621
     */
622
    public function getResourceIDFromSeedKey($seed_key, $context = 'web')
623
    {
624
        if (!isset($this->resource_seek_key_map[$context])) {
625
            $this->resource_seek_key_map[$context] = [];
626
        }
627
        if (!isset($this->resource_seek_key_map[$context][$seed_key])) {
628
            $id = false;
629
            $alias = $this->getAliasFromSeedKey($seed_key);
630
            $resource = $this->modx->getObject('modResource', ['alias' => $alias, 'context_key' => $context]);
631
            if ($resource) {
632
                $id = $resource->get('id');
633
                $this->resource_seek_key_map[$context][$seed_key] = $id;
634
                $this->resource_id_map[$id] = [
635
                    'context' => $context,
636
                    'seed_key' => $seed_key
637
                ];
638
            }
639
            $this->resource_seek_key_map[$context][$seed_key] = $id;
640
        }
641
642
        return $this->resource_seek_key_map[$context][$seed_key];
643
    }
644
645
    /**
646
     * @param string $alias
647
     *
648
     * @return string
649
     */
650
    public function getSeedKeyFromAlias($alias)
651
    {
652
        return str_replace('/', '#', $alias);
653
    }
654
655
    /**
656
     * @param string $seed_key
657
     *
658
     * @return string
659
     */
660
    public function getAliasFromSeedKey($seed_key)
661
    {
662
        return str_replace('#', '/', $seed_key);
663
    }
664
665
    /**
666
     * @deprecated
667
     * @param string $name
668
     * @param string $type ~ template, template-variable, chunk, snippet or plugin
669
     * @return string
670
     */
671
    public function getElementSeedKeyFromName($name, $type)
672
    {
673
        return $type.'_'.$this->getSeedKeyFromName($name);
674
    }
675
676
    /**
677
     * @param string $name
678
     * @return string
679
     */
680
    public function getSeedKeyFromName($name)
681
    {
682
        return str_replace('/', '#', $name);
683
    }
684
685
    /**
686
     * @param string $seed_key
687
     * @return string
688
     */
689
    public function getNameFromSeedKey($seed_key)
690
    {
691
        return str_replace('#', '/', $seed_key);
692
    }
693
694
    /**
695
     * @param string $project
696
     * @return Blender
697
     */
698
    public function setProject(string $project): Blender
699
    {
700
        $this->project = $project;
701
        return $this;
702
    }
703
704
    /**
705
     * @param array $data
706
     */
707
    protected function logCreatedMigration(array $data)
708
    {
709
        $migrator = new Migrator($this, $this->modx, 'local');
710
        $migrator->logMigration($data);
711
    }
712
}
713