Completed
Push — dev ( 6f2681...eae16f )
by Nicolas
01:47
created

getSelectedSectionsArray()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 13
rs 9.2
cc 4
eloc 8
nc 3
nop 0
1
<?php
2
	/*
3
	Copyright: Deux Huit Huit 2014
4
	LICENCE: MIT http://deuxhuithuit.mit-license.org;
5
	*/
6
7
	if (!defined('__IN_SYMPHONY__')) die('<h2>Symphony Error</h2><p>You cannot directly access this file</p>');
8
9
	require_once(TOOLKIT . '/class.field.php');
10
	require_once(EXTENSIONS . '/entry_relationship_field/lib/class.cacheablefetch.php');
11
	
12
	/**
13
	 *
14
	 * Field class that will represent relationships between entries
15
	 * @author Deux Huit Huit
16
	 *
17
	 */
18
	class FieldEntry_relationship extends Field
19
	{
20
		
21
		/**
22
		 *
23
		 * Name of the field table
24
		 *  @var string
25
		 */
26
		const FIELD_TBL_NAME = 'tbl_fields_entry_relationship';
27
		
28
		/**
29
		 * 
30
		 * Separator char for values
31
		 *  @var string
32
		 */
33
		const SEPARATOR = ',';
34
		
35
		
36
		/**
37
		 *
38
		 * Current recursive level of output
39
		 *  @var int
40
		 */
41
		protected $recursiveLevel = 1;
42
		
43
		/**
44
		 *
45
		 * Parent's maximum recursive level of output
46
		 *  @var int
47
		 */
48
		protected $recursiveDeepness = null;
49
		
50
		/**
51
		 *
52
		 * Constructor for the oEmbed Field object
53
		 */
54
		public function __construct()
55
		{
56
			// call the parent constructor
57
			parent::__construct();
58
			// set the name of the field
59
			$this->_name = __('Entry Relationship');
60
			// permits to make it required
61
			$this->_required = true;
62
			// permits the make it show in the table columns
63
			$this->_showcolumn = true;
64
			// permits association
65
			$this->_showassociation = true;
66
			// current recursive level
67
			$this->recursiveLevel = 1;
68
			// parent's maximum recursive level of output
69
			$this->recursiveDeepness = null;
70
			// set as not required by default
71
			$this->set('required', 'no');
72
			// show association by default
73
			$this->set('show_association', 'yes');
74
			// no sections
75
			$this->set('sections', null);
76
			// no max deepness
77
			$this->set('deepness', null);
78
			// no included elements
79
			$this->set('elements', null);
80
			// no limit
81
			$this->set('min_entries', null);
82
			$this->set('max_entries', null);
83
			// all permissions
84
			$this->set('allow_new', 'yes');
85
			$this->set('allow_edit', 'yes');
86
			$this->set('allow_link', 'yes');
87
			$this->set('allow_delete', 'no');
88
			$this->set('allow_collapse', 'yes');
89
		}
90
91
		public function isSortable()
92
		{
93
			return false;
94
		}
95
96
		public function canFilter()
97
		{
98
			return true;
99
		}
100
		
101
		public function canPublishFilter()
102
		{
103
			return false;
104
		}
105
106
		public function canImport()
107
		{
108
			return false;
109
		}
110
111
		public function canPrePopulate()
112
		{
113
			return false;
114
		}
115
		
116
		public function mustBeUnique()
117
		{
118
			return false;
119
		}
120
121
		public function allowDatasourceOutputGrouping()
122
		{
123
			return false;
124
		}
125
126
		public function requiresSQLGrouping()
127
		{
128
			return false;
129
		}
130
131
		public function allowDatasourceParamOutput()
132
		{
133
			return true;
134
		}
135
136
		/**
137
		 * @param string $name
138
		 */
139
		public function getInt($name)
140
		{
141
			return General::intval($this->get($name));
142
		}
143
144
		/**
145
		 * Check if a given property is == 'yes'
146
		 * @param string $name
147
		 * @return bool
148
		 *  True if the current field's value is 'yes'
149
		 */
150
		public function is($name)
151
		{
152
			return $this->get($name) == 'yes';
153
		}
154
155
		/**
156
		 * @return bool
157
		 *  True if the current field is required
158
		 */
159
		public function isRequired()
160
		{
161
			return $this->is('required');
162
		}
163
164
		public static function getEntries(array $data)
165
		{
166
			return array_map(array('General', 'intval'), array_filter(array_map(trim, explode(self::SEPARATOR, $data['entries']))));
167
		}
168
169
		/* ********** INPUT AND FIELD *********** */
170
171
172
		/**
173
		 * 
174
		 * Validates input
175
		 * Called before <code>processRawFieldData</code>
176
		 * @param $data
177
		 * @param $message
178
		 * @param $entry_id
179
		 */
180
		public function checkPostFieldData($data, &$message, $entry_id=NULL)
181
		{
182
			$message = NULL;
183
			$required = $this->isRequired();
184
			
185
			if ($required && (!is_array($data) || count($data) == 0 || strlen($data['entries']) < 1)) {
186
				$message = __("'%s' is a required field.", array($this->get('label')));
187
				return self::__MISSING_FIELDS__;
188
			}
189
			
190
			$entries = $data['entries'];
191
			
192
			if (!is_array($entries)) {
193
				$entries = static::getEntries($data);
194
			}
195
			
196
			// enforce limits only if required or it contains data
197
			if ($required || count($entries) > 0) {
198
				if ($this->getInt('min_entries') > 0 && $this->getInt('min_entries') > count($entries)) {
199
					$message = __("'%s' requires a minimum of %s entries.", array($this->get('label'), $this->getInt('min_entries')));
200
					return self::__INVALID_FIELDS__;
201
				} else if ($this->getInt('max_entries') > 0 && $this->getInt('max_entries') < count($entries)) {
202
					$message = __("'%s' can not contains more than %s entries.", array($this->get('label'), $this->getInt('max_entries')));
203
					return self::__INVALID_FIELDS__;
204
				}
205
			}
206
			
207
			return self::__OK__;
208
		}
209
210
211
		/**
212
		 *
213
		 * Process data before saving into database.
214
		 *
215
		 * @param array $data
216
		 * @param int $status
217
		 * @param boolean $simulate
218
		 * @param int $entry_id
219
		 *
220
		 * @return Array - data to be inserted into DB
221
		 */
222
		public function processRawFieldData($data, &$status, &$message = null, $simulate = false, $entry_id = null)
223
		{
224
			$status = self::__OK__;
225
			$entries = null;
226
			
227
			if (!is_array($data) && !is_string($data)) {
228
				return null;
229
			}
230
			
231
			if (isset($data['entries'])) {
232
				$entries = $data['entries'];
233
			}
234
			else if (is_string($data)) {
235
				$entries = $data;
236
			}
237
			
238
			$row = array(
239
				'entries' => $entries
240
			);
241
			
242
			// return row
243
			return $row;
244
		}
245
246
		/**
247
		 * This function permits parsing different field settings values
248
		 *
249
		 * @param array $settings
250
		 *	the data array to initialize if necessary.
251
		 */
252
		public function setFromPOST(Array &$settings = array())
253
		{
254
			// call the default behavior
255
			parent::setFromPOST($settings);
256
257
			// declare a new setting array
258
			$new_settings = array();
259
260
			// set new settings
261
			$new_settings['sections'] = is_array($settings['sections']) ? 
262
				implode(self::SEPARATOR, $settings['sections']) : 
263
				(is_string($settings['sections']) ? $settings['sections'] : null);
264
				
265
			$new_settings['show_association'] = $settings['show_association'] == 'yes' ? 'yes' : 'no';
266
			$new_settings['deepness'] = General::intval($settings['deepness']);
267
			$new_settings['deepness'] = $new_settings['deepness'] < 1 ? null : $new_settings['deepness'];
268
			$new_settings['elements'] = empty($settings['elements']) ? null : $settings['elements'];
269
			$new_settings['mode'] = empty($settings['mode']) ? null : $settings['mode'];
270
			$new_settings['allow_new'] = $settings['allow_new'] == 'yes' ? 'yes' : 'no';
271
			$new_settings['allow_edit'] = $settings['allow_edit'] == 'yes' ? 'yes' : 'no';
272
			$new_settings['allow_link'] = $settings['allow_link'] == 'yes' ? 'yes' : 'no';
273
			$new_settings['allow_delete'] = $settings['allow_delete'] == 'yes' ? 'yes' : 'no';
274
			$new_settings['allow_collapse'] = $settings['allow_collapse'] == 'yes' ? 'yes' : 'no';
275
			
276
			// save it into the array
277
			$this->setArray($new_settings);
278
		}
279
280
281
		/**
282
		 *
283
		 * Validates the field settings before saving it into the field's table
284
		 */
285
		public function checkFields(Array &$errors, $checkForDuplicates)
286
		{
287
			$parent = parent::checkFields($errors, $checkForDuplicates);
288
			if ($parent != self::__OK__) {
289
				return $parent;
290
			}
291
			
292
			$sections = $this->get('sections');
293
			
294
			if (empty($sections)) {
295
				$errors['sections'] = __('At least one section must be chosen');
296
			}
297
298
			return (!empty($errors) ? self::__ERROR__ : self::__OK__);
299
		}
300
301
		/**
302
		 *
303
		 * Save field settings into the field's table
304
		 */
305
		public function commit()
306
		{
307
			// if the default implementation works...
308
			if(!parent::commit()) return false;
309
			
310
			$id = $this->get('id');
311
			
312
			// exit if there is no id
313
			if($id == false) return false;
314
			
315
			// we are the child, with multiple parents
316
			$child_field_id = $id;
317
			
318
			// delete associations, only where we are the child
319
			self::removeSectionAssociation($child_field_id);
320
			
321
			$sections = $this->getSelectedSectionsArray();
322
			
323
			foreach ($sections as $key => $sectionId) {
324
				if (empty($sectionId)) {
325
					continue;
326
				}
327
				$parent_section_id = General::intval($sectionId);
328
				$parent_section = SectionManager::fetch($sectionId);
329
				$fields = $parent_section->fetchVisibleColumns();
330
				if (empty($fields)) {
331
					// no visible field, revert to all
332
					$fields = $parent_section->fetchFields();
333
				}
334
				$parent_field_id = current(array_keys($fields));
335
				// create association
336
				SectionManager::createSectionAssociation(
337
					$parent_section_id,
338
					$child_field_id,
339
					$parent_field_id,
340
					$this->get('show_association') == 'yes'
341
				);
342
			}
343
			
344
			// declare an array contains the field's settings
345
			$settings = array(
346
				'sections' => $this->get('sections'),
347
				'show_association' => $this->get('show_association'),
348
				'deepness' => $this->get('deepness'),
349
				'elements' => $this->get('elements'),
350
				'mode' => $this->get('mode'),
351
				'min_entries' => $this->get('min_entries'),
352
				'max_entries' => $this->get('max_entries'),
353
				'allow_new' => $this->get('allow_new'),
354
				'allow_edit' => $this->get('allow_edit'),
355
				'allow_link' => $this->get('allow_link'),
356
				'allow_delete' => $this->get('allow_delete'),
357
				'allow_collapse' => $this->get('allow_collapse'),
358
			);
359
360
			return FieldManager::saveSettings($id, $settings);
361
		}
362
363
		/**
364
		 *
365
		 * This function allows Fields to cleanup any additional things before it is removed
366
		 * from the section.
367
		 * @return boolean
368
		 */
369
		public function tearDown()
370
		{
371
			self::removeSectionAssociation($this->get('id'));
372
			return parent::tearDown();
373
		}
374
		
375
		/**
376
		 * Generates the where filter for searching by entry id
377
		 *
378
		 * @param string $value
379
		 * @param @optional string $col
380
		 * @param @optional boolean $andOperation
381
		 */
382
		public function generateWhereFilter($value, $col = 'd', $andOperation = true)
383
		{
384
			$junction = $andOperation ? 'AND' : 'OR';
385
			if (!$value) {
386
				return "{$junction} (`{$col}`.`entries` IS NULL)";
387
			}
388
			return " {$junction} (`{$col}`.`entries` = '{$value}' OR 
389
					`{$col}`.`entries` LIKE '{$value},%' OR 
390
					`{$col}`.`entries` LIKE '%,{$value}' OR 
391
					`{$col}`.`entries` LIKE '%,{$value},%')";
392
		}
393
394
		/**
395
		 * Fetch the number of associated entries for a particular entry id
396
		 *
397
		 * @param string $value
398
		 */
399
		public function fetchAssociatedEntryCount($value)
400
		{
401
			if (!$value) {
402
				return 0;
403
			}
404
			$join = sprintf(" INNER JOIN `tbl_entries_data_%s` AS `d` ON `e`.id = `d`.`entry_id`", $this->get('id'));
405
			$where = $this->generateWhereFilter($value);
406
			
407
			$entries = EntryManager::fetch(null, $this->get('parent_section'), null, 0, $where, $join, false, false, array());
408
			
409
			return count($entries);
410
		}
411
		
412
		public function fetchAssociatedEntrySearchValue($data, $field_id = null, $parent_entry_id = null)
413
		{
414
			return $parent_entry_id;
415
		}
416
		
417
		public function findRelatedEntries($entry_id)
418
		{
419
			$joins = '';
420
			$where = '';
421
			$this->buildDSRetrievalSQL(array($entry_id), $joins, $where, true);
422
			
423
			$entries = EntryManager::fetch(null, $this->get('parent_section'), null, 0, $where, $joins, false, false, array());
424
			
425
			$ids = array();
426
			foreach ($entries as $key => $e) {
427
				$ids[] = $e['id'];
428
			}
429
			return $ids;
430
		}
431
		
432
		public function prepareAssociationsDrawerXMLElement(Entry $e, array $parent_association, $prepolutate = '')
433
		{
434
			$currentSection = SectionManager::fetch($parent_association['child_section_id']);
435
			$visibleCols = $currentSection->fetchVisibleColumns();
436
			$outputFieldId = current(array_keys($visibleCols));
437
			$outputField = FieldManager::fetch($outputFieldId);
438
			
439
			$value = $outputField->prepareReadableValue($e->getData($outputFieldId), $e->get('id'), true, __('None'));
440
			
441
			$li = new XMLElement('li');
442
			$li->setAttribute('class', 'field-' . $this->get('type'));
443
			$a = new XMLElement('a', strip_tags($value));
444
			$a->setAttribute('href', SYMPHONY_URL . '/publish/' . $parent_association['handle'] . '/edit/' . $e->get('id') . '/');
445
			$li->appendChild($a);
446
447
			return $li;
448
		}
449
		
450
		/**
451
		 * @param string $joins
452
		 * @param string $where
453
		 */
454
		public function buildDSRetrievalSQL($data, &$joins, &$where, $andOperation = false)
455
		{
456
			$field_id = $this->get('id');
457
			
458
			// REGEX filtering is a special case, and will only work on the first item
459
			// in the array. You cannot specify multiple filters when REGEX is involved.
460
			if (self::isFilterRegex($data[0])) {
461
				return $this->buildRegexSQL($data[0], array('entries'), $joins, $where);
462
			}
463
			
464
			$this->_key++;
465
			
466
			$where .= ' AND (1=' . ($andOperation ? '1' : '0') . ' ';
467
			
468
			$joins .= "
469
				INNER JOIN
470
					`tbl_entries_data_{$field_id}` AS `t{$field_id}_{$this->_key}`
471
					ON (`e`.`id` = `t{$field_id}_{$this->_key}`.`entry_id`)
472
			";
473
			
474
			foreach ($data as $value) {
475
				$where .= $this->generateWhereFilter($this->cleanValue($value), "t{$field_id}_{$this->_key}", $andOperation);
476
			}
477
			
478
			$where .= ')';
479
			
480
			return true; // this tells the DS Manager that filters are OK!!
481
		}
482
483
		/* ******* EVENTS ******* */
484
485
		public function getExampleFormMarkup()
486
		{
487
			$label = Widget::Label($this->get('label'));
488
			$label->appendChild(Widget::Input('fields['.$this->get('element_name').'][entries]', null, 'hidden'));
489
490
			return $label;
491
		}
492
493
494
		/* ******* DATA SOURCE ******* */
495
		
496
		private function fetchEntry($eId, $elements = array())
497
		{
498
			$entry = EntryManager::fetch($eId, null, 1, 0, null, null, false, true, $elements, false);
499
			if (!is_array($entry) || count($entry) !== 1) {
500
				return null;
501
			}
502
			return $entry[0];
503
		}
504
		
505
		public function fetchIncludableElements()
506
		{
507
			$label = $this->get('element_name');
508
			$elements = array_filter(array_map(trim, explode(self::SEPARATOR, trim($this->get('elements')))));
509
			$includedElements = array($label . ': *');
510
			foreach ($elements as $elem) {
511
				$elem = trim($elem);
512
				if ($elem !== '*') {
513
					$includedElements[] = $label . ': ' . $elem;
514
				}
515
			}
516
			return $includedElements;
517
		}
518
		
519
		/**
520
		 * Appends data into the XML tree of a Data Source
521
		 * @param $wrapper
522
		 * @param $data
523
		 */
524
		public function appendFormattedElement(&$wrapper, $data, $encode = false, $mode = null, $entry_id = null)
525
		{
526
			if(!is_array($data) || empty($data)) return;
527
528
			// root for all values
529
			$root = new XMLElement($this->get('element_name'));
530
			
531
			// selected items
532
			$entries = static::getEntries($data);
533
			
534
			// current linked entries
535
			$root->setAttribute('entries', $data['entries']);
536
			
537
			// available sections
538
			$root->setAttribute('sections', $this->get('sections'));
539
			
540
			// included elements
541
			$elements = static::parseElements($this);
542
			
543
			// cache
544
			$sectionsCache = new CacheableFetch('SectionManager');
545
			
546
			// DS mode
547
			if (!$mode) {
548
				$mode = '*';
549
			}
550
			
551
			$parentDeepness = General::intval($this->recursiveDeepness);
552
			$deepness = General::intval($this->get('deepness'));
553
			
554
			// both deepnesses are defined and parent restricts more
555
			if ($parentDeepness > 0 && $deepness > 0 && $parentDeepness < $deepness) {
556
				$deepness = $parentDeepness;
557
			}
558
			// parent is defined, current is not
559
			else if ($parentDeepness > 0 && $deepness < 1) {
560
				$deepness = $parentDeepness;
561
			}
562
			
563
			// cache recursive level because recursion might
564
			// change its value later on.
565
			$recursiveLevel = $this->recursiveLevel;
566
			
567
			// build entries
568
			foreach ($entries as $eId) {
569
				$item = new XMLElement('item');
570
				// output id
571
				$item->setAttribute('id', $eId);
572
				// output recursive level
573
				$item->setAttribute('level', $recursiveLevel);
574
				$item->setAttribute('max-level', $deepness);
575
				
576
				// max recursion check
577
				if ($deepness < 1 || $recursiveLevel < $deepness) {
578
					// current entry, without data
579
					$entry = $this->fetchEntry($eId);
580
					
581
					// entry not found...
582
					if (!$entry || empty($entry)) {
583
						$error = new XMLElement('error');
584
						$error->setAttribute('id', $eId);
585
						$error->setValue(__('Error: entry `%s` not found', array($eId)));
586
						$root->prependChild($error);
587
						continue;
588
					}
589
					
590
					// fetch section infos
591
					$sectionId = $entry->get('section_id');
592
					$section = $sectionsCache->fetch($sectionId);
593
					$sectionName = $section->get('handle');
594
					// cache fields info
595
					if (!isset($section->er_field_cache)) {
596
						$section->er_field_cache = $section->fetchFields();
597
					}
598
					
599
					// set section related attributes
600
					$item->setAttribute('section-id', $sectionId);
601
					$item->setAttribute('section', $sectionName);
602
					
603
					// Get the valid elements for this section only
604
					$validElements = $elements[$sectionName];
605
					
606
					// adjust the mode for the current section
607
					$curMode = $mode;
608
					
609
					// remove section name from current mode, i.e sectionName.field
610
					if (preg_match('/^(' . $sectionName . '\.)(.*)$/sU', $curMode)) {
611
						$curMode = preg_replace('/^' . $sectionName . '\./sU', '', $curMode);
612
					}
613
					// remove section name from current mode, i.e sectionName
614
					else if (preg_match('/^(' . $sectionName . ')$/sU', $curMode)) {
615
						$curMode = '*';
616
					}
617
					// section name was not found in mode
618
					else if ($curMode != '*') {
619
						// mode forbids this section
620
						$validElements = null;
621
					}
622
					
623
					// this section is not selected, bail out
624
					if (!is_array($validElements)) {
625
						$item->setAttribute('forbidden-by', $curMode);
626
						$root->appendChild($item);
627
						continue;
628
					}
629
					
630
					// selected fields for fetching
631
					$sectionElements = array();
0 ignored issues
show
Unused Code introduced by
$sectionElements is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
632
					
633
					// everything is allowed
634
					if (in_array('*', $validElements)) {
635
						if ($curMode !== '*') {
636
							// get only the mode
637
							$sectionElements = array($curMode);
638
						}
639
						else {
640
							// setting null = get all
641
							$sectionElements = null;
642
						}
643
					}
644
					// only use valid elements
645
					else {
646
						if ($curMode !== '*') {
647
							// is this field allowed ?
648
							if (self::isFieldIncluded($curMode, $validElements)) {
649
								// get only the mode
650
								$sectionElements = array($curMode);
651
							}
652
							else {
653
								// $curMode selects something outside of
654
								// the valid elements: select nothing
655
								$sectionElements = array();
656
							}
657
						}
658
						else {
659
							// use field's valid elements
660
							$sectionElements = $validElements;
661
						}
662
					}
663
					
664
					if (is_array($sectionElements) && empty($sectionElements)) {
665
						$item->setAttribute('selection-empty', 'yes');
666
						$item->setAttribute('forbidden-by', $curMode);
667
						$root->appendChild($item);
668
						continue;
669
					}
670
					
671
					// current entry again, but with data and the allowed schema
672
					$entry = $this->fetchEntry($eId, $sectionElements);
673
					
674
					// cache the entry data
675
					$entryData = $entry->getData();
676
					
677
					// for each field returned for this entry...
678
					foreach ($entryData as $fieldId => $data) {
679
						$filteredData = array_filter($data, function ($value) {
680
							return $value != null;
681
						});
682
						
683
						if (empty($filteredData)) {
684
							continue;
685
						}
686
						
687
						$field = $section->er_field_cache[$fieldId];
688
						$fieldName = $field->get('element_name');
689
						$fieldCurMode = self::extractMode($fieldName, $curMode);
690
						
691
						$parentIncludableElement = self::getSectionElementName($fieldName, $validElements);
692
						$parentIncludableElementMode = self::extractMode($fieldName, $parentIncludableElement);
693
						
694
						// Special treatments for ERF
695
						if ($field instanceof FieldEntry_relationship) {
696
							// Increment recursive level
697
							$field->recursiveLevel = $recursiveLevel + 1;
698
							$field->recursiveDeepness = $deepness;
699
						}
700
						
701
						$submodes = null;
0 ignored issues
show
Unused Code introduced by
$submodes is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
702
						if ($parentIncludableElementMode == null) {
703
							if ($fieldCurMode == null) {
704
								$submodes = null;
705
							}
706
							else {
707
								$submodes = array($fieldCurMode);
708
							}
709
						}
710
						else {
711
							if ($fieldCurMode == null || $fieldCurMode == $parentIncludableElementMode) {
712
								$submodes = array($parentIncludableElementMode);
713
							}
714
							else {
715
								$item->setAttribute('selection-mode-empty', 'yes');
716
								$submodes = array();
717
							}
718
						}
719
						
720
						// current selection does not specify a mode
721 View Code Duplication
						if ($submodes == null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
722
							$submodes = array_map(function ($fieldIncludableElement) use ($fieldName) {
723
								return FieldEntry_relationship::extractMode($fieldName, $fieldIncludableElement);
724
							}, $field->fetchIncludableElements());
725
						}
726
						
727
						foreach ($submodes as $submode) {
728
							$field->appendFormattedElement($item, $data, $encode, $submode, $eId);
729
						}
730
					}
731
					// output current mode
732
					$item->setAttribute('matched-element', $curMode);
733
					// no field selected
734
					if (is_array($sectionElements) && empty($sectionElements)) {
735
						$item->setAttribute('empty-selection', 'yes');
736
					}
737
				}
738
				// append item when done
739
				$root->appendChild($item);
740
			} // end each entries
741
			
742
			// output mode for this field
743
			$root->setAttribute('data-source-mode', $mode);
744
			$root->setAttribute('field-included-elements', $this->get('elements'));
745
			
746
			// add all our data to the wrapper;
747
			$wrapper->appendChild($root);
748
			
749
			// clean up
750
			$this->recursiveLevel = 1;
751
			$this->recursiveDeepness = null;
752
		}
753
754
		public function getParameterPoolValue(array $data, $entry_id = null)
755
		{
756
			if(!is_array($data) || empty($data)) return;
757
			return static::getEntries($data);
758
		}
759
760
		/* ********* Utils *********** */
761
		
762
		/**
763
		 * Return true if $fieldName is allowed in $sectionElements
764
		 * @param string $fieldName
765
		 * @param string $sectionElements
766
		 * @return bool
767
		 */
768
		public static function isFieldIncluded($fieldName, $sectionElements)
769
		{
770
			return self::getSectionElementName($fieldName, $sectionElements) !== null;
771
		}
772
773
		public static function getSectionElementName($fieldName, $sectionElements)
774
		{
775
			if (is_array($sectionElements)) {
776
				foreach ($sectionElements as $element) {
777
					if ($element == '*') {
778
						return $fieldName;
779
					}
780
					if ($fieldName == $element || preg_match('/^' . $fieldName . '\s*:/sU', $element)) {
781
						return $element;
782
					}
783
				}
784
			}
785
			return null;
786
		}
787
		
788
		public static function parseElements($field)
789
		{
790
			$elements = array();
791
			$exElements = array_map(trim, explode(self::SEPARATOR, $field->get('elements')));
792
			
793
			if (in_array('*', $exElements)) {
794
				$sections = array_map(trim, explode(self::SEPARATOR, $field->get('sections')));
795
				$sections = SectionManager::fetch($sections);
796
				return array_reduce($sections, function ($result, $section) {
797
					$result[$section->get('handle')] = array('*');
798
					return $result;
799
				}, array());
800
			}
801
			
802
			foreach ($exElements as $value) {
803
				if (!$value) {
804
					continue;
805
				}
806
				// sectionName.fieldName or sectionName.*
807
				$parts = array_map(trim, explode('.', $value));
808
				// first time seeing this section
809
				if (!isset($elements[$parts[0]])) {
810
					$elements[$parts[0]] = array();
811
				}
812
				// we have a value after the dot
813
				if (isset($parts[1]) && !!$parts[1]) {
814
					$elements[$parts[0]][] = $parts[1];
815
				}
816
				// sectionName only
817
				else if (!isset($parts[1])) {
818
					$elements[$parts[0]][] = '*';
819
				}
820
			}
821
			
822
			return $elements;
823
		}
824
825
		public static function extractMode($fieldName, $mode)
826
		{
827
			$pattern = '/^' . $fieldName . '\s*:\s*/s';
828
			if (!preg_match($pattern, $mode)) {
829
				return null;
830
			}
831
			$mode = preg_replace($pattern, '', $mode, 1);
832
			if ($mode === '*') {
833
				return null;
834
			}
835
			return $mode;
836
		}
837
838
		/**
839
		 * @param string $prefix
840
		 * @param string $name
841
		 * @param @optional bool $multiple
842
		 */
843
		private function createFieldName($prefix, $name, $multiple = false)
844
		{
845
			$name = "fields[$prefix][$name]";
846
			if ($multiple) {
847
				$name .= '[]';
848
			}
849
			return $name;
850
		}
851
		
852
		/**
853
		 * @param string $name
854
		 */
855
		private function createSettingsFieldName($name, $multiple = false)
856
		{
857
			return $this->createFieldName($this->get('sortorder'), $name, $multiple);
858
		}
859
		
860
		/**
861
		 * @param string $name
862
		 */
863
		private function createPublishFieldName($name, $multiple = false)
864
		{
865
			return $this->createFieldName($this->get('element_name'), $name, $multiple);
866
		}
867
		
868
		private function getSelectedSectionsArray()
869
		{
870
			$selectedSections = $this->get('sections');
871
			if (!is_array($selectedSections)) {
872
				if (is_string($selectedSections) && strlen($selectedSections) > 0) {
873
					$selectedSections = explode(self::SEPARATOR, $selectedSections);
874
				}
875
				else {
876
					$selectedSections = array();
877
				}
878
			}
879
			return $selectedSections;
880
		}
881
		
882
		private function buildSectionSelect($name)
883
		{
884
			$sections = SectionManager::fetch();
885
			$options = array();
886
			$selectedSections = $this->getSelectedSectionsArray();
887
			
888
			foreach ($sections as $section) {
889
				$driver = $section->get('id');
890
				$selected = in_array($driver, $selectedSections);
891
				$options[] = array($driver, $selected, $section->get('name'));
892
			}
893
			
894
			return Widget::Select($name, $options, array('multiple' => 'multiple'));
895
		} 
896
		
897
		private function appendSelectionSelect(&$wrapper)
898
		{
899
			$name = $this->createSettingsFieldName('sections', true);
900
901
			$input = $this->buildSectionSelect($name);
902
			$input->setAttribute('class', 'entry_relationship-sections');
903
904
			$label = Widget::Label();
905
			$label->setAttribute('class', 'column');
906
907
			$label->setValue(__('Available sections %s', array($input->generate())));
908
909
			$wrapper->appendChild($label);
910
		}
911
912
		private function createEntriesList($entries)
913
		{
914
			$wrap = new XMLElement('div');
915
			$wrap->setAttribute('class', 'frame collapsible orderable' . (count($entries) > 0 ? '' : ' empty'));
916
			
917
			$list = new XMLElement('ul');
918
			$list->setAttribute('class', '');
919
			if ($this->is('allow_collapse')) {
920
				$list->setAttribute('data-collapsible', '');
921
			}
922
			
923
			$wrap->appendChild($list);
924
			
925
			return $wrap;
926
		}
927
		
928
		private function createEntriesHiddenInput($data)
929
		{
930
			$hidden = new XMLElement('input', null, array(
931
				'type' => 'hidden',
932
				'name' => $this->createPublishFieldName('entries'),
933
				'value' => $data['entries']
934
			));
935
			
936
			return $hidden;
937
		}
938
		
939
		private function createPublishMenu($sections)
940
		{
941
			$wrap = new XMLElement('fieldset');
942
			$wrap->setAttribute('class', 'single');
943
			
944
			if ($this->is('allow_new') || $this->is('allow_link')) {
945
				$selectWrap = new XMLElement('div');
946
				$selectWrap->appendChild(new XMLElement('span', __('Related section: ')));
947
				$options = array();
948
				foreach ($sections as $section) {
949
					$options[] = array($section->get('handle'), false, $section->get('name'));
950
				}
951
				$select = Widget::Select('', $options, array('class' => 'sections'));
952
				$selectWrap->appendChild($select);
953
				$wrap->appendChild($selectWrap);
954
			}
955 View Code Duplication
			if ($this->is('allow_new')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
956
				$wrap->appendChild(new XMLElement('button', __('Create new'), array(
957
					'type' => 'button',
958
					'class' => 'create',
959
					'data-create' => '',
960
				)));
961
			}
962 View Code Duplication
			if ($this->is('allow_link')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
963
				$wrap->appendChild(new XMLElement('button', __('Link to entry'), array(
964
					'type' => 'button',
965
					'class' => 'link',
966
					'data-link' => '',
967
				)));
968
			}
969
			
970
			return $wrap;
971
		}
972
973
		/* ********* UI *********** */
974
		
975
		/**
976
		 *
977
		 * Builds the UI for the field's settings when creating/editing a section
978
		 * @param XMLElement $wrapper
979
		 * @param array $errors
980
		 */
981
		public function displaySettingsPanel(&$wrapper, $errors=NULL)
982
		{
983
			/* first line, label and such */
984
			parent::displaySettingsPanel($wrapper, $errors);
985
			
986
			// sections
987
			$sections = new XMLElement('div');
988
			$sections->setAttribute('class', '');
989
			
990
			$this->appendSelectionSelect($sections);
991
			if (is_array($errors) && isset($errors['sections'])) {
992
				$sections = Widget::Error($sections, $errors['sections']);
993
			}
994
			$wrapper->appendChild($sections);
995
			
996
			// xsl mode
997
			$xslmode = Widget::Label();
998
			$xslmode->setValue(__('XSL mode applied in the backend xsl file'));
999
			$xslmode->setAttribute('class', 'column');
1000
			$xslmode->appendChild(Widget::Input($this->createSettingsFieldName('mode'), $this->get('mode'), 'text'));
1001
			
1002
			// deepness
1003
			$deepness = Widget::Label();
1004
			$deepness->setValue(__('Maximum level of recursion in Data Sources'));
1005
			$deepness->setAttribute('class', 'column');
1006
			$deepness->appendChild(Widget::Input($this->createSettingsFieldName('deepness'), $this->get('deepness'), 'number', array(
1007
				'min' => 0,
1008
				'max' => 99
1009
			)));
1010
			
1011
			// association
1012
			$assoc = new XMLElement('div');
1013
			$assoc->setAttribute('class', 'three columns');
1014
			$this->appendShowAssociationCheckbox($assoc);
1015
			$assoc->appendChild($xslmode);
1016
			$assoc->appendChild($deepness);
1017
			$wrapper->appendChild($assoc);
1018
			
1019
			// elements
1020
			$elements = new XMLElement('div');
1021
			$elements->setAttribute('class', '');
1022
			$element = Widget::Label();
1023
			$element->setValue(__('Included elements in Data Sources and Backend Templates'));
1024
			$element->setAttribute('class', 'column');
1025
			$element->appendChild(Widget::Input($this->createSettingsFieldName('elements'), $this->get('elements'), 'text', array(
1026
				'class' => 'entry_relationship-elements'
1027
			)));
1028
			$elements->appendChild($element);
1029
			$elements_choices = new XMLElement('ul', null, array('class' => 'tags singular entry_relationship-field-choices'));
1030
			
1031
			$elements->appendChild($elements_choices);
1032
			$wrapper->appendChild($elements);
1033
			
1034
			// limit entries
1035
			$limits = new XMLElement('fieldset');
1036
			$limits->setAttribute('class', 'two columns');
1037
			// min
1038
			$limit_min = Widget::Label();
1039
			$limit_min->setValue(__('Minimum count of entries in this field'));
1040
			$limit_min->setAttribute('class', 'column');
1041
			$limit_min->appendChild(Widget::Input($this->createSettingsFieldName('min_entries'), $this->get('min_entries'), 'number', array(
1042
				'min' => 0,
1043
				'max' => 99999
1044
			)));
1045
			$limits->appendChild($limit_min);
1046
			// max
1047
			$limit_max = Widget::Label();
1048
			$limit_max->setValue(__('Maximum count of entries in this field'));
1049
			$limit_max->setAttribute('class', 'column');
1050
			$limit_max->appendChild(Widget::Input($this->createSettingsFieldName('max_entries'), $this->get('max_entries'), 'number', array(
1051
				'min' => 0,
1052
				'max' => 99999
1053
			)));
1054
			$limits->appendChild($limit_max);
1055
			
1056
			$wrapper->appendChild($limits);
1057
			
1058
			// permissions
1059
			$permissions = new XMLElement('fieldset');
1060
			$permissions->setAttribute('class', 'two columns');
1061
			$permissions->appendChild($this->createCheckbox('allow_new', 'Show new button'));
1062
			$permissions->appendChild($this->createCheckbox('allow_edit', 'Show edit button'));
1063
			$permissions->appendChild($this->createCheckbox('allow_link', 'Show link button'));
1064
			$permissions->appendChild($this->createCheckbox('allow_delete', 'Show delete button'));
1065
			$permissions->appendChild($this->createCheckbox('allow_collapse', 'Allow content collapsing'));
1066
			
1067
			$wrapper->appendChild($permissions);
1068
			
1069
			// footer
1070
			$this->appendStatusFooter($wrapper);
1071
		}
1072
		
1073
		/**
1074
		 * @param string $fieldName
1075
		 * @param string $text
1076
		 */
1077
		private function createCheckbox($fieldName, $text) {
1078
			$chk = Widget::Label();
1079
			$chk->setAttribute('class', 'column');
1080
			$attrs = null;
1081
			if ($this->get($fieldName) == 'yes') {
1082
				$attrs = array('checked' => 'checked');
1083
			}
1084
			$chk->appendChild(Widget::Input($this->createSettingsFieldName($fieldName), 'yes', 'checkbox', $attrs));
1085
			$chk->setValue(__($text));
1086
			return $chk;
1087
		}
1088
1089
		/**
1090
		 *
1091
		 * Builds the UI for the publish page
1092
		 * @param XMLElement $wrapper
1093
		 * @param mixed $data
1094
		 * @param mixed $flagWithError
1095
		 * @param string $fieldnamePrefix
1096
		 * @param string $fieldnamePostfix
1097
		 */
1098
		public function displayPublishPanel(&$wrapper, $data=NULL, $flagWithError=NULL, $fieldnamePrefix=NULL, $fieldnamePostfix=NULL, $entry_id = null)
1099
		{
1100
			$entriesId = array();
1101
			$sectionsId = $this->getSelectedSectionsArray();
1102
			
1103
			if ($data['entries'] != null) {
1104
				$entriesId = static::getEntries($data);
1105
			}
1106
			
1107
			$sectionsId = array_map(array('General', 'intval'), $sectionsId);
1108
			$sections = SectionManager::fetch($sectionsId);
1109
			
1110
			$label = Widget::Label($this->get('label'));
1111
			$notes = '';
1112
			
1113
			// min note
1114
			if ($this->getInt('min_entries') > 0) {
1115
				$notes .= __('Minimum number of entries: <b>%s</b>. ', array($this->get('min_entries')));
1116
			}
1117
			// max note
1118
			if ($this->getInt('max_entries') > 0) {
1119
				$notes .= __('Maximum number of entries: <b>%s</b>. ', array($this->get('max_entries')));
1120
			}
1121
			// not required note
1122
			if (!$this->isRequired()) {
1123
				$notes .= __('Optional');
1124
			}
1125
			// append notes
1126
			if ($notes) {
1127
				$label->appendChild(new XMLElement('i', $notes));
1128
			}
1129
			
1130
			// label error management
1131
			if ($flagWithError != NULL) {
1132
				$wrapper->appendChild(Widget::Error($label, $flagWithError));
1133
			} else {
1134
				$wrapper->appendChild($label);
1135
			}
1136
			
1137
			$wrapper->appendChild($this->createEntriesList($entriesId));
1138
			$wrapper->appendChild($this->createPublishMenu($sections));
1139
			$wrapper->appendChild($this->createEntriesHiddenInput($data));
1140
			$wrapper->setAttribute('data-value', $data['entries']);
1141
			$wrapper->setAttribute('data-field-id', $this->get('id'));
1142
			$wrapper->setAttribute('data-field-label', $this->get('label'));
1143
			$wrapper->setAttribute('data-min', $this->get('min_entries'));
1144
			$wrapper->setAttribute('data-max', $this->get('max_entries'));
1145
			if (isset($_REQUEST['debug'])) {
1146
				$wrapper->setAttribute('data-debug', true);
1147
			}
1148
		}
1149
1150
		/**
1151
		 * @param integer $count
1152
		 */
1153
		private static function formatCount($count)
1154
		{
1155
			if ($count == 0) {
1156
				return __('No item');
1157
			} else if ($count == 1) {
1158
				return __('1 item');
1159
			}
1160
			return __('%s items', array($count));
1161
		}
1162
1163
		/**
1164
		 *
1165
		 * Return a plain text representation of the field's data
1166
		 * @param array $data
1167
		 * @param int $entry_id
1168
		 */
1169
		public function prepareTextValue($data, $entry_id = null)
1170
		{
1171
			if ($entry_id == null || empty($data)) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $entry_id of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
1172
				return __('None');
1173
			}
1174
			$entries = static::getEntries($data);
1175
			$realEntries = array();
1176
			foreach ($entries as $entryId) {
1177
				$e = EntryManager::fetch($entryId);
1178
				if (is_array($e) && !empty($e)) {
1179
					$realEntries = array_merge($realEntries, $e);
1180
				}
1181
			}
1182
			$count = count($entries);
1183
			$realCount = count($realEntries);
1184
			if ($count === $realCount) {
1185
				return self::formatCount($count);
1186
			}
1187
			return self::formatCount($realCount) . ' (' . self::formatCount($count - $realCount) . ' not found)';
1188
		}
1189
1190
1191
1192
		/* ********* SQL Data Definition ************* */
1193
1194
		/**
1195
		 *
1196
		 * Creates table needed for entries of individual fields
1197
		 */
1198
		public function createTable()
1199
		{
1200
			$id = $this->get('id');
1201
1202
			return Symphony::Database()->query("
1203
				CREATE TABLE `tbl_entries_data_$id` (
1204
					`id` int(11) 		unsigned NOT NULL AUTO_INCREMENT,
1205
					`entry_id` 			int(11) unsigned NOT NULL,
1206
					`entries` 			text COLLATE utf8_unicode_ci NULL,
1207
					PRIMARY KEY  (`id`),
1208
					UNIQUE KEY `entry_id` (`entry_id`)
1209
				) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1210
			");
1211
		}
1212
1213
		/**
1214
		 * Creates the table needed for the settings of the field
1215
		 */
1216
		public static function createFieldTable()
1217
		{
1218
			$tbl = self::FIELD_TBL_NAME;
1219
1220
			return Symphony::Database()->query("
1221
				CREATE TABLE IF NOT EXISTS `$tbl` (
1222
					`id` 			int(11) unsigned NOT NULL AUTO_INCREMENT,
1223
					`field_id` 		int(11) unsigned NOT NULL,
1224
					`sections`		varchar(255) NULL COLLATE utf8_unicode_ci,
1225
					`show_association` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1226
					`deepness` 		int(2) unsigned NULL,
1227
					`elements` 		varchar(1024) NULL COLLATE utf8_unicode_ci,
1228
					`mode`			varchar(50) NULL COLLATE utf8_unicode_ci,
1229
					`min_entries`	int(5) unsigned NULL,
1230
					`max_entries`	int(5) unsigned NULL,
1231
					`allow_edit` 	enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1232
					`allow_new` 	enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1233
					`allow_link` 	enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1234
					`allow_delete` 	enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no',
1235
					PRIMARY KEY (`id`),
1236
					UNIQUE KEY `field_id` (`field_id`)
1237
				) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1238
			");
1239
		}
1240
		
1241
		public static function update_102()
1242
		{
1243
			$tbl = self::FIELD_TBL_NAME;
1244
			$sql = "
1245
				ALTER TABLE `$tbl`
1246
					ADD COLUMN `allow_edit` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci  DEFAULT 'yes',
1247
					ADD COLUMN `allow_new` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci  DEFAULT 'yes',
1248
					ADD COLUMN `allow_link` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci  DEFAULT 'yes'
1249
					AFTER `max_entries`
1250
			";
1251
			$addColumns = Symphony::Database()->query($sql);
1252
			if (!$addColumns) {
1253
				return false;
1254
			}
1255
1256
			$fields = FieldManager::fetch(null, null, null, 'id', 'entry_relationship');
1257
			if (!empty($fields) && is_array($fields)) {
1258
				foreach ($fields as $fieldId => $field) {
1259
					$sql = "ALTER TABLE `tbl_entries_data_$fieldId` MODIFY `entries` TEXT";
1260
					if (!Symphony::Database()->query($sql)) {
1261
						throw new Exception(__('Could not update table `tbl_entries_data_%s`.', array($fieldId)));
1262
					}
1263
				}
1264
			}
1265
			return true;
1266
		}
1267
		
1268
		public static function update_103()
1269
		{
1270
			$tbl = self::FIELD_TBL_NAME;
1271
			$sql = "
1272
				ALTER TABLE `$tbl`
1273
					ADD COLUMN `allow_delete` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci  DEFAULT 'no'
1274
					AFTER `allow_link`
1275
			";
1276
			return Symphony::Database()->query($sql);
1277
		}
1278
		
1279
		public static function update_200()
1280
		{
1281
			$tbl = self::FIELD_TBL_NAME;
1282
			$sql = "
1283
				ALTER TABLE `$tbl`
1284
					ADD COLUMN `allow_collapse` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1285
					AFTER `allow_delete`
1286
			";
1287
			return Symphony::Database()->query($sql);
1288
		}
1289
		
1290
		/**
1291
		 *
1292
		 * Drops the table needed for the settings of the field
1293
		 */
1294
		public static function deleteFieldTable()
1295
		{
1296
			$tbl = self::FIELD_TBL_NAME;
1297
			
1298
			return Symphony::Database()->query("
1299
				DROP TABLE IF EXISTS `$tbl`
1300
			");
1301
		}
1302
		
1303
		private static function removeSectionAssociation($child_field_id)
1304
		{
1305
			return Symphony::Database()->delete('tbl_sections_association', "`child_section_field_id` = {$child_field_id}");
1306
		}
1307
	}