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
8/**
9 * TransitionLib
10 *
11 */
12class TransitionLib
13{
14	private $transitionType;
15
16	/**
17	 * @param $transitionType
18	 */
19	function __construct($transitionType)
20	{
21		$this->transitionType = $transitionType;
22	}
23
24	/**
25	 * @param $object
26	 * @param null $type
27	 * @return array
28	 */
29	function getAvailableTransitions($object, $type = null)
30	{
31		$states = $this->getCurrentStates($object, $type);
32
33		$transitions = $this->getTransitionsFromStates($states);
34		$transitions = Perms::filter(
35			['type' => 'transition'],
36			'object',
37			$transitions,
38			['object' => 'transitionId'],
39			'trigger_transition'
40		);
41
42		foreach ($transitions as & $tr) {
43			$object = new Tiki_Transition($tr['from'], $tr['to']);
44			$object->setStates($states);
45			foreach ($tr['guards'] as $guard) {
46				call_user_func_array([$object, 'addGuard' ], $guard);
47			}
48
49			$tr['enabled'] = $object->isReady();
50			$tr['explain'] = $object->explain();
51		}
52
53		return $transitions;
54	}
55
56	/**
57	 * @param $state
58	 * @param $object
59	 * @param null $type
60	 * @return array
61	 */
62	function getAvailableTransitionsFromState($state, $object, $type = null)
63	{
64		$transitions = $this->getAvailableTransitions($object, $type);
65
66		$out = [];
67		foreach ($transitions as $tr) {
68			if ($tr['from'] == $state) {
69				$out[$tr['transitionId']] = $tr['name'];
70			}
71		}
72
73		return $out;
74	}
75
76	/**
77	 * @param $transitionId
78	 * @param $object
79	 * @param null $type
80	 * @return bool
81	 */
82	function triggerTransition($transitionId, $object, $type = null)
83	{
84		// Make sure the transition exists
85		if (! $transition = $this->getTransition($transitionId)) {
86			return false;
87		}
88
89		// Make sure the user can use it
90		$perms = Perms::get(['type' => 'transition', 'object' => $transitionId]);
91		if (! $perms->trigger_transition) {
92			return false;
93		}
94
95		// Verify that the states are consistent
96		$states = $this->getCurrentStates($object, $type);
97
98		$tr = new Tiki_Transition($transition['from'], $transition['to']);
99		$tr->setStates($states);
100
101		foreach ($transition['guards'] as $guard) {
102			call_user_func_array([$tr, 'addGuard'], $guard);
103		}
104
105		if (! $tr->isReady()) {
106			return false;
107		}
108
109		$this->addState($transition['to'], $object, $type);
110		if (! $transition['preserve']) {
111			$this->removeState($transition['from'], $object, $type);
112		}
113
114		return true;
115	}
116
117	/**
118	 * @param $states
119	 * @return array
120	 */
121	function listTransitions($states)
122	{
123		$db = TikiDb::get();
124
125		if (empty($states)) {
126			return [];
127		}
128
129		$bindvars = [$this->transitionType];
130		$query = "SELECT `transitionId`, `preserve`, `name`, `from`, `to`, `guards` FROM `tiki_transitions` WHERE `type` = ? AND ( " .
131						$db->in('from', $states, $bindvars) .
132						' OR ' . $db->in('to', $states, $bindvars) . ')';
133
134		$result = $db->fetchAll($query, $bindvars);
135
136		return array_map([$this, 'expandGuards'], $result);
137	}
138
139	// Database interaction
140
141	/**
142	 * @param $from
143	 * @param $to
144	 * @param $name
145	 * @param bool $preserve
146	 * @param array $guards
147	 * @return mixed
148	 */
149	function addTransition($from, $to, $name, $preserve = false, array $guards = [])
150	{
151		$db = TikiDb::get();
152
153		$db->query(
154			"INSERT INTO `tiki_transitions` ( `type`, `from`, `to`, `name`, `preserve`, `guards`) VALUES( ?, ?, ?, ?, ?, ? )",
155			[$this->transitionType, $from, $to, $name, (int) $preserve, json_encode($guards)]
156		);
157
158		return $db->getOne('SELECT MAX(`transitionId`) FROM `tiki_transitions`');
159	}
160
161	/**
162	 * @param $transitionId
163	 * @param $from
164	 * @param $to
165	 * @param $label
166	 * @param $preserve
167	 */
168	function updateTransition($transitionId, $from, $to, $label, $preserve)
169	{
170		$db = TikiDb::get();
171		$db->query(
172			'UPDATE `tiki_transitions` SET `name` = ?, `from` = ?, `to` = ?, `preserve` = ? WHERE `transitionId` = ?',
173			[$label, $from, $to, (int) $preserve, (int) $transitionId]
174		);
175	}
176
177	/**
178	 * @param $transitionId
179	 * @param array $guards
180	 */
181	function updateGuards($transitionId, array $guards)
182	{
183		$db = TikiDb::get();
184		$db->query(
185			'UPDATE `tiki_transitions` SET `guards` = ? WHERE `transitionId` = ?',
186			[json_encode($guards), (int) $transitionId]
187		);
188	}
189
190	/**
191	 * @param $transitionId
192	 */
193	function removeTransition($transitionId)
194	{
195		$db = TikiDb::get();
196
197		$db->query('DELETE FROM `tiki_transitions` WHERE `transitionId` = ?', [$transitionId]);
198	}
199
200	/**
201	 * @param $states
202	 * @return array
203	 */
204	private function getTransitionsFromStates($states)
205	{
206		$db = TikiDb::get();
207
208		if (empty($states)) {
209			return [];
210		}
211
212		$bindvars = [$this->transitionType];
213		$query = "SELECT `transitionId`, `preserve`, `name`, `from`, `to`, `guards` FROM `tiki_transitions` WHERE `type` = ? AND " .
214						$db->in('from', $states, $bindvars) . ' AND NOT (' .
215						$db->in('to', $states, $bindvars) . ')';
216
217		$result = $db->fetchAll($query, $bindvars);
218
219		return array_map([$this, 'expandGuards'], $result);
220	}
221
222	/**
223	 * @param $transitionId
224	 * @return mixed
225	 */
226	function getTransition($transitionId)
227	{
228		$db = TikiDb::get();
229
230		$bindvars = [$this->transitionType, $transitionId];
231		$query = "SELECT `transitionId`, `preserve`, `name`, `from`, `to`, `guards` FROM" .
232							" `tiki_transitions` WHERE `type` = ? AND `transitionId` = ?";
233		$result = $db->fetchAll($query, $bindvars);
234
235		return $this->expandGuards(reset($result));
236	}
237
238	/**
239	 * @param $transition
240	 * @return mixed
241	 */
242	private function expandGuards($transition)
243	{
244		$transition['guards'] = json_decode($transition['guards'], true);
245		if (! $transition['guards']) {
246			$transition['guards'] = [];
247		}
248
249		return $transition;
250	}
251
252	// The following functions vary depending on the transition type
253
254	/**
255	 * @param $object
256	 * @param $type
257	 * @return array
258	 */
259	private function getCurrentStates($object, $type)
260	{
261		switch ($this->transitionType) {
262			case 'group':
263				$userlib = TikiLib::lib('user');
264				return $userlib->get_user_groups($object);
265			case 'category':
266				$categlib = TikiLib::lib('categ');
267				return $categlib->get_object_categories($type, $object);
268		}
269	}
270
271	/**
272	 * @param $state
273	 * @param $object
274	 * @param $type
275	 */
276	private function addState($state, $object, $type)
277	{
278		global $prefs;
279
280		switch ($this->transitionType) {
281			case 'group':
282				$userlib = TikiLib::lib('user');
283				$userlib->assign_user_to_group($object, $state);
284				if ($prefs['default_group_transitions'] === 'y') {
285					$userlib->set_default_group($object, $state);
286				}
287				return;
288			case 'category':
289				$categlib = TikiLib::lib('categ');
290				$categlib->categorize_any($type, $object, $state);
291				return;
292		}
293	}
294
295	/**
296	 * @param $state
297	 * @param $object
298	 * @param $type
299	 */
300	private function removeState($state, $object, $type)
301	{
302		switch ($this->transitionType) {
303			case 'group':
304				$userlib = TikiLib::lib('user');
305				$userlib->remove_user_from_group($object, $state);
306				return;
307			case 'category':
308				$categlib = TikiLib::lib('categ');
309				if ($catobj = $categlib->is_categorized($type, $object)) {
310					$categlib->uncategorize($catobj, $state);
311				}
312				return;
313		}
314	}
315}
316