Completed
Pull Request — dev (#67)
by
unknown
02:13
created

FieldEntry_Relationship::buildDSRetrievalSQL()   B

Complexity

Conditions 7
Paths 13

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 8.3786
c 0
b 0
f 0
cc 7
nc 13
nop 4

2 Methods

Rating   Name   Duplication   Size   Complexity  
A FieldEntry_Relationship::fetchEntryWithData() 0 14 2
A FieldEntry_Relationship::fetchEntryOnly() 0 8 1
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.erfxsltutilities.php');
11
	require_once(EXTENSIONS . '/entry_relationship_field/lib/class.sectionsinfos.php');
12
	require_once(EXTENSIONS . '/entry_relationship_field/lib/class.entryqueryentryrelationshipadapter.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
		// cache 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
			// EQFA
75
			$this->entryQueryFieldAdapter = new EntryQueryEntryrelationshipAdapter($this);
76
			// set the name of the field
77
			$this->_name = __('Entry Relationship');
78
			// permits to make it required
79
			$this->_required = true;
80
			// permits the make it show in the table columns
81
			$this->_showcolumn = true;
82
			// permits association
83
			$this->_showassociation = true;
84
			// current recursive level
85
			$this->recursiveLevel = 1;
86
			// parent's maximum recursive level of output
87
			$this->recursiveDeepness = null;
88
			// set as orderable
89
			$this->orderable = true;
90
			// set as not required by default
91
			$this->set('required', 'no');
92
			// show association by default
93
			$this->set('show_association', 'yes');
94
			// no sections
95
			$this->set('sections', null);
96
			// no max deepness
97
			$this->set('deepness', null);
98
			// no included elements
99
			$this->set('elements', null);
100
			// no modes
101
			$this->set('mode', null);
102
			$this->set('mode_table', null);
103
			$this->set('mode_header', null);
104
			$this->set('mode_footer', null);
105
			// no limit
106
			$this->set('min_entries', null);
107
			$this->set('max_entries', null);
108
			// all permissions
109
			$this->set('allow_new', 'yes');
110
			$this->set('allow_edit', 'yes');
111
			$this->set('allow_link', 'yes');
112
			$this->set('allow_delete', 'no');
113
			// display options
114
			$this->set('allow_collapse', 'yes');
115
			$this->set('allow_search', 'no');
116
			$this->set('show_header', 'yes');
117
			// cache managers
118
			$this->sectionManager = new SectionManager;
119
			$this->entryManager = new EntryManager;
120
			$this->fieldManager = new FieldManager;
121
			$this->sectionInfos = new SectionsInfos;
122
		}
123
124
		public function isSortable()
125
		{
126
			return false;
127
		}
128
129
		public function canFilter()
130
		{
131
			return true;
132
		}
133
134
		public function canPublishFilter()
135
		{
136
			return true;
137
		}
138
139
		public function canImport()
140
		{
141
			return false;
142
		}
143
144
		public function canPrePopulate()
145
		{
146
			return true;
147
		}
148
149
		public function mustBeUnique()
150
		{
151
			return false;
152
		}
153
154
		public function allowDatasourceOutputGrouping()
155
		{
156
			return false;
157
		}
158
159
		public function requiresSQLGrouping()
160
		{
161
			return false;
162
		}
163
164
		public function allowDatasourceParamOutput()
165
		{
166
			return true;
167
		}
168
169
		/* ********** INPUT AND FIELD *********** */
170
171
172
		/**
173
		 *
174
		 * Validates input
175
		 * Called before <code>processRawFieldData</code>
176
		 * @param $data
177
		 * @param $message
178
		 * @param $entry_id
179
		 */
180
		public function checkPostFieldData($data, &$message, $entry_id = null)
181
		{
182
			$message = null;
183
			$required = $this->isRequired();
184
185
			if ($required && (!is_array($data) || count($data) == 0 || strlen($data['entries']) < 1)) {
186
				$message = __("'%s' is a required field.", array($this->get('label')));
187
				return self::__MISSING_FIELDS__;
188
			}
189
190
			$entries = $data['entries'];
191
192
			if (!is_array($entries)) {
193
				$entries = static::getEntries($data);
194
			}
195
196
			// enforce limits only if required or it contains data
197
			if ($required || count($entries) > 0) {
198
				if ($this->getInt('min_entries') > 0 && $this->getInt('min_entries') > count($entries)) {
199
					$message = __("'%s' requires a minimum of %s entries.", array($this->get('label'), $this->getInt('min_entries')));
200
					return self::__INVALID_FIELDS__;
201
				} else if ($this->getInt('max_entries') > 0 && $this->getInt('max_entries') < count($entries)) {
202
					$message = __("'%s' can not contains more than %s entries.", array($this->get('label'), $this->getInt('max_entries')));
203
					return self::__INVALID_FIELDS__;
204
				}
205
			}
206
207
			return self::__OK__;
208
		}
209
210
211
		/**
212
		 *
213
		 * Process data before saving into database.
214
		 *
215
		 * @param array $data
216
		 * @param int $status
217
		 * @param boolean $simulate
218
		 * @param int $entry_id
219
		 *
220
		 * @return Array - data to be inserted into DB
221
		 */
222
		public function processRawFieldData($data, &$status, &$message = null, $simulate = false, $entry_id = null)
223
		{
224
			$status = self::__OK__;
225
			$entries = null;
226
227
			if (!is_array($data) && !is_string($data)) {
228
				return null;
229
			}
230
231
			if (isset($data['entries'])) {
232
				$entries = $data['entries'];
233
			}
234
			else if (is_string($data)) {
235
				$entries = $data;
236
			}
237
238
			$row = array(
239
				'entries' => empty($entries) ? null : $entries
240
			);
241
242
			// return row
243
			return $row;
244
		}
245
246
		/**
247
		 * This function permits parsing different field settings values
248
		 *
249
		 * @param array $settings
250
		 *	the data array to initialize if necessary.
251
		 */
252
		public function setFromPOST(Array $settings = array())
253
		{
254
			// call the default behavior
255
			parent::setFromPOST($settings);
256
257
			// declare a new setting array
258
			$new_settings = array();
259
260
			// set new settings
261
			$new_settings['sections'] = is_array($settings['sections']) ?
262
				implode(self::SEPARATOR, $settings['sections']) :
263
				(is_string($settings['sections']) ? $settings['sections'] : null);
264
265
			$new_settings['show_association'] = $settings['show_association'] == 'yes' ? 'yes' : 'no';
266
			$new_settings['deepness'] = General::intval($settings['deepness']);
267
			$new_settings['deepness'] = $new_settings['deepness'] < 1 ? null : $new_settings['deepness'];
268
			$new_settings['elements'] = empty($settings['elements']) ? null : $settings['elements'];
269
			$new_settings['mode'] = empty($settings['mode']) ? null : $settings['mode'];
270
			$new_settings['mode_table'] = empty($settings['mode_table']) ? null : $settings['mode_table'];
271
			$new_settings['mode_header'] = empty($settings['mode_header']) ? null : $settings['mode_header'];
272
			$new_settings['mode_footer'] = empty($settings['mode_footer']) ? null : $settings['mode_footer'];
273
			$new_settings['allow_new'] = $settings['allow_new'] == 'yes' ? 'yes' : 'no';
274
			$new_settings['allow_edit'] = $settings['allow_edit'] == 'yes' ? 'yes' : 'no';
275
			$new_settings['allow_link'] = $settings['allow_link'] == 'yes' ? 'yes' : 'no';
276
			$new_settings['allow_delete'] = $settings['allow_delete'] == 'yes' ? 'yes' : 'no';
277
			$new_settings['allow_collapse'] = $settings['allow_collapse'] == 'yes' ? 'yes' : 'no';
278
			$new_settings['allow_search'] = $settings['allow_search'] == 'yes' ? 'yes' : 'no';
279
			$new_settings['show_header'] = $settings['show_header'] == 'yes' ? 'yes' : 'no';
280
281
			// save it into the array
282
			$this->setArray($new_settings);
283
		}
284
285
286
		/**
287
		 *
288
		 * Validates the field settings before saving it into the field's table
289
		 */
290
		public function checkFields(Array &$errors, $checkForDuplicates = true)
291
		{
292
			$parent = parent::checkFields($errors, $checkForDuplicates);
293
			if ($parent != self::__OK__) {
294
				return $parent;
295
			}
296
297
			$sections = $this->get('sections');
298
299
			if (empty($sections)) {
300
				$errors['sections'] = __('At least one section must be chosen');
301
			}
302
303
			return (!empty($errors) ? self::__ERROR__ : self::__OK__);
304
		}
305
306
		/**
307
		 *
308
		 * Save field settings into the field's table
309
		 */
310
		public function commit()
311
		{
312
			// if the default implementation works...
313
			if(!parent::commit()) return false;
314
315
			$id = $this->get('id');
316
317
			// exit if there is no id
318
			if($id == false) return false;
319
320
			// we are the child, with multiple parents
321
			$child_field_id = $id;
322
323
			// delete associations, only where we are the child
324
			self::removeSectionAssociation($child_field_id);
325
326
			$sections = $this->getSelectedSectionsArray();
327
328
			foreach ($sections as $key => $sectionId) {
329
				if (empty($sectionId)) {
330
					continue;
331
				}
332
				$parent_section_id = General::intval($sectionId);
333
				if ($parent_section_id < 1) {
334
					// section not found, bail out
335
					continue;
336
				}
337
				$parent_section = $this->sectionManager
338
					->select()
339
					->section($parent_section_id)
340
					->execute()
341
					->next();
342
				if (!$parent_section) {
343
					// section not found, bail out
344
					continue;
345
				}
346
				$fields = $parent_section->fetchVisibleColumns();
347
				if (empty($fields)) {
348
					// no visible field, revert to all
349
					$fields = $parent_section->fetchFields();
350
				}
351
				if (empty($fields)) {
352
					// no fields found, bail out
353
					continue;
354
				}
355
				$parent_field_id = current($fields)->get('id');
356
				// create association
357
				SectionManager::createSectionAssociation(
358
					$parent_section_id,
359
					$child_field_id,
360
					$parent_field_id,
361
					$this->get('show_association') == 'yes'
362
				);
363
			}
364
365
			// declare an array contains the field's settings
366
			$settings = array(
367
				'sections' => $this->get('sections'),
368
				'show_association' => $this->get('show_association'),
369
				'deepness' => $this->get('deepness'),
370
				'elements' => $this->get('elements'),
371
				'mode' => $this->get('mode'),
372
				'mode_table' => $this->get('mode_table'),
373
				'mode_header' => $this->get('mode_header'),
374
				'mode_footer' => $this->get('mode_footer'),
375
				'min_entries' => $this->get('min_entries'),
376
				'max_entries' => $this->get('max_entries'),
377
				'allow_new' => $this->get('allow_new'),
378
				'allow_edit' => $this->get('allow_edit'),
379
				'allow_link' => $this->get('allow_link'),
380
				'allow_delete' => $this->get('allow_delete'),
381
				'allow_collapse' => $this->get('allow_collapse'),
382
				'allow_search' => $this->get('allow_search'),
383
				'show_header' => $this->get('show_header'),
384
			);
385
386
			return FieldManager::saveSettings($id, $settings);
387
		}
388
389
		/**
390
		 *
391
		 * This function allows Fields to cleanup any additional things before it is removed
392
		 * from the section.
393
		 * @return boolean
394
		 */
395
		public function tearDown()
396
		{
397
			self::removeSectionAssociation($this->get('id'));
398
			return parent::tearDown();
399
		}
400
401
		/**
402
		 * Generates the where filter for searching by entry id
403
		 *
404
		 * @param string $value
405
		 * @param @optional string $col
406
		 */
407
		public function generateWhereFilter($value, $col = 'd')
408
		{
409
			$junction = $andOperation ? 'and' : 'or';
0 ignored issues
show
Bug introduced by
The variable $andOperation does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
410
411
			if (!$value) {
412
				return [
413
					$junction => [
414
						[$col . '.entries' => null],
415
					],
416
				];
417
			}
418
419
			return [
420
				'or' => [
421
					[$col . '.entries' => $value],
422
					[$col . '.entries' => ['like' => $value . ',%']],
423
					[$col . '.entries' => ['like' => '%,' . $value]],
424
					[$col . '.entries' => ['like' => '%,' . $value . ',%']],
425
				]
426
			];
427
		}
428
429
		/**
430
		 * Fetch the number of associated entries for a particular entry id
431
		 *
432
		 * @param string $value
433
		 */
434
		public function fetchAssociatedEntryCount($value)
435
		{
436
			if (!$value) {
437
				return 0;
438
			}
439
440
			$where = $this->generateWhereFilter($value);
441
442
			$entries = Symphony::Database()
443
				->select(['e.id'])
444
				->from('tbl_entries', 'e')
445
				->innerJoin('tbl_entries_data_' . $this->get('id'), 'd')
446
				->on(['e.id' => '$d.entry_id'])
447
				->where(['e.section_id' => $this->get('parent_section')])
448
				->where($where)
449
				->execute()
450
				->rows();
451
452
			return count($entries);
453
		}
454
455
		public function fetchAssociatedEntrySearchValue($data, $field_id = null, $parent_entry_id = null)
456
		{
457
			return $parent_entry_id;
458
		}
459
460
		public function findRelatedEntries($entry_id, $parent_field_id)
461
		{
462
			$entries = $this->entryManager
463
				->select()
464
				->section($this->get('parent_section'))
465
				->filter($this->get('id'), [(string)$entry_id])
466
				->schema(['id'])
467
				->execute()
468
				->rows();
469
470
			return array_map(function ($e) {
471
				return $e['id'];
472
			}, $entries);
473
		}
474
475
		public function findParentRelatedEntries($parent_field_id, $entry_id)
476
		{
477
			$entry = $this->entryManager
478
				->select()
479
				->entry($entry_id)
480
				->section($this->entryManager->fetchEntrySectionID($entry_id))
481
				->includeAllFields()
482
				->schema(array($this->get('label')))
483
				->execute()
484
				->next();
485
486
			if (!$entry) {
487
				return array();
488
			}
489
490
			return self::getEntries($entry->getData($this->get('id')));
491
		}
492
493
		public function fetchFilterableOperators()
494
		{
495
			return array(
496
				array(
497
					'title' => 'links to',
498
					'filter' => ' ',
499
					'help' => __('Find entries that links to the specified filter')
500
				),
501
				array(
502
					'filter' => 'sql: NOT NULL',
503
					'title' => 'is not empty',
504
					'help' => __('Find entries with a non-empty value.')
505
				),
506
				array(
507
					'filter' => 'sql: NULL',
508
					'title' => 'is empty',
509
					'help' => __('Find entries with an empty value.')
510
				),
511
				array(
512
					'title' => 'contains',
513
					'filter' => 'regexp: ',
514
					'help' => __('Find values that match the given <a href="%s">MySQL regular expressions</a>.', array(
515
						'https://dev.mysql.com/doc/mysql/en/regexp.html'
516
					))
517
				),
518
				array(
519
					'title' => 'does not contain',
520
					'filter' => 'not-regexp: ',
521
					'help' => __('Find values that do not match the given <a href="%s">MySQL regular expressions</a>.', array(
522
						'https://dev.mysql.com/doc/mysql/en/regexp.html'
523
					))
524
				),
525
			);
526
		}
527
528
		public function fetchSuggestionTypes()
529
		{
530
			return array('association');
531
		}
532
533
		public function fetchIDsfromValue($value)
534
		{
535
			$ids = array();
536
			$sections = $this->getArray('sections');
537
538
			foreach ($sections as $sectionId) {
539
				$section = $this->sectionManager
540
					->select()
541
					->section($sectionId)
542
					->execute()
543
					->next();
544
545
				if (!$section) {
546
					continue;
547
				}
548
				$filterableFields = $section->fetchFilterableFields();
549
				if (empty($filterableFields)) {
550
					continue;
551
				}
552
				foreach ($filterableFields as $fId => $field) {
553
					if ($field instanceof FieldRelationship) {
554
						continue;
555
					}
556
					$fEntries = (new EntryManager)
557
						->select(['e.id'])
558
						->section($sectionId)
559
						->filter($fId, [$value])
560
						->execute()
561
						->rows();
562
563
					if (!empty($fEntries)) {
564
						$ids = array_merge($ids, $fEntries);
565
						break;
566
					}
567
				}
568
			}
569
570
			return array_map(function ($e) {
571
				return $e['id'];
572
			}, $ids);
573
		}
574
575
		public function prepareAssociationsDrawerXMLElement(Entry $e, array $parent_association, $prepolutate = '')
576
		{
577
			$currentSection = $this->sectionManager
578
				->select()
579
				->section($parent_association['child_section_id'])
580
				->execute()
581
				->next();
582
			$visibleCols = $currentSection->fetchVisibleColumns();
583
			$outputFieldId = current($visibleCols)->get('id');
584
			$outputField = $this->fieldManager
585
				->select()
586
				->field($outputFieldId)
587
				->execute()
588
				->next();
589
590
			$value = $outputField->prepareReadableValue($e->getData($outputFieldId), $e->get('id'), true, __('None'));
591
592
			$li = new XMLElement('li');
593
			$li->setAttribute('class', 'field-' . $this->get('type'));
594
			$a = new XMLElement('a', strip_tags($value));
595
			$a->setAttribute('href', SYMPHONY_URL . '/publish/' . $parent_association['handle'] . '/edit/' . $e->get('id') . '/');
596
			$li->appendChild($a);
597
598
			return $li;
599
		}
600
601
		/* ******* EVENTS ******* */
602
603
		public function getExampleFormMarkup()
604
		{
605
			$label = Widget::Label($this->get('label'));
606
			$label->appendChild(Widget::Input('fields['.$this->get('element_name').'][entries]', null, 'hidden'));
607
608
			return $label;
609
		}
610
611
612
		/* ******* DATA SOURCE ******* */
613
614
		private function fetchEntryWithData($eId, $sectionId, $elements = array())
615
		{
616
			$q = $this->entryManager
617
				->select()
618
				->entry($eId)
619
				->section($sectionId);
620
			if ($elements === null) {
621
				$q->includeAllFields();
622
			} else {
623
				$q->schema($elements);
624
			}
625
			return $q->execute()
626
				->next();
627
		}
628
629
		private function fetchEntryOnly($eId)
630
		{
631
			return $this->entryManager
632
				->select()
633
				->entry($eId)
634
				->execute()
635
				->next();
636
		}
637
638
		protected function fetchAllIncludableElements()
639
		{
640
			$sections = $this->getArray('sections');
641
			return $allElements = array_reduce($this->sectionInfos->fetch($sections), function ($memo, $item) {
642
				return array_merge($memo, array_map(function ($field) use ($item) {
643
					return $item['handle'] . '.' . $field['handle'];
644
				}, $item['fields']));
645
			}, array());
646
		}
647
648
		public function fetchIncludableElements()
649
		{
650
			$label = $this->get('element_name');
651
			$elements = $this->getArray('elements');
652
			if (empty($elements)) {
653
				$elements = array('*');
654
			}
655
			$includedElements = array();
656
			// Include everything
657
			if ($this->expandIncludableElements) {
658
				$includedElements[] = $label . ': *';
659
			}
660
			// Include individual elements
661
			foreach ($elements as $elem) {
662
				$elem = trim($elem);
663
				if ($elem !== '*') {
664
					$includedElements[] = $label . ': ' . $elem;
665
				} else if ($this->expandIncludableElements) {
666
					$includedElements = array_unique(array_merge($includedElements, array_map(function ($item) use ($label) {
667
						return $label . ': ' . $item;
668
					}, $this->fetchAllIncludableElements())));
669
				} else {
670
					$includedElements = array('*');
671
				}
672
			}
673
			return $includedElements;
674
		}
675
676
		/**
677
		 * Appends data into the XML tree of a Data Source
678
		 * @param $wrapper
679
		 * @param $data
680
		 */
681
		public function appendFormattedElement(XMLElement &$wrapper, $data, $encode = false, $mode = null, $entry_id = null)
682
		{
683
			if (!is_array($data) || empty($data)) {
684
				return;
685
			}
686
687
			// try to find an existing root
688
			$root = null;
689
			$newRoot = false;
690
			foreach (array_reverse($wrapper->getChildren()) as $xmlField) {
691
				if ($xmlField->getName() === $this->get('element_name')) {
692
					$root = $xmlField;
693
					break;
694
				}
695
			}
696
697
			// root was not found, create one
698
			if (!$root) {
699
				$root = new XMLElement($this->get('element_name'));
700
				$newRoot = true;
701
			}
702
703
			// devkit will load
704
			$devkit = isset($_GET['debug']) && (empty($_GET['debug']) || $_GET['debug'] == 'xml');
705
706
			// selected items
707
			$entries = static::getEntries($data);
708
709
			// current linked entries
710
			$root->setAttribute('entries', $data['entries']);
711
712
			// available sections
713
			$root->setAttribute('sections', $this->get('sections'));
714
715
			// included elements
716
			$elements = static::parseElements($this);
717
718
			// DS mode
719
			if (!$mode) {
720
				$mode = '*';
721
			}
722
723
			$parentDeepness = General::intval($this->recursiveDeepness);
724
			$deepness = General::intval($this->get('deepness'));
725
726
			// both deepnesses are defined and parent restricts more
727
			if ($parentDeepness > 0 && $deepness > 0 && $parentDeepness < $deepness) {
728
				$deepness = $parentDeepness;
729
			}
730
			// parent is defined, current is not
731
			else if ($parentDeepness > 0 && $deepness < 1) {
732
				$deepness = $parentDeepness;
733
			}
734
735
			// cache recursive level because recursion might
736
			// change its value later on.
737
			$recursiveLevel = $this->recursiveLevel;
738
739
			// build entries
740
			foreach ($entries as $eId) {
741
				// try to find and existing item
742
				// TODO: keep last index found since it should be the next
743
				$item = null;
744
				$newItem = false;
745
				foreach ($root->getChildren() as $xmlItem) {
746
					if (General::intval($xmlItem->getAttribute('id')) === General::intval($eId)) {
747
						$item = $xmlItem;
748
						break;
749
					}
750
				}
751
752
				// item was not found, create one
753
				if (!$item) {
754
					$item = new XMLElement('item');
755
					$item->setAllowEmptyAttributes(false);
756
					// output id
757
					$item->setAttribute('id', $eId);
758
					// output recursive level
759
					$item->setAttribute('level', $recursiveLevel);
760
					$item->setAttribute('max-level', $deepness);
761
					$newItem = true;
762
				// item was found, but it is an error, so we can skip it
763
				} else if ($item->getName() === 'error') {
764
					continue;
765
				}
766
767
				// max recursion check
768
				if ($deepness < 1 || $recursiveLevel < $deepness) {
769
					// current entry, without data
770
					$entry = $this->fetchEntryOnly($eId);
771
772
					// entry not found...
773
					if (!$entry || empty($entry)) {
774
						$error = new XMLElement('error');
775
						$error->setAttribute('id', $eId);
776
						$error->setValue(__('Error: entry `%s` not found', array($eId)));
777
						$root->prependChild($error);
778
						continue;
779
					}
780
781
					// fetch section infos
782
					$sectionId = $entry->get('section_id');
783
					$section = $this->sectionManager
784
						->select()
785
						->section($sectionId)
786
						->execute()
787
						->next();
788
					$sectionName = $section->get('handle');
789
790
					// set section related attributes
791
					$item->setAttribute('section-id', $sectionId);
792
					$item->setAttribute('section', $sectionName);
793
794
					// Get the valid elements for this section only
795
					$validElements = $elements[$sectionName];
796
797
					// adjust the mode for the current section
798
					$curMode = $mode;
799
800
					// remove section name from current mode so "sectionName.field" becomes simply "field"
801
					if (preg_match('/^(' . $sectionName . '\.)(.*)$/sU', $curMode)) {
802
						$curMode = preg_replace('/^' . $sectionName . '\./sU', '', $curMode);
803
					}
804
					// remove section name from current mode "sectionName" and
805
					// treat it like if it is "sectionName: *"
806
					else if (preg_match('/^(' . $sectionName . ')$/sU', $curMode)) {
807
						$curMode = '*';
808
					}
809
					// section name was not found in mode, check if the mode is "*"
810
					else if ($curMode != '*') {
811
						// mode forbids this section
812
						$validElements = null;
813
					}
814
815
					// this section is not selected, bail out
816 View Code Duplication
					if (!is_array($validElements)) {
817
						if ($newItem) {
818
							if ($devkit) {
819
								$item->setAttribute('x-forbidden-by-ds', $curMode);
820
							}
821
							$root->appendChild($item);
822
						}
823
						continue;
824
					} else {
825
						if ($devkit) {
826
							$item->setAttribute('x-forbidden-by-ds', null);
827
						}
828
					}
829
830
					// selected fields for fetching
831
					$sectionElements = array();
832
833
					// everything is allowed
834
					if (in_array('*', $validElements)) {
835
						if ($curMode !== '*') {
836
							// get only the mode
837
							$sectionElements = array($curMode);
838
						}
839
						else {
840
							// setting null = get all
841
							$sectionElements = null;
842
						}
843
					}
844
					// only use valid elements
845
					else {
846
						if ($curMode !== '*') {
847
							// is this field allowed ?
848
							if (self::isFieldIncluded($curMode, $validElements)) {
849
								// get only the mode
850
								$sectionElements = array($curMode);
851
							}
852
							else {
853
								// $curMode selects something outside of
854
								// the valid elements: select nothing
855
								$sectionElements = array();
856
							}
857
						}
858
						else {
859
							// use field's valid elements
860
							$sectionElements = $validElements;
861
						}
862
					}
863
864
					// Filtering is enabled, but nothing is selected
865 View Code Duplication
					if (is_array($sectionElements) && empty($sectionElements)) {
866
						if ($newItem) {
867
							$root->appendChild($item);
868
							if ($devkit) {
869
								$item->setAttribute('x-forbidden-by-selection', $curMode);
870
							}
871
						}
872
						continue;
873
					} else {
874
						if ($devkit) {
875
							$item->setAttribute('x-forbidden-by-selection', null);
876
						}
877
					}
878
879
					// fetch current entry again, but with data for the allowed schema
880
					$entry = $this->fetchEntryWithData($eId, $sectionId, $sectionElements);
881
882
					// cache the entry data
883
					$entryData = $entry->getData();
884
885
					// for each field returned for this entry...
886
					foreach ($entryData as $fieldId => $data) {
887
						$filteredData = array_filter($data, function ($value) {
888
							return $value != null;
889
						});
890
891
						if (empty($filteredData)) {
892
							continue;
893
						}
894
895
						$field = $this->fieldManager
896
							->select()
897
							->field($fieldId)
898
							->execute()
899
							->next();
900
						$fieldName = $field->get('element_name');
901
						$fieldCurMode = self::extractMode($fieldName, $curMode);
902
903
						$parentIncludableElement = self::getSectionElementName($fieldName, $validElements);
904
						$parentIncludableElementMode = self::extractMode($fieldName, $parentIncludableElement);
905
906
						// Special treatments for ERF
907
						if ($field instanceof FieldEntry_relationship) {
908
							// Increment recursive level
909
							$field->recursiveLevel = $recursiveLevel + 1;
910
							$field->recursiveDeepness = $deepness;
911
						}
912
913
						$submodes = null;
914
						// Parent mode is not defined (we are selecting the whole section)
915
						if ($parentIncludableElementMode === null) {
916
							if ($fieldCurMode == null) {
917
								// Field does not defined a mode either: use the field's default
918
								$submodes = null;
919
							} else {
920
								// Use the current field's mode
921
								$submodes = array($fieldCurMode);
922
							}
923
							if ($devkit) {
924
								$item->setAttribute('x-selection-mode-empty', null);
925
							}
926
						} else {
927
							// Field mode is not defined or it is the same as the parent
928
							if ($fieldCurMode === null || $fieldCurMode == $parentIncludableElementMode) {
929
								if ($devkit) {
930
									$item->setAttribute('x-selection-mode-empty', null);
931
								}
932
								// Use parent mode
933
								$submodes = array($parentIncludableElementMode);
934
							} else {
935
								if ($devkit) {
936
									$item->setAttribute('x-selection-mode-empty', 'yes');
937
								}
938
								// Empty selection
939
								$submodes = array();
940
							}
941
						}
942
943
						// current selection does not specify a mode
944
						if ($submodes === null) {
945
							if ($field instanceof FieldEntry_Relationship) {
946
								$field->expandIncludableElements = false;
947
							}
948
							$submodes = array_map(function ($fieldIncludableElement) use ($fieldName) {
949
								return FieldEntry_relationship::extractMode($fieldName, $fieldIncludableElement);
950
							}, $field->fetchIncludableElements());
951
							if ($field instanceof FieldEntry_Relationship) {
952
								$field->expandIncludableElements = true;
953
							}
954
						}
955
956
						// Append the formatted element for each requested mode
957
						foreach ($submodes as $submode) {
958
							$field->appendFormattedElement($item, $data, $encode, $submode, $eId);
959
						}
960
					}
961
					// output current mode
962
					$item->setAttribute('matched-element', $curMode);
963
					// no field selected
964
					if (is_array($sectionElements) && empty($sectionElements)) {
965
						$item->setAttribute('empty-selection', 'yes');
966
					}
967
				} // end max recursion check
968
969
				if ($newItem) {
970
					// append item when done
971
					$root->appendChild($item);
972
				}
973
			} // end each entries
974
975
			if ($newRoot) {
976
				// output mode for this field
977
				if ($devkit) {
978
					$root->setAttribute('x-data-source-mode', $mode);
979
					$root->setAttribute('x-field-included-elements', $this->get('elements'));
980
				}
981
982
				// add all our data to the wrapper;
983
				$wrapper->appendChild($root);
984
			} else {
985
				if ($devkit) {
986
					$root->setAttribute('x-data-source-mode', $root->getAttribute('x-data-source-mode') . ', ' . $mode);
987
				}
988
			}
989
990
			// clean up
991
			$this->recursiveLevel = 1;
992
			$this->recursiveDeepness = null;
993
		}
994
995
		public function getParameterPoolValue(array $data, $entry_id = null)
996
		{
997
			if(!is_array($data) || empty($data)) return;
998
			return static::getEntries($data);
999
		}
1000
1001
		/* ********* Utils *********** */
1002
1003
		/**
1004
		 * Return true if $fieldName is allowed in $sectionElements
1005
		 * @param string $fieldName
1006
		 * @param string $sectionElements
1007
		 * @return bool
1008
		 */
1009
		public static function isFieldIncluded($fieldName, $sectionElements)
1010
		{
1011
			return self::getSectionElementName($fieldName, $sectionElements) !== null;
1012
		}
1013
1014
		public static function getSectionElementName($fieldName, $sectionElements)
1015
		{
1016
			if (is_array($sectionElements)) {
1017
				foreach ($sectionElements as $element) {
1018
					// everything is allowed, use "fieldName" directly
1019
					if ($element === '*') {
1020
						return $fieldName;
1021
					}
1022
					// make "fieldName: *" the same as "fieldName"
1023
					if (preg_match('/\s*:\s*\*/sU', $fieldName)) {
1024
						$fieldName = trim(current(explode(':', $fieldName)));
1025
					}
1026
					// "fieldName" is included as-is or element starts with "fieldName:"
1027
					if ($fieldName === $element || preg_match('/^' . $fieldName . '\s*:/sU', $element)) {
1028
						return $element;
1029
					}
1030
				}
1031
			}
1032
			return null;
1033
		}
1034
1035
		public static function parseElements($field)
1036
		{
1037
			$elements = array();
1038
			$exElements = $field->getArray('elements');
1039
1040
1041
			if (in_array('*', $exElements)) {
1042
				$sections = $field->getArray('sections');
1043
				$sm = new SectionManager;
1044
				$sections = $sm
1045
					->select()
1046
					->sections($sections)
1047
					->execute()
1048
					->rows();
1049
				return array_reduce($sections, function ($result, $section) {
1050
					$result[$section->get('handle')] = array('*');
1051
					return $result;
1052
				}, array());
1053
			}
1054
1055
			foreach ($exElements as $value) {
1056
				if (!$value) {
1057
					continue;
1058
				}
1059
				// sectionName.fieldName or sectionName.*
1060
				$parts = array_map('trim', explode('.', $value, 2));
1061
				// first time seeing this section
1062
				if (!isset($elements[$parts[0]])) {
1063
					$elements[$parts[0]] = array();
1064
				}
1065
				// we have a value after the dot
1066
				if (isset($parts[1]) && !!$parts[1]) {
1067
					$elements[$parts[0]][] = $parts[1];
1068
				}
1069
				// sectionName only
1070
				else if (!isset($parts[1])) {
1071
					$elements[$parts[0]][] = '*';
1072
				}
1073
			}
1074
1075
			return $elements;
1076
		}
1077
1078
		public static function extractMode($fieldName, $mode)
1079
		{
1080
			$pattern = '/^' . $fieldName . '\s*:\s*/s';
1081
			if (!preg_match($pattern, $mode)) {
1082
				return null;
1083
			}
1084
			$mode = preg_replace($pattern, '', $mode, 1);
1085
			if ($mode === '*') {
1086
				return null;
1087
			}
1088
			return $mode;
1089
		}
1090
1091
		private function buildSectionSelect($name)
1092
		{
1093
			// $sections = SectionManager::fetch();
1094
			$sections = $this->sectionManager
1095
				->select()
1096
				->execute()
1097
				->rows();
1098
			$options = array();
1099
			$selectedSections = $this->getSelectedSectionsArray();
1100
1101 View Code Duplication
			foreach ($sections as $section) {
1102
				$driver = $section->get('id');
1103
				$selected = in_array($driver, $selectedSections);
1104
				$options[] = array($driver, $selected, General::sanitize($section->get('name')));
1105
			}
1106
1107
			return Widget::Select($name, $options, array('multiple' => 'multiple'));
1108
		}
1109
1110 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...
1111
		{
1112
			$name = $this->createSettingsFieldName('sections', true);
1113
1114
			$input = $this->buildSectionSelect($name);
1115
			$input->setAttribute('class', 'entry_relationship-sections');
1116
1117
			$label = Widget::Label();
1118
			$label->setAttribute('class', 'column');
1119
1120
			$label->setValue(__('Available sections %s', array($input->generate())));
1121
1122
			$wrapper->appendChild($label);
1123
		}
1124
1125
		private function createEntriesHiddenInput($data)
1126
		{
1127
			$hidden = new XMLElement('input', null, array(
1128
				'type' => 'hidden',
1129
				'name' => $this->createPublishFieldName('entries'),
1130
				'value' => $data['entries']
1131
			));
1132
1133
			return $hidden;
1134
		}
1135
1136
		private function createActionBarMenu($sections)
1137
		{
1138
			$wrap = new XMLElement('div');
1139
			$actionBar = '';
1140
			$modeFooter = $this->get('mode_footer');
1141
			if ($modeFooter) {
1142
				$section = $this->sectionManager
1143
					->select()
1144
					->section($this->get('parent_section'))
1145
					->execute()
1146
					->next();
1147
				$actionBar = ERFXSLTUTilities::processXSLT($this, null, $section->get('handle'), null, 'mode_footer', isset($_REQUEST['debug']), 'field');
1148
			}
1149
			if (empty($actionBar)) {
1150
				$fieldset = new XMLElement('fieldset');
1151
				$fieldset->setAttribute('class', 'single');
1152
				$div = new XMLElement('div');
1153
1154
				if ($this->is('allow_search')) {
1155
					$searchWrap = new XMLElement('div');
1156
					$searchWrap->setAttribute('data-interactive', 'data-interactive');
1157
					$searchWrap->setAttribute('class', 'search');
1158
					$searchInput = Widget::Input('', null, 'text', array(
1159
						'class' => 'search',
1160
						'data-search' => '',
1161
						'placeholder' => __('Search for entries')
1162
					));
1163
					$searchWrap->appendChild($searchInput);
1164
					$searchSuggestions = new XMLElement('ul');
1165
					$searchSuggestions->setAttribute('class', 'suggestions');
1166
					$searchWrap->appendChild($searchSuggestions);
1167
					$fieldset->appendChild($searchWrap);
1168
				}
1169
1170
				if ($this->is('allow_new') || $this->is('allow_link') || $this->is('allow_search')) {
1171
					$selectWrap = new XMLElement('div');
1172
					$selectWrap->appendChild(new XMLElement('span', __('Related section: '), array('class' => 'sections-selection')));
1173
					$options = array();
1174
					foreach ($sections as $section) {
1175
						$options[] = array($section->get('handle'), false, $section->get('name'));
1176
					}
1177
					$select = Widget::Select('', $options, array('class' => 'sections sections-selection'));
1178
					$selectWrap->appendChild($select);
1179
					$div->appendChild($selectWrap);
1180
				}
1181 View Code Duplication
				if ($this->is('allow_new')) {
1182
					$div->appendChild(new XMLElement('button', __('Create new'), array(
1183
						'type' => 'button',
1184
						'class' => 'create',
1185
						'data-create' => '',
1186
					)));
1187
				}
1188 View Code Duplication
				if ($this->is('allow_link')) {
1189
					$div->appendChild(new XMLElement('button', __('Link to entry'), array(
1190
						'type' => 'button',
1191
						'class' => 'link',
1192
						'data-link' => '',
1193
					)));
1194
				}
1195
				$fieldset->appendChild($div);
1196
				$wrap->appendChild($fieldset);
1197
			}
1198
			else {
1199
				$wrap->setValue($actionBar);
1200
			}
1201
1202
			return $wrap;
1203
		}
1204
1205
		/* ********* UI *********** */
1206
1207
		/**
1208
		 *
1209
		 * Builds the UI for the field's settings when creating/editing a section
1210
		 * @param XMLElement $wrapper
1211
		 * @param array $errors
1212
		 */
1213
		public function displaySettingsPanel(XMLElement &$wrapper, $errors = null)
1214
		{
1215
			/* first line, label and such */
1216
			parent::displaySettingsPanel($wrapper, $errors);
1217
1218
			// sections
1219
			$sections = new XMLElement('fieldset');
1220
1221
			$this->appendSelectionSelect($sections);
1222 View Code Duplication
			if (is_array($errors) && isset($errors['sections'])) {
1223
				$sections = Widget::Error($sections, $errors['sections']);
1224
			}
1225
			$wrapper->appendChild($sections);
1226
1227
			// elements
1228
			$elements = new XMLElement('div');
1229
			$element = Widget::Label();
1230
			$element->setValue(__('Included elements in Data Sources and Backend Templates'));
1231
			$element->setAttribute('class', 'column');
1232
			$element->appendChild(Widget::Input($this->createSettingsFieldName('elements'), $this->get('elements'), 'text', array(
1233
				'class' => 'entry_relationship-elements'
1234
			)));
1235
			$elements->appendChild($element);
1236
			$elements_choices = new XMLElement('ul', null, array('class' => 'tags singular entry_relationship-field-choices'));
1237
			$elements->appendChild($elements_choices);
1238
			$wrapper->appendChild($elements);
1239
1240
			// limit entries
1241
			$limits = new XMLElement('fieldset');
1242
			$limits->appendChild(new XMLElement('legend', __('Limits')));
1243
			$limits_cols = new XMLElement('div');
1244
			$limits_cols->setAttribute('class', 'three columns');
1245
			// min
1246
			$limit_min = Widget::Label();
1247
			$limit_min->setValue(__('Minimum count of entries in this field'));
1248
			$limit_min->setAttribute('class', 'column');
1249
			$limit_min->appendChild(Widget::Input($this->createSettingsFieldName('min_entries'), $this->get('min_entries'), 'number', array(
1250
				'min' => 0,
1251
				'max' => 99999
1252
			)));
1253
			$limits_cols->appendChild($limit_min);
1254
			// max
1255
			$limit_max = Widget::Label();
1256
			$limit_max->setValue(__('Maximum count of entries in this field'));
1257
			$limit_max->setAttribute('class', 'column');
1258
			$limit_max->appendChild(Widget::Input($this->createSettingsFieldName('max_entries'), $this->get('max_entries'), 'number', array(
1259
				'min' => 0,
1260
				'max' => 99999
1261
			)));
1262
			$limits_cols->appendChild($limit_max);
1263
1264
			// deepness
1265
			$deepness = Widget::Label();
1266
			$deepness->setValue(__('Maximum level of recursion in Data Sources'));
1267
			$deepness->setAttribute('class', 'column');
1268
			$deepness->appendChild(Widget::Input($this->createSettingsFieldName('deepness'), $this->get('deepness'), 'number', array(
1269
				'min' => 0,
1270
				'max' => 99
1271
			)));
1272
			$limits_cols->appendChild($deepness);
1273
			$limits->appendChild($limits_cols);
1274
			$wrapper->appendChild($limits);
1275
1276
			// xsl
1277
			$xsl = new XMLElement('fieldset');
1278
			$xsl->appendChild(new XMLElement('legend', __('Backend XSL templates options')));
1279
			$xsl_cols = new XMLElement('div');
1280
			$xsl_cols->setAttribute('class', 'four columns');
1281
1282
			// xsl mode
1283
			$xslmode = Widget::Label();
1284
			$xslmode->setValue(__('XSL mode for entries content template'));
1285
			$xslmode->setAttribute('class', 'column');
1286
			$xslmode->appendChild(Widget::Input($this->createSettingsFieldName('mode'), $this->get('mode'), 'text'));
1287
			$xsl_cols->appendChild($xslmode);
1288
			// xsl header mode
1289
			$xslmodetable = Widget::Label();
1290
			$xslmodetable->setValue(__('XSL mode for entries header template'));
1291
			$xslmodetable->setAttribute('class', 'column');
1292
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_header'), $this->get('mode_header'), 'text'));
1293
			$xsl_cols->appendChild($xslmodetable);
1294
			// xsl table mode
1295
			$xslmodetable = Widget::Label();
1296
			$xslmodetable->setValue(__('XSL mode for publish table value'));
1297
			$xslmodetable->setAttribute('class', 'column');
1298
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_table'), $this->get('mode_table'), 'text'));
1299
			$xsl_cols->appendChild($xslmodetable);
1300
			// xsl action bar mode
1301
			$xslmodetable = Widget::Label();
1302
			$xslmodetable->setValue(__('XSL mode for publish action bar'));
1303
			$xslmodetable->setAttribute('class', 'column');
1304
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_footer'), $this->get('mode_footer'), 'text'));
1305
			$xsl_cols->appendChild($xslmodetable);
1306
1307
			$xsl->appendChild($xsl_cols);
1308
			$wrapper->appendChild($xsl);
1309
1310
			// permissions
1311
			$permissions = new XMLElement('fieldset');
1312
			$permissions->appendChild(new XMLElement('legend', __('Permissions')));
1313
			$permissions_cols = new XMLElement('div');
1314
			$permissions_cols->setAttribute('class', 'four columns');
1315
			$permissions_cols->appendChild($this->createCheckbox('allow_new', 'Show new button'));
1316
			$permissions_cols->appendChild($this->createCheckbox('allow_edit', 'Show edit button'));
1317
			$permissions_cols->appendChild($this->createCheckbox('allow_link', 'Show link button'));
1318
			$permissions_cols->appendChild($this->createCheckbox('allow_delete', 'Show delete button'));
1319
			$permissions->appendChild($permissions_cols);
1320
			$wrapper->appendChild($permissions);
1321
1322
			// display options
1323
			$display = new XMLElement('fieldset');
1324
			$display->appendChild(new XMLElement('legend', __('Display options')));
1325
			$display_cols = new XMLElement('div');
1326
			$display_cols->setAttribute('class', 'four columns');
1327
			$display_cols->appendChild($this->createCheckbox('allow_collapse', 'Allow content collapsing'));
1328
			$display_cols->appendChild($this->createCheckbox('allow_search', 'Allow search linking'));
1329
			$display_cols->appendChild($this->createCheckbox('show_header', 'Show the header box before entries templates'));
1330
			$display->appendChild($display_cols);
1331
			$wrapper->appendChild($display);
1332
1333
			// assoc
1334
			$assoc = new XMLElement('fieldset');
1335
			$assoc->appendChild(new XMLElement('legend', __('Associations')));
1336
			$assoc_cols = new XMLElement('div');
1337
			$assoc_cols->setAttribute('class', 'three columns');
1338
			$this->appendShowAssociationCheckbox($assoc_cols);
1339
			$assoc->appendChild($assoc_cols);
1340
			$wrapper->appendChild($assoc);
1341
1342
			// footer
1343
			$this->appendStatusFooter($wrapper);
1344
		}
1345
1346
		/**
1347
		 *
1348
		 * Builds the UI for the publish page
1349
		 * @param XMLElement $wrapper
1350
		 * @param mixed $data
1351
		 * @param mixed $flagWithError
1352
		 * @param string $fieldnamePrefix
1353
		 * @param string $fieldnamePostfix
1354
		 */
1355
		public function displayPublishPanel(XMLElement &$wrapper, $data = null, $flagWithError = null, $fieldnamePrefix = null, $fieldnamePostfix = null, $entry_id = null)
1356
		{
1357
			$entriesId = array();
1358
			$sectionsId = $this->getSelectedSectionsArray();
1359
1360
			if ($data['entries'] != null) {
1361
				$entriesId = static::getEntries($data);
1362
			}
1363
1364
			$sectionsId = array_map(array('General', 'intval'), $sectionsId);
1365
			$sections = $this->sectionManager
1366
				->select()
1367
				->sections($sectionsId)
1368
				->execute()
1369
				->rows();
1370
			usort($sections, function ($a, $b) {
1371
				if ($a->get('name') === $b->get('name')) {
1372
					return 0;
1373
				}
1374
				return $a->get('name') < $b->get('name') ? -1 : 1;
1375
			});
1376
1377
			$label = Widget::Label($this->get('label'));
1378
			$notes = '';
1379
1380
			// min note
1381
			if ($this->getInt('min_entries') > 0) {
1382
				$notes .= __('Minimum number of entries: <b>%s</b>. ', array($this->get('min_entries')));
1383
			}
1384
			// max note
1385
			if ($this->getInt('max_entries') > 0) {
1386
				$notes .= __('Maximum number of entries: <b>%s</b>. ', array($this->get('max_entries')));
1387
			}
1388
			// not required note
1389
			if (!$this->isRequired()) {
1390
				$notes .= __('Optional');
1391
			}
1392
			// append notes
1393
			if ($notes) {
1394
				$label->appendChild(new XMLElement('i', $notes));
1395
			}
1396
1397
			// label error management
1398
			if ($flagWithError != null) {
1399
				$wrapper->appendChild(Widget::Error($label, $flagWithError));
1400
			} else {
1401
				$wrapper->appendChild($label);
1402
			}
1403
1404
			$wrapper->appendChild($this->createEntriesList($entriesId));
1405
			$wrapper->appendChild($this->createActionBarMenu($sections));
1406
			$wrapper->appendChild($this->createEntriesHiddenInput($data));
1407
			$wrapper->setAttribute('data-value', $data['entries']);
1408
			$wrapper->setAttribute('data-field-id', $this->get('id'));
1409
			$wrapper->setAttribute('data-field-label', $this->get('label'));
1410
			$wrapper->setAttribute('data-min', $this->get('min_entries'));
1411
			$wrapper->setAttribute('data-max', $this->get('max_entries'));
1412
			$wrapper->setAttribute('data-required', $this->get('required'));
1413
			if (isset($_REQUEST['debug'])) {
1414
				$wrapper->setAttribute('data-debug', true);
1415
			}
1416
		}
1417
1418
		/**
1419
		 *
1420
		 * Return a plain text representation of the field's data
1421
		 * @param array $data
1422
		 * @param int $entry_id
1423
		 */
1424
		public function prepareTextValue($data, $entry_id = null)
1425
		{
1426
			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...
1427
				return '';
1428
			}
1429
			return $data['entries'];
1430
		}
1431
1432
		/**
1433
		 * Format this field value for display as readable text value.
1434
		 *
1435
		 * @param array $data
1436
		 *  an associative array of data for this string. At minimum this requires a
1437
		 *  key of 'value'.
1438
		 * @param integer $entry_id (optional)
1439
		 *  An option entry ID for more intelligent processing. Defaults to null.
1440
		 * @param string $defaultValue (optional)
1441
		 *  The value to use when no plain text representation of the field's data
1442
		 *  can be made. Defaults to null.
1443
		 * @return string
1444
		 *  the readable text summary of the values of this field instance.
1445
		 */
1446
		public function prepareReadableValue($data, $entry_id = null, $truncate = false, $defaultValue = 'None')
1447
		{
1448
			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...
1449
				return __($defaultValue);
1450
			}
1451
			$entries = static::getEntries($data);
1452
			$realEntries = array();
1453
			foreach ($entries as $entryId) {
1454
				$e = $this->entryManager
1455
					->select()
1456
					->entry($entryId)
1457
					->execute()
1458
					->rows();
1459
				if (!empty($e)) {
1460
					$realEntries = array_merge($realEntries, $e);
1461
				}
1462
			}
1463
			$count = count($entries);
1464
			$realCount = count($realEntries);
1465
			if ($count === $realCount) {
1466
				return self::formatCount($count);
1467
			}
1468
			return self::formatCount($realCount) . ' (' . self::formatCount($count - $realCount) . ' not found)';
1469
		}
1470
1471
		/**
1472
		 * Format this field value for display in the publish index tables.
1473
		 *
1474
		 * @param array $data
1475
		 *  an associative array of data for this string. At minimum this requires a
1476
		 *  key of 'value'.
1477
		 * @param XMLElement $link (optional)
1478
		 *  an XML link structure to append the content of this to provided it is not
1479
		 *  null. it defaults to null.
1480
		 * @param integer $entry_id (optional)
1481
		 *  An option entry ID for more intelligent processing. defaults to null
1482
		 * @return string
1483
		 *  the formatted string summary of the values of this field instance.
1484
		 */
1485
		public function prepareTableValue($data, XMLElement $link = null, $entry_id = null)
1486
		{
1487
			$value = $this->prepareReadableValue($data, $entry_id, false, __('None'));
1488
1489
			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...
1490
				$entries = static::getEntries($data);
1491
				$cellcontent = '';
1492
				foreach ($entries as $position => $child_entry_id) {
1493
					$entry = $this->entryManager
1494
						->select()
1495
						->entry($child_entry_id)
1496
						->execute()
1497
						->next();
1498
					if (!$entry || empty($entry)) {
1499
						continue;
1500
					}
1501
					$section = $this->sectionManager
1502
						->select()
1503
						->section($entry->get('section_id'))
1504
						->execute()
1505
						->next();
1506
					$content = ERFXSLTUTilities::processXSLT($this, $entry, $section->get('handle'), $section->fetchFields(), 'mode_table', isset($_REQUEST['debug']), 'entry', $position + 1);
1507
					if ($content) {
1508
						$cellcontent .= $content;
1509
					}
1510
				}
1511
1512
				$cellcontent = trim($cellcontent);
1513
1514
				if (General::strlen($cellcontent)) {
1515
					if ($link) {
1516
						$link->setValue($cellcontent);
1517
						return $link->generate();
1518
					}
1519
					return $cellcontent;
1520
				}
1521
			} else if ($link) {
1522
				$link->setValue($value);
1523
				return $link->generate();
1524
			}
1525
1526
			return $value;
1527
		}
1528
1529
		/* ********* SQL Data Definition ************* */
1530
1531
		/**
1532
		 *
1533
		 * Creates table needed for entries of individual fields
1534
		 */
1535
		public function createTable()
1536
		{
1537
			return Symphony::Database()
1538
				->create('tbl_entries_data_' . $this->get('id'))
1539
				->ifNotExists()
1540
				->fields([
1541
					'id' => [
1542
						'type' => 'int(11)',
1543
						'auto' => true,
1544
					],
1545
					'entry_id' => 'int(11)',
1546
					'entries' => [
1547
						'type' => 'text',
1548
						'null' => true,
1549
					],
1550
				])
1551
				->keys([
1552
					'id' => 'primary',
1553
					'entry_id' => 'unique',
1554
				])
1555
				->execute()
1556
				->success();
1557
		}
1558
1559
		/**
1560
		 * Creates the table needed for the settings of the field
1561
		 */
1562
		public static function createFieldTable()
1563
		{
1564
			return Symphony::Database()
1565
				->create(self::FIELD_TBL_NAME)
1566
				->ifNotExists()
1567
				->fields([
1568
					'id' => [
1569
						'type' => 'int(11)',
1570
						'auto' => true,
1571
					],
1572
					'field_id' => 'int(11)',
1573
					'sections' => [
1574
						'type' => 'varchar(2048)',
1575
						'null' => true,
1576
					],
1577
					'show_association' => [
1578
						'type' => 'enum',
1579
						'values' => ['yes','no'],
1580
						'default' => 'yes',
1581
					],
1582
					'deepness' => [
1583
						'type' => 'int(2)',
1584
						'null' => true,
1585
					],
1586
					'elements' => [
1587
						'type' => 'text',
1588
						'null' => true,
1589
					],
1590
					'mode' => [
1591
						'type' => 'varchar(50)',
1592
						'null' => true,
1593
					],
1594
					'mode_table' => [
1595
						'type' => 'varchar(50)',
1596
						'null' => true,
1597
					],
1598
					'mode_header' => [
1599
						'type' => 'varchar(50)',
1600
						'null' => true,
1601
					],
1602
					'mode_footer' => [
1603
						'type' => 'varchar(50)',
1604
						'null' => true,
1605
					],
1606
					'min_entries' => [
1607
						'type' => 'int(5)',
1608
						'null' => true,
1609
					],
1610
					'max_entries' => [
1611
						'type' => 'int(5)',
1612
						'null' => true,
1613
					],
1614
					'allow_edit' => [
1615
						'type' => 'enum',
1616
						'values' => ['yes','no'],
1617
						'default' => 'yes',
1618
					],
1619
					'allow_new' => [
1620
						'type' => 'enum',
1621
						'values' => ['yes','no'],
1622
						'default' => 'yes',
1623
					],
1624
					'allow_link' => [
1625
						'type' => 'enum',
1626
						'values' => ['yes','no'],
1627
						'default' => 'yes',
1628
					],
1629
					'allow_delete' => [
1630
						'type' => 'enum',
1631
						'values' => ['yes','no'],
1632
						'default' => 'no',
1633
					],
1634
					'allow_collapse' => [
1635
						'type' => 'enum',
1636
						'values' => ['yes','no'],
1637
						'default' => 'yes',
1638
					],
1639
					'allow_search' => [
1640
						'type' => 'enum',
1641
						'values' => ['yes','no'],
1642
						'default' => 'no',
1643
					],
1644
					'show_header' => [
1645
						'type' => 'enum',
1646
						'values' => ['yes','no'],
1647
						'default' => 'yes',
1648
					],
1649
				])
1650
				->keys([
1651
					'id' => 'primary',
1652
					'field_id' => 'unique',
1653
				])
1654
				->execute()
1655
				->success();
1656
		}
1657
1658
		public static function update_102()
1659
		{
1660
			$addColumns = Symphony::Database()
1661
				->alter(self::FIELD_TBL_NAME)
1662
				->add([
1663
					'allow_edit' => [
1664
						'type' => 'enum',
1665
						'values' => ['yes','no'],
1666
						'default' => 'yes',
1667
					],
1668
					'allow_new' => [
1669
						'type' => 'enum',
1670
						'values' => ['yes','no'],
1671
						'default' => 'yes',
1672
					],
1673
					'allow_link' => [
1674
						'type' => 'enum',
1675
						'values' => ['yes','no'],
1676
						'default' => 'yes',
1677
					],
1678
				])
1679
				->after('max_entries')
1680
				->execute()
1681
				->success();
1682
1683
			if (!$addColumns) {
1684
				return false;
1685
			}
1686
1687
			$fields = $this->fieldManager
0 ignored issues
show
Bug introduced by
The variable $this does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1688
				->select()
1689
				->sort('id', 'asc')
1690
				->type('entry_relationship')
1691
				->execute()
1692
				->rows();
1693
1694
			if (!empty($fields) && is_array($fields)) {
1695
				foreach ($fields as $fieldId => $field) {
1696
					if (!Symphony::Database()
1697
						->alter('tbl_entries_data_' . $fieldId)
1698
						->modify([
1699
							'entries' => [
1700
								'type' => 'text',
1701
								'null' => true,
1702
							],
1703
						])
1704
						->execute()
1705
						->success()
1706
					) {
1707
						throw new Exception(__('Could not update table `tbl_entries_data_%s`.', array($fieldId)));
1708
					}
1709
				}
1710
			}
1711
			return true;
1712
		}
1713
1714 View Code Duplication
		public static function update_103()
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...
1715
		{
1716
			return Symphony::Database()
1717
				->alter(self::FIELD_TBL_NAME)
1718
				->add([
1719
					'allow_delete' => [
1720
						'type' => 'enum',
1721
						'values' => ['yes','no'],
1722
						'default' => 'no',
1723
					],
1724
				])
1725
				->after('allow_link')
1726
				->execute()
1727
				->success();
1728
		}
1729
1730
		public static function update_200()
1731
		{
1732
			return Symphony::Database()
1733
				->alter(self::FIELD_TBL_NAME)
1734
				->add([
1735
					'allow_collapse' => [
1736
						'type' => 'enum',
1737
						'values' => ['yes','no'],
1738
						'default' => 'yes',
1739
					],
1740
				])
1741
				->after('allow_delete')
1742
				->add([
1743
					'mode_table' => [
1744
						'type' => 'varchar(50)',
1745
						'null' => true,
1746
					],
1747
					'mode_header' => [
1748
						'type' => 'varchar(50)',
1749
						'null' => true,
1750
					],
1751
					'mode_footer' => [
1752
						'type' => 'varchar(50)',
1753
						'null'
1754
					],
1755
				])
1756
				->after('mode')
1757
				->add([
1758
					'show_header' => [
1759
						'type' => 'enum',
1760
						'values' => ['yes','no'],
1761
						'default' => 'yes',
1762
					],
1763
				])
1764
				->after('allow_collapse')
1765
				->change([
1766
					'sections' => [
1767
						'type' => 'varchar(2048)',
1768
						'null' => true,
1769
					],
1770
					'elements' => [
1771
						'type' => 'text',
1772
						'null' => true,
1773
					],
1774
				])
1775
				->execute()
1776
				->success();
1777
		}
1778
1779 View Code Duplication
		public static function update_2008()
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...
1780
		{
1781
			return Symphony::Database()
1782
				->alter(self::FIELD_TBL_NAME)
1783
				->add([
1784
					'allow_search' => [
1785
						'type' => 'enum',
1786
						'values' => ['yes','no'],
1787
						'default' => 'no',
1788
					],
1789
				])
1790
				->after('allow_collapse')
1791
				->execute()
1792
				->success();
1793
		}
1794
1795
		/**
1796
		 *
1797
		 * Drops the table needed for the settings of the field
1798
		 */
1799
		public static function deleteFieldTable()
1800
		{
1801
			return Symphony::Database()
1802
				->drop(self::FIELD_TBL_NAME)
1803
				->ifExists()
1804
				->execute()
1805
				->success();
1806
		}
1807
1808
		private static function removeSectionAssociation($child_field_id)
1809
		{
1810
			return Symphony::Database()
1811
				->delete('tbl_sections_association')
1812
				->where(['child_section_field_id' => $child_field_id])
1813
				->execute()
1814
				->success();
1815
		}
1816
	}
1817