Completed
Pull Request — dev (#67)
by
unknown
01:20
created

FieldEntry_Relationship::displayPublishPanel()   C

Complexity

Conditions 10
Paths 128

Size

Total Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 62
rs 6.7757
c 0
b 0
f 0
cc 10
nc 128
nop 6

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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