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

FieldEntry_Relationship::update_102()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 55
rs 8.3595
c 0
b 0
f 0
cc 6
nc 4
nop 0

How to fix   Long Method   

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