0aa6aa3b679eee1571b849aa362eaae20b47dc9d
[yaffs-website] / web / core / modules / views / src / Plugin / views / join / JoinPluginBase.php
1 <?php
2
3 namespace Drupal\views\Plugin\views\join;
4
5 use Drupal\Core\Plugin\PluginBase;
6
7 /**
8  * @defgroup views_join_handlers Views join handler plugins
9  * @{
10  * Handler plugins for Views table joins.
11  *
12  * Handler plugins help build the view query object. Join handler plugins
13  * handle table joins.
14  *
15  * Views join handlers extend \Drupal\views\Plugin\views\join\JoinPluginBase.
16  * They must be annotated with \Drupal\views\Annotation\ViewsJoin annotation,
17  * and they must be in namespace directory Plugin\views\join.
18  *
19  * Here are some examples of configuration for the join plugins.
20  *
21  * For this SQL:
22  * @code
23  * LEFT JOIN {two} ON one.field_a = two.field_b
24  * @endcode
25  * Use this configuration:
26  * @code
27  * $configuration = array(
28  *   'table' => 'two',
29  *   'field' => 'field_b',
30  *   'left_table' => 'one',
31  *   'left_field' => 'field_a',
32  *   'operator' => '=',
33  * );
34  * $join = Views::pluginManager('join')->createInstance('standard', $configuration);
35  * @endcode
36  * Note that the default join type is a LEFT join when 'type' is not supplied in
37  * the join plugin configuration.
38  *
39  * For this SQL:
40  * @code
41  * INNER JOIN {two} ON one.field_a = two.field_b AND one.field_c = 'some_val'
42  * @endcode
43  * Use this configuration:
44  * @code
45  * $configuration = array(
46  *   'type' => 'INNER',
47  *   'table' => 'two',
48  *   'field' => 'field_b',
49  *   'left_table' => 'one',
50  *   'left_field' => 'field_a',
51  *   'operator' => '=',
52  *   'extra' => array(
53  *     0 => array(
54  *       'left_field' => 'field_c',
55  *       'value' => 'some_val',
56  *     ),
57  *   ),
58  * );
59  * $join = Views::pluginManager('join')->createInstance('standard', $configuration);
60  * @endcode
61  *
62  * For this SQL:
63  * @code
64  * INNER JOIN {two} ON one.field_a = two.field_b AND two.field_d = 'other_val'
65  * @endcode
66  * Use this configuration:
67  * @code
68  * $configuration = array(
69  *   'type' => 'INNER',
70  *   'table' => 'two',
71  *   'field' => 'field_b',
72  *   'left_table' => 'one',
73  *   'left_field' => 'field_a',
74  *   'operator' => '=',
75  *   'extra' => array(
76  *     0 => array(
77  *       'field' => 'field_d',
78  *       'value' => 'other_val',
79  *     ),
80  *   ),
81  * );
82  * $join = Views::pluginManager('join')->createInstance('standard', $configuration);
83  * @endcode
84  *
85  * For this SQL:
86  * @code
87  * INNER JOIN {two} ON one.field_a = two.field_b AND one.field_c = two.field_d
88  * @endcode
89  * Use this configuration:
90  * @code
91  * $configuration = array(
92  *   'type' => 'INNER',
93  *   'table' => 'two',
94  *   'field' => 'field_b',
95  *   'left_table' => 'one',
96  *   'left_field' => 'field_a',
97  *   'operator' => '=',
98  *   'extra' => array(
99  *     0 => array(
100  *       'left_field' => 'field_c',
101  *       'field' => 'field_d',
102  *     ),
103  *   ),
104  * );
105  * $join = Views::pluginManager('join')->createInstance('standard', $configuration);
106  * @endcode
107  *
108  * Here is an example of a more complex join:
109  * @code
110  * class JoinComplex extends JoinPluginBase {
111  *   public function buildJoin($select_query, $table, $view_query) {
112  *     // Add an additional hardcoded condition to the query.
113  *     $this->extra = 'foo.bar = baz.boing';
114  *     parent::buildJoin($select_query, $table, $view_query);
115  *   }
116  * }
117  * @endcode
118  *
119  * @ingroup views_plugins
120  * @see plugin_api
121  */
122
123 /**
124  * Represents a join and creates the SQL necessary to implement the join.
125  *
126  * Extensions of this class can be used to create more interesting joins.
127  */
128 class JoinPluginBase extends PluginBase implements JoinPluginInterface {
129
130   /**
131    * The table to join (right table).
132    *
133    * @var string
134    */
135   public $table;
136
137   /**
138    * The field to join on (right field).
139    *
140    * @var string
141    */
142   public $field;
143
144   /**
145    * The table we join to.
146    *
147    * @var string
148    */
149   public $leftTable;
150
151   /**
152    * The field we join to.
153    *
154    * @var string
155    */
156   public $leftField;
157
158   /**
159    * An array of extra conditions on the join.
160    *
161    * Each condition is either a string that's directly added, or an array of
162    * items:
163    *   - table(optional): If not set, current table; if NULL, no table. If you
164    *     specify a table in cached configuration, Views will try to load from an
165    *     existing alias. If you use realtime joins, it works better.
166    *   - field(optional): Field or formula. In formulas we can reference the
167    *     right table by using %alias.
168    *   - left_field(optional): Field or formula. In formulas we can reference
169    *     the left table by using %alias.
170    *   - operator(optional): The operator used, Defaults to "=".
171    *   - value: Must be set. If an array, operator will be defaulted to IN.
172    *   - numeric: If true, the value will not be surrounded in quotes.
173    *
174    * @see SelectQueryInterface::addJoin()
175    *
176    * @var array
177    */
178   public $extra;
179
180   /**
181    * The join type, so for example LEFT (default) or INNER.
182    *
183    * @var string
184    */
185   public $type;
186
187   /**
188    * The configuration array passed by initJoin.
189    *
190    * @var array
191    *
192    * @see \Drupal\views\Plugin\views\join\JoinPluginBase::initJoin()
193    */
194   public $configuration = [];
195
196   /**
197    * How all the extras will be combined. Either AND or OR.
198    *
199    * @var string
200    */
201   public $extraOperator;
202
203   /**
204    * Defines whether a join has been adjusted.
205    *
206    * Views updates the join object to set the table alias instead of the table
207    * name. Once views has changed the alias it sets the adjusted value so it
208    * does not have to be updated anymore. If you create your own join object
209    * you should set the adjusted in the definition array to TRUE if you already
210    * know the table alias.
211    *
212    * @var bool
213    *
214    * @see \Drupal\views\Plugin\HandlerBase::getTableJoin()
215    * @see \Drupal\views\Plugin\views\query\Sql::adjustJoin()
216    * @see \Drupal\views\Plugin\views\relationship\RelationshipPluginBase::query()
217    */
218   public $adjusted;
219
220   /**
221    * Constructs a Drupal\views\Plugin\views\join\JoinPluginBase object.
222    */
223   public function __construct(array $configuration, $plugin_id, $plugin_definition) {
224     parent::__construct($configuration, $plugin_id, $plugin_definition);
225     // Merge in some default values.
226     $configuration += [
227       'type' => 'LEFT',
228       'extra_operator' => 'AND'
229     ];
230     $this->configuration = $configuration;
231
232     if (!empty($configuration['table'])) {
233       $this->table = $configuration['table'];
234     }
235
236     $this->leftTable = $configuration['left_table'];
237     $this->leftField = $configuration['left_field'];
238     $this->field = $configuration['field'];
239
240     if (!empty($configuration['extra'])) {
241       $this->extra = $configuration['extra'];
242     }
243
244     if (isset($configuration['adjusted'])) {
245       $this->adjusted = $configuration['adjusted'];
246     }
247
248     $this->extraOperator = strtoupper($configuration['extra_operator']);
249     $this->type = $configuration['type'];
250   }
251
252   /**
253    * {@inheritdoc}
254    */
255   public function buildJoin($select_query, $table, $view_query) {
256     if (empty($this->configuration['table formula'])) {
257       $right_table = $this->table;
258     }
259     else {
260       $right_table = $this->configuration['table formula'];
261     }
262
263     if ($this->leftTable) {
264       $left = $view_query->getTableInfo($this->leftTable);
265       $left_field = "$left[alias].$this->leftField";
266     }
267     else {
268       // This can be used if left_field is a formula or something. It should be used only *very* rarely.
269       $left_field = $this->leftField;
270     }
271
272     $condition = "$left_field = $table[alias].$this->field";
273     $arguments = [];
274
275     // Tack on the extra.
276     if (isset($this->extra)) {
277       if (is_array($this->extra)) {
278         $extras = [];
279         foreach ($this->extra as $info) {
280           // Do not require 'value' to be set; allow for field syntax instead.
281           $info += [
282             'value' => NULL,
283           ];
284           // Figure out the table name. Remember, only use aliases provided
285           // if at all possible.
286           $join_table = '';
287           if (!array_key_exists('table', $info)) {
288             $join_table = $table['alias'] . '.';
289           }
290           elseif (isset($info['table'])) {
291             // If we're aware of a table alias for this table, use the table
292             // alias instead of the table name.
293             if (isset($left) && $left['table'] == $info['table']) {
294               $join_table = $left['alias'] . '.';
295             }
296             else {
297               $join_table = $info['table'] . '.';
298             }
299           }
300
301           // Convert a single-valued array of values to the single-value case,
302           // and transform from IN() notation to = notation
303           if (is_array($info['value']) && count($info['value']) == 1) {
304             $info['value'] = array_shift($info['value']);
305           }
306           if (is_array($info['value'])) {
307             // We use an SA-CORE-2014-005 conformant placeholder for our array
308             // of values. Also, note that the 'IN' operator is implicit.
309             // @see https://www.drupal.org/node/2401615.
310             $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
311             $placeholder = ':views_join_condition_' . $select_query->nextPlaceholder() . '[]';
312             $placeholder_sql = "( $placeholder )";
313           }
314           else {
315             // With a single value, the '=' operator is implicit.
316             $operator = !empty($info['operator']) ? $info['operator'] : '=';
317             $placeholder = $placeholder_sql = ':views_join_condition_' . $select_query->nextPlaceholder();
318           }
319           // Set 'field' as join table field if available or set 'left field' as
320           // join table field is not set.
321           if (isset($info['field'])) {
322             $join_table_field = "$join_table$info[field]";
323             // Allow the value to be set either with the 'value' element or
324             // with 'left_field'.
325             if (isset($info['left_field'])) {
326               $placeholder_sql = "$left[alias].$info[left_field]";
327             }
328             else {
329               $arguments[$placeholder] = $info['value'];
330             }
331           }
332           // Set 'left field' as join table field is not set.
333           else {
334             $join_table_field = "$left[alias].$info[left_field]";
335             $arguments[$placeholder] = $info['value'];
336           }
337           // Render out the SQL fragment with parameters.
338           $extras[] = "$join_table_field $operator $placeholder_sql";
339         }
340
341         if ($extras) {
342           if (count($extras) == 1) {
343             $condition .= ' AND ' . array_shift($extras);
344           }
345           else {
346             $condition .= ' AND (' . implode(' ' . $this->extraOperator . ' ', $extras) . ')';
347           }
348         }
349       }
350       elseif ($this->extra && is_string($this->extra)) {
351         $condition .= " AND ($this->extra)";
352       }
353     }
354
355     $select_query->addJoin($this->type, $right_table, $table['alias'], $condition, $arguments);
356   }
357
358 }
359
360 /**
361  * @}
362  */