FieldEntry_Relationship::setFromPOST()   F
last analyzed

Complexity

Conditions 17
Paths > 20000

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 32
rs 1.0499
c 0
b 0
f 0
cc 17
nc 65536
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
	/**
3
	 * Copyright: Deux Huit Huit 2014
4
	 * LICENCE: MIT https://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(EXTENSIONS . '/entry_relationship_field/lib/class.field.relationship.php');
10
	require_once(EXTENSIONS . '/entry_relationship_field/lib/class.cacheablefetch.php');
11
	require_once(EXTENSIONS . '/entry_relationship_field/lib/class.erfxsltutilities.php');
12
	require_once(EXTENSIONS . '/entry_relationship_field/lib/class.sectionsinfos.php');
13
	
14
	/**
15
	 *
16
	 * Field class that will represent relationships between entries
17
	 * @author Deux Huit Huit
18
	 *
19
	 */
20
	class FieldEntry_Relationship extends FieldRelationship
21
	{
22
		/**
23
		 *
24
		 * Name of the field table
25
		 *  @var string
26
		 */
27
		const FIELD_TBL_NAME = 'tbl_fields_entry_relationship';
28
		
29
		/**
30
		 *
31
		 * Current recursive level of output
32
		 *  @var int
33
		 */
34
		protected $recursiveLevel = 1;
35
		public function getRecursiveLevel()
36
		{
37
			return $this->recursiveLevel;
38
		}
39
		public function incrementRecursiveLevel($inc = 1)
40
		{
41
			return $this->recursiveLevel += $inc;
42
		}
43
		
44
		/**
45
		 *
46
		 * Parent's maximum recursive level of output
47
		 *  @var int
48
		 */
49
		protected $recursiveDeepness = null;
50
		public function getRecursiveDeepness()
51
		{
52
			return $this->recursiveDeepness;
53
		}
54
		public function setRecursiveDeepness($deepness)
55
		{
56
			return $this->recursiveDeepness = $deepness;
57
		}
58
		
59
		/* Cacheable Managers */
60
		private $sectionManager;
61
		private $entryManager;
62
		private $sectionInfos;
63
		
64
		public $expandIncludableElements = true;
65
		
66
		/**
67
		 *
68
		 * Constructor for the Entry_Relationship Field object
69
		 */
70
		public function __construct()
71
		{
72
			// call the parent constructor
73
			parent::__construct();
74
			// set the name of the field
75
			$this->_name = __('Entry Relationship');
76
			// permits to make it required
77
			$this->_required = true;
78
			// permits the make it show in the table columns
79
			$this->_showcolumn = true;
80
			// permits association
81
			$this->_showassociation = true;
82
			// current recursive level
83
			$this->recursiveLevel = 1;
84
			// parent's maximum recursive level of output
85
			$this->recursiveDeepness = null;
86
			// set as orderable
87
			$this->orderable = true;
88
			// set as not required by default
89
			$this->set('required', 'no');
90
			// show association by default
91
			$this->set('show_association', 'yes');
92
			// no sections
93
			$this->set('sections', null);
94
			// no max deepness
95
			$this->set('deepness', null);
96
			// no included elements
97
			$this->set('elements', null);
98
			// no modes
99
			$this->set('mode', null);
100
			$this->set('mode_table', null);
101
			$this->set('mode_header', null);
102
			$this->set('mode_footer', null);
103
			// no limit
104
			$this->set('min_entries', null);
105
			$this->set('max_entries', null);
106
			// all permissions
107
			$this->set('allow_new', 'yes');
108
			$this->set('allow_edit', 'yes');
109
			$this->set('allow_link', 'yes');
110
			$this->set('allow_delete', 'no');
111
			// display options
112
			$this->set('allow_collapse', 'yes');
113
			$this->set('allow_search', 'no');
114
			$this->set('show_header', 'yes');
115
			$this->sectionManager = new CacheableFetch('SectionManager');
116
			$this->entryManager = new CacheableFetch('EntryManager');
117
			$this->sectionInfos = new CacheableFetch('SectionsInfos');
118
		}
119
120
		public function isSortable()
121
		{
122
			return false;
123
		}
124
125
		public function canFilter()
126
		{
127
			return true;
128
		}
129
		
130
		public function canPublishFilter()
131
		{
132
			return true;
133
		}
134
135
		public function canImport()
136
		{
137
			return false;
138
		}
139
140
		public function canPrePopulate()
141
		{
142
			return false;
143
		}
144
		
145
		public function mustBeUnique()
146
		{
147
			return false;
148
		}
149
150
		public function allowDatasourceOutputGrouping()
151
		{
152
			return false;
153
		}
154
155
		public function requiresSQLGrouping()
156
		{
157
			return false;
158
		}
159
160
		public function allowDatasourceParamOutput()
161
		{
162
			return true;
163
		}
164
165
		/* ********** INPUT AND FIELD *********** */
166
167
168
		/**
169
		 * 
170
		 * Validates input
171
		 * Called before <code>processRawFieldData</code>
172
		 * @param $data
173
		 * @param $message
174
		 * @param $entry_id
175
		 */
176
		public function checkPostFieldData($data, &$message, $entry_id=null)
177
		{
178
			$message = null;
179
			$required = $this->isRequired();
180
			
181
			if ($required && (!is_array($data) || count($data) == 0 || strlen($data['entries']) < 1)) {
182
				$message = __("'%s' is a required field.", array($this->get('label')));
183
				return self::__MISSING_FIELDS__;
184
			}
185
			
186
			$entries = $data['entries'];
187
			
188
			if (!is_array($entries)) {
189
				$entries = static::getEntries($data);
190
			}
191
			
192
			// enforce limits only if required or it contains data
193
			if ($required || count($entries) > 0) {
194
				if ($this->getInt('min_entries') > 0 && $this->getInt('min_entries') > count($entries)) {
195
					$message = __("'%s' requires a minimum of %s entries.", array($this->get('label'), $this->getInt('min_entries')));
196
					return self::__INVALID_FIELDS__;
197
				} else if ($this->getInt('max_entries') > 0 && $this->getInt('max_entries') < count($entries)) {
198
					$message = __("'%s' can not contains more than %s entries.", array($this->get('label'), $this->getInt('max_entries')));
199
					return self::__INVALID_FIELDS__;
200
				}
201
			}
202
			
203
			return self::__OK__;
204
		}
205
206
207
		/**
208
		 *
209
		 * Process data before saving into database.
210
		 *
211
		 * @param array $data
212
		 * @param int $status
213
		 * @param boolean $simulate
214
		 * @param int $entry_id
215
		 *
216
		 * @return Array - data to be inserted into DB
217
		 */
218
		public function processRawFieldData($data, &$status, &$message = null, $simulate = false, $entry_id = null)
219
		{
220
			$status = self::__OK__;
221
			$entries = null;
222
			
223
			if (!is_array($data) && !is_string($data)) {
224
				return null;
225
			}
226
			
227
			if (isset($data['entries'])) {
228
				$entries = $data['entries'];
229
			}
230
			else if (is_string($data)) {
231
				$entries = $data;
232
			}
233
			
234
			$row = array(
235
				'entries' => $entries
236
			);
237
			
238
			// return row
239
			return $row;
240
		}
241
242
		/**
243
		 * This function permits parsing different field settings values
244
		 *
245
		 * @param array $settings
246
		 *	the data array to initialize if necessary.
247
		 */
248
		public function setFromPOST(Array $settings = array())
249
		{
250
			// call the default behavior
251
			parent::setFromPOST($settings);
252
253
			// declare a new setting array
254
			$new_settings = array();
255
256
			// set new settings
257
			$new_settings['sections'] = is_array($settings['sections']) ? 
258
				implode(self::SEPARATOR, $settings['sections']) : 
259
				(is_string($settings['sections']) ? $settings['sections'] : null);
260
				
261
			$new_settings['show_association'] = $settings['show_association'] == 'yes' ? 'yes' : 'no';
262
			$new_settings['deepness'] = General::intval($settings['deepness']);
263
			$new_settings['deepness'] = $new_settings['deepness'] < 1 ? null : $new_settings['deepness'];
264
			$new_settings['elements'] = empty($settings['elements']) ? null : $settings['elements'];
265
			$new_settings['mode'] = empty($settings['mode']) ? null : $settings['mode'];
266
			$new_settings['mode_table'] = empty($settings['mode_table']) ? null : $settings['mode_table'];
267
			$new_settings['mode_header'] = empty($settings['mode_header']) ? null : $settings['mode_header'];
268
			$new_settings['mode_footer'] = empty($settings['mode_footer']) ? null : $settings['mode_footer'];
269
			$new_settings['allow_new'] = $settings['allow_new'] == 'yes' ? 'yes' : 'no';
270
			$new_settings['allow_edit'] = $settings['allow_edit'] == 'yes' ? 'yes' : 'no';
271
			$new_settings['allow_link'] = $settings['allow_link'] == 'yes' ? 'yes' : 'no';
272
			$new_settings['allow_delete'] = $settings['allow_delete'] == 'yes' ? 'yes' : 'no';
273
			$new_settings['allow_collapse'] = $settings['allow_collapse'] == 'yes' ? 'yes' : 'no';
274
			$new_settings['allow_search'] = $settings['allow_search'] == 'yes' ? 'yes' : 'no';
275
			$new_settings['show_header'] = $settings['show_header'] == 'yes' ? 'yes' : 'no';
276
			
277
			// save it into the array
278
			$this->setArray($new_settings);
279
		}
280
281
282
		/**
283
		 *
284
		 * Validates the field settings before saving it into the field's table
285
		 */
286
		public function checkFields(Array &$errors, $checkForDuplicates = true)
287
		{
288
			$parent = parent::checkFields($errors, $checkForDuplicates);
289
			if ($parent != self::__OK__) {
290
				return $parent;
291
			}
292
			
293
			$sections = $this->get('sections');
294
			
295
			if (empty($sections)) {
296
				$errors['sections'] = __('At least one section must be chosen');
297
			}
298
299
			return (!empty($errors) ? self::__ERROR__ : self::__OK__);
300
		}
301
302
		/**
303
		 *
304
		 * Save field settings into the field's table
305
		 */
306
		public function commit()
307
		{
308
			// if the default implementation works...
309
			if(!parent::commit()) return false;
310
			
311
			$id = $this->get('id');
312
			
313
			// exit if there is no id
314
			if($id == false) return false;
315
			
316
			// we are the child, with multiple parents
317
			$child_field_id = $id;
318
			
319
			// delete associations, only where we are the child
320
			self::removeSectionAssociation($child_field_id);
321
			
322
			$sections = $this->getSelectedSectionsArray();
323
			
324
			foreach ($sections as $key => $sectionId) {
325
				if (empty($sectionId)) {
326
					continue;
327
				}
328
				$parent_section_id = General::intval($sectionId);
329
				if ($parent_section_id < 1) {
330
					// section not found, bail out
331
					continue;
332
				}
333
				$parent_section = SectionManager::fetch($parent_section_id);
334
				if (!$parent_section) {
335
					// section not found, bail out
336
					continue;
337
				}
338
				$fields = $parent_section->fetchVisibleColumns();
339
				if (empty($fields)) {
340
					// no visible field, revert to all
341
					$fields = $parent_section->fetchFields();
342
				}
343
				if (empty($fields)) {
344
					// no fields found, bail out
345
					continue;
346
				}
347
				$parent_field_id = current(array_keys($fields));
348
				// create association
349
				SectionManager::createSectionAssociation(
350
					$parent_section_id,
351
					$child_field_id,
352
					$parent_field_id,
353
					$this->get('show_association') == 'yes'
354
				);
355
			}
356
			
357
			// declare an array contains the field's settings
358
			$settings = array(
359
				'sections' => $this->get('sections'),
360
				'show_association' => $this->get('show_association'),
361
				'deepness' => $this->get('deepness'),
362
				'elements' => $this->get('elements'),
363
				'mode' => $this->get('mode'),
364
				'mode_table' => $this->get('mode_table'),
365
				'mode_header' => $this->get('mode_header'),
366
				'mode_footer' => $this->get('mode_footer'),
367
				'min_entries' => $this->get('min_entries'),
368
				'max_entries' => $this->get('max_entries'),
369
				'allow_new' => $this->get('allow_new'),
370
				'allow_edit' => $this->get('allow_edit'),
371
				'allow_link' => $this->get('allow_link'),
372
				'allow_delete' => $this->get('allow_delete'),
373
				'allow_collapse' => $this->get('allow_collapse'),
374
				'allow_search' => $this->get('allow_search'),
375
				'show_header' => $this->get('show_header'),
376
			);
377
378
			return FieldManager::saveSettings($id, $settings);
379
		}
380
381
		/**
382
		 *
383
		 * This function allows Fields to cleanup any additional things before it is removed
384
		 * from the section.
385
		 * @return boolean
386
		 */
387
		public function tearDown()
388
		{
389
			self::removeSectionAssociation($this->get('id'));
390
			return parent::tearDown();
391
		}
392
		
393
		/**
394
		 * Generates the where filter for searching by entry id
395
		 *
396
		 * @param string $value
397
		 * @param @optional string $col
398
		 * @param @optional boolean $andOperation
399
		 */
400
		public function generateWhereFilter($value, $col = 'd', $andOperation = true)
401
		{
402
			$junction = $andOperation ? 'AND' : 'OR';
403
			if (!$value) {
404
				return "{$junction} (`{$col}`.`entries` IS NULL)";
405
			}
406
			return " {$junction} (`{$col}`.`entries` = '{$value}' OR 
407
					`{$col}`.`entries` LIKE '{$value},%' OR 
408
					`{$col}`.`entries` LIKE '%,{$value}' OR 
409
					`{$col}`.`entries` LIKE '%,{$value},%')";
410
		}
411
412
		/**
413
		 * Fetch the number of associated entries for a particular entry id
414
		 *
415
		 * @param string $value
416
		 */
417
		public function fetchAssociatedEntryCount($value)
418
		{
419
			if (!$value) {
420
				return 0;
421
			}
422
			$join = sprintf(" INNER JOIN `tbl_entries_data_%s` AS `d` ON `e`.id = `d`.`entry_id`", $this->get('id'));
423
			$where = $this->generateWhereFilter($value);
424
			
425
			$entries = EntryManager::fetch(null, $this->get('parent_section'), null, 0, $where, $join, false, false, array());
426
			
427
			return count($entries);
428
		}
429
		
430
		public function fetchAssociatedEntrySearchValue($data, $field_id = null, $parent_entry_id = null)
431
		{
432
			return $parent_entry_id;
433
		}
434
		
435
		public function findRelatedEntries($entry_id, $parent_field_id)
436
		{
437
			$joins = '';
438
			$where = '';
439
			$this->buildDSRetrievalSQL(array($entry_id), $joins, $where, true);
440
			
441
			$entries = EntryManager::fetch(null, $this->get('parent_section'), null, 0, $where, $joins, false, false, array());
442
			
443
			return array_map(function ($e) {
444
				return $e['id'];
445
			}, $entries);
446
		}
447
		
448
		public function findParentRelatedEntries($parent_field_id, $entry_id)
449
		{
450
			$entry = $this->fetchEntry($entry_id, array($this->get('label')));
451
			
452
			if (!$entry) {
453
				return array();
454
			}
455
			
456
			return self::getEntries($entry->getData($this->get('id')));
457
		}
458
459
		public function fetchFilterableOperators()
460
		{
461
			return array(
462
				array(
463
					'title' => 'links to',
464
					'filter' => ' ',
465
					'help' => __('Find entries that links to the specified filter')
466
				),
467
			);
468
		}
469
470
		public function fetchSuggestionTypes()
471
		{
472
			return array('association');
473
		}
474
475
		public function fetchIDsfromValue($value)
476
		{
477
			$ids = array();
478
			$sections = $this->getArray('sections');
479
			//$value = Lang::createHandle($value);
480
481
			foreach ($sections as $sectionId) {
482
				$section = $this->sectionManager->fetch($sectionId);
483
				if (!$section) {
484
					continue;
485
				}
486
				$filterableFields = $section->fetchFilterableFields();
487
				if (empty($filterableFields)) {
488
					continue;
489
				}
490
				foreach ($filterableFields as $fId => $field) {
491
					$joins = '';
492
					$where = '';
493
					if ($field instanceof FieldRelationship) {
494
						continue;
495
					}
496
					$field->buildDSRetrievalSQL(array($value), $joins, $where, false);
497
					$fEntries = $this->entryManager->fetch(null, $sectionId, null, null, $where, $joins, true, false, null, false);
498
					if (!empty($fEntries)) {
499
						$ids = array_merge($ids, $fEntries);
500
						break;
501
					}
502
				}
503
			}
504
505
			return array_map(function ($e) {
506
				return $e['id'];
507
			}, $ids);
508
		}
509
510
		public function prepareAssociationsDrawerXMLElement(Entry $e, array $parent_association, $prepolutate = '')
511
		{
512
			$currentSection = SectionManager::fetch($parent_association['child_section_id']);
513
			$visibleCols = $currentSection->fetchVisibleColumns();
514
			$outputFieldId = current(array_keys($visibleCols));
515
			$outputField = FieldManager::fetch($outputFieldId);
516
			
517
			$value = $outputField->prepareReadableValue($e->getData($outputFieldId), $e->get('id'), true, __('None'));
518
			
519
			$li = new XMLElement('li');
520
			$li->setAttribute('class', 'field-' . $this->get('type'));
521
			$a = new XMLElement('a', strip_tags($value));
522
			$a->setAttribute('href', SYMPHONY_URL . '/publish/' . $parent_association['handle'] . '/edit/' . $e->get('id') . '/');
523
			$li->appendChild($a);
524
525
			return $li;
526
		}
527
		
528
		/**
529
		 * @param string $joins
530
		 * @param string $where
531
		 */
532
		public function buildDSRetrievalSQL($data, &$joins, &$where, $andOperation = false)
533
		{
534
			$field_id = $this->get('id');
535
			
536
			// REGEX filtering is a special case, and will only work on the first item
537
			// in the array. You cannot specify multiple filters when REGEX is involved.
538
			if (self::isFilterRegex($data[0])) {
539
				return $this->buildRegexSQL($data[0], array('entries'), $joins, $where);
540
			}
541
			
542
			$this->_key++;
543
			
544
			$where .= ' AND (1=' . ($andOperation ? '1' : '0') . ' ';
545
			
546
			$joins .= "
547
				INNER JOIN
548
					`tbl_entries_data_{$field_id}` AS `t{$field_id}_{$this->_key}`
549
					ON (`e`.`id` = `t{$field_id}_{$this->_key}`.`entry_id`)
550
			";
551
			
552
			$normalizedValues = array();
553
			foreach ($data as $value) {
554
				if (!is_numeric($value) && !is_null($value)) {
555
					$normalizedValues = array_merge($normalizedValues, $this->fetchIDsfromValue($value));
556
				} else {
557
					$normalizedValues[] = $value;
558
				}
559
			}
560
			
561
			foreach ($normalizedValues as $value) {
562
				$where .= $this->generateWhereFilter($this->cleanValue($value), "t{$field_id}_{$this->_key}", $andOperation);
563
			}
564
			
565
			$where .= ')';
566
			
567
			return true; // this tells the DS Manager that filters are OK!!
568
		}
569
570
		/* ******* EVENTS ******* */
571
572
		public function getExampleFormMarkup()
573
		{
574
			$label = Widget::Label($this->get('label'));
575
			$label->appendChild(Widget::Input('fields['.$this->get('element_name').'][entries]', null, 'hidden'));
576
577
			return $label;
578
		}
579
580
581
		/* ******* DATA SOURCE ******* */
582
		
583
		private function fetchEntry($eId, $elements = array())
584
		{
585
			$entry = EntryManager::fetch($eId, null, 1, 0, null, null, false, true, $elements, false);
586
			if (!is_array($entry) || count($entry) !== 1) {
587
				return null;
588
			}
589
			return $entry[0];
590
		}
591
		
592
		protected function fetchAllIncludableElements()
593
		{
594
			$sections = $this->getArray('sections');
595
			return $allElements = array_reduce($this->sectionInfos->fetch($sections), function ($memo, $item) {
596
				return array_merge($memo, array_map(function ($field) use ($item) {
597
					return $item['handle'] . '.' . $field['handle'];
598
				}, $item['fields']));
599
			}, array());
600
		}
601
		
602
		public function fetchIncludableElements()
603
		{
604
			$label = $this->get('element_name');
605
			$elements = $this->getArray('elements');
606
			if (empty($elements)) {
607
				$elements = array('*');
608
			}
609
			$includedElements = array();
610
			// Include everything
611
			if ($this->expandIncludableElements) {
612
				$includedElements[] = $label . ': *';
613
			}
614
			// Include individual elements
615
			foreach ($elements as $elem) {
616
				$elem = trim($elem);
617
				if ($elem !== '*') {
618
					$includedElements[] = $label . ': ' . $elem;
619
				} else if ($this->expandIncludableElements) {
620
					$includedElements = array_unique(array_merge($includedElements, array_map(function ($item) use ($label) {
621
						return $label . ': ' . $item;
622
					}, $this->fetchAllIncludableElements())));
623
				} else {
624
					$includedElements = array('*');
625
				}
626
			}
627
			// Include system dates
628
			$includedElements[] = $label . ': system:creation-date';
629
			$includedElements[] = $label . ': system:modification-date';
630
			return $includedElements;
631
		}
632
		
633
		/**
634
		 * Appends data into the XML tree of a Data Source
635
		 * @param $wrapper
636
		 * @param $data
637
		 */
638
		public function appendFormattedElement(XMLElement &$wrapper, $data, $encode = false, $mode = null, $entry_id = null)
639
		{
640
			if (!is_array($data) || empty($data)) {
641
				return;
642
			}
643
644
			// try to find an existing root
645
			$root = null;
646
			$newRoot = false;
647
			foreach (array_reverse($wrapper->getChildren()) as $xmlField) {
648
				if ($xmlField->getName() === $this->get('element_name')) {
649
					$root = $xmlField;
650
					break;
651
				}
652
			}
653
654
			// root was not found, create one
655
			if (!$root) {
656
				$root = new XMLElement($this->get('element_name'));
657
				$newRoot = true;
658
			}
659
			
660
			// devkit will load
661
			$devkit = isset($_GET['debug']) && (empty($_GET['debug']) || $_GET['debug'] == 'xml');
662
			
663
			// selected items
664
			$entries = static::getEntries($data);
665
			
666
			// current linked entries
667
			$root->setAttribute('entries', $data['entries']);
668
			
669
			// available sections
670
			$root->setAttribute('sections', $this->get('sections'));
671
			
672
			// included elements
673
			$elements = static::parseElements($this);
674
			
675
			// DS mode
676
			if (!$mode) {
677
				$mode = '*';
678
			}
679
			
680
			$parentDeepness = General::intval($this->recursiveDeepness);
681
			$deepness = General::intval($this->get('deepness'));
682
			
683
			// both deepnesses are defined and parent restricts more
684
			if ($parentDeepness > 0 && $deepness > 0 && $parentDeepness < $deepness) {
685
				$deepness = $parentDeepness;
686
			}
687
			// parent is defined, current is not
688
			else if ($parentDeepness > 0 && $deepness < 1) {
689
				$deepness = $parentDeepness;
690
			}
691
			
692
			// cache recursive level because recursion might
693
			// change its value later on.
694
			$recursiveLevel = $this->recursiveLevel;
695
			
696
			// build entries
697
			foreach ($entries as $eId) {
698
				// try to find and existing item
699
				// TODO: keep last index found since it should be the next
700
				$item = null;
701
				$newItem = false;
702
				foreach ($root->getChildren() as $xmlItem) {
703
					if (General::intval($xmlItem->getAttribute('id')) === General::intval($eId)) {
704
						$item = $xmlItem;
705
						break;
706
					}
707
				}
708
709
				// item was not found, create one
710
				if (!$item) {
711
					$item = new XMLElement('item');
712
					$item->setAllowEmptyAttributes(false);
713
					// output id
714
					$item->setAttribute('id', $eId);
715
					// output recursive level
716
					$item->setAttribute('level', $recursiveLevel);
717
					$item->setAttribute('max-level', $deepness);
718
					$newItem = true;
719
				// item was found, but it is an error, so we can skip it
720
				} else if ($item->getName() === 'error') {
721
					continue;
722
				}
723
724
				// max recursion check
725
				if ($deepness < 1 || $recursiveLevel < $deepness) {
726
					// current entry, without data
727
					$entry = $this->fetchEntry($eId);
728
					
729
					// entry not found...
730
					if (!$entry || empty($entry)) {
731
						$error = new XMLElement('error');
732
						$error->setAttribute('id', $eId);
733
						$error->setValue(__('Error: entry `%s` not found', array($eId)));
734
						$root->prependChild($error);
735
						continue;
736
					}
737
					
738
					// fetch section infos
739
					$sectionId = $entry->get('section_id');
740
					$section = $this->sectionManager->fetch($sectionId);
741
					$sectionName = $section->get('handle');
742
					// cache fields info
743
					if (!isset($section->er_field_cache)) {
744
						$section->er_field_cache = $section->fetchFields();
745
					}
746
					
747
					// set section related attributes
748
					$item->setAttribute('section-id', $sectionId);
749
					$item->setAttribute('section', $sectionName);
750
					
751
					// Get the valid elements for this section only
752
					$validElements = $elements[$sectionName];
753
					
754
					// adjust the mode for the current section
755
					$curMode = $mode;
756
					
757
					// remove section name from current mode so "sectionName.field" becomes simply "field"
758
					if (preg_match('/^(' . $sectionName . '\.)(.*)$/sU', $curMode)) {
759
						$curMode = preg_replace('/^' . $sectionName . '\./sU', '', $curMode);
760
					}
761
					// remove section name from current mode "sectionName" and
762
					// treat it like if it is "sectionName: *"
763
					else if (preg_match('/^(' . $sectionName . ')$/sU', $curMode)) {
764
						$curMode = '*';
765
					}
766
					// checks if the mode is the current field
767
					// treat it like if is is "*"
768
					elseif ($curMode === $this->get('element_name')) {
769
						$curMode = '*';
770
					}
771
					// section name was not found in mode, check if the mode is "*"
772
					else if ($curMode !== '*') {
773
						// mode forbids this section
774
						$validElements = null;
775
					}
776
					
777
					// special case for system:creation-date
778
					if (preg_match('/^system:\s*creation-date$/sU', $curMode)) {
779
						$item->appendChild(
780
							General::createXMLDateObject(
781
								DateTimeObj::get('U', $entry->get('creation_date')),
782
								'created'
783
							)
784
						);
785
						continue;
786
					} elseif (preg_match('/^system:\s*modification-date$/sU', $curMode)) {
787
						$item->appendChild(
788
							General::createXMLDateObject(
789
								DateTimeObj::get('U', $entry->get('modification_date')),
790
								'modified'
791
							)
792
						);
793
						continue;
794
					}
795
					
796
					// this section is not selected, bail out
797 View Code Duplication
					if (!is_array($validElements)) {
798
						if ($newItem) {
799
							if ($devkit) {
800
								$item->setAttribute('x-forbidden-by-ds', $curMode);
801
							}
802
							$root->appendChild($item);
803
						}
804
						continue;
805
					} else {
806
						if ($devkit) {
807
							$item->setAttribute('x-forbidden-by-ds', null);
808
						}
809
					}
810
					
811
					// selected fields for fetching
812
					$sectionElements = array();
813
					
814
					// everything is allowed
815
					if (in_array('*', $validElements)) {
816
						if ($curMode !== '*') {
817
							// get only the mode
818
							$sectionElements = array($curMode);
819
						}
820
						else {
821
							// setting null = get all
822
							$sectionElements = null;
823
						}
824
					}
825
					// only use valid elements
826
					else {
827
						if ($curMode !== '*') {
828
							// is this field allowed ?
829
							if (self::isFieldIncluded($curMode, $validElements)) {
830
								// get only the mode
831
								$sectionElements = array($curMode);
832
							}
833
							else {
834
								// $curMode selects something outside of
835
								// the valid elements: select nothing
836
								$sectionElements = array();
837
							}
838
						}
839
						else {
840
							// use field's valid elements
841
							$sectionElements = $validElements;
842
						}
843
					}
844
					
845
					// Filtering is enabled, but nothing is selected
846 View Code Duplication
					if (is_array($sectionElements) && empty($sectionElements)) {
847
						if ($newItem) {
848
							$root->appendChild($item);
849
							if ($devkit) {
850
								$item->setAttribute('x-forbidden-by-selection', $curMode);
851
							}
852
						}
853
						continue;
854
					} else {
855
						if ($devkit) {
856
							$item->setAttribute('x-forbidden-by-selection', null);
857
						}
858
					}
859
					
860
					// fetch current entry again, but with data for the allowed schema
861
					$entry = $this->fetchEntry($eId, $sectionElements);
862
					
863
					// cache the entry data
864
					$entryData = $entry->getData();
865
					
866
					// for each field returned for this entry...
867
					foreach ($entryData as $fieldId => $data) {
868
						$filteredData = array_filter($data, function ($value) {
869
							return $value != null;
870
						});
871
						
872
						if (empty($filteredData)) {
873
							continue;
874
						}
875
						
876
						$field = $section->er_field_cache[$fieldId];
877
						$fieldName = $field->get('element_name');
878
						
879
						$submodes = null;
880
						if ($curMode === '*') {
881
							$submodes = self::getAllSelectedFieldModes($fieldName, $validElements);
882
						} else {
883
							$submodes = array($curMode);
884
						}
885
						
886
						// Special treatments for ERF
887
						if ($field instanceof FieldEntry_relationship) {
888
							// Increment recursive level
889
							$field->recursiveLevel = $recursiveLevel + 1;
890
							$field->recursiveDeepness = $deepness;
891
						}
892
						
893
						// current selection does not specify a mode
894
						if ($submodes === null) {
895
							if ($field instanceof FieldEntry_Relationship) {
896
								$field->expandIncludableElements = false;
897
							}
898
							$submodes = array_map(function ($fieldIncludableElement) use ($fieldName) {
899
								return FieldEntry_relationship::extractMode($fieldName, $fieldIncludableElement);
900
							}, $field->fetchIncludableElements());
901
							if ($field instanceof FieldEntry_Relationship) {
902
								$field->expandIncludableElements = true;
903
							}
904
						} elseif (empty($submodes)) {
905
							if ($devkit) {
906
								$item->setAttribute('x-selection-mode-empty', $fieldName);
907
							}
908
						}
909
						
910
						// Append the formatted element for each requested mode
911
						foreach ($submodes as $submode) {
912
							$field->appendFormattedElement($item, $data, $encode, $submode, $eId);
913
						}
914
					}
915
					// output current mode
916
					$item->setAttribute('matched-element', $curMode);
917
					// no field selected
918
					if (is_array($sectionElements) && empty($sectionElements)) {
919
						$item->setAttribute('empty-selection', 'yes');
920
					}
921
				} // end max recursion check
922
923
				if ($newItem) {
924
					// append item when done
925
					$root->appendChild($item);
926
				}
927
			} // end each entries
928
929
			if ($newRoot) {
930
				// output mode for this field
931
				if ($devkit) {
932
					$root->setAttribute('x-data-source-mode', $mode);
933
					$root->setAttribute('x-field-included-elements', $this->get('elements'));
934
				}
935
				// add all our data to the wrapper;
936
				$wrapper->appendChild($root);
937
			} else {
938
				if ($devkit) {
939
					$root->setAttribute('x-data-source-mode', $root->getAttribute('x-data-source-mode') . ', ' . $mode);
940
				}
941
			}
942
943
			// clean up
944
			$this->recursiveLevel = 1;
945
			$this->recursiveDeepness = null;
946
		}
947
948
		public function getParameterPoolValue(array $data, $entry_id = null)
949
		{
950
			if(!is_array($data) || empty($data)) return;
951
			return static::getEntries($data);
952
		}
953
954
		/* ********* Utils *********** */
955
		
956
		/**
957
		 * Return true if $fieldName is allowed in $sectionElements
958
		 * @param string $fieldName
959
		 * @param string $sectionElements
960
		 * @return bool
961
		 */
962
		public static function isFieldIncluded($fieldName, $sectionElements)
963
		{
964
			$modes = self::getAllSelectedFieldModes($fieldName, $sectionElements);
965
			return $modes === null || !empty($modes);
966
		}
967
968
		/**
969
		 * Returns all selected modes allowed in $sectionElements or null if they are all allowed
970
		 * @param string $fieldName
971
		 * @param string $sectionElements
972
		 * @return array|null
973
		 */
974
		public static function getAllSelectedFieldModes($fieldName, $sectionElements)
975
		{
976
			$elements = array();
977
			if (is_array($sectionElements)) {
978
				foreach ($sectionElements as $element) {
979
					// everything is allowed, use "fieldName" directly
980
					if ($element === '*' || $fieldName === $element) {
981
						return null;
982
					}
983
					// make "fieldName: *" the same as "fieldName"
984
					if (preg_match('/\s*:\s*\*/sU', $fieldName)) {
985
						$fieldName = trim(current(explode(':', $fieldName)));
986
					}
987
					// "fieldName" is included as-is
988
					if ($fieldName === $element) {
989
						return null;
990
					}
991
992
					// element starts with "fieldName:"
993
					if (preg_match('/^' . $fieldName . '\s*:/sU', $element)) {
994
						$elements[] = self::extractMode($fieldName, $element);
995
					}
996
				}
997
				if (!empty($elements)) {
998
					return $elements;
999
				}
1000
			}
1001
			return $elements;
1002
		}
1003
		
1004
		public static function parseElements($field)
1005
		{
1006
			$elements = array();
1007
			$exElements = $field->getArray('elements');
1008
			
1009
			if (in_array('*', $exElements)) {
1010
				$sections = $field->getArray('sections');
1011
				$sections = SectionManager::fetch($sections);
1012
				return array_reduce($sections, function ($result, $section) {
1013
					$result[$section->get('handle')] = array('*');
1014
					return $result;
1015
				}, array());
1016
			}
1017
			
1018
			foreach ($exElements as $value) {
1019
				if (!$value) {
1020
					continue;
1021
				}
1022
				// sectionName.fieldName or sectionName.*
1023
				$parts = array_map(trim, explode('.', $value, 2));
1024
				// first time seeing this section
1025
				if (!isset($elements[$parts[0]])) {
1026
					$elements[$parts[0]] = array();
1027
				}
1028
				// we have a value after the dot
1029
				if (isset($parts[1]) && !!$parts[1]) {
1030
					$elements[$parts[0]][] = $parts[1];
1031
				}
1032
				// sectionName only
1033
				else if (!isset($parts[1])) {
1034
					$elements[$parts[0]][] = '*';
1035
				}
1036
			}
1037
			
1038
			return $elements;
1039
		}
1040
1041
		public static function extractMode($fieldName, $mode)
1042
		{
1043
			$pattern = '/^' . $fieldName . '\s*:\s*/s';
1044
			if (!preg_match($pattern, $mode)) {
1045
				return null;
1046
			}
1047
			$mode = preg_replace($pattern, '', $mode, 1);
1048
			if ($mode === '*') {
1049
				return null;
1050
			}
1051
			return $mode;
1052
		}
1053
1054
		private function buildSectionSelect($name)
1055
		{
1056
			$sections = SectionManager::fetch();
1057
			$options = array();
1058
			$selectedSections = $this->getSelectedSectionsArray();
1059
			
1060 View Code Duplication
			foreach ($sections as $section) {
1061
				$driver = $section->get('id');
1062
				$selected = in_array($driver, $selectedSections);
1063
				$options[] = array($driver, $selected, General::sanitize($section->get('name')));
1064
			}
1065
			
1066
			return Widget::Select($name, $options, array('multiple' => 'multiple'));
1067
		}
1068
1069 View Code Duplication
		private function appendSelectionSelect(&$wrapper)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
1070
		{
1071
			$name = $this->createSettingsFieldName('sections', true);
1072
1073
			$input = $this->buildSectionSelect($name);
1074
			$input->setAttribute('class', 'entry_relationship-sections');
1075
1076
			$label = Widget::Label();
1077
			$label->setAttribute('class', 'column');
1078
1079
			$label->setValue(__('Available sections %s', array($input->generate())));
1080
1081
			$wrapper->appendChild($label);
1082
		}
1083
1084
		private function createEntriesHiddenInput($data)
1085
		{
1086
			$hidden = new XMLElement('input', null, array(
1087
				'type' => 'hidden',
1088
				'name' => $this->createPublishFieldName('entries'),
1089
				'value' => $data['entries']
1090
			));
1091
			
1092
			return $hidden;
1093
		}
1094
		
1095
		private function createActionBarMenu($sections)
1096
		{
1097
			$wrap = new XMLElement('div');
1098
			$actionBar = '';
1099
			$modeFooter = $this->get('mode_footer');
1100
			if ($modeFooter) {
1101
				$section = $this->sectionManager->fetch($this->get('parent_section'));
1102
				$actionBar = ERFXSLTUTilities::processXSLT($this, null, $section->get('handle'), null, 'mode_footer', isset($_REQUEST['debug']), 'field');
1103
			}
1104
			if (empty($actionBar)) {
1105
				$fieldset = new XMLElement('fieldset');
1106
				$fieldset->setAttribute('class', 'single');
1107
				if ($this->is('allow_search')) {
1108
					$searchWrap = new XMLElement('div');
1109
					$searchWrap->setAttribute('data-interactive', 'data-interactive');
1110
					$searchWrap->setAttribute('class', 'search');
1111
					$searchInput = Widget::Input('', null, 'text', array(
1112
						'class' => 'search',
1113
						'data-search' => '',
1114
						'placeholder' => __('Search for entries')
1115
					));
1116
					$searchWrap->appendChild($searchInput);
1117
					$searchSuggestions = new XMLElement('ul');
1118
					$searchSuggestions->setAttribute('class', 'suggestions');
1119
					$searchWrap->appendChild($searchSuggestions);
1120
					$fieldset->appendChild($searchWrap);
1121
				}
1122
				
1123
				if ($this->is('allow_new') || $this->is('allow_link') || $this->is('allow_search')) {
1124
					$selectWrap = new XMLElement('div');
1125
					$selectWrap->appendChild(new XMLElement('span', __('Related section: '), array('class' => 'sections-selection')));
1126
					$options = array();
1127
					foreach ($sections as $section) {
1128
						$options[] = array($section->get('handle'), false, $section->get('name'));
1129
					}
1130
					$select = Widget::Select('', $options, array('class' => 'sections sections-selection'));
1131
					$selectWrap->appendChild($select);
1132
					$fieldset->appendChild($selectWrap);
1133
				}
1134 View Code Duplication
				if ($this->is('allow_new')) {
1135
					$fieldset->appendChild(new XMLElement('button', __('Create new'), array(
1136
						'type' => 'button',
1137
						'class' => 'create',
1138
						'data-create' => '',
1139
					)));
1140
				}
1141 View Code Duplication
				if ($this->is('allow_link')) {
1142
					$fieldset->appendChild(new XMLElement('button', __('Link to entry'), array(
1143
						'type' => 'button',
1144
						'class' => 'link',
1145
						'data-link' => '',
1146
					)));
1147
				}
1148
				$wrap->appendChild($fieldset);
1149
			}
1150
			else {
1151
				$wrap->setValue($actionBar);
1152
			}
1153
			
1154
			return $wrap;
1155
		}
1156
1157
		/* ********* UI *********** */
1158
		
1159
		/**
1160
		 *
1161
		 * Builds the UI for the field's settings when creating/editing a section
1162
		 * @param XMLElement $wrapper
1163
		 * @param array $errors
1164
		 */
1165
		public function displaySettingsPanel(XMLElement &$wrapper, $errors=null)
1166
		{
1167
			/* first line, label and such */
1168
			parent::displaySettingsPanel($wrapper, $errors);
1169
			
1170
			// sections
1171
			$sections = new XMLElement('fieldset');
1172
			
1173
			$this->appendSelectionSelect($sections);
1174 View Code Duplication
			if (is_array($errors) && isset($errors['sections'])) {
1175
				$sections = Widget::Error($sections, $errors['sections']);
1176
			}
1177
			$wrapper->appendChild($sections);
1178
			
1179
			// elements
1180
			$elements = new XMLElement('div');
1181
			$element = Widget::Label();
1182
			$element->setValue(__('Included elements in Data Sources and Backend Templates'));
1183
			$element->setAttribute('class', 'column');
1184
			$element->appendChild(Widget::Input($this->createSettingsFieldName('elements'), $this->get('elements'), 'text', array(
1185
				'class' => 'entry_relationship-elements'
1186
			)));
1187
			$elements->appendChild($element);
1188
			$elements_choices = new XMLElement('ul', null, array('class' => 'tags singular entry_relationship-field-choices'));
1189
			$elements->appendChild($elements_choices);
1190
			$wrapper->appendChild($elements);
1191
			
1192
			// limit entries
1193
			$limits = new XMLElement('fieldset');
1194
			$limits->appendChild(new XMLElement('legend', __('Limits')));
1195
			$limits_cols = new XMLElement('div');
1196
			$limits_cols->setAttribute('class', 'three columns');
1197
			// min
1198
			$limit_min = Widget::Label();
1199
			$limit_min->setValue(__('Minimum count of entries in this field'));
1200
			$limit_min->setAttribute('class', 'column');
1201
			$limit_min->appendChild(Widget::Input($this->createSettingsFieldName('min_entries'), $this->get('min_entries'), 'number', array(
1202
				'min' => 0,
1203
				'max' => 99999
1204
			)));
1205
			$limits_cols->appendChild($limit_min);
1206
			// max
1207
			$limit_max = Widget::Label();
1208
			$limit_max->setValue(__('Maximum count of entries in this field'));
1209
			$limit_max->setAttribute('class', 'column');
1210
			$limit_max->appendChild(Widget::Input($this->createSettingsFieldName('max_entries'), $this->get('max_entries'), 'number', array(
1211
				'min' => 0,
1212
				'max' => 99999
1213
			)));
1214
			$limits_cols->appendChild($limit_max);
1215
			
1216
			// deepness
1217
			$deepness = Widget::Label();
1218
			$deepness->setValue(__('Maximum level of recursion in Data Sources'));
1219
			$deepness->setAttribute('class', 'column');
1220
			$deepness->appendChild(Widget::Input($this->createSettingsFieldName('deepness'), $this->get('deepness'), 'number', array(
1221
				'min' => 0,
1222
				'max' => 99
1223
			)));
1224
			$limits_cols->appendChild($deepness);
1225
			$limits->appendChild($limits_cols);
1226
			$wrapper->appendChild($limits);
1227
			
1228
			// xsl
1229
			$xsl = new XMLElement('fieldset');
1230
			$xsl->appendChild(new XMLElement('legend', __('Backend XSL templates options')));
1231
			$xsl_cols = new XMLElement('div');
1232
			$xsl_cols->setAttribute('class', 'four columns');
1233
			
1234
			// xsl mode
1235
			$xslmode = Widget::Label();
1236
			$xslmode->setValue(__('XSL mode for entries content template'));
1237
			$xslmode->setAttribute('class', 'column');
1238
			$xslmode->appendChild(Widget::Input($this->createSettingsFieldName('mode'), $this->get('mode'), 'text'));
1239
			$xsl_cols->appendChild($xslmode);
1240
			// xsl header mode
1241
			$xslmodetable = Widget::Label();
1242
			$xslmodetable->setValue(__('XSL mode for entries header template'));
1243
			$xslmodetable->setAttribute('class', 'column');
1244
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_header'), $this->get('mode_header'), 'text'));
1245
			$xsl_cols->appendChild($xslmodetable);
1246
			// xsl table mode
1247
			$xslmodetable = Widget::Label();
1248
			$xslmodetable->setValue(__('XSL mode for publish table value'));
1249
			$xslmodetable->setAttribute('class', 'column');
1250
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_table'), $this->get('mode_table'), 'text'));
1251
			$xsl_cols->appendChild($xslmodetable);
1252
			// xsl action bar mode
1253
			$xslmodetable = Widget::Label();
1254
			$xslmodetable->setValue(__('XSL mode for publish action bar'));
1255
			$xslmodetable->setAttribute('class', 'column');
1256
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_footer'), $this->get('mode_footer'), 'text'));
1257
			$xsl_cols->appendChild($xslmodetable);
1258
			
1259
			$xsl->appendChild($xsl_cols);
1260
			$wrapper->appendChild($xsl);
1261
			
1262
			// permissions
1263
			$permissions = new XMLElement('fieldset');
1264
			$permissions->appendChild(new XMLElement('legend', __('Permissions')));
1265
			$permissions_cols = new XMLElement('div');
1266
			$permissions_cols->setAttribute('class', 'four columns');
1267
			$permissions_cols->appendChild($this->createCheckbox('allow_new', 'Show new button'));
1268
			$permissions_cols->appendChild($this->createCheckbox('allow_edit', 'Show edit button'));
1269
			$permissions_cols->appendChild($this->createCheckbox('allow_link', 'Show link button'));
1270
			$permissions_cols->appendChild($this->createCheckbox('allow_delete', 'Show delete button'));
1271
			$permissions->appendChild($permissions_cols);
1272
			$wrapper->appendChild($permissions);
1273
			
1274
			// display options
1275
			$display = new XMLElement('fieldset');
1276
			$display->appendChild(new XMLElement('legend', __('Display options')));
1277
			$display_cols = new XMLElement('div');
1278
			$display_cols->setAttribute('class', 'four columns');
1279
			$display_cols->appendChild($this->createCheckbox('allow_collapse', 'Allow content collapsing'));
1280
			$display_cols->appendChild($this->createCheckbox('allow_search', 'Allow search linking'));
1281
			$display_cols->appendChild($this->createCheckbox('show_header', 'Show the header box before entries templates'));
1282
			$display->appendChild($display_cols);
1283
			$wrapper->appendChild($display);
1284
			
1285
			// assoc
1286
			$assoc = new XMLElement('fieldset');
1287
			$assoc->appendChild(new XMLElement('legend', __('Associations')));
1288
			$assoc_cols = new XMLElement('div');
1289
			$assoc_cols->setAttribute('class', 'three columns');
1290
			$this->appendShowAssociationCheckbox($assoc_cols);
1291
			$assoc->appendChild($assoc_cols);
1292
			$wrapper->appendChild($assoc);
1293
			
1294
			// footer
1295
			$this->appendStatusFooter($wrapper);
1296
		}
1297
1298
		/**
1299
		 *
1300
		 * Builds the UI for the publish page
1301
		 * @param XMLElement $wrapper
1302
		 * @param mixed $data
1303
		 * @param mixed $flagWithError
1304
		 * @param string $fieldnamePrefix
1305
		 * @param string $fieldnamePostfix
1306
		 */
1307
		public function displayPublishPanel(XMLElement &$wrapper, $data = null, $flagWithError = null, $fieldnamePrefix = null, $fieldnamePostfix = null, $entry_id = null)
1308
		{
1309
			$entriesId = array();
1310
			$sectionsId = $this->getSelectedSectionsArray();
1311
			
1312
			if ($data['entries'] != null) {
1313
				$entriesId = static::getEntries($data);
1314
			}
1315
			
1316
			$sectionsId = array_map(array('General', 'intval'), $sectionsId);
1317
			$sections = $this->sectionManager->fetch($sectionsId);
1318
			usort($sections, function ($a, $b) {
1319
				if ($a->get('name') === $b->get('name')) {
1320
					return 0;
1321
				}
1322
				return $a->get('name') < $b->get('name') ? -1 : 1;
1323
			});
1324
			
1325
			$label = Widget::Label($this->get('label'));
1326
			$notes = '';
1327
			
1328
			// min note
1329
			if ($this->getInt('min_entries') > 0) {
1330
				$notes .= __('Minimum number of entries: <b>%s</b>. ', array($this->get('min_entries')));
1331
			}
1332
			// max note
1333
			if ($this->getInt('max_entries') > 0) {
1334
				$notes .= __('Maximum number of entries: <b>%s</b>. ', array($this->get('max_entries')));
1335
			}
1336
			// not required note
1337
			if (!$this->isRequired()) {
1338
				$notes .= __('Optional');
1339
			}
1340
			// append notes
1341
			if ($notes) {
1342
				$label->appendChild(new XMLElement('i', $notes));
1343
			}
1344
			
1345
			// label error management
1346
			if ($flagWithError != null) {
1347
				$wrapper->appendChild(Widget::Error($label, $flagWithError));
1348
			} else {
1349
				$wrapper->appendChild($label);
1350
			}
1351
			
1352
			$wrapper->appendChild($this->createEntriesList($entriesId));
1353
			$wrapper->appendChild($this->createActionBarMenu($sections));
1354
			$wrapper->appendChild($this->createEntriesHiddenInput($data));
1355
			$wrapper->setAttribute('data-value', $data['entries']);
1356
			$wrapper->setAttribute('data-field-id', $this->get('id'));
1357
			$wrapper->setAttribute('data-field-label', $this->get('label'));
1358
			$wrapper->setAttribute('data-min', $this->get('min_entries'));
1359
			$wrapper->setAttribute('data-max', $this->get('max_entries'));
1360
			$wrapper->setAttribute('data-required', $this->get('required'));
1361
			if (isset($_REQUEST['debug'])) {
1362
				$wrapper->setAttribute('data-debug', true);
1363
			}
1364
		}
1365
1366
		/**
1367
		 *
1368
		 * Return a plain text representation of the field's data
1369
		 * @param array $data
1370
		 * @param int $entry_id
1371
		 */
1372
		public function prepareTextValue($data, $entry_id = null)
1373
		{
1374
			if ($entry_id == null || !is_array($data) || 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...
1375
				return '';
1376
			}
1377
			return $data['entries'];
1378
		}
1379
1380
		/**
1381
		 * Format this field value for display as readable text value.
1382
		 *
1383
		 * @param array $data
1384
		 *  an associative array of data for this string. At minimum this requires a
1385
		 *  key of 'value'.
1386
		 * @param integer $entry_id (optional)
1387
		 *  An option entry ID for more intelligent processing. Defaults to null.
1388
		 * @param string $defaultValue (optional)
1389
		 *  The value to use when no plain text representation of the field's data
1390
		 *  can be made. Defaults to null.
1391
		 * @return string
1392
		 *  the readable text summary of the values of this field instance.
1393
		 */
1394
		public function prepareReadableValue($data, $entry_id = null, $truncate = false, $defaultValue = 'None')
1395
		{
1396
			if ($entry_id == null || !is_array($data) || 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...
1397
				return __($defaultValue);
1398
			}
1399
			$entries = static::getEntries($data);
1400
			$realEntries = array();
1401
			foreach ($entries as $entryId) {
1402
				$e = EntryManager::fetch($entryId);
1403
				if (is_array($e) && !empty($e)) {
1404
					$realEntries = array_merge($realEntries, $e);
1405
				}
1406
			}
1407
			$count = count($entries);
1408
			$realCount = count($realEntries);
1409
			if ($count === $realCount) {
1410
				return self::formatCount($count);
1411
			}
1412
			return self::formatCount($realCount) . ' (' . self::formatCount($count - $realCount) . ' not found)';
1413
		}
1414
1415
		/**
1416
		 * Format this field value for display in the publish index tables.
1417
		 *
1418
		 * @param array $data
1419
		 *  an associative array of data for this string. At minimum this requires a
1420
		 *  key of 'value'.
1421
		 * @param XMLElement $link (optional)
1422
		 *  an XML link structure to append the content of this to provided it is not
1423
		 *  null. it defaults to null.
1424
		 * @param integer $entry_id (optional)
1425
		 *  An option entry ID for more intelligent processing. defaults to null
1426
		 * @return string
1427
		 *  the formatted string summary of the values of this field instance.
1428
		 */
1429
		public function prepareTableValue($data, XMLElement $link = null, $entry_id = null)
1430
		{
1431
			$value = $this->prepareReadableValue($data, $entry_id, false, __('None'));
1432
1433
			if ($entry_id && $this->get('mode_table')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entry_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1434
				$entries = static::getEntries($data);
1435
				$cellcontent = '';
1436
				foreach ($entries as $position => $child_entry_id) {
1437
					$entry = $this->entryManager->fetch($child_entry_id);
1438
					if (!$entry || !is_array($entry) || empty($entry)) {
1439
						continue;
1440
					}
1441
					reset($entry);
1442
					$entry = current($entry);
1443
					$section = $this->sectionManager->fetch($entry->get('section_id'));
1444
					$content = ERFXSLTUTilities::processXSLT($this, $entry, $section->get('handle'), $section->fetchFields(), 'mode_table', isset($_REQUEST['debug']), 'entry', $position + 1);
1445
					if ($content) {
1446
						$cellcontent .= $content;
1447
					}
1448
				}
1449
				
1450
				$cellcontent = trim($cellcontent);
1451
				
1452
				if (General::strlen($cellcontent)) {
1453
					if ($link) {
1454
						$link->setValue($cellcontent);
1455
						return $link->generate();
1456
					}
1457
					return $cellcontent;
1458
				}
1459
			} else if ($link) {
1460
				$link->setValue($value);
1461
				return $link->generate();
1462
			}
1463
1464
			return $value;
1465
		}
1466
1467
		/* ********* SQL Data Definition ************* */
1468
1469
		/**
1470
		 *
1471
		 * Creates table needed for entries of individual fields
1472
		 */
1473
		public function createTable()
1474
		{
1475
			$id = $this->get('id');
1476
1477
			return Symphony::Database()->query("
1478
				CREATE TABLE `tbl_entries_data_$id` (
1479
					`id` int(11) 		unsigned NOT NULL AUTO_INCREMENT,
1480
					`entry_id` 			int(11) unsigned NOT NULL,
1481
					`entries` 			text COLLATE utf8_unicode_ci NULL,
1482
					PRIMARY KEY  (`id`),
1483
					UNIQUE KEY `entry_id` (`entry_id`)
1484
				) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1485
			");
1486
		}
1487
1488
		/**
1489
		 * Creates the table needed for the settings of the field
1490
		 */
1491
		public static function createFieldTable()
1492
		{
1493
			$tbl = self::FIELD_TBL_NAME;
1494
1495
			return Symphony::Database()->query("
1496
				CREATE TABLE IF NOT EXISTS `$tbl` (
1497
					`id` 				int(11) unsigned NOT NULL AUTO_INCREMENT,
1498
					`field_id` 			int(11) unsigned NOT NULL,
1499
					`sections`			varchar(2048) NULL COLLATE utf8_unicode_ci,
1500
					`show_association` 	enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1501
					`deepness` 			int(2) unsigned NULL,
1502
					`elements` 			text COLLATE utf8_unicode_ci NULL,
1503
					`mode`				varchar(50) NULL COLLATE utf8_unicode_ci,
1504
					`mode_table`		varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL,
1505
					`mode_header`		varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL,
1506
					`mode_footer`		varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL,
1507
					`min_entries`		int(5) unsigned NULL,
1508
					`max_entries`		int(5) unsigned NULL,
1509
					`allow_edit` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1510
					`allow_new` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1511
					`allow_link` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1512
					`allow_delete` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no',
1513
					`allow_collapse` 	enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1514
					`allow_search` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no',
1515
					`show_header` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1516
					PRIMARY KEY (`id`),
1517
					UNIQUE KEY `field_id` (`field_id`)
1518
				) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1519
			");
1520
		}
1521
		
1522
		public static function update_102()
1523
		{
1524
			$tbl = self::FIELD_TBL_NAME;
1525
			$sql = "
1526
				ALTER TABLE `$tbl`
1527
					ADD COLUMN `allow_edit` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1528
					ADD COLUMN `allow_new` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1529
					ADD COLUMN `allow_link` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1530
					AFTER `max_entries`
1531
			";
1532
			$addColumns = Symphony::Database()->query($sql);
1533
			if (!$addColumns) {
1534
				return false;
1535
			}
1536
1537
			$fields = FieldManager::fetch(null, null, null, 'id', 'entry_relationship');
1538
			if (!empty($fields) && is_array($fields)) {
1539
				foreach ($fields as $fieldId => $field) {
1540
					$sql = "ALTER TABLE `tbl_entries_data_$fieldId` MODIFY `entries` TEXT";
1541
					if (!Symphony::Database()->query($sql)) {
1542
						throw new Exception(__('Could not update table `tbl_entries_data_%s`.', array($fieldId)));
1543
					}
1544
				}
1545
			}
1546
			return true;
1547
		}
1548
		
1549
		public static function update_103()
1550
		{
1551
			$tbl = self::FIELD_TBL_NAME;
1552
			$sql = "
1553
				ALTER TABLE `$tbl`
1554
					ADD COLUMN `allow_delete` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no'
1555
						AFTER `allow_link`
1556
			";
1557
			return Symphony::Database()->query($sql);
1558
		}
1559
		
1560
		public static function update_200()
1561
		{
1562
			$tbl = self::FIELD_TBL_NAME;
1563
			$sql = "
1564
				ALTER TABLE `$tbl`
1565
					ADD COLUMN `allow_collapse` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1566
						AFTER `allow_delete`,
1567
					ADD COLUMN `mode_table` varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL
1568
						AFTER `mode`,
1569
					ADD COLUMN `mode_header` varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL
1570
						AFTER `mode_table`,
1571
					ADD COLUMN `show_header` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1572
						AFTER `allow_collapse`,
1573
					ADD COLUMN `mode_footer` varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL
1574
						AFTER `mode_header`,
1575
					CHANGE `sections` `sections` varchar(2048) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
1576
					CHANGE `elements` `elements` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL
1577
			";
1578
			return Symphony::Database()->query($sql);
1579
		}
1580
		
1581
		public static function update_2008()
1582
		{
1583
			$tbl = self::FIELD_TBL_NAME;
1584
			$sql = "
1585
				ALTER TABLE `$tbl`
1586
					ADD COLUMN `allow_search` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no'
1587
						AFTER `allow_collapse`
1588
			";
1589
			return Symphony::Database()->query($sql);
1590
		}
1591
		
1592
		/**
1593
		 *
1594
		 * Drops the table needed for the settings of the field
1595
		 */
1596
		public static function deleteFieldTable()
1597
		{
1598
			$tbl = self::FIELD_TBL_NAME;
1599
			
1600
			return Symphony::Database()->query("
1601
				DROP TABLE IF EXISTS `$tbl`
1602
			");
1603
		}
1604
		
1605
		private static function removeSectionAssociation($child_field_id)
1606
		{
1607
			return Symphony::Database()->delete('tbl_sections_association', "`child_section_field_id` = {$child_field_id}");
1608
		}
1609
	}
1610