Completed
Push — master ( 2867c5...18d669 )
by Nicolas
08:20 queued 04:11
created

FieldEntry_Relationship::formatCount()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 3
eloc 6
nc 3
nop 1
1
<?php
2
	/**
3
	 * Copyright: Deux Huit Huit 2014
4
	 * LICENCE: MIT https://deuxhuithuit.mit-license.org
5
	 */
6
7
	if (!defined('__IN_SYMPHONY__')) die('<h2>Symphony Error</h2><p>You cannot directly access this file</p>');
8
9
	require_once(EXTENSIONS . '/entry_relationship_field/lib/class.field.relationship.php');
10
	require_once(EXTENSIONS . '/entry_relationship_field/lib/class.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
				} else {
612
					$includedElements = array('*');
613
				}
614
			}
615
			return $includedElements;
616
		}
617
		
618
		/**
619
		 * Appends data into the XML tree of a Data Source
620
		 * @param $wrapper
621
		 * @param $data
622
		 */
623
		public function appendFormattedElement(XMLElement &$wrapper, $data, $encode = false, $mode = null, $entry_id = null)
624
		{
625
			if (!is_array($data) || empty($data)) {
626
				return;
627
			}
628
629
			// try to find an existing root
630
			$root = null;
631
			$newRoot = false;
632
			foreach (array_reverse($wrapper->getChildren()) as $xmlField) {
633
				if ($xmlField->getName() === $this->get('element_name')) {
634
					$root = $xmlField;
635
					break;
636
				}
637
			}
638
639
			// root was not found, create one
640
			if (!$root) {
641
				$root = new XMLElement($this->get('element_name'));
642
				$newRoot = true;
643
			}
644
			
645
			// devkit will load
646
			$devkit = isset($_GET['debug']) && (empty($_GET['debug']) || $_GET['debug'] == 'xml');
647
			
648
			// selected items
649
			$entries = static::getEntries($data);
650
			
651
			// current linked entries
652
			$root->setAttribute('entries', $data['entries']);
653
			
654
			// available sections
655
			$root->setAttribute('sections', $this->get('sections'));
656
			
657
			// included elements
658
			$elements = static::parseElements($this);
659
			
660
			// DS mode
661
			if (!$mode) {
662
				$mode = '*';
663
			}
664
			
665
			$parentDeepness = General::intval($this->recursiveDeepness);
666
			$deepness = General::intval($this->get('deepness'));
667
			
668
			// both deepnesses are defined and parent restricts more
669
			if ($parentDeepness > 0 && $deepness > 0 && $parentDeepness < $deepness) {
670
				$deepness = $parentDeepness;
671
			}
672
			// parent is defined, current is not
673
			else if ($parentDeepness > 0 && $deepness < 1) {
674
				$deepness = $parentDeepness;
675
			}
676
			
677
			// cache recursive level because recursion might
678
			// change its value later on.
679
			$recursiveLevel = $this->recursiveLevel;
680
			
681
			// build entries
682
			foreach ($entries as $eId) {
683
				// try to find and existing item
684
				// TODO: keep last index found since it should be the next
685
				$item = null;
686
				$newItem = false;
687
				foreach ($root->getChildren() as $xmlItem) {
688
					if (General::intval($xmlItem->getAttribute('id')) === General::intval($eId)) {
689
						$item = $xmlItem;
690
						break;
691
					}
692
				}
693
694
				// item was not found, create one
695
				if (!$item) {
696
					$item = new XMLElement('item');
697
					$item->setAllowEmptyAttributes(false);
698
					// output id
699
					$item->setAttribute('id', $eId);
700
					// output recursive level
701
					$item->setAttribute('level', $recursiveLevel);
702
					$item->setAttribute('max-level', $deepness);
703
					$newItem = true;
704
				// item was found, but it is an error, so we can skip it
705
				} else if ($item->getName() === 'error') {
706
					continue;
707
				}
708
709
				// max recursion check
710
				if ($deepness < 1 || $recursiveLevel < $deepness) {
711
					// current entry, without data
712
					$entry = $this->fetchEntry($eId);
713
					
714
					// entry not found...
715
					if (!$entry || empty($entry)) {
716
						$error = new XMLElement('error');
717
						$error->setAttribute('id', $eId);
718
						$error->setValue(__('Error: entry `%s` not found', array($eId)));
719
						$root->prependChild($error);
720
						continue;
721
					}
722
					
723
					// fetch section infos
724
					$sectionId = $entry->get('section_id');
725
					$section = $this->sectionManager->fetch($sectionId);
726
					$sectionName = $section->get('handle');
727
					// cache fields info
728
					if (!isset($section->er_field_cache)) {
729
						$section->er_field_cache = $section->fetchFields();
730
					}
731
					
732
					// set section related attributes
733
					$item->setAttribute('section-id', $sectionId);
734
					$item->setAttribute('section', $sectionName);
735
					
736
					// Get the valid elements for this section only
737
					$validElements = $elements[$sectionName];
738
					
739
					// adjust the mode for the current section
740
					$curMode = $mode;
741
					
742
					// remove section name from current mode so "sectionName.field" becomes simply "field"
743
					if (preg_match('/^(' . $sectionName . '\.)(.*)$/sU', $curMode)) {
744
						$curMode = preg_replace('/^' . $sectionName . '\./sU', '', $curMode);
745
					}
746
					// remove section name from current mode "sectionName" and
747
					// treat it like if it is "sectionName: *"
748
					else if (preg_match('/^(' . $sectionName . ')$/sU', $curMode)) {
749
						$curMode = '*';
750
					}
751
					// section name was not found in mode, check if the mode is "*"
752
					else if ($curMode != '*') {
753
						// mode forbids this section
754
						$validElements = null;
755
					}
756
					
757
					// this section is not selected, bail out
758 View Code Duplication
					if (!is_array($validElements)) {
759
						if ($newItem) {
760
							if ($devkit) {
761
								$item->setAttribute('x-forbidden-by-ds', $curMode);
762
							}
763
							$root->appendChild($item);
764
						}
765
						continue;
766
					} else {
767
						if ($devkit) {
768
							$item->setAttribute('x-forbidden-by-ds', null);
769
						}
770
					}
771
					
772
					// selected fields for fetching
773
					$sectionElements = array();
774
					
775
					// everything is allowed
776
					if (in_array('*', $validElements)) {
777
						if ($curMode !== '*') {
778
							// get only the mode
779
							$sectionElements = array($curMode);
780
						}
781
						else {
782
							// setting null = get all
783
							$sectionElements = null;
784
						}
785
					}
786
					// only use valid elements
787
					else {
788
						if ($curMode !== '*') {
789
							// is this field allowed ?
790
							if (self::isFieldIncluded($curMode, $validElements)) {
791
								// get only the mode
792
								$sectionElements = array($curMode);
793
							}
794
							else {
795
								// $curMode selects something outside of
796
								// the valid elements: select nothing
797
								$sectionElements = array();
798
							}
799
						}
800
						else {
801
							// use field's valid elements
802
							$sectionElements = $validElements;
803
						}
804
					}
805
					
806
					// Filtering is enabled, but nothing is selected
807 View Code Duplication
					if (is_array($sectionElements) && empty($sectionElements)) {
808
						if ($newItem) {
809
							$root->appendChild($item);
810
							if ($devkit) {
811
								$item->setAttribute('x-forbidden-by-selection', $curMode);
812
							}
813
						}
814
						continue;
815
					} else {
816
						if ($devkit) {
817
							$item->setAttribute('x-forbidden-by-selection', null);
818
						}
819
					}
820
					
821
					// fetch current entry again, but with data for the allowed schema
822
					$entry = $this->fetchEntry($eId, $sectionElements);
823
					
824
					// cache the entry data
825
					$entryData = $entry->getData();
826
					
827
					// for each field returned for this entry...
828
					foreach ($entryData as $fieldId => $data) {
829
						$filteredData = array_filter($data, function ($value) {
830
							return $value != null;
831
						});
832
						
833
						if (empty($filteredData)) {
834
							continue;
835
						}
836
						
837
						$field = $section->er_field_cache[$fieldId];
838
						$fieldName = $field->get('element_name');
839
						$fieldCurMode = self::extractMode($fieldName, $curMode);
840
						
841
						$parentIncludableElement = self::getSectionElementName($fieldName, $validElements);
842
						$parentIncludableElementMode = self::extractMode($fieldName, $parentIncludableElement);
843
						
844
						// Special treatments for ERF
845
						if ($field instanceof FieldEntry_relationship) {
846
							// Increment recursive level
847
							$field->recursiveLevel = $recursiveLevel + 1;
848
							$field->recursiveDeepness = $deepness;
849
						}
850
						
851
						$submodes = null;
852
						// Parent mode is not defined (we are selecting the whole section)
853
						if ($parentIncludableElementMode === null) {
854
							if ($fieldCurMode == null) {
855
								// Field does not defined a mode either: use the field's default
856
								$submodes = null;
857
							} else {
858
								// Use the current field's mode
859
								$submodes = array($fieldCurMode);
860
							}
861
							if ($devkit) {
862
								$item->setAttribute('x-selection-mode-empty', null);
863
							}
864
						} else {
865
							// Field mode is not defined or it is the same as the parent
866
							if ($fieldCurMode === null || $fieldCurMode == $parentIncludableElementMode) {
867
								if ($devkit) {
868
									$item->setAttribute('x-selection-mode-empty', null);
869
								}
870
								// Use parent mode
871
								$submodes = array($parentIncludableElementMode);
872
							} else {
873
								if ($devkit) {
874
									$item->setAttribute('x-selection-mode-empty', 'yes');
875
								}
876
								// Empty selection
877
								$submodes = array();
878
							}
879
						}
880
881
						// current selection does not specify a mode
882
						if ($submodes === null) {
883
							if ($field instanceof FieldEntry_Relationship) {
884
								$field->expandIncludableElements = false;
885
							}
886
							$submodes = array_map(function ($fieldIncludableElement) use ($fieldName) {
887
								return FieldEntry_relationship::extractMode($fieldName, $fieldIncludableElement);
888
							}, $field->fetchIncludableElements());
889
							if ($field instanceof FieldEntry_Relationship) {
890
								$field->expandIncludableElements = true;
891
							}
892
						}
893
						
894
						// Append the formatted element for each requested mode
895
						foreach ($submodes as $submode) {
896
							$field->appendFormattedElement($item, $data, $encode, $submode, $eId);
897
						}
898
					}
899
					// output current mode
900
					$item->setAttribute('matched-element', $curMode);
901
					// no field selected
902
					if (is_array($sectionElements) && empty($sectionElements)) {
903
						$item->setAttribute('empty-selection', 'yes');
904
					}
905
				} // end max recursion check
906
907
				if ($newItem) {
908
					// append item when done
909
					$root->appendChild($item);
910
				}
911
			} // end each entries
912
913
			if ($newRoot) {
914
				// output mode for this field
915
				if ($devkit) {
916
					$root->setAttribute('x-data-source-mode', $mode);
917
					$root->setAttribute('x-field-included-elements', $this->get('elements'));
918
				}
919
				// add all our data to the wrapper;
920
				$wrapper->appendChild($root);
921
			} else {
922
				if ($devkit) {
923
					$root->setAttribute('x-data-source-mode', $root->getAttribute('x-data-source-mode') . ', ' . $mode);
924
				}
925
			}
926
927
			// clean up
928
			$this->recursiveLevel = 1;
929
			$this->recursiveDeepness = null;
930
		}
931
932
		public function getParameterPoolValue(array $data, $entry_id = null)
933
		{
934
			if(!is_array($data) || empty($data)) return;
935
			return static::getEntries($data);
936
		}
937
938
		/* ********* Utils *********** */
939
		
940
		/**
941
		 * Return true if $fieldName is allowed in $sectionElements
942
		 * @param string $fieldName
943
		 * @param string $sectionElements
944
		 * @return bool
945
		 */
946
		public static function isFieldIncluded($fieldName, $sectionElements)
947
		{
948
			return self::getSectionElementName($fieldName, $sectionElements) !== null;
949
		}
950
951
		public static function getSectionElementName($fieldName, $sectionElements)
952
		{
953
			if (is_array($sectionElements)) {
954
				foreach ($sectionElements as $element) {
955
					// everything is allowed, use "fieldName" directly
956
					if ($element === '*') {
957
						return $fieldName;
958
					}
959
					// make "fieldName: *" the same as "fieldName"
960
					if (preg_match('/\s*:\s*\*/sU', $fieldName)) {
961
						$fieldName = trim(current(explode(':', $fieldName)));
962
					}
963
					// "fieldName" is included as-is or element starts with "fieldName:"
964
					if ($fieldName === $element || preg_match('/^' . $fieldName . '\s*:/sU', $element)) {
965
						return $element;
966
					}
967
				}
968
			}
969
			return null;
970
		}
971
		
972
		public static function parseElements($field)
973
		{
974
			$elements = array();
975
			$exElements = $field->getArray('elements');
976
			
977
			if (in_array('*', $exElements)) {
978
				$sections = $field->getArray('sections');
979
				$sections = SectionManager::fetch($sections);
980
				return array_reduce($sections, function ($result, $section) {
981
					$result[$section->get('handle')] = array('*');
982
					return $result;
983
				}, array());
984
			}
985
			
986
			foreach ($exElements as $value) {
987
				if (!$value) {
988
					continue;
989
				}
990
				// sectionName.fieldName or sectionName.*
991
				$parts = array_map(trim, explode('.', $value, 2));
992
				// first time seeing this section
993
				if (!isset($elements[$parts[0]])) {
994
					$elements[$parts[0]] = array();
995
				}
996
				// we have a value after the dot
997
				if (isset($parts[1]) && !!$parts[1]) {
998
					$elements[$parts[0]][] = $parts[1];
999
				}
1000
				// sectionName only
1001
				else if (!isset($parts[1])) {
1002
					$elements[$parts[0]][] = '*';
1003
				}
1004
			}
1005
			
1006
			return $elements;
1007
		}
1008
1009
		public static function extractMode($fieldName, $mode)
1010
		{
1011
			$pattern = '/^' . $fieldName . '\s*:\s*/s';
1012
			if (!preg_match($pattern, $mode)) {
1013
				return null;
1014
			}
1015
			$mode = preg_replace($pattern, '', $mode, 1);
1016
			if ($mode === '*') {
1017
				return null;
1018
			}
1019
			return $mode;
1020
		}
1021
1022
		private function buildSectionSelect($name)
1023
		{
1024
			$sections = SectionManager::fetch();
1025
			$options = array();
1026
			$selectedSections = $this->getSelectedSectionsArray();
1027
			
1028 View Code Duplication
			foreach ($sections as $section) {
1029
				$driver = $section->get('id');
1030
				$selected = in_array($driver, $selectedSections);
1031
				$options[] = array($driver, $selected, General::sanitize($section->get('name')));
1032
			}
1033
			
1034
			return Widget::Select($name, $options, array('multiple' => 'multiple'));
1035
		}
1036
1037 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...
1038
		{
1039
			$name = $this->createSettingsFieldName('sections', true);
1040
1041
			$input = $this->buildSectionSelect($name);
1042
			$input->setAttribute('class', 'entry_relationship-sections');
1043
1044
			$label = Widget::Label();
1045
			$label->setAttribute('class', 'column');
1046
1047
			$label->setValue(__('Available sections %s', array($input->generate())));
1048
1049
			$wrapper->appendChild($label);
1050
		}
1051
1052
		private function createEntriesHiddenInput($data)
1053
		{
1054
			$hidden = new XMLElement('input', null, array(
1055
				'type' => 'hidden',
1056
				'name' => $this->createPublishFieldName('entries'),
1057
				'value' => $data['entries']
1058
			));
1059
			
1060
			return $hidden;
1061
		}
1062
		
1063
		private function createActionBarMenu($sections)
1064
		{
1065
			$wrap = new XMLElement('div');
1066
			$actionBar = '';
1067
			$modeFooter = $this->get('mode_footer');
1068
			if ($modeFooter) {
1069
				$section = $this->sectionManager->fetch($this->get('parent_section'));
1070
				$actionBar = ERFXSLTUTilities::processXSLT($this, null, $section->get('handle'), null, 'mode_footer', isset($_REQUEST['debug']), 'field');
1071
			}
1072
			if (empty($actionBar)) {
1073
				$fieldset = new XMLElement('fieldset');
1074
				$fieldset->setAttribute('class', 'single');
1075
				if ($this->is('allow_search')) {
1076
					$searchWrap = new XMLElement('div');
1077
					$searchWrap->setAttribute('data-interactive', 'data-interactive');
1078
					$searchWrap->setAttribute('class', 'search');
1079
					$searchInput = Widget::Input('', null, 'text', array(
1080
						'class' => 'search',
1081
						'data-search' => '',
1082
						'placeholder' => __('Search for entries')
1083
					));
1084
					$searchWrap->appendChild($searchInput);
1085
					$searchSuggestions = new XMLElement('ul');
1086
					$searchSuggestions->setAttribute('class', 'suggestions');
1087
					$searchWrap->appendChild($searchSuggestions);
1088
					$fieldset->appendChild($searchWrap);
1089
				}
1090
				
1091
				if ($this->is('allow_new') || $this->is('allow_link') || $this->is('allow_search')) {
1092
					$selectWrap = new XMLElement('div');
1093
					$selectWrap->appendChild(new XMLElement('span', __('Related section: '), array('class' => 'sections-selection')));
1094
					$options = array();
1095
					foreach ($sections as $section) {
1096
						$options[] = array($section->get('handle'), false, $section->get('name'));
1097
					}
1098
					$select = Widget::Select('', $options, array('class' => 'sections sections-selection'));
1099
					$selectWrap->appendChild($select);
1100
					$fieldset->appendChild($selectWrap);
1101
				}
1102 View Code Duplication
				if ($this->is('allow_new')) {
1103
					$fieldset->appendChild(new XMLElement('button', __('Create new'), array(
1104
						'type' => 'button',
1105
						'class' => 'create',
1106
						'data-create' => '',
1107
					)));
1108
				}
1109 View Code Duplication
				if ($this->is('allow_link')) {
1110
					$fieldset->appendChild(new XMLElement('button', __('Link to entry'), array(
1111
						'type' => 'button',
1112
						'class' => 'link',
1113
						'data-link' => '',
1114
					)));
1115
				}
1116
				$wrap->appendChild($fieldset);
1117
			}
1118
			else {
1119
				$wrap->setValue($actionBar);
1120
			}
1121
			
1122
			return $wrap;
1123
		}
1124
1125
		/* ********* UI *********** */
1126
		
1127
		/**
1128
		 *
1129
		 * Builds the UI for the field's settings when creating/editing a section
1130
		 * @param XMLElement $wrapper
1131
		 * @param array $errors
1132
		 */
1133
		public function displaySettingsPanel(XMLElement &$wrapper, $errors=null)
1134
		{
1135
			/* first line, label and such */
1136
			parent::displaySettingsPanel($wrapper, $errors);
1137
			
1138
			// sections
1139
			$sections = new XMLElement('fieldset');
1140
			
1141
			$this->appendSelectionSelect($sections);
1142 View Code Duplication
			if (is_array($errors) && isset($errors['sections'])) {
1143
				$sections = Widget::Error($sections, $errors['sections']);
1144
			}
1145
			$wrapper->appendChild($sections);
1146
			
1147
			// elements
1148
			$elements = new XMLElement('div');
1149
			$element = Widget::Label();
1150
			$element->setValue(__('Included elements in Data Sources and Backend Templates'));
1151
			$element->setAttribute('class', 'column');
1152
			$element->appendChild(Widget::Input($this->createSettingsFieldName('elements'), $this->get('elements'), 'text', array(
1153
				'class' => 'entry_relationship-elements'
1154
			)));
1155
			$elements->appendChild($element);
1156
			$elements_choices = new XMLElement('ul', null, array('class' => 'tags singular entry_relationship-field-choices'));
1157
			$elements->appendChild($elements_choices);
1158
			$wrapper->appendChild($elements);
1159
			
1160
			// limit entries
1161
			$limits = new XMLElement('fieldset');
1162
			$limits->appendChild(new XMLElement('legend', __('Limits')));
1163
			$limits_cols = new XMLElement('div');
1164
			$limits_cols->setAttribute('class', 'three columns');
1165
			// min
1166
			$limit_min = Widget::Label();
1167
			$limit_min->setValue(__('Minimum count of entries in this field'));
1168
			$limit_min->setAttribute('class', 'column');
1169
			$limit_min->appendChild(Widget::Input($this->createSettingsFieldName('min_entries'), $this->get('min_entries'), 'number', array(
1170
				'min' => 0,
1171
				'max' => 99999
1172
			)));
1173
			$limits_cols->appendChild($limit_min);
1174
			// max
1175
			$limit_max = Widget::Label();
1176
			$limit_max->setValue(__('Maximum count of entries in this field'));
1177
			$limit_max->setAttribute('class', 'column');
1178
			$limit_max->appendChild(Widget::Input($this->createSettingsFieldName('max_entries'), $this->get('max_entries'), 'number', array(
1179
				'min' => 0,
1180
				'max' => 99999
1181
			)));
1182
			$limits_cols->appendChild($limit_max);
1183
			
1184
			// deepness
1185
			$deepness = Widget::Label();
1186
			$deepness->setValue(__('Maximum level of recursion in Data Sources'));
1187
			$deepness->setAttribute('class', 'column');
1188
			$deepness->appendChild(Widget::Input($this->createSettingsFieldName('deepness'), $this->get('deepness'), 'number', array(
1189
				'min' => 0,
1190
				'max' => 99
1191
			)));
1192
			$limits_cols->appendChild($deepness);
1193
			$limits->appendChild($limits_cols);
1194
			$wrapper->appendChild($limits);
1195
			
1196
			// xsl
1197
			$xsl = new XMLElement('fieldset');
1198
			$xsl->appendChild(new XMLElement('legend', __('Backend XSL templates options')));
1199
			$xsl_cols = new XMLElement('div');
1200
			$xsl_cols->setAttribute('class', 'four columns');
1201
			
1202
			// xsl mode
1203
			$xslmode = Widget::Label();
1204
			$xslmode->setValue(__('XSL mode for entries content template'));
1205
			$xslmode->setAttribute('class', 'column');
1206
			$xslmode->appendChild(Widget::Input($this->createSettingsFieldName('mode'), $this->get('mode'), 'text'));
1207
			$xsl_cols->appendChild($xslmode);
1208
			// xsl header mode
1209
			$xslmodetable = Widget::Label();
1210
			$xslmodetable->setValue(__('XSL mode for entries header template'));
1211
			$xslmodetable->setAttribute('class', 'column');
1212
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_header'), $this->get('mode_header'), 'text'));
1213
			$xsl_cols->appendChild($xslmodetable);
1214
			// xsl table mode
1215
			$xslmodetable = Widget::Label();
1216
			$xslmodetable->setValue(__('XSL mode for publish table value'));
1217
			$xslmodetable->setAttribute('class', 'column');
1218
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_table'), $this->get('mode_table'), 'text'));
1219
			$xsl_cols->appendChild($xslmodetable);
1220
			// xsl action bar mode
1221
			$xslmodetable = Widget::Label();
1222
			$xslmodetable->setValue(__('XSL mode for publish action bar'));
1223
			$xslmodetable->setAttribute('class', 'column');
1224
			$xslmodetable->appendChild(Widget::Input($this->createSettingsFieldName('mode_footer'), $this->get('mode_footer'), 'text'));
1225
			$xsl_cols->appendChild($xslmodetable);
1226
			
1227
			$xsl->appendChild($xsl_cols);
1228
			$wrapper->appendChild($xsl);
1229
			
1230
			// permissions
1231
			$permissions = new XMLElement('fieldset');
1232
			$permissions->appendChild(new XMLElement('legend', __('Permissions')));
1233
			$permissions_cols = new XMLElement('div');
1234
			$permissions_cols->setAttribute('class', 'four columns');
1235
			$permissions_cols->appendChild($this->createCheckbox('allow_new', 'Show new button'));
1236
			$permissions_cols->appendChild($this->createCheckbox('allow_edit', 'Show edit button'));
1237
			$permissions_cols->appendChild($this->createCheckbox('allow_link', 'Show link button'));
1238
			$permissions_cols->appendChild($this->createCheckbox('allow_delete', 'Show delete button'));
1239
			$permissions->appendChild($permissions_cols);
1240
			$wrapper->appendChild($permissions);
1241
			
1242
			// display options
1243
			$display = new XMLElement('fieldset');
1244
			$display->appendChild(new XMLElement('legend', __('Display options')));
1245
			$display_cols = new XMLElement('div');
1246
			$display_cols->setAttribute('class', 'four columns');
1247
			$display_cols->appendChild($this->createCheckbox('allow_collapse', 'Allow content collapsing'));
1248
			$display_cols->appendChild($this->createCheckbox('allow_search', 'Allow search linking'));
1249
			$display_cols->appendChild($this->createCheckbox('show_header', 'Show the header box before entries templates'));
1250
			$display->appendChild($display_cols);
1251
			$wrapper->appendChild($display);
1252
			
1253
			// assoc
1254
			$assoc = new XMLElement('fieldset');
1255
			$assoc->appendChild(new XMLElement('legend', __('Associations')));
1256
			$assoc_cols = new XMLElement('div');
1257
			$assoc_cols->setAttribute('class', 'three columns');
1258
			$this->appendShowAssociationCheckbox($assoc_cols);
1259
			$assoc->appendChild($assoc_cols);
1260
			$wrapper->appendChild($assoc);
1261
			
1262
			// footer
1263
			$this->appendStatusFooter($wrapper);
1264
		}
1265
1266
		/**
1267
		 *
1268
		 * Builds the UI for the publish page
1269
		 * @param XMLElement $wrapper
1270
		 * @param mixed $data
1271
		 * @param mixed $flagWithError
1272
		 * @param string $fieldnamePrefix
1273
		 * @param string $fieldnamePostfix
1274
		 */
1275
		public function displayPublishPanel(XMLElement &$wrapper, $data = null, $flagWithError = null, $fieldnamePrefix = null, $fieldnamePostfix = null, $entry_id = null)
1276
		{
1277
			$entriesId = array();
1278
			$sectionsId = $this->getSelectedSectionsArray();
1279
			
1280
			if ($data['entries'] != null) {
1281
				$entriesId = static::getEntries($data);
1282
			}
1283
			
1284
			$sectionsId = array_map(array('General', 'intval'), $sectionsId);
1285
			$sections = SectionManager::fetch($sectionsId);
1286
			
1287
			$label = Widget::Label($this->get('label'));
1288
			$notes = '';
1289
			
1290
			// min note
1291
			if ($this->getInt('min_entries') > 0) {
1292
				$notes .= __('Minimum number of entries: <b>%s</b>. ', array($this->get('min_entries')));
1293
			}
1294
			// max note
1295
			if ($this->getInt('max_entries') > 0) {
1296
				$notes .= __('Maximum number of entries: <b>%s</b>. ', array($this->get('max_entries')));
1297
			}
1298
			// not required note
1299
			if (!$this->isRequired()) {
1300
				$notes .= __('Optional');
1301
			}
1302
			// append notes
1303
			if ($notes) {
1304
				$label->appendChild(new XMLElement('i', $notes));
1305
			}
1306
			
1307
			// label error management
1308
			if ($flagWithError != null) {
1309
				$wrapper->appendChild(Widget::Error($label, $flagWithError));
1310
			} else {
1311
				$wrapper->appendChild($label);
1312
			}
1313
			
1314
			$wrapper->appendChild($this->createEntriesList($entriesId));
1315
			$wrapper->appendChild($this->createActionBarMenu($sections));
1316
			$wrapper->appendChild($this->createEntriesHiddenInput($data));
1317
			$wrapper->setAttribute('data-value', $data['entries']);
1318
			$wrapper->setAttribute('data-field-id', $this->get('id'));
1319
			$wrapper->setAttribute('data-field-label', $this->get('label'));
1320
			$wrapper->setAttribute('data-min', $this->get('min_entries'));
1321
			$wrapper->setAttribute('data-max', $this->get('max_entries'));
1322
			$wrapper->setAttribute('data-required', $this->get('required'));
1323
			if (isset($_REQUEST['debug'])) {
1324
				$wrapper->setAttribute('data-debug', true);
1325
			}
1326
		}
1327
1328
		/**
1329
		 *
1330
		 * Return a plain text representation of the field's data
1331
		 * @param array $data
1332
		 * @param int $entry_id
1333
		 */
1334
		public function prepareTextValue($data, $entry_id = null)
1335
		{
1336
			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...
1337
				return '';
1338
			}
1339
			return $data['entries'];
1340
		}
1341
1342
		/**
1343
		 * Format this field value for display as readable text value.
1344
		 *
1345
		 * @param array $data
1346
		 *  an associative array of data for this string. At minimum this requires a
1347
		 *  key of 'value'.
1348
		 * @param integer $entry_id (optional)
1349
		 *  An option entry ID for more intelligent processing. Defaults to null.
1350
		 * @param string $defaultValue (optional)
1351
		 *  The value to use when no plain text representation of the field's data
1352
		 *  can be made. Defaults to null.
1353
		 * @return string
1354
		 *  the readable text summary of the values of this field instance.
1355
		 */
1356
		public function prepareReadableValue($data, $entry_id = null, $truncate = false, $defaultValue = 'None')
1357
		{
1358
			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...
1359
				return __($defaultValue);
1360
			}
1361
			$entries = static::getEntries($data);
1362
			$realEntries = array();
1363
			foreach ($entries as $entryId) {
1364
				$e = EntryManager::fetch($entryId);
1365
				if (is_array($e) && !empty($e)) {
1366
					$realEntries = array_merge($realEntries, $e);
1367
				}
1368
			}
1369
			$count = count($entries);
1370
			$realCount = count($realEntries);
1371
			if ($count === $realCount) {
1372
				return self::formatCount($count);
1373
			}
1374
			return self::formatCount($realCount) . ' (' . self::formatCount($count - $realCount) . ' not found)';
1375
		}
1376
1377
		/**
1378
		 * Format this field value for display in the publish index tables.
1379
		 *
1380
		 * @param array $data
1381
		 *  an associative array of data for this string. At minimum this requires a
1382
		 *  key of 'value'.
1383
		 * @param XMLElement $link (optional)
1384
		 *  an XML link structure to append the content of this to provided it is not
1385
		 *  null. it defaults to null.
1386
		 * @param integer $entry_id (optional)
1387
		 *  An option entry ID for more intelligent processing. defaults to null
1388
		 * @return string
1389
		 *  the formatted string summary of the values of this field instance.
1390
		 */
1391
		public function prepareTableValue($data, XMLElement $link = null, $entry_id = null)
1392
		{
1393
			$value = $this->prepareReadableValue($data, $entry_id, false, __('None'));
1394
1395
			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...
1396
				$entries = static::getEntries($data);
1397
				$cellcontent = '';
1398
				foreach ($entries as $position => $child_entry_id) {
1399
					$entry = $this->entryManager->fetch($child_entry_id);
1400
					if (!$entry || !is_array($entry) || empty($entry)) {
1401
						continue;
1402
					}
1403
					reset($entry);
1404
					$entry = current($entry);
1405
					$section = $this->sectionManager->fetch($entry->get('section_id'));
1406
					$content = ERFXSLTUTilities::processXSLT($this, $entry, $section->get('handle'), $section->fetchFields(), 'mode_table', isset($_REQUEST['debug']), 'entry', $position + 1);
1407
					if ($content) {
1408
						$cellcontent .= $content;
1409
					}
1410
				}
1411
				
1412
				$cellcontent = trim($cellcontent);
1413
				
1414
				if (General::strlen($cellcontent)) {
1415
					if ($link) {
1416
						$link->setValue($cellcontent);
1417
						return $link->generate();
1418
					}
1419
					return $cellcontent;
1420
				}
1421
			} else if ($link) {
1422
				$link->setValue($value);
1423
				return $link->generate();
1424
			}
1425
1426
			return $value;
1427
		}
1428
1429
		/* ********* SQL Data Definition ************* */
1430
1431
		/**
1432
		 *
1433
		 * Creates table needed for entries of individual fields
1434
		 */
1435
		public function createTable()
1436
		{
1437
			$id = $this->get('id');
1438
1439
			return Symphony::Database()->query("
1440
				CREATE TABLE `tbl_entries_data_$id` (
1441
					`id` int(11) 		unsigned NOT NULL AUTO_INCREMENT,
1442
					`entry_id` 			int(11) unsigned NOT NULL,
1443
					`entries` 			text COLLATE utf8_unicode_ci NULL,
1444
					PRIMARY KEY  (`id`),
1445
					UNIQUE KEY `entry_id` (`entry_id`)
1446
				) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1447
			");
1448
		}
1449
1450
		/**
1451
		 * Creates the table needed for the settings of the field
1452
		 */
1453
		public static function createFieldTable()
1454
		{
1455
			$tbl = self::FIELD_TBL_NAME;
1456
1457
			return Symphony::Database()->query("
1458
				CREATE TABLE IF NOT EXISTS `$tbl` (
1459
					`id` 				int(11) unsigned NOT NULL AUTO_INCREMENT,
1460
					`field_id` 			int(11) unsigned NOT NULL,
1461
					`sections`			varchar(2048) NULL COLLATE utf8_unicode_ci,
1462
					`show_association` 	enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1463
					`deepness` 			int(2) unsigned NULL,
1464
					`elements` 			text COLLATE utf8_unicode_ci NULL,
1465
					`mode`				varchar(50) NULL COLLATE utf8_unicode_ci,
1466
					`mode_table`		varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL,
1467
					`mode_header`		varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL,
1468
					`mode_footer`		varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL,
1469
					`min_entries`		int(5) unsigned NULL,
1470
					`max_entries`		int(5) unsigned NULL,
1471
					`allow_edit` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1472
					`allow_new` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1473
					`allow_link` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1474
					`allow_delete` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no',
1475
					`allow_collapse` 	enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1476
					`allow_search` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no',
1477
					`show_header` 		enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1478
					PRIMARY KEY (`id`),
1479
					UNIQUE KEY `field_id` (`field_id`)
1480
				) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
1481
			");
1482
		}
1483
		
1484
		public static function update_102()
1485
		{
1486
			$tbl = self::FIELD_TBL_NAME;
1487
			$sql = "
1488
				ALTER TABLE `$tbl`
1489
					ADD COLUMN `allow_edit` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1490
					ADD COLUMN `allow_new` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes',
1491
					ADD COLUMN `allow_link` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1492
					AFTER `max_entries`
1493
			";
1494
			$addColumns = Symphony::Database()->query($sql);
1495
			if (!$addColumns) {
1496
				return false;
1497
			}
1498
1499
			$fields = FieldManager::fetch(null, null, null, 'id', 'entry_relationship');
1500
			if (!empty($fields) && is_array($fields)) {
1501
				foreach ($fields as $fieldId => $field) {
1502
					$sql = "ALTER TABLE `tbl_entries_data_$fieldId` MODIFY `entries` TEXT";
1503
					if (!Symphony::Database()->query($sql)) {
1504
						throw new Exception(__('Could not update table `tbl_entries_data_%s`.', array($fieldId)));
1505
					}
1506
				}
1507
			}
1508
			return true;
1509
		}
1510
		
1511
		public static function update_103()
1512
		{
1513
			$tbl = self::FIELD_TBL_NAME;
1514
			$sql = "
1515
				ALTER TABLE `$tbl`
1516
					ADD COLUMN `allow_delete` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no'
1517
						AFTER `allow_link`
1518
			";
1519
			return Symphony::Database()->query($sql);
1520
		}
1521
		
1522
		public static function update_200()
1523
		{
1524
			$tbl = self::FIELD_TBL_NAME;
1525
			$sql = "
1526
				ALTER TABLE `$tbl`
1527
					ADD COLUMN `allow_collapse` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1528
						AFTER `allow_delete`,
1529
					ADD COLUMN `mode_table` varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL
1530
						AFTER `mode`,
1531
					ADD COLUMN `mode_header` varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL
1532
						AFTER `mode_table`,
1533
					ADD COLUMN `show_header` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'yes'
1534
						AFTER `allow_collapse`,
1535
					ADD COLUMN `mode_footer` varchar(50) NULL COLLATE utf8_unicode_ci DEFAULT NULL
1536
						AFTER `mode_header`,
1537
					CHANGE `sections` `sections` varchar(2048) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
1538
					CHANGE `elements` `elements` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL
1539
			";
1540
			return Symphony::Database()->query($sql);
1541
		}
1542
		
1543
		public static function update_2008()
1544
		{
1545
			$tbl = self::FIELD_TBL_NAME;
1546
			$sql = "
1547
				ALTER TABLE `$tbl`
1548
					ADD COLUMN `allow_search` enum('yes','no') NOT NULL COLLATE utf8_unicode_ci DEFAULT 'no'
1549
						AFTER `allow_collapse`
1550
			";
1551
			return Symphony::Database()->query($sql);
1552
		}
1553
		
1554
		/**
1555
		 *
1556
		 * Drops the table needed for the settings of the field
1557
		 */
1558
		public static function deleteFieldTable()
1559
		{
1560
			$tbl = self::FIELD_TBL_NAME;
1561
			
1562
			return Symphony::Database()->query("
1563
				DROP TABLE IF EXISTS `$tbl`
1564
			");
1565
		}
1566
		
1567
		private static function removeSectionAssociation($child_field_id)
1568
		{
1569
			return Symphony::Database()->delete('tbl_sections_association', "`child_section_field_id` = {$child_field_id}");
1570
		}
1571
	}
1572