ad575854b63de7611e484eccc783de9bc9ccd5f2
[yaffs-website] / web / modules / contrib / drupalmoduleupgrader / src / Plugin / DMU / Indexer / Functions.php
1 <?php
2
3 namespace Drupal\drupalmoduleupgrader\Plugin\DMU\Indexer;
4
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;
15
16 /**
17  * @Indexer(
18  *  id = "function"
19  * )
20  */
21 class Functions extends IndexerBase implements IndexerExecutionInterface, IndexerUsageInterface {
22
23   protected function prepareID($id) {
24     return preg_replace('/^hook_/', $this->target->id() . '_', $id);
25   }
26
27   /**
28    * {@inheritdoc}
29    */
30   public function has($identifier) {
31     return parent::has($this->prepareID($identifier));
32   }
33
34   /**
35    * {@inheritdoc}
36    */
37   public function hasAny(array $identifiers) {
38     return parent::hasAny(array_map([ $this, 'prepareID' ], $identifiers));
39   }
40
41   /**
42    * {@inheritdoc}
43    */
44   public function hasAll(array $identifiers) {
45     return parent::hasAll(array_map([ $this, 'prepareID' ], $identifiers));
46   }
47
48   /**
49    * {@inheritdoc}
50    */
51   public function addFile($path) {
52     $doc = Parser::parseFile($path);
53
54     $doc
55       ->children(Filter::isInstanceOf('\Pharborist\Functions\FunctionDeclarationNode'))
56       ->each([ $this, 'add' ]);
57
58     $doc
59       ->find(Filter::isInstanceOf('\Pharborist\Functions\FunctionCallNode'))
60       ->each([ $this, 'add' ]);
61   }
62
63   /**
64    * {@inheritdoc}
65    */
66   public function add(NodeInterface $node) {
67     /** @var \Pharborist\Functions\FunctionDeclarationNode|\Pharborist\Functions\FunctionCallNode $node */
68     $fields = [
69       'id' => (string) $node->getName(),
70       'file' => $node->getFilename(),
71       'type' => get_class($node),
72     ];
73
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);
79     }
80
81     $this->db
82       ->insert($this->table)
83       ->fields($fields)
84       ->execute();
85   }
86
87   /**
88    * {@inheritdoc}
89    */
90   public function delete($id) {
91     parent::delete($this->prepareID($id));
92   }
93
94   /**
95    * {@inheritdoc}
96    */
97   public function get($identifier) {
98     $identifier = $this->prepareID($identifier);
99
100     $file = $this->getQuery(['file'])
101       ->condition('id', $identifier)
102       ->execute()
103       ->fetchField();
104
105     return $this->target
106       ->open($file)
107       ->children(Filter::isFunction($identifier))
108       ->get(0);
109   }
110
111   /**
112    * {@inheritdoc}
113    */
114   public function getMultiple(array $identifiers) {
115     return parent::getMultiple(array_map([ $this, 'prepareID' ], $identifiers));
116   }
117
118   /**
119    * {@inheritdoc}
120    */
121   public function getFields() {
122     $fields = parent::getFields();
123
124     $fields['type'] = array(
125       'type' => 'varchar',
126       'length' => 255,
127       'not null' => TRUE,
128     );
129     $fields['has_logic'] = array(
130       'type' => 'int',
131       'size' => 'tiny',
132       'unsigned' => TRUE,
133     );
134
135     return $fields;
136   }
137
138   /**
139    * {@inheritdoc}
140    */
141   public function getQuery(array $fields = []) {
142     return parent::getQuery($fields)->condition('type', 'Pharborist\Functions\FunctionDeclarationNode');
143   }
144
145   /**
146    * {@inheritdoc}
147    */
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)
153         ->countQuery()
154         ->execute()
155         ->fetchField();
156       return $ret;
157     }
158     else {
159       return FALSE;
160     }
161   }
162
163   /**
164    * {@inheritdoc}
165    */
166   public function execute($identifier, array $arguments = []) {
167     $function = $this->prepareID($identifier);
168
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);
173     }
174     else {
175       if ($this->hasExecutable($function)) {
176         eval($this->get($function)->get(0)->getText());
177         return $this->execute($function, $arguments);
178       }
179       else {
180         $variables = [
181           '@function' => $function,
182         ];
183         throw new \LogicException(SafeMarkup::format('Cowardly refusing to execute @function.', $variables));
184       }
185     }
186   }
187
188   /**
189    * {@inheritdoc}
190    */
191   public function getUsages($identifier) {
192     $function = $this->prepareID($identifier);
193
194     $files = $this->getQuery(['file'])
195       ->distinct()
196       ->condition('id', $function)
197       ->condition('type', 'Pharborist\Functions\FunctionCallNode')
198       ->execute()
199       ->fetchCol();
200
201     $usages = new NodeCollection();
202     foreach ($files as $file) {
203       $this->target
204         ->open($file)
205         ->find(Filter::isFunctionCall($function))
206         ->addTo($usages);
207     }
208
209     return $usages;
210   }
211
212 }