6116474d85744b0ca0640da9d9b557f94922f538
[yaffs-website] / vendor / drupal / console-core / src / Utils / ChainDiscovery.php
1 <?php
2 /**
3  * @file
4  * Contains Drupal\Console\Core\Utils\Site.
5  */
6
7 namespace Drupal\Console\Core\Utils;
8
9 use Symfony\Component\Finder\Finder;
10 use Symfony\Component\Yaml\Yaml;
11
12 /**
13  * Class ChainDiscovery
14  *
15  * @package Drupal\Console\Core\Utils
16  */
17 class ChainDiscovery
18 {
19     /**
20      * @var string
21      */
22     protected $appRoot;
23
24     /**
25      * @var ConfigurationManager
26      */
27     protected $configurationManager;
28
29     /**
30      * @var MessageManager
31      */
32     protected $messageManager;
33
34     /**
35      * @var TranslatorManagerInterface
36      */
37     protected $translatorManager;
38
39     /**
40      * @var array
41      */
42     private $directories = [];
43
44     /**
45      * @var array
46      */
47     private $files = [];
48
49     /**
50      * @var array
51      */
52     private $filesPerDirectory = [];
53
54     const INLINE_REGEX = '/{{(.*?)}}/';
55     const ENV_REGEX =  '/%env\((.*?)\)%/';
56
57     /**
58      * @var array
59      */
60     private $inlineRegexLegacy = [
61         '/%{{(.*?)}}/',
62         '/%{{ (.*?) }}/',
63     ];
64
65     /**
66      * @var array
67      */
68     private $envRegexLegacy = [
69         '/\${{(.*?)}}/',
70         '/\${{ (.*?) }}/',
71         '/%env\((.*?)\)%/',
72         '/% env\((.*?)\) %/'
73     ];
74
75     /**
76      * ChainDiscovery constructor.
77      *
78      * @param string                     $appRoot
79      * @param ConfigurationManager       $configurationManager
80      * @param MessageManager             $messageManager
81      * @param TranslatorManagerInterface $translatorManager
82      */
83     public function __construct(
84         $appRoot,
85         ConfigurationManager $configurationManager,
86         MessageManager $messageManager,
87         TranslatorManagerInterface $translatorManager
88     ) {
89         $this->appRoot = $appRoot;
90         $this->configurationManager = $configurationManager;
91         $this->messageManager = $messageManager;
92         $this->translatorManager = $translatorManager;
93
94         $directories = array_map(
95             function ($item) {
96                 return $item . 'chain/';
97             },
98             $configurationManager->getConfigurationDirectories(true)
99         );
100
101         $this->addDirectories($directories);
102     }
103
104     /**
105      * @param array $directories
106      */
107     public function addDirectories(array $directories)
108     {
109         $this->directories = array_merge($this->directories, $directories);
110     }
111
112     /**
113      * @deprecated
114      *
115      * @return array
116      */
117     public function getChainFiles()
118     {
119         return $this->getFiles();
120     }
121
122     /**
123      * @return array
124      */
125     public function getFiles()
126     {
127         if ($this->files) {
128             return $this->files;
129         }
130
131         foreach ($this->directories as $directory) {
132             if (!is_dir($directory)) {
133                 continue;
134             }
135             $finder = new Finder();
136             $finder->files()
137                 ->name('*.yml')
138                 ->in($directory);
139             foreach ($finder as $file) {
140
141                 $filePath = $file->getRealPath();
142                 if (empty($filePath)) {
143                     $filePath = $directory . $file->getBasename();
144                 }
145
146                 if (!is_file($filePath)) {
147                     continue;
148                 }
149                 $this->files[$filePath] = [
150                     'directory' => $directory,
151                     'file_name' => $file->getBasename(),
152                     'messages' => []
153                 ];
154
155                 $this->getFileContents($filePath);
156                 $this->getFileMetadata($filePath);
157
158                 if ($this->files[$filePath]['messages']) {
159                     $this->messageManager->comment(
160                         $filePath,
161                         0,
162                         'list'
163                     );
164                     $this->messageManager->listing(
165                         $this->files[$filePath]['messages'],
166                         0,
167                         'list'
168                     );
169                 }
170
171                 $this->filesPerDirectory[$directory][] = $file->getBasename();
172             }
173         }
174
175         return $this->files;
176     }
177
178     /**
179      * @return array
180      */
181     public function getFilesPerDirectory()
182     {
183         return $this->filesPerDirectory;
184     }
185
186     /**
187      * @return array
188      */
189     public function getChainCommands()
190     {
191         $chainCommands = [];
192         $files = array_keys($this->getFiles());
193         foreach ($files as $file) {
194             $chainMetadata = $this->getFileMetadata($file);
195
196             if (!$chainMetadata) {
197                 continue;
198             }
199
200             $name = $chainMetadata['command']['name'];
201             $description = $chainMetadata['command']['description'];
202
203             $chainCommands[$name] = [
204                 'description' => $description,
205                 'file' => $file,
206             ];
207
208             $this->files[$file]['command'] = $name;
209             $this->files[$file]['description'] = $description;
210         }
211
212         return $chainCommands;
213     }
214
215     public function parseContent($file, $placeholders)
216     {
217         $placeholders = array_filter(
218             $placeholders,
219             function ($element) {
220                 return $element !== null;
221             }
222         );
223
224         unset($placeholders['file']);
225         unset($placeholders['placeholder']);
226
227         $contents = $this->getFileContents($file);
228
229         $loader = new \Twig_Loader_Array(
230             [
231                 'chain' => $contents,
232             ]
233         );
234
235         $twig = new \Twig_Environment($loader);
236         $envFunction = new \Twig_SimpleFunction(
237             'env',
238             function ($variableName) {
239                 $variableValue = getenv($variableName);
240                 if (!empty($variableValue)) {
241                     return $variableValue;
242                 }
243
244                 return '%env('.$variableName.')%';
245             }
246         );
247         $twig->addFunction($envFunction);
248
249         $variables = $this->extractInlinePlaceHolderNames($contents);
250
251         foreach ($variables as $variable) {
252             if (!array_key_exists($variable, $placeholders)) {
253                 $placeholders[$variable] = '{{ ' . $variable . ' }}';
254             }
255         }
256
257         return $twig->render('chain', $placeholders);
258     }
259
260     public function getFileMetadata($file)
261     {
262         if ($metadata = $this->getCacheMetadata($file)) {
263             return $metadata;
264         }
265
266         $contents = $this->getFileContents($file);
267
268         $line = strtok($contents, PHP_EOL);
269         $index = 0;
270         while ($line !== false) {
271             $index++;
272
273             if ($index === 1 && $line !== 'command:') {
274                 break;
275             }
276
277             if ($index > 1 && substr($line, 0, 2) !== "  ") {
278                 break;
279             }
280
281             $metadata .= $line . PHP_EOL;
282             $line = strtok(PHP_EOL);
283         }
284
285         $chainMetadata = $this->processMetadata($metadata);
286
287         if (!$chainMetadata) {
288             $this->files[$file]['messages'][] = $this->translatorManager
289                 ->trans('commands.chain.messages.metadata-registration');
290             return [];
291         }
292
293         $this->files[$file]['metadata'] = $chainMetadata;
294
295         return $chainMetadata;
296     }
297
298     private function processMetadata($metadata) {
299         if (!$metadata) {
300             return [];
301         }
302
303         $chainMetadata = Yaml::parse($metadata);
304
305         if (!$chainMetadata || !is_array($chainMetadata)) {
306             return [];
307         }
308
309         if (!array_key_exists('command', $chainMetadata) || !is_array($chainMetadata['command'])) {
310             return [];
311         }
312
313         if (!array_key_exists('name', $chainMetadata['command'])) {
314             return [];
315         }
316
317         if (!array_key_exists('description', $chainMetadata['command'])) {
318             $chainMetadata['command']['description']  = '';
319         }
320
321         return $chainMetadata;
322     }
323
324     /**
325      * Helper to load and clean up the chain file.
326      *
327      * @param string $file The file name
328      *
329      * @return string $contents The contents of the file
330      */
331     public function getFileContents($file)
332     {
333         if (empty($file)) {
334             return '';
335         }
336
337         if ($contents = $this->getCacheContent($file)) {
338             return $contents;
339         }
340
341         $contents = file_get_contents($file);
342
343         // Support BC for legacy inline variables.
344         $inlineLegacyContent = preg_replace(
345             $this->inlineRegexLegacy,
346             '{{ $1 }}',
347             $contents
348         );
349
350         if ($contents !== $inlineLegacyContent) {
351             $this->files[$file]['messages'][] = $this->translatorManager
352                 ->trans('commands.chain.messages.legacy-inline');
353             $contents = $inlineLegacyContent;
354         }
355
356         // Support BC for legacy environment variables.
357         $envLegacyContent = preg_replace(
358             $this->envRegexLegacy,
359             '{{ env("$1") }}',
360             $contents
361         );
362
363         if ($contents !== $envLegacyContent) {
364             $this->files[$file]['messages'][] = $this->translatorManager
365                 ->trans('commands.chain.messages.legacy-environment');
366             $contents = $envLegacyContent;
367         }
368
369         // Remove lines with comments.
370         $contents = preg_replace(
371             '![ \t]*#.*[ \t]*[\r|\r\n|\n]!',
372             PHP_EOL,
373             $contents
374         );
375
376         //  Strip blank lines
377         $contents = preg_replace(
378             "/(^[\r\n]*|[\r\n]+)[\t]*[\r\n]+/",
379             PHP_EOL,
380             $contents
381         );
382
383         $this->files[$file]['content'] = $contents;
384
385         return $contents;
386     }
387
388     private function getCacheContent($file)
389     {
390         if (!array_key_exists($file, $this->files)) {
391             return null;
392         }
393
394         if (!array_key_exists('content', $this->files[$file])) {
395             return null;
396         }
397
398         return $this->files[$file]['content'];
399     }
400
401     private function getCacheMetadata($file)
402     {
403         if (!array_key_exists($file, $this->files)) {
404             return null;
405         }
406
407         if (!array_key_exists('metadata', $this->files[$file])) {
408             return null;
409         }
410
411         return $this->files[$file]['metadata'];
412     }
413
414     private function extractPlaceHolders(
415         $chainContent,
416         $regex
417     ) {
418         $placeHoldersExtracted = [];
419         preg_match_all(
420             $regex,
421             $chainContent,
422             $placeHoldersExtracted
423         );
424
425         if (!$placeHoldersExtracted) {
426             return [];
427         }
428
429         return array_unique($placeHoldersExtracted[1]);
430     }
431
432     public function extractInlinePlaceHolderNames($content)
433     {
434         preg_match_all($this::INLINE_REGEX, $content, $matches);
435
436         return array_map(
437             function ($item) {
438                 return trim($item);
439             },
440             $matches[1]
441         );
442     }
443
444     public function extractInlinePlaceHolders($chainContent)
445     {
446         $extractedInlinePlaceHolders = $this->extractPlaceHolders(
447             $chainContent,
448             $this::INLINE_REGEX
449         );
450         $extractedVars = $this->extractVars($chainContent);
451
452         $inlinePlaceHolders = [];
453         foreach ($extractedInlinePlaceHolders as $key => $inlinePlaceHolder) {
454             $inlinePlaceHolder = trim($inlinePlaceHolder);
455             $placeholderValue = null;
456             if (array_key_exists($inlinePlaceHolder, $extractedVars)) {
457                 $placeholderValue = $extractedVars[$inlinePlaceHolder];
458             }
459             $inlinePlaceHolders[$inlinePlaceHolder] = $placeholderValue;
460         }
461
462         return $inlinePlaceHolders;
463     }
464
465     public function extractEnvironmentPlaceHolders($chainContent)
466     {
467         return $this->extractPlaceHolders($chainContent, $this::ENV_REGEX);
468     }
469
470     public function extractVars($chainContent)
471     {
472         $chain = Yaml::parse($chainContent);
473         if (!array_key_exists('vars', $chain)) {
474             return [];
475         }
476
477         return $chain['vars'];
478     }
479 }