Further modules included.
[yaffs-website] / web / modules / contrib / drupalmoduleupgrader / src / Plugin / DMU / Rewriter / Generic.php
diff --git a/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/Generic.php b/web/modules/contrib/drupalmoduleupgrader/src/Plugin/DMU/Rewriter/Generic.php
new file mode 100644 (file)
index 0000000..e3f07b4
--- /dev/null
@@ -0,0 +1,306 @@
+<?php
+
+namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Rewriter;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\drupalmoduleupgrader\PluginBase;
+use Drupal\drupalmoduleupgrader\RewriterInterface;
+use Drupal\drupalmoduleupgrader\Utility\Filter\FieldValueFilter;
+use Drupal\drupalmoduleupgrader\Utility\Filter\NodeAssignmentFilter;
+use Pharborist\ArrayLookupNode;
+use Pharborist\Constants\ConstantNode;
+use Pharborist\ExpressionNode;
+use Pharborist\Filter;
+use Pharborist\Functions\CallNode;
+use Pharborist\Functions\EmptyNode;
+use Pharborist\Functions\IssetNode;
+use Pharborist\Functions\ParameterNode;
+use Pharborist\Node;
+use Pharborist\NodeCollection;
+use Pharborist\Objects\ClassConstantLookupNode;
+use Pharborist\Objects\ObjectMethodCallNode;
+use Pharborist\Objects\ObjectPropertyNode;
+use Pharborist\Operators\AssignNode;
+use Pharborist\Operators\BooleanNotNode;
+use Pharborist\Parser;
+use Pharborist\Types\StringNode;
+use Pharborist\Variables\VariableNode;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @Rewriter(
+ *  id = "_rewriter",
+ *  deriver = "\Drupal\drupalmoduleupgrader\Plugin\DMU\Rewriter\GenericDeriver"
+ * )
+ */
+class Generic extends PluginBase implements RewriterInterface {
+
+  /**
+   * @var \Drupal\drupalmoduleupgrader\Utility\Filter\NodeAssignmentFilter
+   */
+  protected $isAssigned;
+
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, TranslationInterface $translator, LoggerInterface $log) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $translator, $log);
+    $this->isAssigned = new NodeAssignmentFilter();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rewrite(ParameterNode $parameter) {
+    // Don't even try to rewrite the function if the parameter is reassigned.
+    if ($this->isReassigned($parameter)) {
+      $error = $this->t('@function() cannot be parametrically rewritten because @parameter is reassigned.', [
+        '@parameter' => $parameter->getName(),
+        '@function' => $parameter->getFunction()->getName()->getText(),
+      ]);
+      throw new \LogicException($error);
+    }
+
+    foreach ($this->getExpressions($parameter)->not($this->isAssigned) as $expr) {
+      $property = $this->getProperty($expr);
+      if (empty($property)) {
+        continue;
+      }
+
+      $getter = $this->rewriteAsGetter($expr, $property);
+      if ($getter) {
+        $empty = $expr->closest(Filter::isFunctionCall('empty', 'isset'));
+
+        // If the original expression was wrapped by a call to isset() or
+        // empty(), we need to replace it entirely.
+        if ($getter instanceof CallNode && $empty instanceof CallNode) {
+          // If the isset() or empty() call was negated, reverse that logic.
+          $parent = $empty->parent();
+          if ($parent instanceof BooleanNotNode) {
+            $parent->replaceWith($getter);
+          }
+          else {
+            $empty->replaceWith(BooleanNotNode::fromExpression($getter));
+          }
+        }
+        else {
+          $expr->replaceWith($getter);
+        }
+      }
+    }
+
+    foreach ($this->getExpressions($parameter)->filter($this->isAssigned) as $expr) {
+      // If the property cannot be determined, don't even try to rewrite the
+      // expression.
+      $property = $this->getProperty($expr);
+      if (empty($property)) {
+        continue;
+      }
+
+      $assignment = $expr->closest(Filter::isInstanceOf('\Pharborist\Operators\AssignNode'));
+
+      $setter = $this->rewriteAsSetter($expr, $property, $assignment);
+      if ($setter) {
+        $assignment->replaceWith($setter);
+      }
+    }
+
+    // Set the type hint, if one is defined.
+    if (isset($this->pluginDefinition['type_hint'])) {
+      $parameter->setTypeHint($this->pluginDefinition['type_hint']);
+
+      // If the type hint extends FieldableEntityInterface, rewrite any field
+      // lookups (e.g. $node->body[LANGUAGE_NONE][0]['value']).
+      if (in_array('Drupal\Core\Entity\FieldableEntityInterface', class_implements($this->pluginDefinition['type_hint']))) {
+        $filter = new FieldValueFilter($parameter->getName());
+
+        foreach ($parameter->getFunction()->find($filter) as $lookup) {
+          $lookup->replaceWith(self::rewriteFieldLookup($lookup));
+        }
+      }
+    }
+  }
+
+  /**
+   * Finds every rewritable expression in the function body.
+   *
+   * @param \Pharborist\Functions\ParameterNode $parameter
+   *  The parameter on which the rewrite is based.
+   *
+   * @return \Pharborist\NodeCollection
+   */
+  protected function getExpressions(ParameterNode $parameter) {
+    $filter = Filter::isInstanceOf('\Pharborist\ArrayLookupNode', '\Pharborist\Objects\ObjectPropertyNode');
+    $expressions = new NodeCollection();
+
+    $parameter
+      ->getFunction()
+      ->find(Filter::isInstanceOf('\Pharborist\Variables\VariableNode'))
+      ->filter(function(VariableNode $variable) use ($parameter) {
+        return $variable->getName() == $parameter->getName();
+      })
+      ->each(function(VariableNode $variable) use ($filter, $expressions) {
+        $root = $variable->furthest($filter);
+        if ($root) {
+          $expressions->add($root);
+        }
+      });
+
+    return $expressions;
+  }
+
+  /**
+   * Returns the property used by a rewritable expression, or NULL if the
+   * property cannot be determined.
+   *
+   * @param \Pharborist\ExpressionNode $expr
+   *  The rewritable expression.
+   *
+   * @return string|NULL
+   */
+  protected function getProperty(ExpressionNode $expr) {
+    if ($expr instanceof ObjectPropertyNode) {
+      return $expr->getPropertyName();
+    }
+    elseif ($expr instanceof ArrayLookupNode) {
+      $key = $expr->getKey(0);
+
+      if ($key instanceof StringNode) {
+        return $key->toValue();
+      }
+    }
+  }
+
+  /**
+   * Rewrites the given expression as a property getter. Returns NULL if the
+   * expression cannot be rewritten.
+   *
+   * @param \Pharborist\ExpressionNode $expr
+   *  The expression to rewrite.
+   * @param string $property
+   *  The property being used in the expression.
+   *
+   * @return \Pharborist\ExpressionNode|NULL
+   */
+  public function rewriteAsGetter(ExpressionNode $expr, $property) {
+    if ($expr instanceof ObjectPropertyNode) {
+      // Should be getRootObject() or getLookupRoot().
+      // @see Pharborist issue #191
+      $object = clone $expr->getObject();
+    }
+    elseif ($expr instanceof ArrayLookupNode) {
+      $object = clone $expr->getRootArray();
+    }
+
+    if (isset($object) && isset($this->pluginDefinition['properties'][$property]['get'])) {
+      return ObjectMethodCallNode::create($object, $this->pluginDefinition['properties'][$property]['get']);
+    }
+  }
+
+  /**
+   * Rewrites an assignment expression as a property setter. Returns NULL if
+   * the expression cannot be rewritten.
+   *
+   * @param \Pharborist\ExpressionNode $expr
+   *  The expression to rewrite.
+   * @param string $property
+   *  The property being used in the expression.
+   * @param \Pharborist\Operators\AssignNode $assignment
+   *  The entire assignment expression being rewritten.
+   *
+   * @return \Pharborist\ExpressionNode|NULL
+   */
+  public function rewriteAsSetter(ExpressionNode $expr, $property, AssignNode $assignment) {
+    if ($expr instanceof ObjectPropertyNode) {
+      // Should be getRootObject() or getLookupRoot().
+      // @see Pharborist issue #191
+      $object = clone $expr->getObject();
+    }
+    elseif ($expr instanceof ArrayLookupNode) {
+      $object = clone $expr->getRootArray();
+    }
+
+    if (isset($object) && isset($this->pluginDefinition['properties'][$property]['set'])) {
+      return ObjectMethodCallNode::create($object, $this->pluginDefinition['properties'][$property]['set'])
+        ->appendArgument(clone $assignment->getRightOperand());
+    }
+  }
+
+  /**
+   * Returns if the parameter is fully reassigned anywhere in the function.
+   *
+   * @param \Pharborist\Functions\ParameterNode $parameter
+   *  The parameter to check.
+   *
+   * @return boolean
+   */
+  protected function isReassigned(ParameterNode $parameter) {
+    return (boolean) $parameter
+      ->getFunction()
+      ->find(Filter::isInstanceOf('\Pharborist\Variables\VariableNode'))
+      ->filter(function(VariableNode $variable) use ($parameter) {
+        return $variable->getName() == $parameter->getName();
+      })
+      ->filter($this->isAssigned)
+      ->count();
+  }
+
+  /**
+   * Rewrites a Drupal 7 field lookup like so:
+   *
+   * $node->body[LANGUAGE_NONE][0]['value'] --> $node->body[0]->value
+   * $node->body['fr'][0]['value'] --> $node->getTranslation('fr')->body[0]->value
+   *
+   * @param \Pharborist\ArrayLookupNode $node
+   *  The original field lookup.
+   *
+   * @return \Pharborist\ExpressionNode
+   */
+  public static function rewriteFieldLookup(ArrayLookupNode $node) {
+    $keys = $node->getKeys();
+    /** @var \Pharborist\Objects\ObjectPropertyNode $root */
+    $root = $node->getRootArray();
+    $expr = $root->getObject()->getText();
+
+    if (self::isTranslation($keys[0])) {
+      $expr .= '->getTranslation(' . $keys[0] . ')';
+    }
+    $expr .= '->' . $root->getPropertyName() . '[' . $keys[1] . ']';
+
+    /** @var \Pharborist\Types\StringNode|\Pharborist\Node $column */
+    foreach (array_slice($keys, 2) as $column) {
+      $expr .= '->';
+      $expr .= $column instanceof StringNode ? $column->toValue() : $column->getText();
+    }
+
+    return Parser::parseExpression($expr);
+  }
+
+  /**
+   * Checks if a field lookup key is translated. This will be TRUE unless one
+   * of the following conditions applies:
+   *
+   * - The key is the Drupal\Core\Language\Language::LANGCODE_NOT_SPECIFIED
+   *   constant.
+   * - The key is the LANGUAGE_NONE constant from Drupal 7.
+   * - The key is the string 'und'.
+   *
+   * @param Node $key
+   *  The key to check.
+   *
+   * @return boolean
+   */
+  public static function isTranslation(Node $key) {
+    if ($key instanceof ClassConstantLookupNode) {
+      $constant = $key->getClassName() . '::' . $key->getConstantName();
+      return $constant != '\Drupal\Core\Language\Language::LANGCODE_NOT_SPECIFIED';
+    }
+    elseif ($key instanceof ConstantNode) {
+      return $key->getConstantName() != 'LANGUAGE_NONE';
+    }
+    elseif ($key instanceof StringNode) {
+      return $key->toValue() != 'und';
+    }
+    else {
+      return TRUE;
+    }
+  }
+
+}