Further modules included.
[yaffs-website] / web / modules / contrib / drupalmoduleupgrader / src / ConverterBase.php
1 <?php
2
3 namespace Drupal\drupalmoduleupgrader;
4
5 use Drupal\Component\Serialization\Yaml;
6 use Drupal\drupalmoduleupgrader\Utility\Filter\ContainsLogicFilter;
7 use Drupal\drupalmoduleupgrader\Utility\Filter\FunctionCallArgumentFilter;
8 use Pharborist\DocCommentNode;
9 use Pharborist\Filter;
10 use Pharborist\Functions\FunctionCallNode;
11 use Pharborist\Functions\FunctionDeclarationNode;
12 use Pharborist\Functions\ParameterNode;
13 use Pharborist\LineCommentBlockNode;
14 use Pharborist\Objects\ClassNode;
15 use Pharborist\Parser;
16 use Pharborist\Variables\VariableNode;
17 use Pharborist\WhitespaceNode;
18 use Symfony\Component\Filesystem\Filesystem;
19
20 /**
21  * Base class for converters.
22  */
23 abstract class ConverterBase extends PluginBase implements ConverterInterface {
24
25   // Used by buildFixMe() to determine the comment style of the generated
26   // FIXME notice.
27   const LINE_COMMENT = '//';
28   const DOC_COMMENT = '/**/';
29
30   /**
31    * {@inheritdoc}
32    */
33   public function isExecutable(TargetInterface $target) {
34     // If the plugin applies to particular hook(s), only return TRUE if the
35     // target module implements any of the hooks. Otherwise, return TRUE
36     // unconditionally.
37     if (isset($this->pluginDefinition['hook'])) {
38       return (boolean) array_filter((array) $this->pluginDefinition['hook'], [ $target->getIndexer('function'), 'has' ]);
39     }
40     else {
41       return TRUE;
42     }
43   }
44
45   /**
46    * Executes the target module's implementation of the specified hook, and
47    * returns the result.
48    *
49    * @return mixed
50    *
51    * @throws \LogicException if the target module doesn't implement the
52    * specified hook, or if the implementation contains logic.
53    *
54    * @deprecated
55    */
56   protected function executeHook(TargetInterface $target, $hook) {
57     $indexer = $target->getIndexer('function');
58
59     if ($indexer->has($hook)) {
60       // Configure the ContainsLogicFilter so that certain "safe" functions
61       // will pass it.
62       $has_logic = new ContainsLogicFilter();
63       $has_logic->whitelist('t');
64       $has_logic->whitelist('drupal_get_path');
65
66       $function = $indexer->get($hook);
67       if ($function->is($has_logic)) {
68         throw new \LogicException('{target}_{hook} cannot be executed because it contains logic.');
69       }
70       else {
71         $function_name = $function->getName()->getText();
72         if (! function_exists($function_name)) {
73           eval($function->getText());
74         }
75         return call_user_func($function_name);
76       }
77     }
78     else {
79       throw new \LogicException('{target} does not implement hook_{hook}.');
80     }
81   }
82
83   /**
84    * Creates an empty implementation of a hook.
85    *
86    * @param TargetInterface $target
87    *  The target module.
88    * @param string $hook
89    *  The hook to implement, without the hook_ prefix.
90    *
91    * @return \Pharborist\Functions\FunctionDeclarationNode
92    *  The hook implementation, appended to the main module file.
93    */
94   protected function implement(TargetInterface $target, $hook) {
95     $function = FunctionDeclarationNode::create($target->id() . '_' . $hook);
96     $function->setDocComment(DocCommentNode::create('Implements hook_' . $hook . '().'));
97
98     $module_file = $target->getPath('.module');
99     $target->open($module_file)->append($function);
100
101     WhitespaceNode::create("\n")->insertBefore($function);
102     WhitespaceNode::create("\n")->insertAfter($function);
103
104     return $function;
105   }
106
107   /**
108    * Writes a file to the target module's directory.
109    *
110    * @param TargetInterface $target
111    *  The target module.
112    * @param string $path
113    *  The path of the file to write, relative to the module root.
114    * @param string $data
115    *  The file contents.
116    *
117    * @return string
118    *  The path of the file, including the target's base path.
119    */
120   public function write(TargetInterface $target, $path, $data) {
121     static $fs;
122     if (empty($fs)) {
123       $fs = new Filesystem();
124     }
125
126     $destination_path = $target->getPath($path);
127     $fs->dumpFile($destination_path, (string) $data);
128
129     return $destination_path;
130   }
131
132   /**
133    * Writes a class to the target module's PSR-4 root.
134    *
135    * @param TargetInterface $target
136    *  The target module.
137    * @param ClassNode $class
138    *  The class to write. The path will be determined from the class'
139    *  fully qualified name.
140    *
141    * @return string
142    *  The generated path to the class.
143    */
144   public function writeClass(TargetInterface $target, ClassNode $class) {
145     $class_path = ltrim($class->getName()->getAbsolutePath(), '\\');
146     $path = str_replace([ 'Drupal\\' . $target->id(), '\\', ], [ 'src', '/' ], $class_path) . '.php';
147
148     return $this->write($target, $path, $class->parents()->get(0));
149   }
150
151   /**
152    * Writes out arbitrary data in YAML format.
153    *
154    * @param TargetInterface $target
155    *  The target module.
156    * @param string $group
157    *  The name of the YAML file. It will be prefixed with the module's machine
158    *  name and suffixed with .yml. For example, a group value of 'routing'
159    *  will write MODULE.routing.yml.
160    * @param array $data
161    *  The data to write.
162    *
163    * @todo This should be writeYAML, not writeInfo.
164    */
165   protected function writeInfo(TargetInterface $target, $group, array $data) {
166     $destination = $target->getPath('.' . $group . '.yml');
167     file_put_contents($destination, Yaml::encode($data));
168   }
169
170   /**
171    * Writes a service definition to the target module's services.yml file.
172    *
173    * @param TargetInterface $target
174    *  The target module.
175    * @param string $service_id
176    *  The service ID. If an existing one with the same ID already exists,
177    *  it will be overwritten.
178    * @param array $service_definition
179    */
180   protected function writeService(TargetInterface $target, $service_id, array $service_definition) {
181     $services = $target->getServices();
182     $services->set($service_id, $service_definition);
183     $this->writeInfo($target, 'services', [ 'services' => $services->toArray() ]);
184   }
185
186   /**
187    * Parses a generated class into a syntax tree.
188    *
189    * @param string|array $class
190    *  The class to parse, either as a string of PHP code or a renderable array.
191    *
192    * @return \Pharborist\Objects\ClassNode
193    */
194   protected function parse($class) {
195     if (is_array($class)) {
196       $class = \Drupal::service('renderer')->renderPlain($class);
197     }
198     return Parser::parseSnippet($class)->find(Filter::isInstanceOf('Pharborist\Objects\ClassNode'))[0];
199   }
200
201   /**
202    * Builds a FIXME notice using either the text in the plugin definition,
203    * or passed-in text.
204    *
205    * @param string|NULL $text
206    *  The FIXME notice's text, with variable placeholders and no translation.
207    * @param array $variables
208    *  Optional variables to use in translation. If empty, the FIXME will not
209    *  be translated.
210    * @param string|NULL $style
211    *  The comment style. Returns a LineCommentBlockNode if this is set to
212    *  self::LINE_COMMENT, a DocCommentNode if self::DOC_COMMENT, or the FIXME
213    *  as a string if set to anything else.
214    *
215    * @return mixed
216    */
217   protected function buildFixMe($text = NULL, array $variables = [], $style = self::LINE_COMMENT) {
218     $fixMe = "@FIXME\n" . ($text ?: $this->pluginDefinition['fixme']);
219
220     if (isset($this->pluginDefinition['documentation'])) {
221       $fixMe .= "\n";
222       foreach ($this->pluginDefinition['documentation'] as $doc) {
223         $fixMe .= "\n@see ";
224         $fixMe .= (isset($doc['url']) ? $doc['url'] : (string) $doc);
225       }
226     }
227
228     if ($variables) {
229       $fixMe = $this->t($fixMe, $variables);
230     }
231
232     switch ($style) {
233       case self::LINE_COMMENT:
234         return LineCommentBlockNode::create($fixMe);
235
236       case self::DOC_COMMENT:
237         return DocCommentNode::create($fixMe);
238
239       default:
240         return $fixMe;
241     }
242   }
243
244   /**
245    * Parametrically rewrites a function.
246    *
247    * @param \Drupal\drupalmoduleupgrader\RewriterInterface $rewriter
248    *  A fully configured parametric rewriter.
249    * @param \Pharborist\Functions\ParameterNode $parameter
250    *  The parameter upon which to base the rewrite.
251    * @param TargetInterface $target
252    *  The target module.
253    * @param boolean $recursive
254    *  If TRUE, rewriting will recurse into called functions which are passed
255    *  the rewritten parameter as an argument.
256    */
257   protected function rewriteFunction(RewriterInterface $rewriter, ParameterNode $parameter, TargetInterface $target, $recursive = TRUE) {
258     $rewriter->rewrite($parameter);
259     $target->save($parameter);
260
261     // Find function calls within the rewritten function which are called
262     // with the rewritten parameter.
263     $indexer = $target->getIndexer('function');
264     $next = $parameter
265       ->getFunction()
266       ->find(new FunctionCallArgumentFilter($parameter->getName()))
267       ->filter(function(FunctionCallNode $call) use ($indexer) {
268         return $indexer->has($call->getName()->getText());
269       });
270
271     /** @var \Pharborist\Functions\FunctionCallNode $call */
272     foreach ($next as $call) {
273       /** @var \Pharborist\Functions\FunctionDeclarationNode $function */
274       $function = $indexer->get($call->getName()->getText());
275
276       foreach ($call->getArguments() as $index => $argument) {
277         if ($argument instanceof VariableNode && $argument->getName() == $parameter->getName()) {
278           $this->rewriteFunction($rewriter, $function->getParameterAtIndex($index), $target, $recursive);
279           break;
280         }
281       }
282     }
283   }
284
285 }