3 namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer;
5 use Drupal\Component\Utility\SafeMarkup;
6 use Drupal\drupalmoduleupgrader\IndexerBase;
7 use Drupal\drupalmoduleupgrader\IndexerExecutionInterface;
8 use Drupal\drupalmoduleupgrader\IndexerUsageInterface;
9 use Drupal\drupalmoduleupgrader\Utility\Filter\ContainsLogicFilter;
10 use Pharborist\Filter;
11 use Pharborist\Functions\FunctionDeclarationNode;
12 use Pharborist\NodeCollection;
13 use Pharborist\NodeInterface;
14 use Pharborist\Parser;
21 class Functions extends IndexerBase implements IndexerExecutionInterface, IndexerUsageInterface {
23 protected function prepareID($id) {
24 return preg_replace('/^hook_/', $this->target->id() . '_', $id);
30 public function has($identifier) {
31 return parent::has($this->prepareID($identifier));
37 public function hasAny(array $identifiers) {
38 return parent::hasAny(array_map([ $this, 'prepareID' ], $identifiers));
44 public function hasAll(array $identifiers) {
45 return parent::hasAll(array_map([ $this, 'prepareID' ], $identifiers));
51 public function addFile($path) {
52 $doc = Parser::parseFile($path);
55 ->children(Filter::isInstanceOf('\Pharborist\Functions\FunctionDeclarationNode'))
56 ->each([ $this, 'add' ]);
59 ->find(Filter::isInstanceOf('\Pharborist\Functions\FunctionCallNode'))
60 ->each([ $this, 'add' ]);
66 public function add(NodeInterface $node) {
67 /** @var \Pharborist\Functions\FunctionDeclarationNode|\Pharborist\Functions\FunctionCallNode $node */
69 'id' => (string) $node->getName(),
70 'file' => $node->getFilename(),
71 'type' => get_class($node),
74 if ($node instanceof FunctionDeclarationNode) {
75 $logical = new ContainsLogicFilter();
76 $logical->whitelist('t');
77 $logical->whitelist('drupal_get_path');
78 $fields['has_logic'] = (int) $node->is($logical);
82 ->insert($this->table)
90 public function delete($id) {
91 parent::delete($this->prepareID($id));
97 public function get($identifier) {
98 $identifier = $this->prepareID($identifier);
100 $file = $this->getQuery(['file'])
101 ->condition('id', $identifier)
107 ->children(Filter::isFunction($identifier))
114 public function getMultiple(array $identifiers) {
115 return parent::getMultiple(array_map([ $this, 'prepareID' ], $identifiers));
121 public function getFields() {
122 $fields = parent::getFields();
124 $fields['type'] = array(
129 $fields['has_logic'] = array(
141 public function getQuery(array $fields = []) {
142 return parent::getQuery($fields)->condition('type', 'Pharborist\Functions\FunctionDeclarationNode');
148 public function hasExecutable($identifier) {
149 if ($this->has($identifier)) {
150 $ret = $this->getQuery()
151 ->condition('id', $this->prepareID($identifier))
152 ->condition('has_logic', 0)
166 public function execute($identifier, array $arguments = []) {
167 $function = $this->prepareID($identifier);
169 // If the function already exists, we can safely assume that it's already
170 // been scanned for dangerous logic and evaluated into existence.
171 if (function_exists($function)) {
172 return call_user_func_array($function, $arguments);
175 if ($this->hasExecutable($function)) {
176 eval($this->get($function)->get(0)->getText());
177 return $this->execute($function, $arguments);
181 '@function' => $function,
183 throw new \LogicException(SafeMarkup::format('Cowardly refusing to execute @function.', $variables));
191 public function getUsages($identifier) {
192 $function = $this->prepareID($identifier);
194 $files = $this->getQuery(['file'])
196 ->condition('id', $function)
197 ->condition('type', 'Pharborist\Functions\FunctionCallNode')
201 $usages = new NodeCollection();
202 foreach ($files as $file) {
205 ->find(Filter::isFunctionCall($function))