Completed
Push — dev ( 77da81...185bfd )
by Nicolas
02:52
created

FieldEntry_Relationship::fetchIncludableElements()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.439
c 0
b 0
f 0
cc 6
eloc 17
nc 16
nop 0
1
<?php
2
	/**
3
	 * Copyright: Deux Huit Huit 2014
4
	 * LICENCE: MIT https://deuxhuithuit.mit-license.org
5
	 */
6
7
	if (!defined('__IN_SYMPHONY__')) die('<h2>Symphony Error</h2><p>You cannot directly access this file</p>');
8
9
	require_once(EXTENSIONS . '/entry_relationship_field/lib/class.field.relationship.php');
10
	require_once(EXTENSIONS . '/entry_relationship_field/lib/class.cacheablefetch.php');
11
	require_once(EXTENSIONS . '/entry_relationship_field/lib/class.erfxsltutilities.php');
12
	require_once(EXTENSIONS . '/entry_relationship_field/lib/class.sectionsinfos.php');
13
	
14
	/**
15
	 *
16
	 * Field class that will represent relationships between entries
17
	 * @author Deux Huit Huit
18
	 *
19
	 */
20
	class FieldEntry_Relationship extends FieldRelationship
21
	{
22
		/**
23
		 *
24
		 * Name of the field table
25
		 *  @var string
26
		 */
27
		const FIELD_TBL_NAME = 'tbl_fields_entry_relationship';
28
		
29
		/**
30
		 *
31
		 * Current recursive level of output
32
		 *  @var int
33
		 */
34
		protected $recursiveLevel = 1;
35
		public function getRecursiveLevel()
36
		{
37
			return $this->recursiveLevel;
38
		}
39
		public function incrementRecursiveLevel($inc = 1)
40
		{
41
			return $this->recursiveLevel += $inc;
42
		}
43
		
44
		/**
45
		 *
46
		 * Parent's maximum recursive level of output
47
		 *  @var int
48
		 */
49
		protected $recursiveDeepness = null;
50
		public function getRecursiveDeepness()
51
		{
52
			return $this->recursiveDeepness;
53
		}
54
		public function setRecursiveDeepness($deepness)
55
		{
56
			return $this->recursiveDeepness = $deepness;
57
		}
58
		
59
		/* Cacheable Managers */
60
		private $sectionManager;
61
		private $entryManager;
62
		private $sectionInfos;
63
		
64
		public $expandIncludableElements = true;
65
		
66
		/**
67
		 *
68
		 * Constructor for the Entry_Relationship Field object
69
		 */
70
		public function __construct()
71
		{
72
			// call the parent constructor
73
			parent::__construct();
74
			// set the name of the field
75
			$this->_name = __('Entry Relationship');
76
			// permits to make it required
77
			$this->_required = true;
78
			// permits the make it show in the table columns
79
			$this->_showcolumn = true;
80
			// permits association
81
			$this->_showassociation = true;
82
			// current recursive level
83
			$this->recursiveLevel = 1;
84
			// parent's maximum recursive level of output
85
			$this->recursiveDeepness = null;
86
			// set as orderable
87
			$this->orderable = true;
88
			// set as not required by default
89
			$this->set('required', 'no');
90
			// show association by default
91
			$this->set('show_association', 'yes');
92
			// no sections
93
			$this->set('sections', null);
94
			// no max deepness
95
			$this->set('deepness', null);
96
			// no included elements
97
			$this->set('elements', null);
98
			// no modes
99
			$this->set('mode', null);
100
			$this->set('mode_table', null);
101
			$this->set('mode_header', null);
102
			$this->set('mode_footer', null);
103
			// no limit
104
			$this->set('min_entries', null);
105
			$this->set('max_entries', null);
106
			// all permissions
107
			$this->set('allow_new', 'yes');
108
			$this->set('allow_edit', 'yes');
109
			$this->set('allow_link', 'yes');
110
			$this->set('allow_delete', 'no');
111
			// display options
112
			$this->set('allow_collapse', 'yes');
113
			$this->set('allow_search', 'no');
114
			$this->set('show_header', 'yes');
115
			$this->sectionManager = new CacheableFetch('SectionManager');
116
			$this->entryManager = new CacheableFetch('EntryManager');
117
			$this->sectionInfos = new CacheableFetch('SectionsInfos');
118
		}
119
120
		public function isSortable()
121
		{
122
			return false;
123
		}
124
125
		public function canFilter()
126
		{
127
			return true;
128
		}
129
		
130
		public function canPublishFilter()
131
		{
132
			return true;
133
		}
134
135
		public function canImport()
136
		{
137
			return false;
138
		}
139
140
		public function canPrePopulate()
141
		{
142
			return false;
143
		}
144
		
145
		public function mustBeUnique()
146
		{
147
			return false;
148
		}
149
150
		public function allowDatasourceOutputGrouping()
151
		{
152
			return false;
153
		}
154
155
		public function requiresSQLGrouping()
156
		{
157
			return false;
158
		}
159
160
		public function allowDatasourceParamOutput()
161
		{
162
			return true;
163
		}
164
165
		/* ********** INPUT AND FIELD *********** */
166
167
168
		/**
169
		 * 
170
		 * Validates input
171
		 * Called before <code>processRawFieldData</code>
172
		 * @param $data
173
		 * @param $message
174
		 * @param $entry_id
175
		 */
176
		public function checkPostFieldData($data, &$message, $entry_id=null)
177
		{
178
			$message = null;
179
			$required = $this->isRequired();
180
			
181
			if ($required && (!is_array($data) || count($data) == 0 || strlen($data['entries']) < 1)) {
182
				$message = __("'%s' is a required field.", array($this->get('label')));
183
				return self::__MISSING_FIELDS__;
184
			}
185
			
186
			$entries = $data['entries'];
187
			
188
			if (!is_array($entries)) {
189
				$entries = static::getEntries($data);
190
			}
191
			
192
			// enforce limits only if required or it contains data
193
			if ($required || count($entries) > 0) {
194
				if ($this->getInt('min_entries') > 0 && $this->getInt('min_entries') > count($entries)) {
195
					$message = __("'%s' requires a minimum of %s entries.", array($this->get('label'), $this->getInt('min_entries')));
196
					return self::__INVALID_FIELDS__;
197
				} else if ($this->getInt('max_entries') > 0 && $this->getInt('max_entries') < count($entries)) {
198
					$message = __("'%s' can not contains more than %s entries.", array($this->get('label'), $this->getInt('max_entries')));
199
					return self::__INVALID_FIELDS__;
200
				}
201
			}
202
			
203
			return self::__OK__;
204
		}
205
206
207
		/**
208
		 *
209
		 * Process data before saving into database.
210
		 *
211
		 * @param array $data
212
		 * @param int $status
213
		 * @param boolean $simulate
214
		 * @param int $entry_id
215
		 *
216
		 * @return Array - data to be inserted into DB
217
		 */
218
		public function processRawFieldData($data, &$status, &$message = null, $simulate = false, $entry_id = null)
219
		{
220
			$status = self::__OK__;
221
			$entries = null;
222
			
223
			if (!is_array($data) && !is_string($data)) {
224
				return null;
225
			}
226
			
227
			if (isset($data['entries'])) {
228
				$entries = $data['entries'];
229
			}
230
			else if (is_string($data)) {
231
				$entries = $data;
232
			}
233
			
234
			$row = array(
235
				'entries' => $entries
236
			);
237
			
238
			// return row
239
			return $row;
240
		}
241
242
		/**
243
		 * This function permits parsing different field settings values
244
		 *
245
		 * @param array $settings
246
		 *	the data array to initialize if necessary.
247
		 */
248
		public function setFromPOST(Array $settings = array())
249
		{
250
			// call the default behavior
251
			parent::setFromPOST($settings);
252
253
			// declare a new setting array
254
			$new_settings = array();
255
256
			// set new settings
257
			$new_settings['sections'] = is_array($settings['sections']) ? 
258
				implode(self::SEPARATOR, $settings['sections']) : 
259
				(is_string($settings['sections']) ? $settings['sections'] : null);
260
				
261
			$new_settings['show_association'] = $settings['show_association'] == 'yes' ? 'yes' : 'no';
262
			$new_settings['deepness'] = General::intval($settings['deepness']);
263
			$new_settings['deepness'] = $new_settings['deepness'] < 1 ? null : $new_settings['deepness'];
264
			$new_settings['elements'] = empty($settings['elements']) ? null : $settings['elements'];
265
			$new_settings['mode'] = empty($settings['mode']) ? null : $settings['mode'];
266
			$new_settings['mode_table'] = empty($settings['mode_table']) ? null : $settings['mode_table'];
267
			$new_settings['mode_header'] = empty($settings['mode_header']) ? null : $settings['mode_header'];
268
			$new_settings['mode_footer'] = empty($settings['mode_footer']) ? null : $settings['mode_footer'];
269
			$new_settings['allow_new'] = $settings['allow_new'] == 'yes' ? 'yes' : 'no';
270
			$new_settings['allow_edit'] = $settings['allow_edit'] == 'yes' ? 'yes' : 'no';
271
			$new_settings['allow_link'] = $settings['allow_link'] == 'yes' ? 'yes' : 'no';
272
			$new_settings['allow_delete'] = $settings['allow_delete'] == 'yes' ? 'yes' : 'no';
273
			$new_settings['allow_collapse'] = $settings['allow_collapse'] == 'yes' ? 'yes' : 'no';
274
			$new_settings['allow_search'] = $settings['allow_search'] == 'yes' ? 'yes' : 'no';
275
			$new_settings['show_header'] = $settings['show_header'] == 'yes' ? 'yes' : 'no';
276
			
277
			// save it into the array
278
			$this->setArray($new_settings);
279
		}
280
281
282
		/**
283
		 *
284
		 * Validates the field settings before saving it into the field's table
285
		 */
286
		public function checkFields(Array &$errors, $checkForDuplicates = true)
287
		{
288
			$parent = parent::checkFields($errors, $checkForDuplicates);
289
			if ($parent != self::__OK__) {
290
				return $parent;
291
			}
292
			
293
			$sections = $this->get('sections');
294
			
295
			if (empty($sections)) {
296
				$errors['sections'] = __('At least one section must be chosen');
297
			}
298
299
			return (!empty($errors) ? self::__ERROR__ : self::__OK__);
300
		}
301
302
		/**
303
		 *
304
		 * Save field settings into the field's table
305
		 */
306
		public function commit()
307
		{
308
			// if the default implementation works...
309
			if(!parent::commit()) return false;
310
			
311
			$id = $this->get('id');
312
			
313
			// exit if there is no id
314
			if($id == false) return false;
315
			
316
			// we are the child, with multiple parents
317
			$child_field_id = $id;
318
			
319
			// delete associations, only where we are the child
320
			self::removeSectionAssociation($child_field_id);
321
			
322
			$sections = $this->getSelectedSectionsArray();
323
			
324
			foreach ($sections as $key => $sectionId) {
325
				if (empty($sectionId)) {
326
					continue;
327
				}
328
				$parent_section_id = General::intval($sectionId);
329
				$parent_section = SectionManager::fetch($sectionId);
330
				$fields = $parent_section->fetchVisibleColumns();
331
				if (empty($fields)) {
332
					// no visible field, revert to all
333
					$fields = $parent_section->fetchFields();
334
				}
335
				$parent_field_id = current(array_keys($fields));
336
				// create association
337
				SectionManager::createSectionAssociation(
338
					$parent_section_id,
339
					$child_field_id,
340
					$parent_field_id,
341
					$this->get('show_association') == 'yes'
342
				);
343
			}
344
			
345
			// declare an array contains the field's settings
346
			$settings = array(
347
				'sections' => $this->get('sections'),
348
				'show_association' => $this->get('show_association'),
349
				'deepness' => $this->get('deepness'),
350
				'elements' => $this->get('elements'),
351
				'mode' => $this->get('mode'),
352
				'mode_table' => $this->get('mode_table'),
353
				'mode_header' => $this->get('mode_header'),
354
				'mode_footer' => $this->get('mode_footer'),
355
				'min_entries' => $this->get('min_entries'),
356
				'max_entries' => $this->get('max_entries'),
357
				'allow_new' => $this->get('allow_new'),
358
				'allow_edit' => $this->get('allow_edit'),
359
				'allow_link' => $this->get('allow_link'),
360
				'allow_delete' => $this->get('allow_delete'),
361
				'allow_collapse' => $this->get('allow_collapse'),
362
				'allow_search' => $this->get('allow_search'),
363
				'show_header' => $this->get('show_header'),
364
			);
365
366
			return FieldManager::saveSettings($id, $settings);
367
		}
368
369
		/**
370
		 *
371
		 * This function allows Fields to cleanup any additional things before it is removed
372
		 * from the section.
373
		 * @return boolean
374
		 */
375
		public function tearDown()
376
		{
377
			self::removeSectionAssociation($this->get('id'));
378
			return parent::tearDown();
379
		}
380
		
381
		/**
382
		 * Generates the where filter for searching by entry id
383
		 *
384
		 * @param string $value
385
		 * @param @optional string $col
386
		 * @param @optional boolean $andOperation
387
		 */
388
		public function generateWhereFilter($value, $col = 'd', $andOperation = true)
389
		{
390
			$junction = $andOperation ? 'AND' : 'OR';
391
			if (!$value) {
392
				return "{$junction} (`{$col}`.`entries` IS NULL)";
393
			}
394
			return " {$junction} (`{$col}`.`entries` = '{$value}' OR 
395
					`{$col}`.`entries` LIKE '{$value},%' OR 
396
					`{$col}`.`entries` LIKE '%,{$value}' OR 
397
					`{$col}`.`entries` LIKE '%,{$value},%')";
398
		}
399
400
		/**
401
		 * Fetch the number of associated entries for a particular entry id
402
		 *
403
		 * @param string $value
404
		 */
405
		public function fetchAssociatedEntryCount($value)
406
		{
407
			if (!$value) {
408
				return 0;
409
			}
410
			$join = sprintf(" INNER JOIN `tbl_entries_data_%s` AS `d` ON `e`.id = `d`.`entry_id`", $this->get('id'));
411
			$where = $this->generateWhereFilter($value);
412
			
413
			$entries = EntryManager::fetch(null, $this->get('parent_section'), null, 0, $where, $join, false, false, array());
414
			
415
			return count($entries);
416
		}
417
		
418
		public function fetchAssociatedEntrySearchValue($data, $field_id = null, $parent_entry_id = null)
419
		{
420
			return $parent_entry_id;
421
		}
422
		
423
		public function findRelatedEntries($entry_id, $parent_field_id)
424
		{
425
			$joins = '';
426
			$where = '';
427
			$this->buildDSRetrievalSQL(array($entry_id), $joins, $where, true);
428
			
429
			$entries = EntryManager::fetch(null, $this->get('parent_section'), null, 0, $where, $joins, false, false, array());
430
			
431
			return array_map(function ($e) {
432
				return $e['id'];
433
			}, $entries);
434
		}
435
		
436
		public function findParentRelatedEntries($parent_field_id, $entry_id)
437
		{
438
			$entry = $this->fetchEntry($entry_id, array($this->get('label')));
439
			
440
			if (!$entry) {
441
				return array();
442
			}
443
			
444
			return self::getEntries($entry->getData($this->get('id')));
445
		}
446
447
		public function fetchFilterableOperators()
448
		{
449
			return array(
450
				array(
451
					'title' => 'links to',
452
					'filter' => ' ',
453
					'help' => __('Find entries that links to the specified filter')
454
				),
455
			);
456
		}
457
458
		public function fetchSuggestionTypes()
459
		{
460
			return array('association');
461
		}
462
463
		public function fetchIDsfromValue($value)
464
		{
465
			$ids = array();
466
			$sections = $this->getArray('sections');
467
			//$value = Lang::createHandle($value);
468
469
			foreach ($sections as $sectionId) {
470
				$section = $this->sectionManager->fetch($sectionId);
471
				if (!$section) {
472
					continue;
473
				}
474
				$filterableFields = $section->fetchFilterableFields();
475
				if (empty($filterableFields)) {
476
					continue;
477
				}
478
				foreach ($filterableFields as $fId => $field) {
479
					$joins = '';
480
					$where = '';
481
					if ($field instanceof FieldRelationship) {
482
						continue;
483
					}
484
					$field->buildDSRetrievalSQL(array($value), $joins, $where, false);
485
					$fEntries = $this->entryManager->fetch(null, $sectionId, null, null, $where, $joins, true, false, null, false);
486
					if (!empty($fEntries)) {
487
						$ids = array_merge($ids, $fEntries);
488
						break;
489
					}
490
				}
491
			}
492
493
			return array_map(function ($e) {
494
				return $e['id'];
495
			}, $ids);
496
		}
497
498
		public function prepareAssociationsDrawerXMLElement(Entry $e, array $parent_association, $prepolutate = '')
499
		{
500
			$currentSection = SectionManager::fetch($parent_association['child_section_id']);
501
			$visibleCols = $currentSection->fetchVisibleColumns();
502
			$outputFieldId = current(array_keys($visibleCols));
503
			$outputField = FieldManager::fetch($outputFieldId);
504
			
505
			$value = $outputField->prepareReadableValue($e->getData($outputFieldId), $e->get('id'), true, __('None'));
506
			
507
			$li = new XMLElement('li');
508
			$li->setAttribute('class', 'field-' . $this->get('type'));
509
			$a = new XMLElement('a', strip_tags($value));
510
			$a->setAttribute('href', SYMPHONY_URL . '/publish/' . $parent_association['handle'] . '/edit/' . $e->get('id') . '/');
511
			$li->appendChild($a);
512
513
			return $li;
514
		}
515
		
516
		/**
517
		 * @param string $joins
518
		 * @param string $where
519
		 */
520
		public function buildDSRetrievalSQL($data, &$joins, &$where, $andOperation = false)
521
		{
522
			$field_id = $this->get('id');
523
			
524
			// REGEX filtering is a special case, and will only work on the first item
525
			// in the array. You cannot specify multiple filters when REGEX is involved.
526
			if (self::isFilterRegex($data[0])) {
527
				return $this->buildRegexSQL($data[0], array('entries'), $joins, $where);
528
			}
529
			
530
			$this->_key++;
531
			
532
			$where .= ' AND (1=' . ($andOperation ? '1' : '0') . ' ';
533
			
534
			$joins .= "
535
				INNER JOIN
536
					`tbl_entries_data_{$field_id}` AS `t{$field_id}_{$this->_key}`
537
					ON (`e`.`id` = `t{$field_id}_{$this->_key}`.`entry_id`)
538
			";
539
			
540
			$normalizedValues = array();
541
			foreach ($data as $value) {
542
				if (!is_numeric($value) && !is_null($value)) {
543
					$normalizedValues = array_merge($normalizedValues, $this->fetchIDsfromValue($value));
544
				} else {
545
					$normalizedValues[] = $value;
546
				}
547
			}
548
			
549
			foreach ($normalizedValues as $value) {
550
				$where .= $this->generateWhereFilter($this->cleanValue($value), "t{$field_id}_{$this->_key}", $andOperation);
551
			}
552
			
553
			$where .= ')';
554
			
555
			return true; // this tells the DS Manager that filters are OK!!
556
		}
557
558
		/* ******* EVENTS ******* */
559
560
		public function getExampleFormMarkup()
561
		{
562
			$label = Widget::Label($this->get('label'));
563
			$label->appendChild(Widget::Input('fields['.$this->get('element_name').'][entries]', null, 'hidden'));
564
565
			return $label;
566
		}
567
568
569
		/* ******* DATA SOURCE ******* */
570
		
571
		private function fetchEntry($eId, $elements = array())
572
		{
573
			$entry = EntryManager::fetch($eId, null, 1, 0, null, null, false, true, $elements, false);
574
			if (!is_array($entry) || count($entry) !== 1) {
575
				return null;
576
			}
577
			return $entry[0];
578
		}
579
		
580
		protected function fetchAllIncludableElements()
581
		{
582
			$sections = $this->getArray('sections');
583
			return $allElements = array_reduce($this->sectionInfos->fetch($sections), function ($memo, $item) {
584
				return array_merge($memo, array_map(function ($field) use ($item) {
585
					return $item['handle'] . '.' . $field['handle'];
586
				}, $item['fields']));
587
			}, array());
588
		}
589
		
590
		public function fetchIncludableElements()
591
		{
592
			$label = $this->get('element_name');
593
			$elements = $this->getArray('elements');
594
			if (empty($elements)) {
595
				$elements = array('*');
596
			}
597
			$includedElements = array();
598
			// Include everything
599
			if ($this->expandIncludableElements) {
600
				$includedElements[] = $label . ': *';
601
			}
602
			// Include individual elements
603
			foreach ($elements as $elem) {
604
				$elem = trim($elem);
605
				if ($elem !== '*') {
606
					$includedElements[] = $label . ': ' . $elem;
607
				} else if ($this->expandIncludableElements) {
608
					$includedElements = array_unique(array_merge($includedElements, array_map(function ($item) use ($label) {
609
						return $label . ': ' . $item;
610
					}, $this->fetchAllIncludableElements())));
611
				}
612
			}
613
			return $includedElements;
614
		}
615
		
616
		/**
617
		 * Appends data into the XML tree of a Data Source
618
		 * @param $wrapper
619
		 * @param $data
620
		 */
621
		public function appendFormattedElement(XMLElement &$wrapper, $data, $encode = false, $mode = null, $entry_id = null)
622
		{
623
			if (!is_array($data) || empty($data)) {
624
				return;
625
			}
626
627
			// try to find an existing root
628
			$root = null;
629
			$newRoot = false;
630
			foreach (array_reverse($wrapper->getChildren()) as $xmlField) {
631
				if ($xmlField->getName() === $this->get('element_name')) {
632
					$root = $xmlField;
633
					break;
634
				}
635
			}
636
637
			// root was not found, create one
638
			if (!$root) {
639
				$root = new XMLElement($this->get('element_name'));
640
				$newRoot = true;
641
			}
642
			
643
			// devkit will load
644
			$devkit = isset($_GET['debug']) && (empty($_GET['debug']) || $_GET['debug'] == 'xml');
645
			
646
			// selected items
647
			$entries = static::getEntries($data);
648
			
649
			// current linked entries
650
			$root->setAttribute('entries', $data['entries']);
651
			
652
			// available sections
653
			$root->setAttribute('sections', $this->get('sections'));
654
			
655
			// included elements
656
			$elements = static::parseElements($this);
657
			
658
			// DS mode
659
			if (!$mode) {
660
				$mode = '*';
661
			}
662
			
663
			$parentDeepness = General::intval($this->recursiveDeepness);
664
			$deepness = General::intval($this->get('deepness'));
665
			
666
			// both deepnesses are defined and parent restricts more
667
			if ($parentDeepness > 0 && $deepness > 0 && $parentDeepness < $deepness) {
668
				$deepness = $parentDeepness;
669
			}
670
			// parent is defined, current is not
671
			else if ($parentDeepness > 0 && $deepness < 1) {
672
				$deepness = $parentDeepness;
673
			}
674
			
675
			// cache recursive level because recursion might
676
			// change its value later on.
677
			$recursiveLevel = $this->recursiveLevel;
678
			
679
			// build entries
680
			foreach ($entries as $eId) {
681
				// try to find and existing item
682
				// TODO: keep last index found since it should be the next
683
				$item = null;
684
				$newItem = false;
685
				foreach ($root->getChildren() as $xmlItem) {
686
					if (General::intval($xmlItem->getAttribute('id')) === General::intval($eId)) {
687
						$item = $xmlItem;
688
						break;
689
					}
690
				}
691
692
				// item was not found, create one
693
				if (!$item) {
694
					$item = new XMLElement('item');
695
					$item->setAllowEmptyAttributes(false);
696
					// output id
697
					$item->setAttribute('id', $eId);
698
					// output recursive level
699
					$item->setAttribute('level', $recursiveLevel);
700
					$item->setAttribute('max-level', $deepness);
701
					$newItem = true;
702
				// item was found, but it is an error, so we can skip it
703
				} else if ($item->getName() === 'error') {
704
					continue;
705
				}
706
707
				// max recursion check
708
				if ($deepness < 1 || $recursiveLevel < $deepness) {
709
					// current entry, without data
710
					$entry = $this->fetchEntry($eId);
711
					
712
					// entry not found...
713
					if (!$entry || empty($entry)) {
714
						$error = new XMLElement('error');
715
						$error->setAttribute('id', $eId);
716
						$error->setValue(__('Error: entry `%s` not found', array($eId)));
717
						$root->prependChild($error);
718
						continue;
719
					}
720
					
721
					// fetch section infos
722
					$sectionId = $entry->get('section_id');
723
					$section = $this->sectionManager->fetch($sectionId);
724
					$sectionName = $section->get('handle');
725
					// cache fields info
726
					if (!isset($section->er_field_cache)) {
727
						$section->er_field_cache = $section->fetchFields();
728
					}
729
					
730
					// set section related attributes
731
					$item->setAttribute('section-id', $sectionId);
732
					$item->setAttribute('section', $sectionName);
733
					
734
					// Get the valid elements for this section only
735
					$validElements = $elements[$sectionName];
736
					
737
					// adjust the mode for the current section
738
					$curMode = $mode;
739
					
740
					// remove section name from current mode so "sectionName.field" becomes simply "field"
741
					if (preg_match('/^(' . $sectionName . '\.)(.*)$/sU', $curMode)) {
742
						$curMode = preg_replace('/^' . $sectionName . '\./sU', '', $curMode);
743
					}
744
					// remove section name from current mode "sectionName" and
745
					// treat it like if it is "sectionName: *"
746
					else if (preg_match('/^(' . $sectionName . ')$/sU', $curMode)) {
747
						$curMode = '*';
748
					}
749
					// section name was not found in mode, check if the mode is "*"
750
					else if ($curMode != '*') {
751
						// mode forbids this section
752
						$validElements = null;
753
					}
754
					
755
					// this section is not selected, bail out
756 View Code Duplication
					if (!is_array($validElements)) {
757
						if ($newItem) {
758
							if ($devkit) {
759
								$item->setAttribute('x-forbidden-by-ds', $curMode);
760
							}
761
							$root->appendChild($item);
762
						}
763
						continue;
764
					} else {
765
						if ($devkit) {
766
							$item->setAttribute('x-forbidden-by-ds', null);
767
						}
768
					}
769
					
770
					// selected fields for fetching
771
					$sectionElements = array();
772
					
773
					// everything is allowed
774
					if (in_array('*', $validElements)) {
775
						if ($curMode !== '*') {
776
							// get only the mode
777
							$sectionElements = array($curMode);
778
						}
779
						else {
780
							// setting null = get all
781
							$sectionElements = null;
782
						}
783
					}
784
					// only use valid elements
785
					else {
786
						if ($curMode !== '*') {
787
							// is this field allowed ?
788
							if (self::isFieldIncluded($curMode, $validElements)) {
789
								// get only the mode
790
								$sectionElements = array($curMode);
791
							}
792
							else {
793
								// $curMode selects something outside of
794
								// the valid elements: select nothing
795
								$sectionElements = array();
796
							}
797
						}
798
						else {
799
							// use field's valid elements
800
							$sectionElements = $validElements;
801
						}
802
					}
803
					
804
					// Filtering is enabled, but nothing is selected
805 View Code Duplication
					if (is_array($sectionElements) && empty($sectionElements)) {
806
						if ($newItem) {
807
							$root->appendChild($item);
808
							if ($devkit) {
809
								$item->setAttribute('x-forbidden-by-selection', $curMode);
810
							}
811
						}
812
						continue;
813
					} else {
814
						if ($devkit) {
815
							$item->setAttribute('x-forbidden-by-selection', null);
816
						}
817
					}
818
					
819
					// fetch current entry again, but with data for the allowed schema
820
					$entry = $this->fetchEntry($eId, $sectionElements);
821
					
822
					// cache the entry data
823
					$entryData = $entry->getData();
824
					
825
					// for each field returned for this entry...
826
					foreach ($entryData as $fieldId => $data) {
827
						$filteredData = array_filter($data, function ($value) {
828
							return $value != null;
829
						});
830
						
831
						if (empty($filteredData)) {
832
							continue;
833
						}
834
						
835
						$field = $section->er_field_cache[$fieldId];
836
						$fieldName = $field->get('element_name');
837
						$fieldCurMode = self::extractMode($fieldName, $curMode);
838
						
839
						$parentIncludableElement = self::getSectionElementName($fieldName, $validElements);
840
						$parentIncludableElementMode = self::extractMode($fieldName, $parentIncludableElement);
841
						
842
						// Special treatments for ERF
843
						if ($field instanceof FieldEntry_relationship) {
844
							// Increment recursive level
845
							$field->recursiveLevel = $recursiveLevel + 1;
846
							$field->recursiveDeepness = $deepness;
847
						}
848
						
849
						$submodes = null;
850
						// Parent mode is not defined (we are selecting the whole section)
851
						if ($parentIncludableElementMode === null) {
852
							if ($fieldCurMode == null) {
853
								// Field does not defined a mode either: use the field's default
854
								$submodes = null;
855
							} else {
856
								// Use the current field's mode
857
								$submodes = array($fieldCurMode);
858
							}
859
							if ($devkit) {
860
								$item->setAttribute('x-selection-mode-empty', null);
861
							}
862
						} else {
863
							// Field mode is not defined or it is the same as the parent
864
							if ($fieldCurMode === null || $fieldCurMode == $parentIncludableElementMode) {
865
								if ($devkit) {
866
									$item->setAttribute('x-selection-mode-empty', null);
867
								}
868
								// Use parent mode
869
								$submodes = array($parentIncludableElementMode);
870
							} else {
871
								if ($devkit) {
872
									$item->setAttribute('x-selection-mode-empty', 'yes');
873
								}
874
								// Empty selection
875
								$submodes = array();
876
							}
877
						}
878
879
						// current selection does not specify a mode
880
						if ($submodes === null) {
881
							if ($field instanceof FieldEntry_Relationship) {
882
								$field->expandIncludableElements = false;
883
							}
884
							$submodes = array_map(function ($fieldIncludableElement) use ($fieldName) {
885
								return FieldEntry_relationship::extractMode($fieldName, $fieldIncludableElement);
886
							}, $field->fetchIncludableElements());
887
							if ($field instanceof FieldEntry_Relationship) {
888
								$field->expandIncludableElements = true;
889
							}
890
						}
891
						
892
						// Append the formatted element for each requested mode
893
						foreach ($submodes as $submode) {
894
							$field->appendFormattedElement($item, $data, $encode, $submode, $eId);
895
						}
896
					}
897
					// output current mode
898
					$item->setAttribute('matched-element', $curMode);
899
					// no field selected
900
					if (is_array($sectionElements) && empty($sectionElements)) {
901
						$item->setAttribute('empty-selection', 'yes');
902
					}
903
				} // end max recursion check
904
905
				if ($newItem) {
906
					// append item when done
907
					$root->appendChild($item);
908
				}
909
			} // end each entries
910
911
			if ($newRoot) {
912
				// output mode for this field
913
				if ($devkit) {
914
					$root->setAttribute('x-data-source-mode', $mode);
915
					$root->setAttribute('x-field-included-elements', $this->get('elements'));
916
				}
917
				// add all our data to the wrapper;
918
				$wrapper->appendChild($root);
919
			} else {
920
				if ($devkit) {
921
					$root->setAttribute('x-data-source-mode', $root->getAttribute('x-data-source-mode') . ', ' . $mode);
922
				}
923
			}
924
925
			// clean up
926
			$this->recursiveLevel = 1;
927
			$this->recursiveDeepness = null;
928
		}
929
930
		public function getParameterPoolValue(array $data, $entry_id = null)
931
		{
932
			if(!is_array($data) || empty($data)) return;
933
			return static::getEntries($data);
934
		}
935
936
		/* ********* Utils *********** */
937
		
938
		/**
939
		 * Return true if $fieldName is allowed in $sectionElements
940
		 * @param string $fieldName
941
		 * @param string $sectionElements
942
		 * @return bool
943
		 */
944
		public static function isFieldIncluded($fieldName, $sectionElements)
945
		{
946
			return self::getSectionElementName($fieldName, $sectionElements) !== null;
947
		}
948
949
		public static function getSectionElementName($fieldName, $sectionElements)
950
		{
951
			if (is_array($sectionElements)) {
952
				foreach ($sectionElements as $element) {
953
					// everything is allowed, use "fieldName" directly
954
					if ($element === '*') {
955
						return $fieldName;
956
					}
957
					// make "fieldName: *" the same as "fieldName"
958
					if (preg_match('/\s*:\s*\*/sU', $fieldName)) {
959
						$fieldName = trim(current(explode(':', $fieldName)));
960
					}
961
					// "fieldName" is included as-is or element starts with "fieldName:"
962
					if ($fieldName === $element || preg_match('/^' . $fieldName . '\s*:/sU', $element)) {
963
						return $element;
964
					}
965
				}
966
			}
967
			return null;
968
		}
969
		
970
		public static function parseElements($field)
971
		{
972
			$elements = array();
973
			$exElements = $field->getArray('elements');
974
			
975
			if (in_array('*', $exElements)) {
976
				$sections = $field->getArray('sections');
977
				$sections = SectionManager::fetch($sections);
978
				return array_reduce($sections, function ($result, $section) {
979
					$result[$section->get('handle')] = array('*');
980
					return $result;
981
				}, array());
982
			}
983
			
984
			foreach ($exElements as $value) {
985
				if (!$value) {
986
					continue;
987
				}
988
				// sectionName.fieldName or sectionName.*
989
				$parts = array_map(trim, explode('.', $value, 2));
990
				// first time seeing this section
991
				if (!isset($elements[$parts[0]])) {
992
					$elements[$parts[0]] = array();
993
				}
994
				// we have a value after the dot
995
				if (isset($parts[1]) && !!$parts[1]) {
996
					$elements[$parts[0]][] = $parts[1];
997
				}
998
				// sectionName only
999
				else if (!isset($parts[1])) {
1000
					$elements[$parts[0]][] = '*';
1001
				}
1002
			}
1003
			
1004
			return $elements;
1005
		}
1006
1007
		public static function extractMode($fieldName, $mode)
1008
		{
1009
			$pattern = '/^' . $fieldName . '\s*:\s*/s';
1010
			if (!preg_match($pattern, $mode)) {
1011
				return null;
1012
			}
1013
			$mode = preg_replace($pattern, '', $mode, 1);
1014
			if ($mode === '*') {
1015
				return null;
1016
			}
1017
			return $mode;
1018
		}
1019
1020
		private function buildSectionSelect($name)
1021
		{
1022
			$sections = SectionManager::fetch();
1023
			$options = array();
1024
			$selectedSections = $this->getSelectedSectionsArray();
1025
			
1026 View Code Duplication
			foreach ($sections as $section) {
1027
				$driver = $section->get('id');
1028
				$selected = in_array($driver, $selectedSections);
1029
				$options[] = array($driver, $selected, General::sanitize($section->get('name')));
1030
			}
1031
			
1032
			return Widget::Select($name, $options, array('multiple' => 'multiple'));
1033
		}
1034
1035 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...
1036
		{
1037
			$name = $this->createSettingsFieldName('sections', true);
1038
1039
			$input = $this->buildSectionSelect($name);
1040
			$input->setAttribute('class', 'entry_relationship-sections');
1041
1042
			$label = Widget::Label();
1043
			$label->setAttribute('class', 'column');
1044
1045
			$label->setValue(__('Available sections %s', array($input->generate())));
1046
1047
			$wrapper->appendChild($label);
1048
		}
1049
1050
		private function createEntriesHiddenInput($data)
1051
		{
1052
			$hidden = new XMLElement('input', null, array(
1053
				'type' => 'hidden',
1054
				'name' => $this->createPublishFieldName('entries'),
1055
				'value' => $data['entries']
1056
			));
1057
			
1058
			return $hidden;
1059
		}
1060
		
1061
		private function createActionBarMenu($sections)
1062
		{
1063
			$wrap = new XMLElement('div');
1064
			$actionBar = '';
1065
			$modeFooter = $this->get('mode_footer');
1066
			if ($modeFooter) {
1067
				$section = $this->sectionManager->fetch($this->get('parent_section'));
1068
				$actionBar = ERFXSLTUTilities::processXSLT($this, null, $section->get('handle'), null, 'mode_footer', isset($_REQUEST['debug']), 'field');
1069
			}
1070
			if (empty($actionBar)) {
1071
				$fieldset = new XMLElement('fieldset');
1072
				$fieldset->setAttribute('class', 'single');
1073
				if ($this->is('allow_search')) {
1074
					$searchWrap = new XMLElement('div');
1075
					$searchWrap->setAttribute('data-interactive', 'data-interactive');
1076
					$searchWrap->setAttribute('class', 'search');
1077
					$searchInput = Widget::Input('', null, 'text', array(
1078
						'class' => 'search',
1079
						'data-search' => '',
1080
						'placeholder' => __('Search for entries')
1081
					));
1082
					$searchWrap->appendChild($searchInput);
1083
					$searchSuggestions = new XMLElement('ul');
1084
					$searchSuggestions->setAttribute('class', 'suggestions');
1085
					$searchWrap->appendChild($searchSuggestions);
1086
					$fieldset->appendChild($searchWrap);
1087
				}
1088
				
1089
				if ($this->is('allow_new') || $this->is('allow_link') || $this->is('allow_search')) {
1090
					$selectWrap = new XMLElement('div');
1091
					$selectWrap->appendChild(new XMLElement('span', __('Related section: '), array('class' => 'sections-selection')));
1092
					$options = array();
1093
					foreach ($sections as $section) {
1094
						$options[] = array($section->get('handle'), false, $section->get('name'));
1095
					}
1096
					$select = Widget::Select('', $options, array('class' => 'sections sections-selection'));
1097
					$selectWrap->appendChild($select);
1098
					$fieldset->appendChild($selectWrap);
1099
				}
1100 View Code Duplication
				if ($this->is('allow_new')) {
1101
					$fieldset->appendChild(new XMLElement('button', __('Create new'), array(
1102
						'type' => 'button',
1103
						'class' => 'create',
1104
						'data-create' => '',
1105
					)));
1106
				}
1107 View Code Duplication
				if ($this->is('allow_link')) {
1108
					$fieldset->appendChild(new XMLElement('button', __('Link to entry'), array(
1109
						'type' => 'button',
1110
						'class' => 'link',
1111
						'data-link' => '',
1112
					)));
1113
				}
1114
				$wrap->appendChild($fieldset);
1115
			}
1116
			else {
1117
				$wrap->setValue($actionBar);
1118
			}
1119
			
1120
			return $wrap;
1121
		}
1122
1123
		/* ********* UI *********** */
1124
		
1125
		/**
1126
		 *
1127
		 * Builds the UI for the field's settings when creating/editing a section
1128
		 * @param XMLElement $wrapper
1129
		 * @param array $errors
1130
		 */
1131
		public function displaySettingsPanel(XMLElement &$wrapper, $errors=null)
1132
		{
1133
			/* first line, label and such */
1134
			parent::displaySettingsPanel($wrapper, $errors);
1135
			
1136
			// sections
1137
			$sections = new XMLElement('fieldset');
1138
			
1139
			$this->appendSelectionSelect($sections);
1140 View Code Duplication
			if (is_array($errors) && isset($errors['sections'])) {
1141
				$sections = Widget::Error($sections, $errors['sections']);
1142
			}
1143
			$wrapper->appendChild($sections);
1144
			
1145
			// elements
1146
			$elements = new XMLElement('div');
1147
			$element = Widget::Label();
1148
			$element->setValue(__('Included elements in Data Sources and Backend Templates'));
1149
			$element->setAttribute('class', 'column');
1150
			$element->appendChild(Widget::Input($this->createSettingsFieldName('elements'), $this->get('elements'), 'text', array(
1151
				'class' => 'entry_relationship-elements'
1152
			)));
1153
			$elements->appendChild($element);
1154
			$elements_choices = new XMLElement('ul', null, array('class' => 'tags singular entry_relationship-field-choices'));
1155
			$elements->appendChild($elements_choices);
1156
			$wrapper->appendChild($elements);
1157
			
1158
			// limit entries
1159
			$limits = new XMLElement('fieldset');
1160
			$limits->appendChild(new XMLElement('legend', __('Limits')));
1161
			$limits_cols = new XMLElement('div');
1162
			$limits_cols->setAttribute('class', 'three columns');
1163
			// min
1164
			$limit_min = Widget::Label();
1165
			$limit_min->setValue(__('Minimum count of entries in this field'));
1166
			$limit_min->setAttribute('class', 'column');
1167
			$limit_min->appendChild(Widget::Input($this->createSettingsFieldName('min_entries'), $this->get('min_entries'), 'number', array(
1168
				'min' => 0,
1169
				'max' => 99999
1170
			)));
1171
			$limits_cols->appendChild($limit_min);
1172
			// max
1173
			$limit_max = Widget::Label();
1174
			$limit_max->setValue(__('Maximum count of entries in this field'));
1175
			$limit_max->setAttribute('class', 'column');
1176
			$limit_max->appendChild(Widget::Input($this->createSettingsFieldName('max_entries'), $this->get('max_entries'), 'number', array(
1177
				'min' => 0,
1178
				'max' => 99999
1179
			)));
1180
			$limits_cols->appendChild($limit_max);
1181
			
1182
			// deepness
1183
			$deepness = Widget::Label();
1184
			$deepness->setValue(__('Maximum level of recursion in Data Sources'));
1185
			$deepness->setAttribute('class', 'column');
1186
			$deepness->appendChild(Widget::Input($this->createSettingsFieldName('deepness'), $this->get('deepness'), 'number', array(
1187
				'min' => 0,
1188
				'max' => 99
1189
			)));
1190
			$limits_cols->appendChild($deepness);
1191
			$limits->appendChild($limits_cols);
1192
			$wrapper->appendChild($limits);
1193
			
1194
			// xsl
1195
			$xsl = new XMLElement('fieldset');
1196
			$xsl->appendChild(new XMLElement('legend', __('Backend XSL templates options')));
1197
			$xsl_cols = new XMLElement('div');
1198
			$xsl_cols->setAttribute('class', 'four columns');
1199
			
1200
			// xsl mode
1201
			$xslmode = Widget::Label();
1202
			$xslmode->setValue(__('XSL mode for entries content template'));
1203
			$xslmode->setAttribute('class', 'column');
1204
			$xslmode->appendChild(Widget::Input($this->createSettingsFieldName('mode'), $this->get('mode'), 'text'));
1205
			$xsl_cols->appendChild($xslmode);
1206
			// xsl header mode
1207
			$xslmodetable = Widget::Label();
1208
			$xslmodetable->setValue(__('XSL mode for entries header template'));
1209
			$xslmodetable->setAttribute('class', 'column');
1210
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_header'), $this->get('mode_header'), 'text'));
1211
			$xsl_cols->appendChild($xslmodetable);
1212
			// xsl table mode
1213
			$xslmodetable = Widget::Label();
1214
			$xslmodetable->setValue(__('XSL mode for publish table value'));
1215
			$xslmodetable->setAttribute('class', 'column');
1216
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_table'), $this->get('mode_table'), 'text'));
1217
			$xsl_cols->appendChild($xslmodetable);
1218
			// xsl action bar mode
1219
			$xslmodetable = Widget::Label();
1220
			$xslmodetable->setValue(__('XSL mode for publish action bar'));
1221
			$xslmodetable->setAttribute('class', 'column');
1222
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_footer'), $this->get('mode_footer'), 'text'));
1223
			$xsl_cols->appendChild($xslmodetable);
1224
			
1225
			$xsl->appendChild($xsl_cols);
1226
			$wrapper->appendChild($xsl);
1227
			
1228
			// permissions
1229
			$permissions = new XMLElement('fieldset');
1230
			$permissions->appendChild(new XMLElement('legend', __('Permissions')));
1231
			$permissions_cols = new XMLElement('div');
1232
			$permissions_cols->setAttribute('class', 'four columns');
1233
			$permissions_cols->appendChild($this->createCheckbox('allow_new', 'Show new button'));
1234
			$permissions_cols->appendChild($this->createCheckbox('allow_edit', 'Show edit button'));
1235
			$permissions_cols->appendChild($this->createCheckbox('allow_link', 'Show link button'));
1236
			$permissions_cols->appendChild($this->createCheckbox('allow_delete', 'Show delete button'));
1237
			$permissions->appendChild($permissions_cols);
1238
			$wrapper->appendChild($permissions);
1239
			
1240
			// display options
1241
			$display = new XMLElement('fieldset');
1242
			$display->appendChild(new XMLElement('legend', __('Display options')));
1243
			$display_cols = new XMLElement('div');
1244
			$display_cols->setAttribute('class', 'four columns');
1245
			$display_cols->appendChild($this->createCheckbox('allow_collapse', 'Allow content collapsing'));
1246
			$display_cols->appendChild($this->createCheckbox('allow_search', 'Allow search linking'));
1247
			$display_cols->appendChild($this->createCheckbox('show_header', 'Show the header box before entries templates'));
1248
			$display->appendChild($display_cols);
1249
			$wrapper->appendChild($display);
1250
			
1251
			// assoc
1252
			$assoc = new XMLElement('fieldset');
1253
			$assoc->appendChild(new XMLElement('legend', __('Associations')));
1254
			$assoc_cols = new XMLElement('div');
1255
			$assoc_cols->setAttribute('class', 'three columns');
1256
			$this->appendShowAssociationCheckbox($assoc_cols);
1257
			$assoc->appendChild($assoc_cols);
1258
			$wrapper->appendChild($assoc);
1259
			
1260
			// footer
1261
			$this->appendStatusFooter($wrapper);
1262
		}
1263
1264
		/**
1265
		 *
1266
		 * Builds the UI for the publish page
1267
		 * @param XMLElement $wrapper
1268
		 * @param mixed $data
1269
		 * @param mixed $flagWithError
1270
		 * @param string $fieldnamePrefix
1271
		 * @param string $fieldnamePostfix
1272
		 */
1273
		public function displayPublishPanel(XMLElement &$wrapper, $data = null, $flagWithError = null, $fieldnamePrefix = null, $fieldnamePostfix = null, $entry_id = null)
1274
		{
1275
			$entriesId = array();
1276
			$sectionsId = $this->getSelectedSectionsArray();
1277
			
1278
			if ($data['entries'] != null) {
1279
				$entriesId = static::getEntries($data);
1280
			}
1281
			
1282
			$sectionsId = array_map(array('General', 'intval'), $sectionsId);
1283
			$sections = SectionManager::fetch($sectionsId);
1284
			
1285
			$label = Widget::Label($this->get('label'));
1286
			$notes = '';
1287
			
1288
			// min note
1289
			if ($this->getInt('min_entries') > 0) {
1290
				$notes .= __('Minimum number of entries: <b>%s</b>. ', array($this->get('min_entries')));
1291
			}
1292
			// max note
1293
			if ($this->getInt('max_entries') > 0) {
1294
				$notes .= __('Maximum number of entries: <b>%s</b>. ', array($this->get('max_entries')));
1295
			}
1296
			// not required note
1297
			if (!$this->isRequired()) {
1298
				$notes .= __('Optional');
1299
			}
1300
			// append notes
1301
			if ($notes) {
1302
				$label->appendChild(new XMLElement('i', $notes));
1303
			}
1304
			
1305
			// label error management
1306
			if ($flagWithError != null) {
1307
				$wrapper->appendChild(Widget::Error($label, $flagWithError));
1308
			} else {
1309
				$wrapper->appendChild($label);
1310
			}
1311
			
1312
			$wrapper->appendChild($this->createEntriesList($entriesId));
1313
			$wrapper->appendChild($this->createActionBarMenu($sections));
1314
			$wrapper->appendChild($this->createEntriesHiddenInput($data));
1315
			$wrapper->setAttribute('data-value', $data['entries']);
1316
			$wrapper->setAttribute('data-field-id', $this->get('id'));
1317
			$wrapper->setAttribute('data-field-label', $this->get('label'));
1318
			$wrapper->setAttribute('data-min', $this->get('min_entries'));
1319
			$wrapper->setAttribute('data-max', $this->get('max_entries'));
1320
			$wrapper->setAttribute('data-required', $this->get('required'));
1321
			if (isset($_REQUEST['debug'])) {
1322
				$wrapper->setAttribute('data-debug', true);
1323
			}
1324
		}
1325
1326
		/**
1327
		 * @param integer $count
1328
		 */
1329
		private static function formatCount($count)
1330
		{
1331
			if ($count == 0) {
1332
				return __('No item');
1333
			} else if ($count == 1) {
1334
				return __('1 item');
1335
			}
1336
			return __('%s items', array($count));
1337
		}
1338
1339
		/**
1340
		 *
1341
		 * Return a plain text representation of the field's data
1342
		 * @param array $data
1343
		 * @param int $entry_id
1344
		 */
1345
		public function prepareTextValue($data, $entry_id = null)
1346
		{
1347
			if ($entry_id == null || !is_array($data) || empty($data)) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $entry_id of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
1348
				return '';
1349
			}
1350
			return $data['entries'];
1351
		}
1352
1353
		/**
1354
		 * Format this field value for display as readable text value.
1355
		 *
1356
		 * @param array $data
1357
		 *  an associative array of data for this string. At minimum this requires a
1358
		 *  key of 'value'.
1359
		 * @param integer $entry_id (optional)
1360
		 *  An option entry ID for more intelligent processing. Defaults to null.
1361
		 * @param string $defaultValue (optional)
1362
		 *  The value to use when no plain text representation of the field's data
1363
		 *  can be made. Defaults to null.
1364
		 * @return string
1365
		 *  the readable text summary of the values of this field instance.
1366
		 */
1367
		public function prepareReadableValue($data, $entry_id = null, $truncate = false, $defaultValue = 'None')
1368
		{
1369
			if ($entry_id == null || !is_array($data) || empty($data)) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $entry_id of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
1370
				return __($defaultValue);
1371
			}
1372
			$entries = static::getEntries($data);
1373
			$realEntries = array();
1374
			foreach ($entries as $entryId) {
1375
				$e = EntryManager::fetch($entryId);
1376
				if (is_array($e) && !empty($e)) {
1377
					$realEntries = array_merge($realEntries, $e);
1378
				}
1379
			}
1380
			$count = count($entries);
1381
			$realCount = count($realEntries);
1382
			if ($count === $realCount) {
1383
				return self::formatCount($count);
1384
			}
1385
			return self::formatCount($realCount) . ' (' . self::formatCount($count - $realCount) . ' not found)';
1386
		}
1387
1388
		/**
1389
		 * Format this field value for display in the publish index tables.
1390
		 *
1391
		 * @param array $data
1392
		 *  an associative array of data for this string. At minimum this requires a
1393
		 *  key of 'value'.
1394
		 * @param XMLElement $link (optional)
1395
		 *  an XML link structure to append the content of this to provided it is not
1396
		 *  null. it defaults to null.
1397
		 * @param integer $entry_id (optional)
1398
		 *  An option entry ID for more intelligent processing. defaults to null
1399
		 * @return string
1400
		 *  the formatted string summary of the values of this field instance.
1401
		 */
1402
		public function prepareTableValue($data, XMLElement $link = null, $entry_id = null)
1403
		{
1404
			$value = $this->prepareReadableValue($data, $entry_id, false, __('None'));
1405
1406
			if ($entry_id && $this->get('mode_table')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entry_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1407
				$entries = static::getEntries($data);
1408
				$cellcontent = '';
1409
				foreach ($entries as $position => $child_entry_id) {
1410
					$entry = $this->entryManager->fetch($child_entry_id);
1411
					if (!$entry || !is_array($entry) || empty($entry)) {
1412
						continue;
1413
					}
1414
					reset($entry);
1415
					$entry = current($entry);
1416
					$section = $this->sectionManager->fetch($entry->get('section_id'));
1417
					$content = ERFXSLTUTilities::processXSLT($this, $entry, $section->get('handle'), $section->fetchFields(), 'mode_table', isset($_REQUEST['debug']), 'entry', $position + 1);
1418
					if ($content) {
1419
						$cellcontent .= $content;
1420
					}
1421
				}
1422
				
1423
				$cellcontent = trim($cellcontent);
1424
				
1425
				if (General::strlen($cellcontent)) {
1426
					if ($link) {
1427
						$link->setValue($cellcontent);
1428
						return $link->generate();
1429
					}
1430
					return $cellcontent;
1431
				}
1432
			} else if ($link) {
1433
				$link->setValue($value);
1434
				return $link->generate();
1435
			}
1436
1437
			return $value;
1438
		}
1439
1440
		/* ********* SQL Data Definition ************* */
1441
1442
		/**
1443
		 *
1444
		 * Creates table needed for entries of individual fields
1445
		 */
1446
		public function createTable()
1447
		{
1448
			$id = $this->get('id');
1449
1450
			return Symphony::Database()->query("
1451
				CREATE TABLE `tbl_entries_data_$id` (
1452
					`id` int(11) 		unsigned NOT NULL AUTO_INCREMENT,
1453
					`entry_id` 			int(11) unsigned NOT NULL,
1454
					`entries` 			text COLLATE utf8_unicode_ci NULL,
1455
					PRIMARY KEY  (`id`),
1456
					UNIQUE KEY `entry_id` (`entry_id`)
1457
				) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1458
			");
1459
		}
1460
1461
		/**
1462
		 * Creates the table needed for the settings of the field
1463
		 */
1464
		public static function createFieldTable()
1465
		{
1466
			$tbl = self::FIELD_TBL_NAME;
1467
1468
			return Symphony::Database()->query("
1469
				CREATE TABLE IF NOT EXISTS `$tbl` (
1470
					`id` 				int(11) unsigned NOT NULL AUTO_INCREMENT,
1471
					`field_id` 			int(11) unsigned NOT NULL,
1472
					`sections`			varchar(2048) NULL COLLATE utf8_unicode_ci,
1473
					`show_association` 	enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1474
					`deepness` 			int(2) unsigned NULL,
1475
					`elements` 			text COLLATE utf8_unicode_ci NULL,
1476
					`mode`				varchar(50) NULL COLLATE utf8_unicode_ci,
1477
					`mode_table`		varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL,
1478
					`mode_header`		varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL,
1479
					`mode_footer`		varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL,
1480
					`min_entries`		int(5) unsigned NULL,
1481
					`max_entries`		int(5) unsigned NULL,
1482
					`allow_edit` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1483
					`allow_new` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1484
					`allow_link` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1485
					`allow_delete` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no',
1486
					`allow_collapse` 	enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1487
					`allow_search` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no',
1488
					`show_header` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1489
					PRIMARY KEY (`id`),
1490
					UNIQUE KEY `field_id` (`field_id`)
1491
				) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1492
			");
1493
		}
1494
		
1495
		public static function update_102()
1496
		{
1497
			$tbl = self::FIELD_TBL_NAME;
1498
			$sql = "
1499
				ALTER TABLE `$tbl`
1500
					ADD COLUMN `allow_edit` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1501
					ADD COLUMN `allow_new` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1502
					ADD COLUMN `allow_link` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1503
					AFTER `max_entries`
1504
			";
1505
			$addColumns = Symphony::Database()->query($sql);
1506
			if (!$addColumns) {
1507
				return false;
1508
			}
1509
1510
			$fields = FieldManager::fetch(null, null, null, 'id', 'entry_relationship');
1511
			if (!empty($fields) && is_array($fields)) {
1512
				foreach ($fields as $fieldId => $field) {
1513
					$sql = "ALTER TABLE `tbl_entries_data_$fieldId` MODIFY `entries` TEXT";
1514
					if (!Symphony::Database()->query($sql)) {
1515
						throw new Exception(__('Could not update table `tbl_entries_data_%s`.', array($fieldId)));
1516
					}
1517
				}
1518
			}
1519
			return true;
1520
		}
1521
		
1522
		public static function update_103()
1523
		{
1524
			$tbl = self::FIELD_TBL_NAME;
1525
			$sql = "
1526
				ALTER TABLE `$tbl`
1527
					ADD COLUMN `allow_delete` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no'
1528
						AFTER `allow_link`
1529
			";
1530
			return Symphony::Database()->query($sql);
1531
		}
1532
		
1533
		public static function update_200()
1534
		{
1535
			$tbl = self::FIELD_TBL_NAME;
1536
			$sql = "
1537
				ALTER TABLE `$tbl`
1538
					ADD COLUMN `allow_collapse` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1539
						AFTER `allow_delete`,
1540
					ADD COLUMN `mode_table` varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL
1541
						AFTER `mode`,
1542
					ADD COLUMN `mode_header` varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL
1543
						AFTER `mode_table`,
1544
					ADD COLUMN `show_header` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1545
						AFTER `allow_collapse`,
1546
					ADD COLUMN `mode_footer` varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL
1547
						AFTER `mode_header`,
1548
					CHANGE `sections` `sections` varchar(2048) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
1549
					CHANGE `elements` `elements` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL
1550
			";
1551
			return Symphony::Database()->query($sql);
1552
		}
1553
		
1554
		public static function update_2008()
1555
		{
1556
			$tbl = self::FIELD_TBL_NAME;
1557
			$sql = "
1558
				ALTER TABLE `$tbl`
1559
					ADD COLUMN `allow_search` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no'
1560
						AFTER `allow_collapse`
1561
			";
1562
			return Symphony::Database()->query($sql);
1563
		}
1564
		
1565
		/**
1566
		 *
1567
		 * Drops the table needed for the settings of the field
1568
		 */
1569
		public static function deleteFieldTable()
1570
		{
1571
			$tbl = self::FIELD_TBL_NAME;
1572
			
1573
			return Symphony::Database()->query("
1574
				DROP TABLE IF EXISTS `$tbl`
1575
			");
1576
		}
1577
		
1578
		private static function removeSectionAssociation($child_field_id)
1579
		{
1580
			return Symphony::Database()->delete('tbl_sections_association', "`child_section_field_id` = {$child_field_id}");
1581
		}
1582
	}
1583