Completed
Push — dev ( 4d6df7...98cb6a )
by Nicolas
01:11
created

getAllSelectedFieldModes()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 8.0555
c 0
b 0
f 0
cc 9
nc 6
nop 2
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
			return $includedElements;
628
		}
629
		
630
		/**
631
		 * Appends data into the XML tree of a Data Source
632
		 * @param $wrapper
633
		 * @param $data
634
		 */
635
		public function appendFormattedElement(XMLElement &$wrapper, $data, $encode = false, $mode = null, $entry_id = null)
636
		{
637
			if (!is_array($data) || empty($data)) {
638
				return;
639
			}
640
641
			// try to find an existing root
642
			$root = null;
643
			$newRoot = false;
644
			foreach (array_reverse($wrapper->getChildren()) as $xmlField) {
645
				if ($xmlField->getName() === $this->get('element_name')) {
646
					$root = $xmlField;
647
					break;
648
				}
649
			}
650
651
			// root was not found, create one
652
			if (!$root) {
653
				$root = new XMLElement($this->get('element_name'));
654
				$newRoot = true;
655
			}
656
			
657
			// devkit will load
658
			$devkit = isset($_GET['debug']) && (empty($_GET['debug']) || $_GET['debug'] == 'xml');
659
			
660
			// selected items
661
			$entries = static::getEntries($data);
662
			
663
			// current linked entries
664
			$root->setAttribute('entries', $data['entries']);
665
			
666
			// available sections
667
			$root->setAttribute('sections', $this->get('sections'));
668
			
669
			// included elements
670
			$elements = static::parseElements($this);
671
			
672
			// DS mode
673
			if (!$mode) {
674
				$mode = '*';
675
			}
676
			
677
			$parentDeepness = General::intval($this->recursiveDeepness);
678
			$deepness = General::intval($this->get('deepness'));
679
			
680
			// both deepnesses are defined and parent restricts more
681
			if ($parentDeepness > 0 && $deepness > 0 && $parentDeepness < $deepness) {
682
				$deepness = $parentDeepness;
683
			}
684
			// parent is defined, current is not
685
			else if ($parentDeepness > 0 && $deepness < 1) {
686
				$deepness = $parentDeepness;
687
			}
688
			
689
			// cache recursive level because recursion might
690
			// change its value later on.
691
			$recursiveLevel = $this->recursiveLevel;
692
			
693
			// build entries
694
			foreach ($entries as $eId) {
695
				// try to find and existing item
696
				// TODO: keep last index found since it should be the next
697
				$item = null;
698
				$newItem = false;
699
				foreach ($root->getChildren() as $xmlItem) {
700
					if (General::intval($xmlItem->getAttribute('id')) === General::intval($eId)) {
701
						$item = $xmlItem;
702
						break;
703
					}
704
				}
705
706
				// item was not found, create one
707
				if (!$item) {
708
					$item = new XMLElement('item');
709
					$item->setAllowEmptyAttributes(false);
710
					// output id
711
					$item->setAttribute('id', $eId);
712
					// output recursive level
713
					$item->setAttribute('level', $recursiveLevel);
714
					$item->setAttribute('max-level', $deepness);
715
					$newItem = true;
716
				// item was found, but it is an error, so we can skip it
717
				} else if ($item->getName() === 'error') {
718
					continue;
719
				}
720
721
				// max recursion check
722
				if ($deepness < 1 || $recursiveLevel < $deepness) {
723
					// current entry, without data
724
					$entry = $this->fetchEntry($eId);
725
					
726
					// entry not found...
727
					if (!$entry || empty($entry)) {
728
						$error = new XMLElement('error');
729
						$error->setAttribute('id', $eId);
730
						$error->setValue(__('Error: entry `%s` not found', array($eId)));
731
						$root->prependChild($error);
732
						continue;
733
					}
734
					
735
					// fetch section infos
736
					$sectionId = $entry->get('section_id');
737
					$section = $this->sectionManager->fetch($sectionId);
738
					$sectionName = $section->get('handle');
739
					// cache fields info
740
					if (!isset($section->er_field_cache)) {
741
						$section->er_field_cache = $section->fetchFields();
742
					}
743
					
744
					// set section related attributes
745
					$item->setAttribute('section-id', $sectionId);
746
					$item->setAttribute('section', $sectionName);
747
					
748
					// Get the valid elements for this section only
749
					$validElements = $elements[$sectionName];
750
					
751
					// adjust the mode for the current section
752
					$curMode = $mode;
753
					
754
					// remove section name from current mode so "sectionName.field" becomes simply "field"
755
					if (preg_match('/^(' . $sectionName . '\.)(.*)$/sU', $curMode)) {
756
						$curMode = preg_replace('/^' . $sectionName . '\./sU', '', $curMode);
757
					}
758
					// remove section name from current mode "sectionName" and
759
					// treat it like if it is "sectionName: *"
760
					else if (preg_match('/^(' . $sectionName . ')$/sU', $curMode)) {
761
						$curMode = '*';
762
					}
763
					// section name was not found in mode, check if the mode is "*"
764
					else if ($curMode != '*') {
765
						// mode forbids this section
766
						$validElements = null;
767
					}
768
					
769
					// this section is not selected, bail out
770 View Code Duplication
					if (!is_array($validElements)) {
771
						if ($newItem) {
772
							if ($devkit) {
773
								$item->setAttribute('x-forbidden-by-ds', $curMode);
774
							}
775
							$root->appendChild($item);
776
						}
777
						continue;
778
					} else {
779
						if ($devkit) {
780
							$item->setAttribute('x-forbidden-by-ds', null);
781
						}
782
					}
783
					
784
					// selected fields for fetching
785
					$sectionElements = array();
786
					
787
					// everything is allowed
788
					if (in_array('*', $validElements)) {
789
						if ($curMode !== '*') {
790
							// get only the mode
791
							$sectionElements = array($curMode);
792
						}
793
						else {
794
							// setting null = get all
795
							$sectionElements = null;
796
						}
797
					}
798
					// only use valid elements
799
					else {
800
						if ($curMode !== '*') {
801
							// is this field allowed ?
802
							if (self::isFieldIncluded($curMode, $validElements)) {
803
								// get only the mode
804
								$sectionElements = array($curMode);
805
							}
806
							else {
807
								// $curMode selects something outside of
808
								// the valid elements: select nothing
809
								$sectionElements = array();
810
							}
811
						}
812
						else {
813
							// use field's valid elements
814
							$sectionElements = $validElements;
815
						}
816
					}
817
					
818
					// Filtering is enabled, but nothing is selected
819 View Code Duplication
					if (is_array($sectionElements) && empty($sectionElements)) {
820
						if ($newItem) {
821
							$root->appendChild($item);
822
							if ($devkit) {
823
								$item->setAttribute('x-forbidden-by-selection', $curMode);
824
							}
825
						}
826
						continue;
827
					} else {
828
						if ($devkit) {
829
							$item->setAttribute('x-forbidden-by-selection', null);
830
						}
831
					}
832
					
833
					// fetch current entry again, but with data for the allowed schema
834
					$entry = $this->fetchEntry($eId, $sectionElements);
835
					
836
					// cache the entry data
837
					$entryData = $entry->getData();
838
					
839
					// for each field returned for this entry...
840
					foreach ($entryData as $fieldId => $data) {
841
						$filteredData = array_filter($data, function ($value) {
842
							return $value != null;
843
						});
844
						
845
						if (empty($filteredData)) {
846
							continue;
847
						}
848
						
849
						$field = $section->er_field_cache[$fieldId];
850
						$fieldName = $field->get('element_name');
851
						
852
						$submodes = null;
853
						if ($curMode === '*') {
854
							$submodes = self::getAllSelectedFieldModes($fieldName, $validElements);
855
						} else {
856
							$submodes = array($curMode);
857
						}
858
						
859
						// Special treatments for ERF
860
						if ($field instanceof FieldEntry_relationship) {
861
							// Increment recursive level
862
							$field->recursiveLevel = $recursiveLevel + 1;
863
							$field->recursiveDeepness = $deepness;
864
						}
865
						
866
						// current selection does not specify a mode
867
						if ($submodes === null) {
868
							if ($field instanceof FieldEntry_Relationship) {
869
								$field->expandIncludableElements = false;
870
							}
871
							$submodes = array_map(function ($fieldIncludableElement) use ($fieldName) {
872
								return FieldEntry_relationship::extractMode($fieldName, $fieldIncludableElement);
873
							}, $field->fetchIncludableElements());
874
							if ($field instanceof FieldEntry_Relationship) {
875
								$field->expandIncludableElements = true;
876
							}
877
						} elseif (empty($submodes)) {
878
							if ($devkit) {
879
								$item->setAttribute('x-selection-mode-empty', $fieldName);
880
							}
881
						}
882
						
883
						// Append the formatted element for each requested mode
884
						foreach ($submodes as $submode) {
885
							$field->appendFormattedElement($item, $data, $encode, $submode, $eId);
886
						}
887
					}
888
					// output current mode
889
					$item->setAttribute('matched-element', $curMode);
890
					// no field selected
891
					if (is_array($sectionElements) && empty($sectionElements)) {
892
						$item->setAttribute('empty-selection', 'yes');
893
					}
894
				} // end max recursion check
895
896
				if ($newItem) {
897
					// append item when done
898
					$root->appendChild($item);
899
				}
900
			} // end each entries
901
902
			if ($newRoot) {
903
				// output mode for this field
904
				if ($devkit) {
905
					$root->setAttribute('x-data-source-mode', $mode);
906
					$root->setAttribute('x-field-included-elements', $this->get('elements'));
907
				}
908
				// add all our data to the wrapper;
909
				$wrapper->appendChild($root);
910
			} else {
911
				if ($devkit) {
912
					$root->setAttribute('x-data-source-mode', $root->getAttribute('x-data-source-mode') . ', ' . $mode);
913
				}
914
			}
915
916
			// clean up
917
			$this->recursiveLevel = 1;
918
			$this->recursiveDeepness = null;
919
		}
920
921
		public function getParameterPoolValue(array $data, $entry_id = null)
922
		{
923
			if(!is_array($data) || empty($data)) return;
924
			return static::getEntries($data);
925
		}
926
927
		/* ********* Utils *********** */
928
		
929
		/**
930
		 * Return true if $fieldName is allowed in $sectionElements
931
		 * @param string $fieldName
932
		 * @param string $sectionElements
933
		 * @return bool
934
		 */
935
		public static function isFieldIncluded($fieldName, $sectionElements)
936
		{
937
			$modes = self::getAllSelectedFieldModes($fieldName, $sectionElements);
938
			return $modes === null || !empty($modes);
939
		}
940
941
		/**
942
		 * Returns all selected modes allowed in $sectionElements or null if they are all allowed
943
		 * @param string $fieldName
944
		 * @param string $sectionElements
945
		 * @return array|null
946
		 */
947
		public static function getAllSelectedFieldModes($fieldName, $sectionElements)
948
		{
949
			$elements = array();
950
			if (is_array($sectionElements)) {
951
				foreach ($sectionElements as $element) {
952
					// everything is allowed, use "fieldName" directly
953
					if ($element === '*' || $fieldName === $element) {
954
						return null;
955
					}
956
					// make "fieldName: *" the same as "fieldName"
957
					if (preg_match('/\s*:\s*\*/sU', $fieldName)) {
958
						$fieldName = trim(current(explode(':', $fieldName)));
959
					}
960
					// "fieldName" is included as-is
961
					if ($fieldName === $element) {
962
						return null;
963
					}
964
965
					// element starts with "fieldName:"
966
					if (preg_match('/^' . $fieldName . '\s*:/sU', $element)) {
967
						$elements[] = self::extractMode($fieldName, $element);
968
					}
969
				}
970
				if (!empty($elements)) {
971
					return $elements;
972
				}
973
			}
974
			return $elements;
975
		}
976
		
977
		public static function parseElements($field)
978
		{
979
			$elements = array();
980
			$exElements = $field->getArray('elements');
981
			
982
			if (in_array('*', $exElements)) {
983
				$sections = $field->getArray('sections');
984
				$sections = SectionManager::fetch($sections);
985
				return array_reduce($sections, function ($result, $section) {
986
					$result[$section->get('handle')] = array('*');
987
					return $result;
988
				}, array());
989
			}
990
			
991
			foreach ($exElements as $value) {
992
				if (!$value) {
993
					continue;
994
				}
995
				// sectionName.fieldName or sectionName.*
996
				$parts = array_map(trim, explode('.', $value, 2));
997
				// first time seeing this section
998
				if (!isset($elements[$parts[0]])) {
999
					$elements[$parts[0]] = array();
1000
				}
1001
				// we have a value after the dot
1002
				if (isset($parts[1]) && !!$parts[1]) {
1003
					$elements[$parts[0]][] = $parts[1];
1004
				}
1005
				// sectionName only
1006
				else if (!isset($parts[1])) {
1007
					$elements[$parts[0]][] = '*';
1008
				}
1009
			}
1010
			
1011
			return $elements;
1012
		}
1013
1014
		public static function extractMode($fieldName, $mode)
1015
		{
1016
			$pattern = '/^' . $fieldName . '\s*:\s*/s';
1017
			if (!preg_match($pattern, $mode)) {
1018
				return null;
1019
			}
1020
			$mode = preg_replace($pattern, '', $mode, 1);
1021
			if ($mode === '*') {
1022
				return null;
1023
			}
1024
			return $mode;
1025
		}
1026
1027
		private function buildSectionSelect($name)
1028
		{
1029
			$sections = SectionManager::fetch();
1030
			$options = array();
1031
			$selectedSections = $this->getSelectedSectionsArray();
1032
			
1033 View Code Duplication
			foreach ($sections as $section) {
1034
				$driver = $section->get('id');
1035
				$selected = in_array($driver, $selectedSections);
1036
				$options[] = array($driver, $selected, General::sanitize($section->get('name')));
1037
			}
1038
			
1039
			return Widget::Select($name, $options, array('multiple' => 'multiple'));
1040
		}
1041
1042 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...
1043
		{
1044
			$name = $this->createSettingsFieldName('sections', true);
1045
1046
			$input = $this->buildSectionSelect($name);
1047
			$input->setAttribute('class', 'entry_relationship-sections');
1048
1049
			$label = Widget::Label();
1050
			$label->setAttribute('class', 'column');
1051
1052
			$label->setValue(__('Available sections %s', array($input->generate())));
1053
1054
			$wrapper->appendChild($label);
1055
		}
1056
1057
		private function createEntriesHiddenInput($data)
1058
		{
1059
			$hidden = new XMLElement('input', null, array(
1060
				'type' => 'hidden',
1061
				'name' => $this->createPublishFieldName('entries'),
1062
				'value' => $data['entries']
1063
			));
1064
			
1065
			return $hidden;
1066
		}
1067
		
1068
		private function createActionBarMenu($sections)
1069
		{
1070
			$wrap = new XMLElement('div');
1071
			$actionBar = '';
1072
			$modeFooter = $this->get('mode_footer');
1073
			if ($modeFooter) {
1074
				$section = $this->sectionManager->fetch($this->get('parent_section'));
1075
				$actionBar = ERFXSLTUTilities::processXSLT($this, null, $section->get('handle'), null, 'mode_footer', isset($_REQUEST['debug']), 'field');
1076
			}
1077
			if (empty($actionBar)) {
1078
				$fieldset = new XMLElement('fieldset');
1079
				$fieldset->setAttribute('class', 'single');
1080
				if ($this->is('allow_search')) {
1081
					$searchWrap = new XMLElement('div');
1082
					$searchWrap->setAttribute('data-interactive', 'data-interactive');
1083
					$searchWrap->setAttribute('class', 'search');
1084
					$searchInput = Widget::Input('', null, 'text', array(
1085
						'class' => 'search',
1086
						'data-search' => '',
1087
						'placeholder' => __('Search for entries')
1088
					));
1089
					$searchWrap->appendChild($searchInput);
1090
					$searchSuggestions = new XMLElement('ul');
1091
					$searchSuggestions->setAttribute('class', 'suggestions');
1092
					$searchWrap->appendChild($searchSuggestions);
1093
					$fieldset->appendChild($searchWrap);
1094
				}
1095
				
1096
				if ($this->is('allow_new') || $this->is('allow_link') || $this->is('allow_search')) {
1097
					$selectWrap = new XMLElement('div');
1098
					$selectWrap->appendChild(new XMLElement('span', __('Related section: '), array('class' => 'sections-selection')));
1099
					$options = array();
1100
					foreach ($sections as $section) {
1101
						$options[] = array($section->get('handle'), false, $section->get('name'));
1102
					}
1103
					$select = Widget::Select('', $options, array('class' => 'sections sections-selection'));
1104
					$selectWrap->appendChild($select);
1105
					$fieldset->appendChild($selectWrap);
1106
				}
1107 View Code Duplication
				if ($this->is('allow_new')) {
1108
					$fieldset->appendChild(new XMLElement('button', __('Create new'), array(
1109
						'type' => 'button',
1110
						'class' => 'create',
1111
						'data-create' => '',
1112
					)));
1113
				}
1114 View Code Duplication
				if ($this->is('allow_link')) {
1115
					$fieldset->appendChild(new XMLElement('button', __('Link to entry'), array(
1116
						'type' => 'button',
1117
						'class' => 'link',
1118
						'data-link' => '',
1119
					)));
1120
				}
1121
				$wrap->appendChild($fieldset);
1122
			}
1123
			else {
1124
				$wrap->setValue($actionBar);
1125
			}
1126
			
1127
			return $wrap;
1128
		}
1129
1130
		/* ********* UI *********** */
1131
		
1132
		/**
1133
		 *
1134
		 * Builds the UI for the field's settings when creating/editing a section
1135
		 * @param XMLElement $wrapper
1136
		 * @param array $errors
1137
		 */
1138
		public function displaySettingsPanel(XMLElement &$wrapper, $errors=null)
1139
		{
1140
			/* first line, label and such */
1141
			parent::displaySettingsPanel($wrapper, $errors);
1142
			
1143
			// sections
1144
			$sections = new XMLElement('fieldset');
1145
			
1146
			$this->appendSelectionSelect($sections);
1147 View Code Duplication
			if (is_array($errors) && isset($errors['sections'])) {
1148
				$sections = Widget::Error($sections, $errors['sections']);
1149
			}
1150
			$wrapper->appendChild($sections);
1151
			
1152
			// elements
1153
			$elements = new XMLElement('div');
1154
			$element = Widget::Label();
1155
			$element->setValue(__('Included elements in Data Sources and Backend Templates'));
1156
			$element->setAttribute('class', 'column');
1157
			$element->appendChild(Widget::Input($this->createSettingsFieldName('elements'), $this->get('elements'), 'text', array(
1158
				'class' => 'entry_relationship-elements'
1159
			)));
1160
			$elements->appendChild($element);
1161
			$elements_choices = new XMLElement('ul', null, array('class' => 'tags singular entry_relationship-field-choices'));
1162
			$elements->appendChild($elements_choices);
1163
			$wrapper->appendChild($elements);
1164
			
1165
			// limit entries
1166
			$limits = new XMLElement('fieldset');
1167
			$limits->appendChild(new XMLElement('legend', __('Limits')));
1168
			$limits_cols = new XMLElement('div');
1169
			$limits_cols->setAttribute('class', 'three columns');
1170
			// min
1171
			$limit_min = Widget::Label();
1172
			$limit_min->setValue(__('Minimum count of entries in this field'));
1173
			$limit_min->setAttribute('class', 'column');
1174
			$limit_min->appendChild(Widget::Input($this->createSettingsFieldName('min_entries'), $this->get('min_entries'), 'number', array(
1175
				'min' => 0,
1176
				'max' => 99999
1177
			)));
1178
			$limits_cols->appendChild($limit_min);
1179
			// max
1180
			$limit_max = Widget::Label();
1181
			$limit_max->setValue(__('Maximum count of entries in this field'));
1182
			$limit_max->setAttribute('class', 'column');
1183
			$limit_max->appendChild(Widget::Input($this->createSettingsFieldName('max_entries'), $this->get('max_entries'), 'number', array(
1184
				'min' => 0,
1185
				'max' => 99999
1186
			)));
1187
			$limits_cols->appendChild($limit_max);
1188
			
1189
			// deepness
1190
			$deepness = Widget::Label();
1191
			$deepness->setValue(__('Maximum level of recursion in Data Sources'));
1192
			$deepness->setAttribute('class', 'column');
1193
			$deepness->appendChild(Widget::Input($this->createSettingsFieldName('deepness'), $this->get('deepness'), 'number', array(
1194
				'min' => 0,
1195
				'max' => 99
1196
			)));
1197
			$limits_cols->appendChild($deepness);
1198
			$limits->appendChild($limits_cols);
1199
			$wrapper->appendChild($limits);
1200
			
1201
			// xsl
1202
			$xsl = new XMLElement('fieldset');
1203
			$xsl->appendChild(new XMLElement('legend', __('Backend XSL templates options')));
1204
			$xsl_cols = new XMLElement('div');
1205
			$xsl_cols->setAttribute('class', 'four columns');
1206
			
1207
			// xsl mode
1208
			$xslmode = Widget::Label();
1209
			$xslmode->setValue(__('XSL mode for entries content template'));
1210
			$xslmode->setAttribute('class', 'column');
1211
			$xslmode->appendChild(Widget::Input($this->createSettingsFieldName('mode'), $this->get('mode'), 'text'));
1212
			$xsl_cols->appendChild($xslmode);
1213
			// xsl header mode
1214
			$xslmodetable = Widget::Label();
1215
			$xslmodetable->setValue(__('XSL mode for entries header template'));
1216
			$xslmodetable->setAttribute('class', 'column');
1217
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_header'), $this->get('mode_header'), 'text'));
1218
			$xsl_cols->appendChild($xslmodetable);
1219
			// xsl table mode
1220
			$xslmodetable = Widget::Label();
1221
			$xslmodetable->setValue(__('XSL mode for publish table value'));
1222
			$xslmodetable->setAttribute('class', 'column');
1223
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_table'), $this->get('mode_table'), 'text'));
1224
			$xsl_cols->appendChild($xslmodetable);
1225
			// xsl action bar mode
1226
			$xslmodetable = Widget::Label();
1227
			$xslmodetable->setValue(__('XSL mode for publish action bar'));
1228
			$xslmodetable->setAttribute('class', 'column');
1229
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_footer'), $this->get('mode_footer'), 'text'));
1230
			$xsl_cols->appendChild($xslmodetable);
1231
			
1232
			$xsl->appendChild($xsl_cols);
1233
			$wrapper->appendChild($xsl);
1234
			
1235
			// permissions
1236
			$permissions = new XMLElement('fieldset');
1237
			$permissions->appendChild(new XMLElement('legend', __('Permissions')));
1238
			$permissions_cols = new XMLElement('div');
1239
			$permissions_cols->setAttribute('class', 'four columns');
1240
			$permissions_cols->appendChild($this->createCheckbox('allow_new', 'Show new button'));
1241
			$permissions_cols->appendChild($this->createCheckbox('allow_edit', 'Show edit button'));
1242
			$permissions_cols->appendChild($this->createCheckbox('allow_link', 'Show link button'));
1243
			$permissions_cols->appendChild($this->createCheckbox('allow_delete', 'Show delete button'));
1244
			$permissions->appendChild($permissions_cols);
1245
			$wrapper->appendChild($permissions);
1246
			
1247
			// display options
1248
			$display = new XMLElement('fieldset');
1249
			$display->appendChild(new XMLElement('legend', __('Display options')));
1250
			$display_cols = new XMLElement('div');
1251
			$display_cols->setAttribute('class', 'four columns');
1252
			$display_cols->appendChild($this->createCheckbox('allow_collapse', 'Allow content collapsing'));
1253
			$display_cols->appendChild($this->createCheckbox('allow_search', 'Allow search linking'));
1254
			$display_cols->appendChild($this->createCheckbox('show_header', 'Show the header box before entries templates'));
1255
			$display->appendChild($display_cols);
1256
			$wrapper->appendChild($display);
1257
			
1258
			// assoc
1259
			$assoc = new XMLElement('fieldset');
1260
			$assoc->appendChild(new XMLElement('legend', __('Associations')));
1261
			$assoc_cols = new XMLElement('div');
1262
			$assoc_cols->setAttribute('class', 'three columns');
1263
			$this->appendShowAssociationCheckbox($assoc_cols);
1264
			$assoc->appendChild($assoc_cols);
1265
			$wrapper->appendChild($assoc);
1266
			
1267
			// footer
1268
			$this->appendStatusFooter($wrapper);
1269
		}
1270
1271
		/**
1272
		 *
1273
		 * Builds the UI for the publish page
1274
		 * @param XMLElement $wrapper
1275
		 * @param mixed $data
1276
		 * @param mixed $flagWithError
1277
		 * @param string $fieldnamePrefix
1278
		 * @param string $fieldnamePostfix
1279
		 */
1280
		public function displayPublishPanel(XMLElement &$wrapper, $data = null, $flagWithError = null, $fieldnamePrefix = null, $fieldnamePostfix = null, $entry_id = null)
1281
		{
1282
			$entriesId = array();
1283
			$sectionsId = $this->getSelectedSectionsArray();
1284
			
1285
			if ($data['entries'] != null) {
1286
				$entriesId = static::getEntries($data);
1287
			}
1288
			
1289
			$sectionsId = array_map(array('General', 'intval'), $sectionsId);
1290
			$sections = $this->sectionManager->fetch($sectionsId);
1291
			usort($sections, function ($a, $b) {
1292
				if ($a->get('name') === $b->get('name')) {
1293
					return 0;
1294
				}
1295
				return $a->get('name') < $b->get('name') ? -1 : 1;
1296
			});
1297
			
1298
			$label = Widget::Label($this->get('label'));
1299
			$notes = '';
1300
			
1301
			// min note
1302
			if ($this->getInt('min_entries') > 0) {
1303
				$notes .= __('Minimum number of entries: <b>%s</b>. ', array($this->get('min_entries')));
1304
			}
1305
			// max note
1306
			if ($this->getInt('max_entries') > 0) {
1307
				$notes .= __('Maximum number of entries: <b>%s</b>. ', array($this->get('max_entries')));
1308
			}
1309
			// not required note
1310
			if (!$this->isRequired()) {
1311
				$notes .= __('Optional');
1312
			}
1313
			// append notes
1314
			if ($notes) {
1315
				$label->appendChild(new XMLElement('i', $notes));
1316
			}
1317
			
1318
			// label error management
1319
			if ($flagWithError != null) {
1320
				$wrapper->appendChild(Widget::Error($label, $flagWithError));
1321
			} else {
1322
				$wrapper->appendChild($label);
1323
			}
1324
			
1325
			$wrapper->appendChild($this->createEntriesList($entriesId));
1326
			$wrapper->appendChild($this->createActionBarMenu($sections));
1327
			$wrapper->appendChild($this->createEntriesHiddenInput($data));
1328
			$wrapper->setAttribute('data-value', $data['entries']);
1329
			$wrapper->setAttribute('data-field-id', $this->get('id'));
1330
			$wrapper->setAttribute('data-field-label', $this->get('label'));
1331
			$wrapper->setAttribute('data-min', $this->get('min_entries'));
1332
			$wrapper->setAttribute('data-max', $this->get('max_entries'));
1333
			$wrapper->setAttribute('data-required', $this->get('required'));
1334
			if (isset($_REQUEST['debug'])) {
1335
				$wrapper->setAttribute('data-debug', true);
1336
			}
1337
		}
1338
1339
		/**
1340
		 *
1341
		 * Return a plain text representation of the field's data
1342
		 * @param array $data
1343
		 * @param int $entry_id
1344
		 */
1345
		public function prepareTextValue($data, $entry_id = null)
1346
		{
1347
			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...
1348
				return '';
1349
			}
1350
			return $data['entries'];
1351
		}
1352
1353
		/**
1354
		 * Format this field value for display as readable text value.
1355
		 *
1356
		 * @param array $data
1357
		 *  an associative array of data for this string. At minimum this requires a
1358
		 *  key of 'value'.
1359
		 * @param integer $entry_id (optional)
1360
		 *  An option entry ID for more intelligent processing. Defaults to null.
1361
		 * @param string $defaultValue (optional)
1362
		 *  The value to use when no plain text representation of the field's data
1363
		 *  can be made. Defaults to null.
1364
		 * @return string
1365
		 *  the readable text summary of the values of this field instance.
1366
		 */
1367
		public function prepareReadableValue($data, $entry_id = null, $truncate = false, $defaultValue = 'None')
1368
		{
1369
			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...
1370
				return __($defaultValue);
1371
			}
1372
			$entries = static::getEntries($data);
1373
			$realEntries = array();
1374
			foreach ($entries as $entryId) {
1375
				$e = EntryManager::fetch($entryId);
1376
				if (is_array($e) && !empty($e)) {
1377
					$realEntries = array_merge($realEntries, $e);
1378
				}
1379
			}
1380
			$count = count($entries);
1381
			$realCount = count($realEntries);
1382
			if ($count === $realCount) {
1383
				return self::formatCount($count);
1384
			}
1385
			return self::formatCount($realCount) . ' (' . self::formatCount($count - $realCount) . ' not found)';
1386
		}
1387
1388
		/**
1389
		 * Format this field value for display in the publish index tables.
1390
		 *
1391
		 * @param array $data
1392
		 *  an associative array of data for this string. At minimum this requires a
1393
		 *  key of 'value'.
1394
		 * @param XMLElement $link (optional)
1395
		 *  an XML link structure to append the content of this to provided it is not
1396
		 *  null. it defaults to null.
1397
		 * @param integer $entry_id (optional)
1398
		 *  An option entry ID for more intelligent processing. defaults to null
1399
		 * @return string
1400
		 *  the formatted string summary of the values of this field instance.
1401
		 */
1402
		public function prepareTableValue($data, XMLElement $link = null, $entry_id = null)
1403
		{
1404
			$value = $this->prepareReadableValue($data, $entry_id, false, __('None'));
1405
1406
			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...
1407
				$entries = static::getEntries($data);
1408
				$cellcontent = '';
1409
				foreach ($entries as $position => $child_entry_id) {
1410
					$entry = $this->entryManager->fetch($child_entry_id);
1411
					if (!$entry || !is_array($entry) || empty($entry)) {
1412
						continue;
1413
					}
1414
					reset($entry);
1415
					$entry = current($entry);
1416
					$section = $this->sectionManager->fetch($entry->get('section_id'));
1417
					$content = ERFXSLTUTilities::processXSLT($this, $entry, $section->get('handle'), $section->fetchFields(), 'mode_table', isset($_REQUEST['debug']), 'entry', $position + 1);
1418
					if ($content) {
1419
						$cellcontent .= $content;
1420
					}
1421
				}
1422
				
1423
				$cellcontent = trim($cellcontent);
1424
				
1425
				if (General::strlen($cellcontent)) {
1426
					if ($link) {
1427
						$link->setValue($cellcontent);
1428
						return $link->generate();
1429
					}
1430
					return $cellcontent;
1431
				}
1432
			} else if ($link) {
1433
				$link->setValue($value);
1434
				return $link->generate();
1435
			}
1436
1437
			return $value;
1438
		}
1439
1440
		/* ********* SQL Data Definition ************* */
1441
1442
		/**
1443
		 *
1444
		 * Creates table needed for entries of individual fields
1445
		 */
1446
		public function createTable()
1447
		{
1448
			$id = $this->get('id');
1449
1450
			return Symphony::Database()->query("
1451
				CREATE TABLE `tbl_entries_data_$id` (
1452
					`id` int(11) 		unsigned NOT NULL AUTO_INCREMENT,
1453
					`entry_id` 			int(11) unsigned NOT NULL,
1454
					`entries` 			text COLLATE utf8_unicode_ci NULL,
1455
					PRIMARY KEY  (`id`),
1456
					UNIQUE KEY `entry_id` (`entry_id`)
1457
				) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1458
			");
1459
		}
1460
1461
		/**
1462
		 * Creates the table needed for the settings of the field
1463
		 */
1464
		public static function createFieldTable()
1465
		{
1466
			$tbl = self::FIELD_TBL_NAME;
1467
1468
			return Symphony::Database()->query("
1469
				CREATE TABLE IF NOT EXISTS `$tbl` (
1470
					`id` 				int(11) unsigned NOT NULL AUTO_INCREMENT,
1471
					`field_id` 			int(11) unsigned NOT NULL,
1472
					`sections`			varchar(2048) NULL COLLATE utf8_unicode_ci,
1473
					`show_association` 	enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1474
					`deepness` 			int(2) unsigned NULL,
1475
					`elements` 			text COLLATE utf8_unicode_ci NULL,
1476
					`mode`				varchar(50) NULL COLLATE utf8_unicode_ci,
1477
					`mode_table`		varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL,
1478
					`mode_header`		varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL,
1479
					`mode_footer`		varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL,
1480
					`min_entries`		int(5) unsigned NULL,
1481
					`max_entries`		int(5) unsigned NULL,
1482
					`allow_edit` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1483
					`allow_new` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1484
					`allow_link` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1485
					`allow_delete` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no',
1486
					`allow_collapse` 	enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1487
					`allow_search` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no',
1488
					`show_header` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1489
					PRIMARY KEY (`id`),
1490
					UNIQUE KEY `field_id` (`field_id`)
1491
				) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1492
			");
1493
		}
1494
		
1495
		public static function update_102()
1496
		{
1497
			$tbl = self::FIELD_TBL_NAME;
1498
			$sql = "
1499
				ALTER TABLE `$tbl`
1500
					ADD COLUMN `allow_edit` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1501
					ADD COLUMN `allow_new` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1502
					ADD COLUMN `allow_link` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1503
					AFTER `max_entries`
1504
			";
1505
			$addColumns = Symphony::Database()->query($sql);
1506
			if (!$addColumns) {
1507
				return false;
1508
			}
1509
1510
			$fields = FieldManager::fetch(null, null, null, 'id', 'entry_relationship');
1511
			if (!empty($fields) && is_array($fields)) {
1512
				foreach ($fields as $fieldId => $field) {
1513
					$sql = "ALTER TABLE `tbl_entries_data_$fieldId` MODIFY `entries` TEXT";
1514
					if (!Symphony::Database()->query($sql)) {
1515
						throw new Exception(__('Could not update table `tbl_entries_data_%s`.', array($fieldId)));
1516
					}
1517
				}
1518
			}
1519
			return true;
1520
		}
1521
		
1522
		public static function update_103()
1523
		{
1524
			$tbl = self::FIELD_TBL_NAME;
1525
			$sql = "
1526
				ALTER TABLE `$tbl`
1527
					ADD COLUMN `allow_delete` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no'
1528
						AFTER `allow_link`
1529
			";
1530
			return Symphony::Database()->query($sql);
1531
		}
1532
		
1533
		public static function update_200()
1534
		{
1535
			$tbl = self::FIELD_TBL_NAME;
1536
			$sql = "
1537
				ALTER TABLE `$tbl`
1538
					ADD COLUMN `allow_collapse` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1539
						AFTER `allow_delete`,
1540
					ADD COLUMN `mode_table` varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL
1541
						AFTER `mode`,
1542
					ADD COLUMN `mode_header` varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL
1543
						AFTER `mode_table`,
1544
					ADD COLUMN `show_header` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1545
						AFTER `allow_collapse`,
1546
					ADD COLUMN `mode_footer` varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL
1547
						AFTER `mode_header`,
1548
					CHANGE `sections` `sections` varchar(2048) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
1549
					CHANGE `elements` `elements` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL
1550
			";
1551
			return Symphony::Database()->query($sql);
1552
		}
1553
		
1554
		public static function update_2008()
1555
		{
1556
			$tbl = self::FIELD_TBL_NAME;
1557
			$sql = "
1558
				ALTER TABLE `$tbl`
1559
					ADD COLUMN `allow_search` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no'
1560
						AFTER `allow_collapse`
1561
			";
1562
			return Symphony::Database()->query($sql);
1563
		}
1564
		
1565
		/**
1566
		 *
1567
		 * Drops the table needed for the settings of the field
1568
		 */
1569
		public static function deleteFieldTable()
1570
		{
1571
			$tbl = self::FIELD_TBL_NAME;
1572
			
1573
			return Symphony::Database()->query("
1574
				DROP TABLE IF EXISTS `$tbl`
1575
			");
1576
		}
1577
		
1578
		private static function removeSectionAssociation($child_field_id)
1579
		{
1580
			return Symphony::Database()->delete('tbl_sections_association', "`child_section_field_id` = {$child_field_id}");
1581
		}
1582
	}
1583