1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8class Services_Tracker_Controller
9{
10	/**
11	 * @var Services_Tracker_Utilities
12	 */
13	private $utilities;
14
15	function setUp()
16	{
17		global $prefs;
18		$this->utilities = new Services_Tracker_Utilities;
19
20		Services_Exception_Disabled::check('feature_trackers');
21	}
22
23	function action_view($input)
24	{
25		$item = Tracker_Item::fromId($input->id->int());
26
27		if (! $item) {
28			throw new Services_Exception_NotFound(tr('Item not found'));
29		}
30
31		if (! $item->canView()) {
32			throw new Services_Exception_Denied(tr('Permission denied'));
33		}
34
35		$definition = $item->getDefinition();
36
37		$fields = $item->prepareOutput(new JitFilter([]));
38
39		$info = TikiLib::lib('trk')->get_item_info($item->getId());
40
41		return [
42			'title' => TikiLib::lib('object')->get_title('trackeritem', $item->getId()),
43			'format' => $input->format->word(),
44			'itemId' => $item->getId(),
45			'trackerId' => $definition->getConfiguration('trackerId'),
46			'fields' => $fields,
47			'canModify' => $item->canModify(),
48			'item_info' => $info,
49			'info' => $info,
50		];
51	}
52
53	function action_add_field($input)
54	{
55		$modal = $input->modal->int();
56		$trackerId = $input->trackerId->int();
57
58		$perms = Perms::get('tracker', $trackerId);
59		if (! $perms->admin_trackers) {
60			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
61		}
62
63		$trklib = TikiLib::lib('trk');
64		$definition = Tracker_Definition::get($trackerId);
65
66		if (! $definition) {
67			throw new Services_Exception_NotFound;
68		}
69
70		$name = $input->name->text();
71
72		$permName = $trklib::generatePermName($definition, $input->permName->word());
73
74		$type = $input->type->text();
75		$description = $input->description->text();
76		$wikiparse = $input->description_parse->int();
77		$adminOnly = $input->adminOnly->int();
78		$fieldId = 0;
79
80		$types = $this->utilities->getFieldTypes();
81
82		if (empty($type)) {
83			$type = 't';
84		}
85
86		if (! isset($types[$type])) {
87			throw new Services_Exception(tr('Type does not exist'), 400);
88		}
89
90		if ($_SERVER['REQUEST_METHOD'] === 'POST' && $input->type->word()) {
91			if (empty($name)) {
92				throw new Services_Exception_MissingValue('name');
93			}
94
95			if ($definition->getFieldFromName($name)) {
96				throw new Services_Exception_DuplicateValue('name', $name);
97			}
98
99			if ($definition->getFieldFromPermName($permName)) {
100				throw new Services_Exception_DuplicateValue('permName', $permName);
101			}
102
103			$fieldId = $this->utilities->createField(
104				[
105					'trackerId' => $trackerId,
106					'name' => $name,
107					'permName' => $permName,
108					'type' => $type,
109					'description' => $description,
110					'descriptionIsParsed' => $wikiparse,
111					'isHidden' => $adminOnly ? 'y' : 'n',
112				]
113			);
114
115			if ($input->submit_and_edit->none() || $input->next->word() === 'edit') {
116				return [
117					'FORWARD' => [
118						'action' => 'edit_field',
119						'fieldId' => $fieldId,
120						'trackerId' => $trackerId,
121						'modal' => $modal,
122					],
123				];
124			}
125		}
126
127		return [
128			'title' => tr('Add Field'),
129			'trackerId' => $trackerId,
130			'fieldId' => $fieldId,
131			'name' => $name,
132			'permName' => $permName,
133			'type' => $type,
134			'types' => $types,
135			'description' => $description,
136			'descriptionIsParsed' => $wikiparse,
137			'modal' => $modal,
138			'fieldPrefix' => $definition->getConfiguration('fieldPrefix'),
139		];
140	}
141
142	function action_list_fields($input)
143	{
144		global $prefs;
145
146		$trackerId = $input->trackerId->int();
147		$perms = Perms::get('tracker', $trackerId);
148
149		if (! $perms->view_trackers) {
150			throw new Services_Exception_Denied(tr("You don't have permission to view the tracker"));
151		}
152
153		$definition = Tracker_Definition::get($trackerId);
154
155		if (! $definition) {
156			throw new Services_Exception_NotFound;
157		}
158
159		$fields = $definition->getFields();
160		$types = $this->utilities->getFieldTypes();
161		$typesDisabled = [];
162
163		if ($perms->admin_trackers) {
164			$typesDisabled = $this->utilities->getFieldTypesDisabled();
165		}
166
167		$missing = [];
168		$duplicates = [];
169
170		foreach ($fields as $field) {
171			if (! array_key_exists($field['type'], $types) && ! in_array($field['type'], $missing)) {
172				$missing[] = $field['type'];
173			}
174			if ($prefs['unified_engine'] === 'elastic') {
175				$tracker_fields = TikiLib::lib('tiki')->table('tiki_tracker_fields');
176				$dupeFields = $tracker_fields->fetchAll(
177					[
178						'fieldId',
179						'trackerId',
180						'name',
181						'permName',
182						'type',
183					],
184					[
185						'fieldId'  => $tracker_fields->not($field['fieldId']),
186						'type'     => $tracker_fields->not($field['type']),
187						'permName' => $field['permName'],
188					]
189				);
190				if ($dupeFields) {
191					foreach ($dupeFields as & $df) {
192						$df['message'] = tr('Warning: There is a conflict in permanent names, which can cause indexing errors.') .
193							'<br><a href="tiki-admin_tracker_fields.php?trackerId=' . $df['trackerId'] . '">' .
194							tr(
195								'Field #%0 "%1" of type "%2" also found in tracker #%3 with perm name %4',
196								$df['fieldId'],
197								$df['name'],
198								$types[$df['type']]['name'],
199								$df['trackerId'],
200								$df['permName']
201							) .
202							'</a>';
203					}
204					$duplicates[$field['fieldId']] = $dupeFields;
205				}
206			}
207			if ($field['type'] == 'i' && $prefs['tracker_legacy_insert'] !== 'y') {
208				Feedback::error(tr('You are using the image field type, which is deprecated. It is recommended to activate \'Use legacy tracker insertion screen\' found on the <a href="%0">trackers admin configuration</a> screen.', 'tiki-admin.php?page=trackers'));
209			}
210		}
211		if (! empty($missing)) {
212			Feedback::error(tr('Warning: Required field types not enabled: %0', implode(', ', $missing)));
213		}
214
215		return [
216			'fields' => $fields,
217			'types' => $types,
218			'typesDisabled' => $typesDisabled,
219			'duplicates' => $duplicates,
220		];
221	}
222
223	function action_save_fields($input)
224	{
225		$trackerId = $input->trackerId->int();
226
227		$perms = Perms::get('tracker', $trackerId);
228		if (! $perms->admin_trackers) {
229			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
230		}
231
232		$definition = Tracker_Definition::get($trackerId);
233
234		if (! $definition) {
235			throw new Services_Exception_NotFound;
236		}
237
238		$hasList = false;
239		$hasLink = false;
240
241		$tx = TikiDb::get()->begin();
242
243		$fields = [];
244		foreach ($input->field as $key => $value) {
245			$fieldId = (int) $key;
246			$isMain = $value->isMain->int();
247			$isTblVisible = $value->isTblVisible->int();
248
249			$fields[$fieldId] = [
250				'position' => $value->position->int(),
251				'isTblVisible' => $isTblVisible ? 'y' : 'n',
252				'isMain' => $isMain ? 'y' : 'n',
253				'isSearchable' => $value->isSearchable->int() ? 'y' : 'n',
254				'isPublic' => $value->isPublic->int() ? 'y' : 'n',
255				'isMandatory' => $value->isMandatory->int() ? 'y' : 'n',
256			];
257
258			$this->utilities->updateField($trackerId, $fieldId, $fields[$fieldId]);
259
260			$hasList = $hasList || $isTblVisible;
261			$hasLink = $hasLink || $isMain;
262		}
263
264		if (! $hasList) {
265			Feedback::error(tr('Tracker contains no listed field, no meaningful information will be provided in the default list.'), true);
266		}
267
268		if (! $hasLink) {
269			Feedback::error(tr('The tracker contains no field in the title, so no link will be generated.'), true);
270		}
271
272		$tx->commit();
273
274		return [
275			'fields' => $fields,
276		];
277	}
278
279	/**
280	 * @param JitFilter $input
281	 * @return array
282	 * @throws Services_Exception
283	 * @throws Services_Exception_Denied
284	 * @throws Services_Exception_DuplicateValue
285	 * @throws Services_Exception_NotFound
286	 */
287	function action_edit_field($input)
288	{
289		global $prefs;
290
291		$trackerId = $input->trackerId->int();
292
293		$perms = Perms::get('tracker', $trackerId);
294		if (! $perms->admin_trackers) {
295			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
296		}
297
298		$fieldId = $input->fieldId->int();
299		$definition = Tracker_Definition::get($trackerId);
300
301		if (! $definition) {
302			throw new Services_Exception_NotFound;
303		}
304
305		$field = $definition->getField($fieldId);
306		if (! $field) {
307			throw new Services_Exception_NotFound;
308		}
309
310		$types = $this->utilities->getFieldTypes();
311		$typeInfo = $types[$field['type']];
312		if ($prefs['tracker_change_field_type'] !== 'y') {
313			if (empty($typeInfo['supported_changes'])) {
314				$types = [];
315			} else {
316				$types = $this->utilities->getFieldTypes($typeInfo['supported_changes']);
317			}
318		}
319
320		$permName = $input->permName->word();
321		if ($field['permName'] != $permName) {
322			if ($definition->getFieldFromPermName($permName)) {
323				throw new Services_Exception_DuplicateValue('permName', $permName);
324			}
325		}
326
327		if (strlen($permName) > Tracker_Item::PERM_NAME_MAX_ALLOWED_SIZE) {
328			throw new Services_Exception(tr('Tracker Field permanent name cannot contain more than %0 characters', Tracker_Item::PERM_NAME_MAX_ALLOWED_SIZE), 400);
329		}
330
331		if ($_SERVER['REQUEST_METHOD'] === 'POST' && $input->name->text()) {
332			$input->replaceFilters(
333				[
334					'visible_by' => 'groupname',
335					'editable_by' => 'groupname',
336				]
337			);
338			$visibleBy = $input->asArray('visible_by', ',');
339			$editableBy = $input->asArray('editable_by', ',');
340
341			$options = $this->utilities->buildOptions(new JitFilter($input->option), $typeInfo);
342
343			$trklib = TikiLib::lib('trk');
344			$handler = $trklib->get_field_handler($field);
345			if (! $handler) {
346				throw new Services_Exception(tr('Field handler not found'), 400);
347			}
348			if (method_exists($handler, 'validateFieldOptions')) {
349				try {
350					$params = $this->utilities->parseOptions($options, $typeInfo);
351					$handler->validateFieldOptions($params);
352				} catch (Exception $e) {
353					throw new Services_Exception($e->getMessage(), 400);
354				}
355			}
356
357			if (! empty($types)) {
358				$type = $input->type->text();
359				if ($field['type'] !== $type) {
360					if (! isset($types[$type])) {
361						throw new Services_Exception(tr('Type does not exist'), 400);
362					}
363					$oldTypeInfo = $typeInfo;
364					$typeInfo = $types[$type];
365					if (! empty($oldTypeInfo['supported_changes']) && in_array($type, $oldTypeInfo['supported_changes'])) {
366						// changing supported types should not clear all options but only the ones that are not available in the new type
367						$options = Tracker_Options::fromInput(new JitFilter($input->option), $oldTypeInfo);
368						$params = $options->getAllParameters();
369						foreach (array_keys($params) as $param) {
370							if (empty($typeInfo['params'][$param])) {
371								unset($params[$param]);
372							}
373						}
374						// convert underneath data if field type supports it
375						if (method_exists($handler, 'convertFieldTo')) {
376							$convertedOptions = $handler->convertFieldTo($type);
377							$params = array_merge($params, $convertedOptions);
378						}
379						// prepare options
380						$options = json_encode($params);
381					} else {
382						// clear options for unsupported field type changes
383						$options = json_encode([]);
384					}
385				} elseif (method_exists($handler, 'convertFieldOptions')) {
386					$params = $this->utilities->parseOptions($options, $typeInfo);
387					$handler->convertFieldOptions($params);
388				}
389			} else {
390				$type = $field['type'];
391			}
392
393			$rules = '';
394			if ($input->conditions->text()) {
395				$actions = json_decode($input->actions->text());
396				$else = json_decode($input->else->text());
397				// filter out empty defaults - TODO work out how to remove rules in Vue
398				if ($actions->predicates[0]->target_id !== 'NoTarget' && $else->predicates[0]->target_id !== 'NoTarget') {
399					$rules = json_encode([
400						'conditions' => json_decode($input->conditions->text()),
401						'actions'    => $actions,
402						'else'       => $else,
403					]);
404				}
405			}
406
407			$data = [
408				'name' => $input->name->text(),
409				'description' => $input->description->text(),
410				'descriptionIsParsed' => $input->description_parse->int() ? 'y' : 'n',
411				'options' => $options,
412				'validation' => $input->validation_type->word(),
413				'validationParam' => $input->validation_parameter->none(),
414				'validationMessage' => $input->validation_message->text(),
415				'isMultilingual' => $input->multilingual->int() ? 'y' : 'n',
416				'visibleBy' => array_filter(array_map('trim', $visibleBy)),
417				'editableBy' => array_filter(array_map('trim', $editableBy)),
418				'isHidden' => $input->visibility->alpha(),
419				'errorMsg' => $input->error_message->text(),
420				'permName' => $permName,
421				'type' => $type,
422				'rules' => $rules,
423			];
424
425			$this->utilities->updateField(
426				$trackerId,
427				$fieldId,
428				$data
429			);
430
431			// run field specific post save function
432			$handler = TikiLib::lib('trk')->get_field_handler($field);
433			if ($handler && method_exists($handler, 'handleFieldSave')) {
434				$handler->handleFieldSave($data);
435			}
436		}
437
438		array_walk($typeInfo['params'], function (& $param) {
439			if (isset($param['profile_reference'])) {
440				$lib = TikiLib::lib('object');
441				$param['selector_type'] = $lib->getSelectorType($param['profile_reference']);
442				if (isset($param['parent'])) {
443					if (! preg_match('/[\[\]#\.]/', $param['parent'])) {
444						$param['parent'] = "#option-{$param['parent']}";
445					}
446				} else {
447					$param['parent'] = null;
448				}
449				$param['parentkey'] = isset($param['parentkey']) ? $param['parentkey'] : null;
450				$param['sort_order'] = isset($param['sort_order']) ? $param['sort_order'] : null;
451				$param['format'] = isset($param['format']) ? $param['format'] : null;
452			} else {
453				$param['selector_type'] = null;
454			}
455		});
456
457		return [
458			'title' => tr('Edit') . " " . tr('%0', $field['name']),
459			'field' => $field,
460			'info' => $typeInfo,
461			'options' => $this->utilities->parseOptions($field['options'], $typeInfo),
462			'validation_types' => [
463				'' => tr('None'),
464				'captcha' => tr('CAPTCHA'),
465				'distinct' => tr('Distinct'),
466				'pagename' => tr('Page Name'),
467				'password' => tr('Password'),
468				'regex' => tr('Regular Expression (Pattern)'),
469				'username' => tr('Username'),
470			],
471			'types' => $types,
472			'permNameMaxAllowedSize' => Tracker_Item::PERM_NAME_MAX_ALLOWED_SIZE,
473			'fields' => $definition->getFields(),
474		];
475	}
476
477	function action_remove_fields($input)
478	{
479		$trackerId = $input->trackerId->int();
480
481		$perms = Perms::get('tracker', $trackerId);
482		if (! $perms->admin_trackers) {
483			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
484		}
485
486		$fields = $input->fields->int();
487
488		$definition = Tracker_Definition::get($trackerId);
489
490		if (! $definition) {
491			throw new Services_Exception_NotFound;
492		}
493
494		foreach ($fields as $fieldId) {
495			if (! $definition->getField($fieldId)) {
496				throw new Services_Exception_NotFound;
497			}
498		}
499
500		if ($_SERVER['REQUEST_METHOD'] === 'POST' && $input->confirm->int()) {
501			$trklib = TikiLib::lib('trk');
502			$tx = TikiDb::get()->begin();
503			foreach ($fields as $fieldId) {
504				$trklib->remove_tracker_field($fieldId, $trackerId);
505			}
506			$tx->commit();
507
508			return [
509				'status' => 'DONE',
510				'trackerId' => $trackerId,
511				'fields' => $fields,
512			];
513		} else {
514			return [
515				'trackerId' => $trackerId,
516				'fields' => $fields,
517			];
518		}
519	}
520
521	function action_export_fields($input)
522	{
523		$trackerId = $input->trackerId->int();
524
525		$perms = Perms::get('tracker', $trackerId);
526		if (! $perms->admin_trackers) {
527			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
528		}
529
530		$fields = $input->fields->int();
531
532		$definition = Tracker_Definition::get($trackerId);
533
534		if (! $definition) {
535			throw new Services_Exception_NotFound;
536		}
537
538		if ($fields) {
539			$fields = $this->utilities->getFieldsFromIds($definition, $fields);
540		} else {
541			$fields = $definition->getFields();
542		}
543
544		$data = "";
545		foreach ($fields as $field) {
546			$data .= $this->utilities->exportField($field);
547		}
548
549		return [
550			'title' => tr('Export Fields'),
551			'trackerId' => $trackerId,
552			'fields' => $fields,
553			'export' => $data,
554		];
555	}
556
557	function action_import_fields($input)
558	{
559		if (! Perms::get()->admin_trackers) {
560			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
561		}
562
563		$trackerId = $input->trackerId->int();
564		$definition = Tracker_Definition::get($trackerId);
565
566		if (! $definition) {
567			throw new Services_Exception_NotFound;
568		}
569
570		$raw = $input->raw->none();
571		$preserve = $input->preserve_ids->int();
572		$last_position = $input->last_position->int();
573
574		$data = TikiLib::lib('tiki')->read_raw($raw, $preserve);
575
576		if ($_SERVER['REQUEST_METHOD'] == 'POST') {
577			if (! $data) {
578				throw new Services_Exception(tr('Invalid data provided'), 400);
579			}
580
581			$trklib = TikiLib::lib('trk');
582
583			foreach ($data as $info) {
584				$info['permName'] = $trklib::generatePermName($definition, $info['permName']);
585
586				$this->utilities->importField($trackerId, new JitFilter($info), $preserve, $last_position);
587			}
588		}
589
590		return [
591			'title' => tr('Import Tracker Fields'),
592			'trackerId' => $trackerId,
593		];
594	}
595
596	function action_list_trackers($input)
597	{
598		if (! Perms::get()->admin_trackers) {
599			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
600		}
601
602		$trklib = TikiLib::lib('trk');
603		return $trklib->list_trackers();
604	}
605
606	function action_list_items($input)
607	{
608		// TODO : Eventually, this method should filter according to the actual permissions, but because
609		//        it is only to be used for tracker sync at this time, admin privileges are just fine.
610
611		if (! Perms::get()->admin_trackers) {
612			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
613		}
614
615		$trackerId = $input->trackerId->int();
616		$offset = $input->offset->int();
617		$maxRecords = $input->maxRecords->int();
618		$status = $input->status->word();
619		$format = $input->format->word();
620		$modifiedSince = $input->modifiedSince->int();
621
622		$definition = Tracker_Definition::get($trackerId);
623
624		if (! $definition) {
625			throw new Services_Exception_NotFound;
626		}
627
628		$items = $this->utilities->getItems(
629			[
630				'trackerId' => $trackerId,
631				'status' => $status,
632				'modifiedSince' => $modifiedSince,
633			],
634			$maxRecords,
635			$offset
636		);
637
638		if ($format !== 'raw') {
639			foreach ($items as & $item) {
640				$item = $this->utilities->processValues($definition, $item);
641			}
642		}
643
644		return [
645			'trackerId' => $trackerId,
646			'offset' => $offset,
647			'maxRecords' => $maxRecords,
648			'result' => $items,
649		];
650	}
651
652	/**
653	 * @param JitFilter $input
654	 * @return mixed
655	 * @throws Services_Exception_Denied
656	 * @throws Services_Exception_NotFound
657	 */
658	function action_get_item_inputs($input)
659	{
660		$trackerId = $input->trackerId->int();
661		$trackerName = $input->trackerName->text();
662		$itemId = $input->itemId->int();
663		$byName = $input->byName->bool();
664		$defaults = $input->asArray('defaults');
665
666		$this->trackerNameAndId($trackerId, $trackerName);
667
668		$definition = Tracker_Definition::get($trackerId);
669
670		if (! $definition) {
671			throw new Services_Exception_NotFound;
672		}
673
674		$itemObject = Tracker_Item::newItem($trackerId);
675
676		if (! $itemObject->canModify()) {
677			throw new Services_Exception_Denied;
678		}
679
680		$query = Tracker_Query::tracker($byName ? $trackerName : $trackerId)
681			->itemId($itemId);
682
683		if ($input > 0) {
684			$query->byName();
685		}
686		if (! empty($defaults)) {
687			$query->inputDefaults($defaults);
688		}
689
690		$inputs = $query
691			->queryInput();
692
693		return $inputs;
694	}
695
696	function action_clone_item($input)
697	{
698		global $prefs;
699
700		Services_Exception_Disabled::check('tracker_clone_item');
701
702		$trackerId = $input->trackerId->int();
703		$definition = Tracker_Definition::get($trackerId);
704
705		if (! $definition) {
706			throw new Services_Exception_NotFound;
707		}
708
709		$itemId = $input->itemId->int();
710		if (! $itemId) {
711			throw new Services_Exception_Denied(tr('No item to clone'));
712		}
713
714		$itemObject = Tracker_Item::fromId($itemId);
715
716		if (! $itemObject->canView()) {
717			throw new Services_Exception_Denied(tr("The item to clone isn't visible"));
718		}
719
720		$newItem = Tracker_Item::newItem($trackerId);
721
722		if (! $newItem->canModify()) {
723			throw new Services_Exception_Denied(tr("You don't have permission to create new items"));
724		}
725
726		global $prefs;
727		if ($prefs['feature_jquery_validation'] === 'y') {
728			$_REQUEST['itemId'] = 0;	// let the validation code know this will be a new item
729			$validationjs = TikiLib::lib('validators')->generateTrackerValidateJS(
730				$definition->getFields(),
731				'ins_',
732				'',
733				'',
734				// not custom submit handler that is only needed when called by this service
735				'submitHandler: function(form, event){return process_submit(form, event);}'
736			);
737			TikiLib::lib('header')->add_jq_onready('$("#cloneItemForm' . $trackerId . '").validate({' . $validationjs . $this->get_validation_options());
738		}
739
740		$itemObject->asNew();
741		$itemData = $itemObject->getData($input);
742		$processedFields = [];
743
744		$id = 0;
745		if ($_SERVER['REQUEST_METHOD'] == 'POST') {
746			$itemObject = $this->utilities->cloneItem($definition, $itemData, $itemId);
747			$id = $itemObject->getId();
748
749			$processedItem = $this->utilities->processValues($definition, $itemData);
750			$processedFields = $processedItem['fields'];
751		}
752
753		return [
754			'title' => tr('Duplicate Item'),
755			'trackerId' => $trackerId,
756			'itemId' => $itemId,
757			'created' => $id,
758			'data' => $itemData['fields'],
759			'fields' => $itemObject->prepareInput($input),
760			'processedFields' => $processedFields,
761		];
762	}
763
764	function action_insert_item($input)
765	{
766		$processedFields = [];
767
768		$trackerId = $input->trackerId->int();
769
770		if (! $trackerId) {
771			return [
772				'FORWARD' => ['controller' => 'tracker', 'action' => 'select_tracker'],
773			];
774		}
775
776		$trackerName = $this->trackerName($trackerId);
777		$definition = Tracker_Definition::get($trackerId);
778
779		if (! $definition) {
780			throw new Services_Exception_NotFound;
781		}
782
783		$itemObject = Tracker_Item::newItem($trackerId);
784
785		if (! $itemObject->canModify()) {
786			throw new Services_Exception_Denied;
787		}
788
789		$fields = $input->fields->none();
790		$forced = $input->forced->none();
791		$processedFields = $itemObject->prepareInput($input);
792		$suppressFeedback = $input->suppressFeedback->bool();
793		$toRemove = [];
794
795		if (empty($fields)) {
796
797			$fields = [];
798			foreach ($processedFields as $k => $f) {
799				$permName = $f['permName'];
800				$fields[$permName] = $f['value'];
801
802				if (isset($forced[$permName])) {
803					$toRemove[$permName] = $k;
804				}
805			}
806
807			foreach ($toRemove as $permName => $key) {
808				unset($fields[$permName]);
809				unset($processedFields[$key]);
810			}
811		} else {
812			$out = [];
813			foreach ($fields as $key => $value) {
814				if ($itemObject->canModifyField($key)) {
815					$out[$key] = $value;
816				}
817			}
818			$fields = $out;
819
820			// if fields are specified in the form creation url then use only those ones
821			if (! empty($fields) && $_SERVER['REQUEST_METHOD'] !== 'POST') {
822				foreach ($processedFields as $k => $f) {
823					$permName = $f['permName'];
824
825					if (! isset($fields[$permName])) {
826						$toRemove[$permName] = $k;
827					}
828				}
829
830				foreach ($toRemove as $permName => $key) {
831					unset($processedFields[$key]);
832				}
833			}
834		}
835
836		global $prefs;
837		if ($prefs['feature_jquery_validation'] === 'y') {
838			$validationjs = TikiLib::lib('validators')->generateTrackerValidateJS(
839				$definition->getFields(),
840				'ins_',
841				'',
842				'',
843				// not custom submit handler that is only needed when called by this service
844				'submitHandler: function(form, event){return process_submit(form, event);}'
845			);
846			TikiLib::lib('header')->add_jq_onready('$("#insertItemForm' . $trackerId . '").validate({' . $validationjs . $this->get_validation_options('#insertItemForm' . $trackerId));
847		}
848
849		if ($prefs['tracker_field_rules'] === 'y') {
850			$js = TikiLib::lib('vuejs')->generateTrackerRulesJS($definition->getFields());
851			TikiLib::lib('header')->add_jq_onready($js);
852		}
853
854		$itemId = 0;
855		$util = new Services_Utilities();
856		if (! empty($fields) && $util->isActionPost()) {
857			foreach ($forced as $key => $value) {
858				if ($itemObject->canModifyField($key)) {
859					$fields[$key] = $value;
860				}
861			}
862
863			// test if one item per user
864			if ($definition->getConfiguration('oneUserItem', 'n') == 'y') {
865				$perms = Perms::get('tracker', $trackerId);
866
867				if ($perms->admin_trackers) {	// tracker admins can make items for other users
868					$field = $definition->getField($definition->getUserField());
869					$theUser = isset($fields[$field['permName']]) ? $fields[$field['permName']] : null;	// setup error?
870				} else {
871					$theUser = null;
872				}
873
874				$tmp = TikiLib::lib('trk')->get_user_item($trackerId, $definition->getInformation(), $theUser);
875				if ($tmp > 0) {
876					throw new Services_Exception(tr('Item could not be created. Only one item per user is allowed.'), 400);
877				}
878			}
879
880			$itemId = $this->utilities->insertItem(
881				$definition,
882				[
883					'status' => $input->status->word(),
884					'fields' => $fields,
885				]
886			);
887
888			if ($itemId) {
889				TikiLib::lib('unifiedsearch')->processUpdateQueue();
890				TikiLib::events()->trigger('tiki.process.redirect'); // wait for indexing to complete before loading of next request to ensure updated info shown
891
892				if ($next = $input->next->url()) {
893					$access = TikiLib::lib('access');
894					$access->redirect($next, tr('Item created'));
895				}
896
897				$item = $this->utilities->getItem($trackerId, $itemId);
898				$item['itemTitle'] = $this->utilities->getTitle($definition, $item);
899				$processedItem = $this->utilities->processValues($definition, $item);
900				$item['processedFields'] = $processedItem['fields'];
901
902				if ($suppressFeedback !== true) {
903					if ($input->ajax->bool()) {
904						$trackerinfo = $definition->getInformation();
905						$trackername = tr($trackerinfo['name']);
906						$msg = tr('New "%0" item successfully created.', $trackername);
907						Feedback::success($msg);
908						Feedback::send_headers();
909					} else {
910						Feedback::success(tr('New tracker item %0 successfully created.', $itemId));
911					}
912				}
913
914				return $item;
915			} else {
916				throw new Services_Exception(tr('Tracker item could not be created.'), 400);
917			}
918		}
919
920		$editableFields = $input->editable->none();
921		if (empty($editableFields)) {
922			//if editable fields, show all fields in the form (except the ones from forced which have been removed).
923			$displayedFields = $processedFields;
924		} else {
925			// if editableFields is set, only add the field if found in the editableFields array
926			$displayedFields = [];
927			foreach ($processedFields as $k => $f) {
928				$permName = $f['permName'];
929				if (in_array($permName, $editableFields)) {
930					$displayedFields[] = $f;
931				}
932			}
933		}
934		$status = $input->status->word();
935		if ($status === null) { // '=== null' means status was not set. if status is set to "", it skips the status and uses the default
936			$status = $itemObject->getDisplayedStatus();
937		} else {
938			$status = $input->status->word();
939		}
940
941		$title = $input->title->none();
942		if (empty($title)) { // '=== null' means status was not set. if status is set to "", it skips the status and uses the default
943			$title = tr('Create Item');
944		} else {
945			$title = $title;
946		}
947
948		if ($input->format->word()) {
949			$format = $input->format->word();
950		} else {
951			$format = $definition->getConfiguration('sectionFormat');
952		}
953
954		$editItemPretty = '';
955		if ($format === 'config') {
956			$editItemPretty = $definition->getConfiguration('editItemPretty');
957		}
958
959		return [
960			'title' => $title,
961			'trackerId' => $trackerId,
962			'trackerName' => $trackerName,
963			'itemId' => $itemId,
964			'fields' => $displayedFields,
965			'forced' => $forced,
966			'trackerLogo' => $definition->getConfiguration('logo'),
967			'modal' => $input->modal->int(),
968			'status' => $status,
969			'format' => $format,
970			'editItemPretty' => $editItemPretty,
971			'next' => $input->next->url(),
972			'suppressFeedback' => $suppressFeedback,
973		];
974	}
975
976	/**
977	 * @param $input JitFilter
978	 * - "trackerId" required
979	 * - "itemId" required
980	 * - "editable" optional. array of field names. e.g. ['title', 'description', 'user']. If not set, all fields
981	 *    all fields will be editable
982	 * - "forced" optional. associative array of fields where the value is 'forced'. Commonly used with skip_form.
983	 *    e.g ['isArchived'=>'y']. For example, this can be used to create a button that allows you to set the
984	 *    trackeritem to "Closed", or to set a field to a pre-determined value.
985	 * - "skip_form" - Allows users to skip the input form. This must be used with "forced" or "status" otherwise nothing would change
986	 * - "status" - sets a status for the object to be set to. Often used with skip_form
987	 *
988	 * Formatting the edit screen
989	 * - "title" optional. Sets a title for the edit screen.
990	 * - "skip_form_confirm_message" optional. Used with skip_form. E.g. "Are you sure you want to set this item to 'Closed'".
991	 * - "button_label" optional. Used to override the label for the Update/Save button.
992	 * - "redirect" set a url to which a user should be redirected, if any.
993	 *
994	 * @return array
995	 * @throws Exception
996	 * @throws Services_Exception
997	 * @throws Services_Exception_Denied
998	 * @throws Services_Exception_MissingValue
999	 * @throws Services_Exception_NotFound
1000	 *
1001	 */
1002	function action_update_item($input)
1003	{
1004		$trackerId = $input->trackerId->int();
1005		$definition = Tracker_Definition::get($trackerId);
1006		$suppressFeedback = $input->suppressFeedback->bool();
1007
1008		if (! $definition) {
1009			throw new Services_Exception_NotFound;
1010		}
1011
1012		if (! $itemId = $input->itemId->int()) {
1013			throw new Services_Exception_MissingValue('itemId');
1014		}
1015
1016		$itemInfo = TikiLib::lib('trk')->get_tracker_item($itemId);
1017		if (! $itemInfo || $itemInfo['trackerId'] != $trackerId) {
1018			throw new Services_Exception_NotFound;
1019		}
1020
1021		$itemObject = Tracker_Item::fromInfo($itemInfo);
1022		if (! $itemObject->canModify()) {
1023			throw new Services_Exception_Denied;
1024		}
1025
1026		global $prefs;
1027		if ($prefs['feature_jquery_validation'] === 'y') {
1028			$validationjs = TikiLib::lib('validators')->generateTrackerValidateJS(
1029				$definition->getFields(),
1030				'ins_',
1031				'',
1032				'',
1033				// not custom submit handler that is only needed when called by this service
1034				'submitHandler: function(form, event){return process_submit(form, event);}'
1035			);
1036			TikiLib::lib('header')->add_jq_onready('$("#updateItemForm' . $trackerId . '").validate({' . $validationjs . $this->get_validation_options());
1037		}
1038
1039		if ($prefs['tracker_field_rules'] === 'y') {
1040			$js = TikiLib::lib('vuejs')->generateTrackerRulesJS($definition->getFields());
1041			TikiLib::lib('header')->add_jq_onready($js);
1042		}
1043
1044		if ($_SERVER['REQUEST_METHOD'] == 'POST') {
1045			TikiLib::lib('access')->preventRedirect(true);
1046			//fetch the processed fields and the changes made in the form. Put them in the 'fields' variable
1047			$processedFields = $itemObject->prepareInput($input);
1048			$fields = [];
1049			foreach ($processedFields as $k => $f) {
1050				$permName = $f['permName'];
1051				$fields[$permName] = isset($f['value']) ? $f['value'] : '';
1052			}
1053			// for each input from the form, ensure user has modify rights. If so, add to the fields var to be edited.
1054			$userInput = $input->fields->none();
1055			if (! empty($userInput)) {
1056				foreach ($userInput as $key => $value) {
1057					if ($itemObject->canModifyField($key)) {
1058						$fields[$key] = $value;
1059					}
1060				}
1061			}
1062			// for each input from the form, ensure user has modify rights. If so, add to the fields var to be edited.
1063			$forcedInput = $input->forced->none();
1064			if (! empty($forcedInput)) {
1065				foreach ($forcedInput as $key => $value) {
1066					if ($itemObject->canModifyField($key)) {
1067						$fields[$key] = $value;
1068					}
1069				}
1070			}
1071
1072			$result = $this->utilities->updateItem(
1073				$definition,
1074				[
1075					'itemId' => $itemId,
1076					'status' => $input->status->word(),
1077					'fields' => $fields,
1078				]
1079			);
1080
1081			TikiLib::lib('access')->preventRedirect(false);
1082
1083			if ($result !== false) {
1084				TikiLib::lib('unifiedsearch')->processUpdateQueue();
1085				TikiLib::events()->trigger('tiki.process.redirect'); // wait for indexing to complete before loading of next request to ensure updated info shown
1086				//only need feedback if success - feedback already set if there was an update error
1087			}
1088			if (isset($input['edit']) && $input['edit'] === 'inline') {
1089				if ($result && $suppressFeedback !== true) {
1090					Feedback::success(tr('Tracker item %0 has been updated', $itemId), true);
1091				} else {
1092					Feedback::send_headers();
1093				}
1094			} else {
1095				if ($result && $suppressFeedback !== true) {
1096					if ($input->ajax->bool()) {
1097						$trackerinfo = $definition->getInformation();
1098						$trackername = tr($trackerinfo['name']);
1099						$item = $this->utilities->getItem($trackerId, $itemId);
1100						$itemtitle = $this->utilities->getTitle($definition, $item);
1101						$msg = tr('%0: Updated "%1"', $trackername, $itemtitle) . " [" . TikiLib::lib('tiki')->get_long_time( TikiLib::lib('tiki')->now ) . "]";
1102						Feedback::success($msg);
1103						Feedback::send_headers();
1104					} else {
1105						Feedback::success(tr('Tracker item %0 has been updated', $itemId));
1106					}
1107				} else {
1108					Feedback::send_headers();
1109				}
1110				$redirect = $input->redirect->url();
1111
1112				if ($input->saveAndComment->int()) {
1113					$version = TikiLib::lib('trk')->last_log_version($itemId);
1114
1115					return [
1116						'FORWARD' => [
1117							'controller' => 'comment',
1118							'action' => 'post',
1119							'type' => 'trackeritem',
1120							'objectId' => $itemId,
1121							'parentId' => 0,
1122							'version' => $version,
1123							'return_url' => $redirect,
1124							'title' => tr('Comment for edit #%0', $version),
1125						],
1126					];
1127				}
1128				//return to page
1129				if (! $redirect) {
1130					$referer = Services_Utilities::noJsPath();
1131					return Services_Utilities::refresh($referer);
1132				} else {
1133					return Services_Utilities::redirect($redirect);
1134				}
1135			}
1136		}
1137
1138		// sets all fields for the tracker item with their value
1139		$processedFields = $itemObject->prepareInput($input);
1140		// fields that we want to change in the form. If
1141		$editableFields = $input->editable->none();
1142		// fields where the value is forced.
1143		$forcedFields = $input->forced->none();
1144
1145		// if forced fields are set, remove them from the processedFields since they will not show up visually
1146		// in the form; they will be set up separately and hidden.
1147		if (! empty($forcedFields)) {
1148			foreach ($processedFields as $k => $f) {
1149				$permName = $f['permName'];
1150				if (isset($forcedFields[$permName])) {
1151					unset($processedFields[$k]);
1152				}
1153			}
1154		}
1155
1156		if (empty($editableFields)) {
1157			//if editable fields, show all fields in the form (except the ones from forced which have been removed).
1158			$displayedFields = $processedFields;
1159		} else {
1160			// if editableFields is set, only add the field if found in the editableFields array
1161			$displayedFields = [];
1162			foreach ($processedFields as $k => $f) {
1163				$permName = $f['permName'];
1164				if (in_array($permName, $editableFields)) {
1165					$displayedFields[] = $f;
1166				}
1167			}
1168		}
1169
1170		/* Allow overriding of default wording in the template */
1171		if (empty($input->title->text())) {
1172			$title = tr('Update Item');
1173		} else {
1174			$title = $input->title->text();
1175		}
1176
1177		if ($input->format->word()) {
1178			$format = $input->format->word();
1179		} else {
1180			$format = $definition->getConfiguration('sectionFormat');
1181		}
1182
1183		$editItemPretty = '';
1184		if ($format === 'config') {
1185			$editItemPretty = $definition->getConfiguration('editItemPretty');
1186		}
1187
1188		//Used if skip form is set
1189		if (empty($input->skip_form_message->text())) {
1190			$skip_form_message = tr('Are you sure you would like to update this item?');
1191		} else {
1192			$skip_form_message = $input->skip_form_message->text();
1193		}
1194
1195		if (empty($input->button_label->text())) {
1196			$button_label = tr('Save');
1197		} else {
1198			$button_label = $input->button_label->text();
1199		}
1200
1201		if ($input->status->word() === null) {
1202			$status = $itemObject->getDisplayedStatus();
1203		} else {
1204			$status = $input->status->word();
1205		}
1206
1207		$saveAndComment = $definition->getConfiguration('saveAndComment');
1208		if ($saveAndComment !== 'n') {
1209			if (! Tracker_Item::fromId($itemId)->canPostComments()) {
1210				$saveAndComment = 'n';
1211			}
1212		}
1213
1214		return [
1215			'title' => $title,
1216			'trackerId' => $trackerId,
1217			'itemId' => $itemId,
1218			'fields' => $displayedFields,
1219			'forced' => $forcedFields,
1220			'status' => $status,
1221			'skip_form' => $input->skip_form->word(),
1222			'skip_form_message' => $skip_form_message,
1223			'format' => $format,
1224			'editItemPretty' => $editItemPretty,
1225			'button_label' => $button_label,
1226			'redirect' => $input->redirect->none(),
1227			'saveAndComment' => $saveAndComment,
1228			'suppressFeedback' => $suppressFeedback,
1229		];
1230	}
1231
1232	/**
1233	 * Preview tracker items
1234	 *
1235	 * @param JitFilter $input
1236	 * @return null
1237	 */
1238	public function action_preview_item($input)
1239	{
1240		global $prefs;
1241
1242		$input = $input->fields;
1243		$trackerId = $input->trackerId->int();
1244		$definition = Tracker_Definition::get($trackerId);
1245
1246		if (! $definition) {
1247			throw new Services_Exception_NotFound;
1248		}
1249
1250		$itemId = $input->itemId->int();
1251
1252		if ($itemId) {
1253			$itemInfo = TikiLib::lib('trk')->get_tracker_item($itemId);
1254			if (! $itemInfo || $itemInfo['trackerId'] != $trackerId) {
1255				throw new Services_Exception_NotFound;
1256			}
1257		} else {
1258			$itemInfo = ['trackerId' => $trackerId];
1259		}
1260
1261		$trklib = TikiLib::lib('trk');
1262		$smarty = TikiLib::lib('smarty');
1263
1264		$itemObject = Tracker_Item::fromInfo($itemInfo);
1265		$processedFields = $itemObject->prepareInput($input);
1266		$fieldsProcessed = [];
1267		foreach ($processedFields as $k => $f) {
1268			$permName = $f['permName'];
1269			$fieldsProcessed[$permName] = isset($f['value']) ? $f['value'] : '';
1270			if (isset($f['relations'])) {
1271				$fieldsProcessed[$permName] = ['relations' => $f['relations']];
1272			}
1273			if (isset($f['selected'])) {
1274				$fieldsProcessed[$permName] = ['selected' => $f['selected']];
1275			}
1276			if (isset($f['selected_categories'])) {
1277				$fieldsProcessed[$permName] = ['selected_categories' => $f['selected_categories']];
1278			}
1279			if (isset($f['files'])) {
1280				$fieldsProcessed[$permName] = ['files' => $f['files']];
1281			}
1282		}
1283
1284		$fieldDefinitions = $definition->getFields();
1285		$smarty->assign('tracker_is_multilingual', $prefs['feature_multilingual'] == 'y' && $definition->getLanguageField());
1286
1287		if ($prefs['feature_groupalert'] == 'y') {
1288			$groupalertlib = TikiLib::lib('groupalert');
1289			$groupforalert = $groupalertlib->GetGroup('tracker', $trackerId);
1290			if ($groupforalert != "") {
1291				$showeachuser = $groupalertlib->GetShowEachUser('tracker', $trackerId, $groupforalert);
1292				$userlib = TikiLib::lib('user');
1293				$listusertoalert = $userlib->get_users(0, -1, 'login_asc', '', '', false, $groupforalert, '');
1294				$smarty->assign_by_ref('listusertoalert', $listusertoalert['data']);
1295			}
1296			$smarty->assign_by_ref('groupforalert', $groupforalert);
1297			$smarty->assign_by_ref('showeachuser', $showeachuser);
1298		}
1299
1300		$smarty->assign('itemId', $itemId);
1301		$smarty->assign_by_ref('item_info', $itemInfo);
1302		$smarty->assign('item', ['itemId' => $itemId, 'trackerId' => $trackerId]);
1303
1304		$trackerInfo = $definition->getInformation();
1305
1306		include_once('tiki-sefurl.php');
1307
1308		$statusTypes = $trklib->status_types();
1309		$smarty->assign('status_types', $statusTypes);
1310		$fields = [];
1311		$ins_fields = [];
1312		$itemUsers = $trklib->get_item_creators($trackerId, $itemId);
1313		$smarty->assign_by_ref('itemUsers', $itemUsers);
1314
1315		if (empty($trackerInfo)) {
1316			$itemInfo = [];
1317		}
1318
1319		$fieldFactory = $definition->getFieldFactory();
1320
1321		foreach ($fieldDefinitions as &$fieldDefinition) {
1322			$fid = $fieldDefinition["fieldId"];
1323			$fieldDefinition["ins_id"] = 'ins_' . $fid;
1324			$fieldDefinition["filter_id"] = 'filter_' . $fid;
1325		}
1326		unset($fieldDefinition);
1327
1328		$itemObject = Tracker_Item::fromInfo($itemInfo);
1329
1330		foreach ($fieldDefinitions as $i => $currentField) {
1331			$currentFieldIns = null;
1332			$fid = $currentField['fieldId'];
1333
1334			$handler = $fieldFactory->getHandler($currentField, $itemInfo);
1335
1336			$fieldIsVisible = $itemObject->canViewField($fid);
1337			$fieldIsEditable = $itemObject->canModifyField($fid);
1338
1339			if ($fieldIsVisible || $fieldIsEditable) {
1340				$currentFieldIns = $currentField;
1341
1342				if ($handler) {
1343					$insertValues = $handler->getFieldData();
1344
1345					if ($insertValues) {
1346						$currentFieldIns = array_merge($currentFieldIns, $insertValues);
1347					}
1348				}
1349			}
1350
1351			if (! empty($currentFieldIns)) {
1352				if ($fieldIsVisible) {
1353					$fields['data'][$i] = $currentFieldIns;
1354				}
1355				if ($fieldIsEditable) {
1356					$ins_fields['data'][$i] = $currentFieldIns;
1357				}
1358			}
1359		}
1360
1361		if ($trackerInfo['doNotShowEmptyField'] == 'y') {
1362			$trackerlib = TikiLib::lib('trk');
1363			$fields['data'] = $trackerlib->mark_fields_as_empty($fields['data']);
1364		}
1365
1366		foreach ($fields["data"] as &$field) {
1367			$permName = isset($field['permName']) ? $field['permName'] : null;
1368			if (isset($fieldsProcessed[$permName])) {
1369				$field['value'] = $fieldsProcessed[$permName];
1370				$field['pvalue'] = $fieldsProcessed[$permName];
1371				if (isset($fieldsProcessed[$permName]['relations'])) {
1372					$field['relations'] = $fieldsProcessed[$permName]['relations'];
1373				}
1374				if (isset($fieldsProcessed[$permName]['selected'])) {
1375					$field['selected'] = $fieldsProcessed[$permName]['selected'];
1376				}
1377				if (isset($fieldsProcessed[$permName]['selected_categories'])) {
1378					$field['selected_categories'] = $fieldsProcessed[$permName]['selected_categories'];
1379				}
1380				if (isset($field['freetags'])) {
1381					$freetags = trim($fieldsProcessed[$permName]);
1382					$freetags = explode(' ', $freetags);
1383					$field['freetags'] = $freetags;
1384				}
1385				if (isset($fieldsProcessed[$permName]['files'])) {
1386					$field['files'] = $fieldsProcessed[$permName]['files'];
1387				}
1388			}
1389		}
1390
1391		$smarty->assign('trackerId', $trackerId);
1392		$smarty->assign('tracker_info', $trackerInfo);
1393		$smarty->assign_by_ref('info', $itemInfo);
1394		$smarty->assign_by_ref('fields', $fields["data"]);
1395		$smarty->assign_by_ref('ins_fields', $ins_fields["data"]);
1396
1397
1398		if ($trackerInfo['useComments'] == 'y') {
1399			$comCount = $trklib->get_item_nb_comments($itemId);
1400			$smarty->assign("comCount", $comCount);
1401			$smarty->assign("canViewCommentsAsItemOwner", $itemObject->canViewComments());
1402		}
1403
1404		if ($trackerInfo["useAttachments"] == 'y') {
1405			if ($input->removeattach->int()) {
1406				$_REQUEST["show"] = "att";
1407			}
1408			if ($input->editattach->int()) {
1409				$att = $trklib->get_item_attachment($input->editattach->int());
1410				$smarty->assign("attach_comment", $att['comment']);
1411				$smarty->assign("attach_version", $att['version']);
1412				$smarty->assign("attach_longdesc", $att['longdesc']);
1413				$smarty->assign("attach_file", $att["filename"]);
1414				$smarty->assign("attId", $att["attId"]);
1415				$_REQUEST["show"] = "att";
1416			}
1417			// If anything below here is changed, please change lib/wiki-plugins/wikiplugin_attach.php as well.
1418			$attextra = 'n';
1419			if (strstr($trackerInfo["orderAttachments"], '|')) {
1420				$attextra = 'y';
1421			}
1422			$attfields = explode(',', strtok($trackerInfo["orderAttachments"], '|'));
1423			$atts = $trklib->list_item_attachments($itemId, 0, -1, 'comment_asc', '');
1424			$smarty->assign('atts', $atts["data"]);
1425			$smarty->assign('attCount', $atts["cant"]);
1426			$smarty->assign('attfields', $attfields);
1427			$smarty->assign('attextra', $attextra);
1428		}
1429
1430		include_once('tiki-section_options.php');
1431
1432		ask_ticket('view-trackers-items');
1433
1434		$smarty->assign('canView', $itemObject->canView());
1435
1436		// View
1437		$viewItemPretty = [
1438				'override' => false,
1439				'value' => $trackerInfo['viewItemPretty'],
1440				'type' => 'wiki'
1441		];
1442		if (! empty($trackerInfo['viewItemPretty'])) {
1443			// Need to check wether this is a wiki: or tpl: template, bc the smarty template needs to take care of this
1444			if (strpos(strtolower($viewItemPretty['value']), 'wiki:') === false) {
1445				$viewItemPretty['type'] = 'tpl';
1446			}
1447		}
1448		$smarty->assign('viewItemPretty', $viewItemPretty);
1449
1450		try {
1451			$smarty->assign('print_page', 'y');
1452			$smarty->display('templates/tracker/preview_item.tpl');
1453		} catch (SmartyException $e) {
1454			$message = tr('The requested element cannot be displayed. One of the view/edit templates is missing or has errors: %0', $e->getMessage());
1455			trigger_error($e->getMessage(), E_USER_ERROR);
1456			$smarty->loadPlugin('smarty_modifier_sefurl');
1457			$access = TikiLib::lib('access');
1458			$access->redirect(smarty_modifier_sefurl($trackerId, 'tracker'), $message, 302, 'error');
1459		}
1460	}
1461
1462	/**
1463	 * Links wildcard ItemLink entries to the base tracker by cloning wildcard items
1464	 * and removes unselected ItemLink entries that were already linked before.
1465	 * Used by ItemLink update table button to refresh list of associated entries.
1466	 *
1467	 * @param JitFilter $input
1468	 * @return array|string
1469	 * @throws Services_Exception_Denied
1470	 * @throws Services_Exception_NotFound
1471	 */
1472	function action_link_items($input)
1473	{
1474		$trackerId = $input->trackerId->int();
1475		$definition = Tracker_Definition::get($trackerId);
1476
1477		if (! $definition) {
1478			throw new Services_Exception_NotFound;
1479		}
1480
1481		if (! $field = $definition->getField($input->linkField->int())) {
1482			throw new Services_Exception_NotFound;
1483		}
1484
1485		$linkedItemIds = [];
1486		$linkValue = trim($input->linkValue->text());
1487
1488		foreach ($input->items as $itemId) {
1489			$itemObject = Tracker_Item::fromId($itemId);
1490
1491			if (! $itemObject) {
1492				throw new Services_Exception_NotFound;
1493			}
1494
1495			if (! $itemObject->canView()) {
1496				throw new Services_Exception_Denied(tr("The item to clone isn't visible"));
1497			}
1498
1499			$output = $itemObject->prepareFieldOutput($field);
1500			$currentValue = $output['value'];
1501
1502			if ($currentValue === '*') {
1503				$itemData = $itemObject->getData();
1504				$itemData['fields'][$field['permName']] = $linkValue;
1505				$itemObject = $this->utilities->cloneItem($definition, $itemData);
1506				$linkedItemIds[] = $itemObject->getId();
1507			} else {
1508				$this->utilities->updateItem(
1509					$definition,
1510					[
1511						'itemId' => $itemId,
1512						'fields' => [
1513							$field['permName'] => $linkValue
1514						]
1515					]
1516				);
1517				$linkedItemIds[] = $itemId;
1518			}
1519		}
1520
1521		$allItemIds = TikiLib::lib('trk')->get_items_list($trackerId, $field['fieldId'], $linkValue);
1522		$toDelete = array_diff($allItemIds, $linkedItemIds);
1523		foreach ($toDelete as $itemId) {
1524			$itemObject = Tracker_Item::fromId($itemId);
1525
1526			if (! $itemObject) {
1527				throw new Services_Exception_NotFound;
1528			}
1529
1530			if (! $itemObject->canRemove()) {
1531				throw new Services_Exception_Denied(tr("Cannot remove item %0 from this tracker", $itemId));
1532			}
1533
1534			$uncascaded = TikiLib::lib('trk')->findUncascadedDeletes($itemId, $trackerId);
1535			$this->utilities->removeItemAndReferences($definition, $itemObject, $uncascaded, '');
1536		}
1537
1538		if ($trackerlistParams = $input->asArray('trackerlistParams')) {
1539			include_once 'lib/smarty_tiki/block.wikiplugin.php';
1540			$trackerlistParams['_name'] = 'trackerlist';
1541			$trackerlistParams['checkbox'] = preg_replace('#/[\d,]*$#', '/' . implode(',', $linkedItemIds), $trackerlistParams['checkbox']);
1542			return smarty_block_wikiplugin($trackerlistParams, '', TikiLib::lib('smarty')) . TikiLib::lib('header')->output_js();
1543		} else {
1544			return [
1545				'status' => 'ok'
1546			];
1547		}
1548	}
1549
1550	function action_fetch_item_field($input)
1551	{
1552		$trackerId = $input->trackerId->int();
1553		$mode = $input->mode->word();						// output|input (default input)
1554		$listMode = $input->listMode->word();
1555		$definition = Tracker_Definition::get($trackerId);
1556
1557		if (! $definition) {
1558			throw new Services_Exception_NotFound;
1559		}
1560
1561		if (! $field = $definition->getField($input->fieldId->int())) {
1562			throw new Services_Exception_NotFound;
1563		}
1564
1565		if ($itemId = $input->itemId->int()) {
1566			$itemInfo = TikiLib::lib('trk')->get_tracker_item($itemId);
1567			if (! $itemInfo || $itemInfo['trackerId'] != $trackerId) {
1568				throw new Services_Exception_NotFound;
1569			}
1570
1571			$itemObject = Tracker_Item::fromInfo($itemInfo);
1572			if (! $processed = $itemObject->prepareFieldInput($field, $input->none())) {
1573				throw new Services_Exception_Denied;
1574			}
1575		} else {
1576			$itemObject = Tracker_Item::newItem($trackerId);
1577			$processed = $itemObject->prepareFieldInput($field, $input->none());
1578		}
1579
1580		return [
1581			'field' => $processed,
1582			'mode' => $mode,
1583			'listMode' => $listMode,
1584			'itemId' => $itemId
1585		];
1586	}
1587
1588	function action_set_location($input)
1589	{
1590		$location = $input->location->text();
1591
1592		if (! $itemId = $input->itemId->int()) {
1593			throw new Services_Exception_MissingValue('itemId');
1594		}
1595
1596		$itemInfo = TikiLib::lib('trk')->get_tracker_item($itemId);
1597		if (! $itemInfo) {
1598			throw new Services_Exception_NotFound;
1599		}
1600
1601		$trackerId = $itemInfo['trackerId'];
1602		$definition = Tracker_Definition::get($trackerId);
1603		if (! $definition) {
1604			throw new Services_Exception_NotFound;
1605		}
1606
1607		$itemObject = Tracker_Item::fromInfo($itemInfo);
1608		if (! $itemObject->canModify()) {
1609			throw new Services_Exception_Denied;
1610		}
1611
1612		$field = $definition->getGeolocationField();
1613		if (! $field) {
1614			throw new Services_Exception_NotFound;
1615		}
1616
1617		if ($_SERVER['REQUEST_METHOD'] == 'POST') {
1618			$field = $definition->getField($field);
1619
1620			$this->utilities->updateItem(
1621				$definition,
1622				[
1623					'itemId' => $itemId,
1624					'status' => $itemInfo['status'],
1625					'fields' => [
1626						$field['permName'] => $location,
1627					],
1628				]
1629			);
1630			TikiLib::lib('unifiedsearch')->processUpdateQueue();
1631			TikiLib::events()->trigger('tiki.process.redirect'); // wait for indexing to complete before loading of next request to ensure updated info shown
1632		}
1633
1634		return [
1635			'trackerId' => $trackerId,
1636			'itemId' => $itemId,
1637			'location' => $location,
1638		];
1639	}
1640
1641	function action_remove_item($input)
1642	{
1643		$trackerId = $input->trackerId->int();
1644		$definition = Tracker_Definition::get($trackerId);
1645
1646		if (! $definition) {
1647			throw new Services_Exception_NotFound;
1648		}
1649
1650		if (! $itemId = $input->itemId->int()) {
1651			throw new Services_Exception_MissingValue('itemId');
1652		}
1653
1654		$trklib = TikiLib::lib('trk');
1655
1656		$itemInfo = $trklib->get_tracker_item($itemId);
1657		if (! $itemInfo || $itemInfo['trackerId'] != $trackerId) {
1658			throw new Services_Exception_NotFound;
1659		}
1660
1661		$itemObject = Tracker_Item::fromInfo($itemInfo);
1662		if (! $itemObject->canRemove()) {
1663			throw new Services_Exception_Denied;
1664		}
1665
1666		$uncascaded = $trklib->findUncascadedDeletes($itemId, $trackerId);
1667
1668		if ($_SERVER['REQUEST_METHOD'] == 'POST') {
1669			$this->utilities->removeItemAndReferences($definition, $itemObject, $uncascaded, $input->replacement->int() ?: '');
1670
1671			Feedback::success(tr('Tracker item %0 has been successfully deleted.', $itemId));
1672
1673			TikiLib::events()->trigger('tiki.process.redirect'); // wait for indexing to complete before loading of next request to ensure updated info shown
1674		}
1675
1676		return [
1677			'title' => tr('Remove'),
1678			'trackerId' => $trackerId,
1679			'itemId' => $itemId,
1680			'affectedCount' => count($uncascaded['itemIds']),
1681		];
1682	}
1683
1684	function action_remove($input)
1685	{
1686		$trackerId = $input->trackerId->int();
1687		$confirm = $input->confirm->int();
1688
1689		$perms = Perms::get('tracker', $trackerId);
1690		if (! $perms->admin_trackers) {
1691			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
1692		}
1693
1694		$definition = Tracker_Definition::get($trackerId);
1695
1696		if (! $definition) {
1697			throw new Services_Exception_NotFound;
1698		}
1699
1700		if ($_SERVER['REQUEST_METHOD'] === 'POST' && $confirm) {
1701			$this->utilities->removeTracker($trackerId);
1702
1703			return [
1704				'trackerId' => 0,
1705			];
1706		}
1707
1708		return [
1709			'trackerId' => $trackerId,
1710			'name' => $definition->getConfiguration('name'),
1711			'info' => $definition->getInformation(),
1712		];
1713	}
1714
1715	//Function to just change the status of the tracker item
1716	function action_update_item_status($input)
1717	{
1718		if ($input->status->word() == 'DONE') {
1719			return [
1720				'status' => 'DONE',
1721				'redirect' => $input->redirect->word(),
1722			];
1723		}
1724
1725		$trackerId = $input->trackerId->int();
1726		$definition = Tracker_Definition::get($trackerId);
1727
1728		if (! $definition) {
1729			throw new Services_Exception_NotFound;
1730		}
1731
1732		if (! $itemId = $input->itemId->int()) {
1733			throw new Services_Exception_MissingValue('itemId');
1734		}
1735
1736		$itemInfo = TikiLib::lib('trk')->get_tracker_item($itemId);
1737		if (! $itemInfo || $itemInfo['trackerId'] != $trackerId) {
1738			throw new Services_Exception_NotFound;
1739		}
1740
1741		if (empty($input->item_label->text())) {
1742			$item_label = "item";
1743		} else {
1744			$item_label = $input->item_label->text();
1745		}
1746
1747		if (empty($input->title->text())) {
1748			$title = "Change item status";
1749		} else {
1750			$title = $input->title->text();
1751		}
1752
1753		if (empty($input->button_label->text())) {
1754			$button_label = "Update " . $item_label;
1755		} else {
1756			$button_label = $input->button_label->text();
1757		}
1758
1759		$itemObject = Tracker_Item::fromInfo($itemInfo);
1760		if (! $itemObject->canModify()) {
1761			throw new Services_Exception_Denied;
1762		}
1763
1764		if ($_SERVER['REQUEST_METHOD'] === 'POST' && $input->confirm->int()) {
1765			$result = $this->utilities->updateItem(
1766				$definition,
1767				[
1768					'itemId' => $itemId,
1769					'trackerId' => $trackerId,
1770					'status' => $input->status->text(),
1771				]
1772			);
1773
1774			return [
1775				'FORWARD' => [
1776					'controller' => 'tracker',
1777					'action' => 'update_item_status',
1778					'status' => 'DONE',
1779					'redirect' => $input->redirect->text(),
1780				]
1781			];
1782		} else {
1783			return [
1784				'trackerId' => $trackerId,
1785				'itemId' => $itemId,
1786				'item_label' => $item_label,
1787				'status' => $input->status->text(),
1788				'redirect' => $input->redirect->text(),
1789				'confirmation_message' => $input->confirmation_message->text(),
1790				'title' => $title,
1791				'button_label' => $button_label,
1792			];
1793		}
1794		if (false === $result) {
1795			throw new Services_Exception(tr('Validation error'), 406);
1796		}
1797	}
1798
1799	function action_clear($input)
1800	{
1801
1802		return TikiLib::lib('tiki')->allocate_extra(
1803			'tracker_clear_items',
1804			function () use ($input) {
1805				$trackerId = $input->trackerId->int();
1806				$confirm = $input->confirm->int();
1807
1808				$perms = Perms::get('tracker', $trackerId);
1809				if (! $perms->admin_trackers) {
1810					throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
1811				}
1812
1813				$definition = Tracker_Definition::get($trackerId);
1814
1815				if (! $definition) {
1816					throw new Services_Exception_NotFound;
1817				}
1818
1819				if ($_SERVER['REQUEST_METHOD'] === 'POST' && $confirm) {
1820					$this->utilities->clearTracker($trackerId);
1821
1822					return [
1823						'trackerId' => 0,
1824					];
1825				}
1826
1827				return [
1828					'trackerId' => $trackerId,
1829					'name' => $definition->getConfiguration('name'),
1830				];
1831			}
1832		);
1833	}
1834
1835	function action_replace($input)
1836	{
1837		$trackerId = $input->trackerId->int();
1838		$confirm = $input->confirm->int();
1839
1840		$perms = Perms::get('tracker', $trackerId);
1841		if (! $perms->admin_trackers) {
1842			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
1843		}
1844
1845		if ($trackerId) {
1846			$definition = Tracker_Definition::get($trackerId);
1847
1848			if (! $definition) {
1849				throw new Services_Exception_NotFound;
1850			}
1851		} else {
1852			$definition = Tracker_Definition::getDefault();
1853		}
1854
1855		$cat_type = 'tracker';
1856		$cat_objid = $trackerId;
1857
1858		if ($_SERVER['REQUEST_METHOD'] === 'POST' && $confirm) {
1859			$name = $input->name->text();
1860
1861			if (! $name) {
1862				throw new Services_Exception_MissingValue('name');
1863			}
1864
1865			$data = [
1866				'name' => $name,
1867				'description' => $input->description->text(),
1868				'descriptionIsParsed' => $input->descriptionIsParsed->int() ? 'y' : 'n',
1869				'fieldPrefix' => $input->fieldPrefix->text(),
1870				'showStatus' => $input->showStatus->int() ? 'y' : 'n',
1871				'showStatusAdminOnly' => $input->showStatusAdminOnly->int() ? 'y' : 'n',
1872				'showCreated' => $input->showCreated->int() ? 'y' : 'n',
1873				'showCreatedView' => $input->showCreatedView->int() ? 'y' : 'n',
1874				'showCreatedBy' => $input->showCreatedBy->int() ? 'y' : 'n',
1875				'showCreatedFormat' => $input->showCreatedFormat->text(),
1876				'showLastModif' => $input->showLastModif->int() ? 'y' : 'n',
1877				'showLastModifView' => $input->showLastModifView->int() ? 'y' : 'n',
1878				'showLastModifBy' => $input->showLastModifBy->int() ? 'y' : 'n',
1879				'showLastModifFormat' => $input->showLastModifFormat->text(),
1880				'defaultOrderKey' => $input->defaultOrderKey->int(),
1881				'defaultOrderDir' => $input->defaultOrderDir->word(),
1882				'doNotShowEmptyField' => $input->doNotShowEmptyField->int() ? 'y' : 'n',
1883				'showPopup' => $input->showPopup->text(),
1884				'defaultStatus' => implode('', (array) $input->defaultStatus->word()),
1885				'newItemStatus' => $input->newItemStatus->word(),
1886				'modItemStatus' => $input->modItemStatus->word(),
1887				'outboundEmail' => $input->outboundEmail->email(),
1888				'simpleEmail' => $input->simpleEmail->int() ? 'y' : 'n',
1889				'userCanSeeOwn' => $input->userCanSeeOwn->int() ? 'y' : 'n',
1890				'groupCanSeeOwn' => $input->groupCanSeeOwn->int() ? 'y' : 'n',
1891				'writerCanModify' => $input->writerCanModify->int() ? 'y' : 'n',
1892				'writerCanRemove' => $input->writerCanRemove->int() ? 'y' : 'n',
1893				'userCanTakeOwnership' => $input->userCanTakeOwnership->int() ? 'y' : 'n',
1894				'oneUserItem' => $input->oneUserItem->int() ? 'y' : 'n',
1895				'writerGroupCanModify' => $input->writerGroupCanModify->int() ? 'y' : 'n',
1896				'writerGroupCanRemove' => $input->writerGroupCanRemove->int() ? 'y' : 'n',
1897				'useRatings' => $input->useRatings->int() ? 'y' : 'n',
1898				'showRatings' => $input->showRatings->int() ? 'y' : 'n',
1899				'ratingOptions' => $input->ratingOptions->text(),
1900				'useComments' => $input->useComments->int() ? 'y' : 'n',
1901				'showComments' => $input->showComments->int() ? 'y' : 'n',
1902				'showLastComment' => $input->showLastComment->int() ? 'y' : 'n',
1903				'saveAndComment' => $input->saveAndComment->int() ? 'y' : 'n',
1904				'useAttachments' => $input->useAttachments->int() ? 'y' : 'n',
1905				'showAttachments' => $input->showAttachments->int() ? 'y' : 'n',
1906				'orderAttachments' => implode(',', $input->orderAttachments->word()),
1907				'start' => $input->start->int() ? $this->readDate($input, 'start') : 0,
1908				'end' => $input->end->int() ? $this->readDate($input, 'end') : 0,
1909				'autoCreateGroup' => $input->autoCreateGroup->int() ? 'y' : 'n',
1910				'autoCreateGroupInc' => $input->autoCreateGroupInc->groupname(),
1911				'autoAssignCreatorGroup' => $input->autoAssignCreatorGroup->int() ? 'y' : 'n',
1912				'autoAssignCreatorGroupDefault' => $input->autoAssignCreatorGroupDefault->int() ? 'y' : 'n',
1913				'autoAssignGroupItem' => $input->autoAssignGroupItem->int() ? 'y' : 'n',
1914				'autoCopyGroup' => $input->autoCopyGroup->int() ? 'y' : 'n',
1915				'viewItemPretty' => $input->viewItemPretty->text(),
1916				'editItemPretty' => $input->editItemPretty->text(),
1917				'autoCreateCategories' => $input->autoCreateCategories->int() ? 'y' : 'n',
1918				'publishRSS' => $input->publishRSS->int() ? 'y' : 'n',
1919				'sectionFormat' => $input->sectionFormat->word(),
1920				'adminOnlyViewEditItem' => $input->adminOnlyViewEditItem->int() ? 'y' : 'n',
1921				'logo' => $input->logo->text(),
1922				'useFormClasses' => $input->useFormClasses->int() ? 'y' : 'n',
1923				'formClasses' => $input->formClasses->text(),
1924			];
1925
1926			$trackerId = $this->utilities->updateTracker($trackerId, $data);
1927
1928			$cat_desc = $data['description'];
1929			$cat_name = $data['name'];
1930			$cat_href = "tiki-view_tracker.php?trackerId=" . $trackerId;
1931			$cat_objid = $trackerId;
1932			include "categorize.php";
1933
1934			$groupforAlert = $input->groupforAlert->groupname();
1935
1936			if ($groupforAlert) {
1937				$groupalertlib = TikiLib::lib('groupalert');
1938				$showeachuser = $input->showeachuser->int() ? 'y' : 'n';
1939				$groupalertlib->AddGroup('tracker', $trackerId, $groupforAlert, $showeachuser);
1940			}
1941
1942			$definition = Tracker_Definition::get($trackerId);
1943		}
1944
1945		include_once("categorize_list.php");
1946		$trklib = TikiLib::lib('trk');
1947		$groupalertlib = TikiLib::lib('groupalert');
1948		$groupforAlert = $groupalertlib->GetGroup('tracker', 'trackerId');
1949		return [
1950			'title' => $trackerId ? tr('Edit') . " " . tr('%0', $definition->getConfiguration('name')) : tr('Create Tracker'),
1951			'trackerId' => $trackerId,
1952			'info' => $definition->getInformation(),
1953			'statusTypes' => TikiLib::lib('trk')->status_types(),
1954			'statusList' => preg_split('//', $definition->getConfiguration('defaultStatus', 'o'), -1, PREG_SPLIT_NO_EMPTY),
1955			'sortFields' => $this->getSortFields($definition),
1956			'attachmentAttributes' => $this->getAttachmentAttributes($definition->getConfiguration('orderAttachments', 'created,filesize,hits')),
1957			'startDate' => $this->format($definition->getConfiguration('start'), '%Y-%m-%d'),
1958			'startTime' => $this->format($definition->getConfiguration('start'), '%H:%M'),
1959			'endDate' => $this->format($definition->getConfiguration('end'), '%Y-%m-%d'),
1960			'endTime' => $this->format($definition->getConfiguration('end'), '%H:%M'),
1961			'groupList' => $this->getGroupList(),
1962			'groupforAlert' => $groupforAlert,
1963			'showeachuser' => $groupalertlib->GetShowEachUser('tracker', 'trackerId', $groupforAlert),
1964			'sectionFormats' => $trklib->getGlobalSectionFormats(),
1965		];
1966	}
1967
1968	function action_duplicate($input)
1969	{
1970		$confirm = $input->confirm->int();
1971
1972		if ($_SERVER['REQUEST_METHOD'] === 'POST' && $confirm) {
1973			$trackerId = $input->trackerId->int();
1974			$perms = Perms::get('tracker', $trackerId);
1975			if (! $perms->admin_trackers || ! Perms::get()->admin_trackers) {
1976				throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
1977			}
1978			$definition = Tracker_Definition::get($trackerId);
1979			if (! $definition) {
1980				throw new Services_Exception_NotFound;
1981			}
1982			$name = $input->name->text();
1983			if (! $name) {
1984				throw new Services_Exception_MissingValue('name');
1985			}
1986			$newId = $this->utilities->duplicateTracker($trackerId, $name, $input->dupCateg->int(), $input->dupPerms->int());
1987			return [
1988				'trackerId' => $newId,
1989				'name' => $name,
1990			];
1991		} else {
1992			$trackers = $this->action_list_trackers($input);
1993			return [
1994				'title' => tr('Duplicate Tracker'),
1995				'trackers' => $trackers["data"],
1996			];
1997		}
1998	}
1999
2000	function action_export($input)
2001	{
2002		$trackerId = $input->trackerId->int();
2003		$filterField = $input->filterfield->string();
2004		$filterValue = $input->filtervalue->string();
2005
2006		$perms = Perms::get('tracker', $trackerId);
2007		if (! $perms->export_tracker) {
2008			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
2009		}
2010
2011		$definition = Tracker_Definition::get($trackerId);
2012
2013		if (! $definition) {
2014			throw new Services_Exception_NotFound;
2015		}
2016
2017		if ($perms->admin_trackers) {
2018			$info = $definition->getInformation();
2019
2020			$out = "[TRACKER]\n";
2021
2022			foreach ($info as $key => $value) {
2023				if ($key && $value) {
2024					$out .= "$key = $value\n";
2025				}
2026			}
2027		} else {
2028			$out = null;
2029		}
2030
2031		// Check if can view field otherwise exclude it
2032		$fields = $definition->getFields();
2033		$item = Tracker_Item::newItem($trackerId);
2034		foreach ($fields as $k => $field) {
2035			if (!$item->canViewField($field['fieldId'])) {
2036				unset($fields[$k]);
2037			}
2038		}
2039
2040		return [
2041			'title' => tr('Export Items'),
2042			'trackerId' => $trackerId,
2043			'export' => $out,
2044			'fields' => $fields,
2045			'filterfield' => $filterField,
2046			'filtervalue' => $filterValue,
2047			'recordsMax' => $definition->getConfiguration('items'),
2048		];
2049	}
2050
2051	function action_export_items($input)
2052	{
2053		@ini_set('max_execution_time', 0);
2054		TikiLib::lib('tiki')->allocate_extra(
2055			'tracker_export_items',
2056			function () use ($input) {
2057				$trackerId = $input->trackerId->int();
2058
2059				$definition = Tracker_Definition::get($trackerId);
2060
2061				if (! $definition) {
2062					throw new Services_Exception_NotFound;
2063				}
2064
2065				$perms = Perms::get('tracker', $trackerId);
2066				if (! $perms->export_tracker) {
2067					throw new Services_Exception_Denied(tr("You don't have permission to export"));
2068				}
2069
2070				$fields = [];
2071				foreach ((array) $input->listfields->int() as $fieldId) {
2072					if ($f = $definition->getField($fieldId)) {
2073						$fields[$fieldId] = $f;
2074					}
2075				}
2076
2077				if (0 === count($fields)) {
2078					$fields = $definition->getFields();
2079				}
2080
2081				$filterField = $input->filterfield->string();
2082				$filterValue = $input->filtervalue->string();
2083
2084				$showItemId = $input->showItemId->int();
2085				$showStatus = $input->showStatus->int();
2086				$showCreated = $input->showCreated->int();
2087				$showLastModif = $input->showLastModif->int();
2088				$keepItemlinkId = $input->keepItemlinkId->int();
2089				$keepCountryId = $input->keepCountryId->int();
2090				$dateFormatUnixTimestamp = $input->dateFormatUnixTimestamp->int();
2091
2092				$encoding = $input->encoding->text();
2093				if (! in_array($encoding, ['UTF-8', 'ISO-8859-1'])) {
2094					$encoding = 'UTF-8';
2095				}
2096				$separator = $input->separator->none();
2097				$delimitorR = $input->delimitorR->none();
2098				$delimitorL = $input->delimitorL->none();
2099
2100				$cr = $input->CR->none();
2101
2102				$recordsMax = $input->recordsMax->int();
2103				$recordsOffset = $input->recordsOffset->int() - 1;
2104
2105				$writeCsv = function ($fields) use ($separator, $delimitorL, $delimitorR, $encoding, $cr) {
2106					$values = [];
2107					foreach ($fields as $v) {
2108						$values[] = "$delimitorL$v$delimitorR";
2109					}
2110
2111					$line = implode($separator, $values);
2112					$line = str_replace(["\r\n", "\n", "<br/>", "<br />"], $cr, $line);
2113
2114					if ($encoding === 'ISO-8859-1') {
2115						echo utf8_decode($line) . "\n";
2116					} else {
2117						echo $line . "\n";
2118					}
2119				};
2120
2121				 session_write_close();
2122
2123				$trklib = TikiLib::lib('trk');
2124				$trklib->write_export_header($encoding, $trackerId);
2125
2126				$header = [];
2127				if ($showItemId) {
2128					$header[] = 'itemId';
2129				}
2130				if ($showStatus) {
2131					$header[] = 'status';
2132				}
2133				if ($showCreated) {
2134					$header[] = 'created';
2135				}
2136				if ($showLastModif) {
2137					$header[] = 'lastModif';
2138				}
2139				foreach ($fields as $f) {
2140					$header[] = $f['name'] . ' -- ' . $f['fieldId'];
2141				}
2142
2143				$writeCsv($header);
2144
2145				/** @noinspection PhpParamsInspection */
2146				$items = $trklib->list_items($trackerId, $recordsOffset, $recordsMax, 'itemId_asc', $fields, $filterField, $filterValue);
2147
2148				$smarty = TikiLib::lib('smarty');
2149				$smarty->loadPlugin('smarty_modifier_tiki_short_datetime');
2150				foreach ($items['data'] as $row) {
2151					$toDisplay = [];
2152					if ($showItemId) {
2153						$toDisplay[] = $row['itemId'];
2154					}
2155					if ($showStatus) {
2156						$toDisplay[] = $row['status'];
2157					}
2158					if ($showCreated) {
2159						if ($dateFormatUnixTimestamp) {
2160							$toDisplay[] = $row['created'];
2161						} else {
2162							$toDisplay[] = smarty_modifier_tiki_short_datetime($row['created'], '', 'n');
2163						}
2164					}
2165					if ($showLastModif) {
2166						if ($dateFormatUnixTimestamp) {
2167							$toDisplay[] = $row['lastModif'];
2168						} else {
2169							$toDisplay[] = smarty_modifier_tiki_short_datetime($row['lastModif'], '', 'n');
2170						}
2171					}
2172					foreach ($row['field_values'] as $val) {
2173						if (($keepItemlinkId) && ($val['type'] == 'r')) {
2174							$toDisplay[] = $val['value'];
2175						} elseif (($keepCountryId) && ($val['type'] == 'y')) {
2176							$toDisplay[] = $val['value'];
2177						} elseif (($dateFormatUnixTimestamp) && ($val['type'] == 'f')) {
2178							$toDisplay[] = $val['value'];
2179						} elseif (($dateFormatUnixTimestamp) && ($val['type'] == 'j')) {
2180							$toDisplay[] = $val['value'];
2181						} else {
2182							$toDisplay[] = $trklib->get_field_handler($val, $row)->renderOutput([
2183								'list_mode' => 'csv',
2184								'CR' => $cr,
2185								'delimitorL' => $delimitorL,
2186								'delimitorR' => $delimitorR,
2187							]);
2188						}
2189					}
2190
2191					$writeCsv($toDisplay);
2192				}
2193			}
2194		);
2195
2196		exit;
2197	}
2198
2199	function action_dump_items($input)
2200	{
2201		$trackerId = $input->trackerId->int();
2202
2203		$definition = Tracker_Definition::get($trackerId);
2204
2205		if (! $definition) {
2206			throw new Services_Exception_NotFound;
2207		}
2208
2209		$perms = Perms::get('tracker', $trackerId);
2210		if (! $perms->export_tracker) {
2211			throw new Services_Exception_Denied(tr("You don't have permission to export"));
2212		}
2213
2214		$trklib = TikiLib::lib('trk');
2215		$trklib->dump_tracker_csv($trackerId);
2216		exit;
2217	}
2218
2219	function action_export_profile($input)
2220	{
2221		if (! Perms::get()->admin_trackers) {
2222			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
2223		}
2224
2225		$trackerId = $input->trackerId->int();
2226
2227		$profile = Tiki_Profile::fromString('dummy', '');
2228		$data = [];
2229		$profileObject = new Tiki_Profile_Object($data, $profile);
2230		$profileTrackerInstallHandler = new Tiki_Profile_InstallHandler_Tracker($profileObject, []);
2231
2232		$export_yaml = $profileTrackerInstallHandler->_export($trackerId, $profileObject);
2233
2234		include_once 'lib/wiki-plugins/wikiplugin_code.php';
2235		$export_yaml = wikiplugin_code($export_yaml, ['caption' => 'YAML', 'colors' => 'yaml']);
2236		$export_yaml = preg_replace('/~[\/]?np~/', '', $export_yaml);
2237
2238		return [
2239			'trackerId' => $trackerId,
2240			'yaml' => $export_yaml,
2241		];
2242	}
2243
2244	private function trackerName($trackerId)
2245	{
2246		return TikiLib::lib('tiki')->table('tiki_trackers')->fetchOne('name', ['trackerId' => $trackerId]);
2247	}
2248
2249	private function trackerId($trackerName)
2250	{
2251		return TikiLib::lib('tiki')->table('tiki_trackers')->fetchOne('trackerId', ['name' => $trackerName]);
2252	}
2253
2254	private function trackerNameAndId(&$trackerId, &$trackerName)
2255	{
2256		if ($trackerId > 0 && empty($trackerName)) {
2257			$trackerName = $this->trackerName($trackerId);
2258		} elseif ($trackerId < 1 && ! empty($trackerName)) {
2259			$trackerId = $this->trackerId($trackerName);
2260		}
2261	}
2262
2263	function action_import($input)
2264	{
2265		if (! Perms::get()->admin_trackers) {
2266			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
2267		}
2268
2269		unset($success);
2270		$confirm = $input->confirm->int();
2271
2272		if ($_SERVER['REQUEST_METHOD'] === 'POST' && $confirm) {
2273			$raw = $input->raw->none();
2274			$preserve = $input->preserve->int();
2275
2276			$data = TikiLib::lib('tiki')->read_raw($raw);
2277
2278			if (! $data || ! isset($data['tracker'])) {
2279				throw new Services_Exception(tr('Invalid data provided'), 400);
2280			}
2281
2282			$data = $data['tracker'];
2283
2284			$trackerId = 0;
2285			if ($preserve) {
2286				$trackerId = (int) $data['trackerId'];
2287			}
2288
2289			unset($data['trackerId']);
2290			$trackerId = $this->utilities->updateTracker($trackerId, $data);
2291			$success = 1;
2292
2293			return [
2294				'trackerId' => $trackerId,
2295				'name' => $data['name'],
2296				'success' => $success,
2297			];
2298		}
2299
2300		return [
2301			'title' => tr('Import Tracker Structure'),
2302			'modal' => $input->modal->int(),
2303		];
2304	}
2305
2306	function action_import_items($input)
2307	{
2308		$trackerId = $input->trackerId->int();
2309
2310		$perms = Perms::get('tracker', $trackerId);
2311		if (! $perms->admin_trackers) {
2312			throw new Services_Exception_Denied(tr('Reserved for tracker administrators'));
2313		}
2314
2315		$definition = Tracker_Definition::get($trackerId);
2316
2317		if (! $definition) {
2318			throw new Services_Exception_NotFound;
2319		}
2320
2321		if (isset($_FILES['importfile'])) {
2322			if (! is_uploaded_file($_FILES['importfile']['tmp_name'])) {
2323				throw new Services_Exception(tr('File upload failed.'), 400);
2324			}
2325
2326			if (! $fp = @ fopen($_FILES['importfile']['tmp_name'], "rb")) {
2327				throw new Services_Exception(tr('Uploaded file could not be read.'), 500);
2328			}
2329
2330			$trklib = TikiLib::lib('trk');
2331			$count = $trklib->import_csv(
2332				$trackerId,
2333				$fp,
2334				($input->add_items->int() !== 1), // checkbox is "Create as new items" - param is replace_rows
2335				$input->dateFormat->text(),
2336				$input->encoding->text(),
2337				$input->separator->text(),
2338				$input->updateLastModif->int(),
2339				$input->convertItemLinkValues->int()
2340			);
2341
2342			fclose($fp);
2343
2344			return [
2345				'trackerId' => $trackerId,
2346				'return' => $count,
2347				'importfile' => $_FILES['importfile']['name'],
2348			];
2349		}
2350
2351		return [
2352			'title' => tr('Import Items'),
2353			'trackerId' => $trackerId,
2354			'return' => '',
2355		];
2356	}
2357
2358	function action_vote($input)
2359	{
2360		$requestData = [];
2361		$requestData['itemId'] = $input->i->int();
2362		$requestData['fieldId'] = $input->f->int();
2363		$requestData['vote'] = 'y';
2364
2365		$v = $input->v->text();
2366		if ($v !== 'NULL') {
2367			$v = $input->v->int();
2368		}
2369		$requestData['ins_' . $requestData['fieldId']] = $v;
2370
2371		$trklib = TikiLib::lib('trk');
2372		$field = $trklib->get_tracker_field($requestData['fieldId']);
2373
2374		$handler = $trklib->get_field_handler($field);
2375
2376		$result = $handler->getFieldData($requestData);
2377
2378		return [$result];
2379	}
2380
2381	public function action_import_profile($input)
2382	{
2383		$tikilib = TikiLib::lib('tiki');
2384
2385		$perms = Perms::get();
2386		if (! $perms->admin) {
2387			throw new Services_Exception_Denied(tr('Reserved for administrators'));
2388		}
2389
2390		unset($success);
2391		$confirm = $input->confirm->int();
2392
2393		if ($_SERVER['REQUEST_METHOD'] === 'POST' && $confirm) {
2394			$transaction = $tikilib->begin();
2395			$installer = new Tiki_Profile_Installer;
2396
2397			$yaml = $input->yaml->text();
2398			$name = "tracker_import:" . md5($yaml);
2399			$profile = Tiki_Profile::fromString('{CODE(caption="yaml")}' . "\n" . $yaml . "\n" . '{CODE}', $name);
2400
2401			if ($installer->isInstallable($profile) == true) {
2402				if ($installer->isInstalled($profile) == true) {
2403					$installer->forget($profile);
2404				}
2405
2406				$installer->install($profile);
2407				$feedback = $installer->getFeedback();
2408				$transaction->commit();
2409				return $feedback;
2410				$success = 1;
2411			} else {
2412				return false;
2413			}
2414		}
2415		return [
2416			'title' => tr('Import Tracker From Profile/YAML'),
2417			'modal' => $input->modal->int(),
2418		];
2419	}
2420
2421	private function getSortFields($definition)
2422	{
2423		$sorts = [];
2424
2425		foreach ($definition->getFields() as $field) {
2426			$sorts[$field['fieldId']] = $field['name'];
2427		}
2428
2429		$sorts[-1] = tr('Last Modification');
2430		$sorts[-2] = tr('Creation Date');
2431		$sorts[-3] = tr('Item ID');
2432
2433		return $sorts;
2434	}
2435
2436	private function getAttachmentAttributes($active)
2437	{
2438		$active = explode(',', $active);
2439
2440		$available = [
2441			'filename' => tr('Filename'),
2442			'created' => tr('Creation date'),
2443			'hits' => tr('Views'),
2444			'comment' => tr('Comment'),
2445			'filesize' => tr('File size'),
2446			'version' => tr('Version'),
2447			'filetype' => tr('File type'),
2448			'longdesc' => tr('Long description'),
2449			'user' => tr('User'),
2450		];
2451
2452		$active = array_intersect(array_keys($available), $active);
2453
2454		$attributes = array_fill_keys($active, null);
2455		foreach ($available as $key => $label) {
2456			$attributes[$key] = ['label' => $label, 'selected' => in_array($key, $active)];
2457		}
2458
2459		return $attributes;
2460	}
2461
2462	private function readDate($input, $prefix)
2463	{
2464		$date = $input->{$prefix . 'Date'}->text();
2465		$time = $input->{$prefix . 'Time'}->text();
2466
2467		if (! $time) {
2468			$time = '00:00';
2469		}
2470
2471		list($year, $month, $day) = explode('-', $date);
2472		list($hour, $minute) = explode(':', $time);
2473		$second = 0;
2474
2475		$tikilib = TikiLib::lib('tiki');
2476		$tikidate = TikiLib::lib('tikidate');
2477		$display_tz = $tikilib->get_display_timezone();
2478		if ($display_tz == '') {
2479			$display_tz = 'UTC';
2480		}
2481		$tikidate->setTZbyID($display_tz);
2482		$tikidate->setLocalTime($day, $month, $year, $hour, $minute, $second, 0);
2483		return $tikidate->getTime();
2484	}
2485
2486	private function format($date, $format)
2487	{
2488		if ($date) {
2489			return TikiLib::date_format($format, $date);
2490		}
2491	}
2492
2493	private function getGroupList()
2494	{
2495		$userlib = TikiLib::lib('user');
2496		$groups = $userlib->list_all_groupIds();
2497		$out = [];
2498
2499		foreach ($groups as $g) {
2500			$out[] = $g['groupName'];
2501		}
2502
2503		return $out;
2504	}
2505
2506	function action_select_tracker($input)
2507	{
2508		$confirm = $input->confirm->int();
2509
2510		if ($confirm) {
2511			$trackerId = $input->trackerId->int();
2512			return [
2513				'FORWARD' => [
2514						'action' => 'insert_item',
2515						'trackerId' => $trackerId,
2516				],
2517			];
2518		} else {
2519			$trklib = TikiLib::lib('trk');
2520			$trackers = $trklib->list_trackers();
2521			return [
2522				'title' => tr('Select Tracker'),
2523				'trackers' => $trackers["data"],
2524			];
2525		}
2526	}
2527
2528	function action_search_help($input)
2529	{
2530		return [
2531			'title' => tr('Help'),
2532		];
2533	}
2534
2535	function get_validation_options($formId = '')
2536	{
2537		$jsString = ',
2538		onkeyup: false,
2539		errorClass: "invalid-feedback",
2540		errorPlacement: function(error,element) {
2541			if ($(element).parents(".input-group").length > 0) {
2542				error.insertAfter($(element).parents(".input-group").first());
2543			} else {
2544				error.appendTo($(element).parents().first());
2545			}
2546		},
2547		highlight: function(element) {
2548			$(element).addClass("is-invalid");
2549
2550			// Highlight chosen element if exists
2551			$("#" + element.getAttribute("id") + "_chosen").addClass("is-invalid");
2552		},
2553		unhighlight: function(element) {
2554			$(element).removeClass("is-invalid");
2555
2556			// Unhighlight chosen element if exists
2557			$("#" + element.getAttribute("id") + "_chosen").removeClass("is-invalid");
2558		},
2559		ignore: ".ignore"
2560		});';
2561
2562		if ($formId) {
2563			$jsString .= "\n" . '
2564				$("' . $formId . '").on("click.validate", ":submit", function(){$("' . $formId . '").find("[name^=other_ins_]").each(function(key, item){$(item).data("tiki_never_visited","")})});
2565			';
2566		}
2567
2568		return $jsString;
2569	}
2570
2571	function action_itemslist_output($input)
2572	{
2573		$trklib = TikiLib::lib('trk');
2574		$field = $trklib->get_tracker_field($input->field->int());
2575		if (! $field) {
2576			return '';
2577		}
2578		$fieldHandler = $trklib->get_field_handler($field, [
2579			$input->fieldIdHere->int() => $input->value->text()
2580		]);
2581		if (! $fieldHandler) {
2582			return '';
2583		}
2584		return $fieldHandler->renderOutput();
2585	}
2586}
2587