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

FieldEntry_Relationship::commit()   C

Complexity

Conditions 9
Paths 10

Size

Total Lines 78

Duplication

Lines 0
Ratio 0 %

Importance

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