1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22/**
23 * Class containing methods for operations with IT services.
24 *
25 * @package API
26 */
27class CService extends CApiService {
28
29	protected $tableName = 'services';
30	protected $tableAlias = 's';
31	protected $sortColumns = ['sortorder', 'name'];
32
33	public function __construct() {
34		parent::__construct();
35
36		$this->getOptions = array_merge($this->getOptions, [
37			'parentids' => null,
38			'childids' => null,
39			'countOutput' => null,
40			'selectParent' => null,
41			'selectDependencies' => null,
42			'selectParentDependencies' => null,
43			'selectTimes' => null,
44			'selectAlarms' => null,
45			'selectTrigger' => null,
46			'sortfield' => '',
47			'sortorder' => ''
48		]);
49	}
50
51	/**
52	 * Get services.
53	 *
54	 * Allowed options:
55	 * - parentids                      - fetch the services that are hardlinked to the given parent services;
56	 * - childids                       - fetch the services that are hardlinked to the given child services;
57	 * - countOutput                    - return the number of the results as an integer;
58	 * - selectParent                   - include the parent service in the result;
59	 * - selectDependencies             - include service child dependencies in the result;
60	 * - selectParentDependencies       - include service parent dependencies in the result;
61	 * - selectTimes                    - include service times in the result;
62	 * - selectAlarms                   - include alarms generated by the service;
63	 * - selectTrigger                  - include the linked trigger;
64	 * - sortfield                      - name of columns to sort by;
65	 * - sortorder                      - sort order.
66	 *
67	 * @param array $options
68	 *
69	 * @return array
70	 */
71	public function get(array $options) {
72		$options = zbx_array_merge($this->getOptions, $options);
73
74		// build and execute query
75		$sql = $this->createSelectQuery($this->tableName(), $options);
76		$res = DBselect($sql, $options['limit']);
77
78		// fetch results
79		$result = [];
80		while ($row = DBfetch($res)) {
81			// a count query, return a single result
82			if ($options['countOutput'] !== null) {
83				$result = $row['rowscount'];
84			}
85			// a normal select query
86			else {
87				$result[$row[$this->pk()]] = $row;
88			}
89		}
90
91		if ($options['countOutput'] !== null) {
92			return $result;
93		}
94
95		if ($result) {
96			$result = $this->addRelatedObjects($options, $result);
97			$result = $this->unsetExtraFields($result, ['triggerid'], $options['output']);
98		}
99
100		if ($options['preservekeys'] === null) {
101			$result = zbx_cleanHashes($result);
102		}
103
104		return $result;
105	}
106
107	/**
108	 * Validates the input parameters for the create() method.
109	 *
110	 * @throws APIException if the input is invalid
111	 *
112	 * @param array $services
113	 *
114	 * @return void
115	 */
116	protected function validateCreate(array $services) {
117		foreach ($services as $service) {
118			$this->checkName($service);
119			$this->checkAlgorithm($service);
120			$this->checkShowSla($service);
121			$this->checkGoodSla($service);
122			$this->checkSortOrder($service);
123			$this->checkTriggerId($service);
124			$this->checkStatus($service);
125			$this->checkParentId($service);
126
127			$error = _s('Wrong fields for service "%1$s".', $service['name']);
128			$this->checkUnsupportedFields($this->tableName(), $service, $error, [
129				'parentid', 'dependencies', 'times'
130			]);
131		}
132
133		$this->checkTriggerPermissions($services);
134	}
135
136	/**
137	 * Creates the given services.
138	 *
139	 * @param array $services
140	 *
141	 * @return array
142	 */
143	public function create(array $services) {
144		$services = zbx_toArray($services);
145		$this->validateCreate($services);
146
147		// save the services
148		$serviceIds = DB::insert($this->tableName(), $services);
149
150		$dependencies = [];
151		$serviceTimes = [];
152		foreach ($services as $key => $service) {
153			$serviceId = $serviceIds[$key];
154
155			// save dependencies
156			if (!empty($service['dependencies'])) {
157				foreach ($service['dependencies'] as $dependency) {
158					$dependency['serviceid'] = $serviceId;
159					$dependencies[] = $dependency;
160				}
161			}
162
163			// save parent service
164			if (!empty($service['parentid'])) {
165				$dependencies[] = [
166					'serviceid' => $service['parentid'],
167					'dependsOnServiceid' => $serviceId,
168					'soft' => 0
169				];
170			}
171
172			// save service times
173			if (isset($service['times'])) {
174				foreach ($service['times'] as $serviceTime) {
175					$serviceTime['serviceid'] = $serviceId;
176					$serviceTimes[] = $serviceTime;
177				}
178			}
179		}
180
181		if ($dependencies) {
182			$this->addDependencies($dependencies);
183		}
184
185		if ($serviceTimes) {
186			$this->addTimes($serviceTimes);
187		}
188
189		updateItServices();
190
191		return ['serviceids' => $serviceIds];
192	}
193
194	/**
195	 * Validates the input parameters for the update() method.
196	 *
197	 * @throws APIException if the input is invalid
198	 *
199	 * @param array $services
200	 *
201	 * @return void
202	 */
203	public function validateUpdate(array $services) {
204		foreach ($services as $service) {
205			if (empty($service['serviceid'])) {
206				self::exception(ZBX_API_ERROR_PARAMETERS, _('Invalid method parameters.'));
207			}
208		}
209
210		$this->checkServicePermissions(zbx_objectValues($services, 'serviceid'));
211
212		$services = $this->extendObjects($this->tableName(), $services, ['name']);
213		foreach ($services as $service) {
214			$this->checkName($service);
215
216			if (isset($service['algorithm'])) {
217				$this->checkAlgorithm($service);
218			}
219			if (isset($service['showsla'])) {
220				$this->checkShowSla($service);
221			}
222			if (isset($service['goodsla'])) {
223				$this->checkGoodSla($service);
224			}
225			if (isset($service['sortorder'])) {
226				$this->checkSortOrder($service);
227			}
228			if (isset($service['triggerid'])) {
229				$this->checkTriggerId($service);
230			}
231			if (isset($service['status'])) {
232				$this->checkStatus($service);
233			}
234			if (isset($service['parentid'])) {
235				$this->checkParentId($service);
236			}
237
238			$error = _s('Wrong fields for service "%1$s".', $service['name']);
239			$this->checkUnsupportedFields($this->tableName(), $service, $error, [
240				'parentid', 'dependencies', 'times'
241			]);
242		}
243
244		$this->checkTriggerPermissions($services);
245	}
246
247	/**
248	 * Updates the given services.
249	 *
250	 * @param array $services
251	 *
252	 * @return array
253	 */
254	public function update(array $services) {
255		$services = zbx_toArray($services);
256		$this->validateUpdate($services);
257
258		// save the services
259		foreach ($services as $service) {
260			DB::updateByPk($this->tableName(), $service['serviceid'], $service);
261		}
262
263		// update dependencies
264		$dependencies = [];
265		$parentDependencies = [];
266		$serviceTimes = [];
267		$deleteParentsForServiceIds = [];
268		$deleteDependenciesForServiceIds = [];
269		$deleteTimesForServiceIds = [];
270		foreach ($services as $service) {
271			if (isset($service['dependencies'])) {
272				$deleteDependenciesForServiceIds[] = $service['serviceid'];
273
274				if ($service['dependencies']) {
275					foreach ($service['dependencies'] as $dependency) {
276						$dependency['serviceid'] = $service['serviceid'];
277						$dependencies[] = $dependency;
278					}
279				}
280			}
281
282			// update parent
283			if (isset($service['parentid'])) {
284				$deleteParentsForServiceIds[] = $service['serviceid'];
285
286				if ($service['parentid']) {
287					$parentDependencies[] = [
288						'serviceid' => $service['parentid'],
289						'dependsOnServiceid' => $service['serviceid'],
290						'soft' => 0
291					];
292				}
293			}
294
295			// save service times
296			if (isset($service['times'])) {
297				$deleteTimesForServiceIds[] = $service['serviceid'];
298
299				foreach ($service['times'] as $serviceTime) {
300					$serviceTime['serviceid'] = $service['serviceid'];
301					$serviceTimes[] = $serviceTime;
302				}
303			}
304		}
305
306		// replace dependencies
307		if ($deleteParentsForServiceIds) {
308			$this->deleteParentDependencies(zbx_objectValues($services, 'serviceid'));
309		}
310		if ($deleteDependenciesForServiceIds) {
311			$this->deleteDependencies(array_unique($deleteDependenciesForServiceIds));
312		}
313		if ($parentDependencies || $dependencies) {
314			$this->addDependencies(array_merge($parentDependencies, $dependencies));
315		}
316
317		// replace service times
318		if ($deleteTimesForServiceIds) {
319			$this->deleteTimes($deleteTimesForServiceIds);
320		}
321		if ($serviceTimes) {
322			$this->addTimes($serviceTimes);
323		}
324
325		updateItServices();
326
327		return ['serviceids' => zbx_objectValues($services, 'serviceid')];
328	}
329
330	/**
331	 * Validates the input parameters for the delete() method.
332	 *
333	 * @throws APIException if the input is invalid
334	 *
335	 * @param array $serviceIds
336	 *
337	 * @return void
338	 */
339	public function validateDelete($serviceIds) {
340		if (!$serviceIds) {
341			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
342		}
343
344		$this->checkServicePermissions($serviceIds);
345		$this->checkThatServicesDontHaveChildren($serviceIds);
346	}
347
348	/**
349	 * Delete services.
350	 *
351	 * @param array $serviceIds
352	 *
353	 * @return array
354	 */
355	public function delete(array $serviceIds) {
356		$this->validateDelete($serviceIds);
357
358		DB::delete($this->tableName(), ['serviceid' => $serviceIds]);
359
360		updateItServices();
361
362		return ['serviceids' => $serviceIds];
363	}
364
365	/**
366	 * Validates the input parameters for the addDependencies() method.
367	 *
368	 * @throws APIException if the input is invalid
369	 *
370	 * @param array $dependencies
371	 *
372	 * @return void
373	 */
374	protected function validateAddDependencies(array $dependencies) {
375		if (!$dependencies) {
376			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
377		}
378
379		foreach ($dependencies as $dependency) {
380			if (empty($dependency['serviceid']) || empty($dependency['dependsOnServiceid'])) {
381				self::exception(ZBX_API_ERROR_PARAMETERS, _('Invalid method parameters.'));
382			}
383		}
384
385		$serviceIds = array_merge(
386			zbx_objectValues($dependencies, 'serviceid'),
387			zbx_objectValues($dependencies, 'dependsOnServiceid')
388		);
389		$serviceIds = array_unique($serviceIds);
390		$this->checkServicePermissions($serviceIds);
391
392		foreach ($dependencies as $dependency) {
393			$this->checkDependency($dependency);
394
395			$this->checkUnsupportedFields('services_links', $dependency,
396				_s('Wrong fields for dependency for service "%1$s".', $dependency['serviceid']),
397				['dependsOnServiceid', 'serviceid']
398			);
399		}
400
401		$this->checkForHardlinkedDependencies($dependencies);
402		$this->checkThatParentsDontHaveTriggers($dependencies);
403		$this->checkForCircularityInDependencies($dependencies);
404	}
405
406	/**
407	 * Add the given service dependencies.
408	 *
409	 * @param array $dependencies   an array of service dependencies, each pair in the form of
410	 *                              array('serviceid' => 1, 'dependsOnServiceid' => 2, 'soft' => 0)
411	 *
412	 * @return array
413	 */
414	public function addDependencies(array $dependencies) {
415		$dependencies = zbx_toArray($dependencies);
416		$this->validateAddDependencies($dependencies);
417
418		$data = [];
419		foreach ($dependencies as $dependency) {
420			$data[] = [
421				'serviceupid' => $dependency['serviceid'],
422				'servicedownid' => $dependency['dependsOnServiceid'],
423				'soft' => $dependency['soft']
424			];
425		}
426		DB::insert('services_links', $data);
427
428		return ['serviceids' => zbx_objectValues($dependencies, 'serviceid')];
429	}
430
431	/**
432	 * Validates the input for the deleteDependencies() method.
433	 *
434	 * @throws APIException if the given input is invalid
435	 *
436	 * @param array $serviceIds
437	 *
438	 * @return void
439	 */
440	protected function validateDeleteDependencies(array $serviceIds) {
441		if (!$serviceIds) {
442			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
443		}
444
445		$this->checkServicePermissions($serviceIds);
446	}
447
448	/**
449	 * Deletes all dependencies for the given services.
450	 *
451	 * @param array $serviceIds
452	 *
453	 * @return boolean
454	 */
455	public function deleteDependencies($serviceIds) {
456		$serviceIds = zbx_toArray($serviceIds);
457		$this->validateDeleteDependencies($serviceIds);
458
459		DB::delete('services_links', [
460			'serviceupid' =>  $serviceIds
461		]);
462
463		return ['serviceids' => $serviceIds];
464	}
465
466	/**
467	 * Validates the input for the addTimes() method.
468	 *
469	 * @throws APIException if the given input is invalid
470	 *
471	 * @param array $serviceTimes
472	 *
473	 * @return void
474	 */
475	public function validateAddTimes(array $serviceTimes) {
476		foreach ($serviceTimes as $serviceTime) {
477			$this->checkTime($serviceTime);
478
479			$this->checkUnsupportedFields('services_times', $serviceTime,
480				_s('Wrong fields for time for service "%1$s".', $serviceTime['serviceid'])
481			);
482		}
483
484		$this->checkServicePermissions(array_unique(zbx_objectValues($serviceTimes, 'serviceid')));
485	}
486
487	/**
488	 * Adds the given service times.
489	 *
490	 * @param array $serviceTimes an array of service times
491	 *
492	 * @return array
493	 */
494	public function addTimes(array $serviceTimes) {
495		$serviceTimes = zbx_toArray($serviceTimes);
496		$this->validateAddTimes($serviceTimes);
497
498		DB::insert('services_times', $serviceTimes);
499
500		return ['serviceids' => zbx_objectValues($serviceTimes, 'serviceid')];
501	}
502
503	/**
504	 * Validates the input for the deleteTimes() method.
505	 *
506	 * @throws APIException if the given input is invalid
507	 *
508	 * @param array $serviceIds
509	 *
510	 * @return void
511	 */
512	protected function validateDeleteTimes(array $serviceIds) {
513		if (!$serviceIds) {
514			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
515		}
516
517		$this->checkServicePermissions($serviceIds);
518	}
519
520	/**
521	 * Returns availability-related information about the given services during the given time intervals.
522	 *
523	 * Available options:
524	 *  - serviceids    - a single service ID or an array of service IDs;
525	 *  - intervals     - a single time interval or an array of time intervals, each containing:
526	 *      - from          - the beginning of the interval, timestamp;
527	 *      - to            - the end of the interval, timestamp.
528	 *
529	 * Returns the following availability information for each service:
530	 *  - status            - the current status of the service;
531	 *  - problems          - an array of triggers that are currently in problem state and belong to the given service
532	 *                        or it's descendants;
533	 *  - sla               - an array of requested intervals with SLA information:
534	 *      - from              - the beginning of the interval;
535	 *      - to                - the end of the interval;
536	 *      - okTime            - the time the service was in OK state, in seconds;
537	 *      - problemTime       - the time the service was in problem state, in seconds;
538	 *      - downtimeTime      - the time the service was down, in seconds.
539	 *
540	 * If the service calculation algorithm is set to SERVICE_ALGORITHM_NONE, the method will return an empty 'problems'
541	 * array and null for all of the calculated values.
542	 *
543	 * @param array $options
544	 *
545	 * @return array    as array(serviceId2 => data1, serviceId2 => data2, ...)
546	 */
547	public function getSla(array $options) {
548		$serviceIds = (isset($options['serviceids'])) ? zbx_toArray($options['serviceids']) : null;
549		$intervals = (isset($options['intervals'])) ? zbx_toArray($options['intervals']) : [];
550
551		// fetch services
552		$services = $this->get([
553			'output' => ['serviceid', 'name', 'status', 'algorithm'],
554			'selectTimes' => API_OUTPUT_EXTEND,
555			'selectParentDependencies' => ['serviceupid'],
556			'serviceids' => $serviceIds,
557			'preservekeys' => true
558		]);
559
560		$rs = [];
561		if ($services) {
562			$usedSeviceIds = [];
563
564			$problemServiceIds = [];
565			foreach ($services as &$service) {
566				$service['alarms'] = [];
567
568				// don't calculate SLA for services with disabled status calculation
569				if ($this->isStatusEnabled($service)) {
570					$usedSeviceIds[$service['serviceid']] = $service['serviceid'];
571
572					if ($service['status'] > 0) {
573						$problemServiceIds[] = $service['serviceid'];
574					}
575				}
576			}
577			unset($service);
578
579			// initial data
580			foreach ($services as $service) {
581				$rs[$service['serviceid']] = [
582					'status' => ($this->isStatusEnabled($service)) ? $service['status'] : null,
583					'problems' => [],
584					'sla' => []
585				];
586			}
587
588			if ($usedSeviceIds) {
589				// add service alarms
590				if ($intervals) {
591					$intervalConditions = [];
592					foreach ($intervals as $interval) {
593						$intervalConditions[] = 'sa.clock BETWEEN '.zbx_dbstr($interval['from']).' AND '.zbx_dbstr($interval['to']);
594					}
595					$query = DBselect(
596						'SELECT *'.
597						' FROM service_alarms sa'.
598						' WHERE '.dbConditionInt('sa.serviceid', $usedSeviceIds).
599							' AND ('.implode(' OR ', $intervalConditions).')'.
600						' ORDER BY sa.servicealarmid'
601					);
602					while ($data = DBfetch($query)) {
603						$services[$data['serviceid']]['alarms'][] = $data;
604					}
605				}
606
607				// add problem triggers
608				if ($problemServiceIds) {
609					$problemTriggers = $this->fetchProblemTriggers($problemServiceIds);
610					$rs = $this->escalateProblems($services, $problemTriggers, $rs);
611				}
612
613				$slaCalculator = new CServicesSlaCalculator();
614
615				// calculate SLAs
616				foreach ($intervals as $interval) {
617					$latestValues = $this->fetchLatestValues($usedSeviceIds, $interval['from']);
618
619					foreach ($services as $service) {
620						$serviceId = $service['serviceid'];
621
622						// only calculate the sla for services which require it
623						if (isset($usedSeviceIds[$serviceId])) {
624							$latestValue = (isset($latestValues[$serviceId])) ? $latestValues[$serviceId] : 0;
625							$intervalSla = $slaCalculator->calculateSla($service['alarms'], $service['times'],
626								$interval['from'], $interval['to'], $latestValue
627							);
628						}
629						else {
630							$intervalSla = [
631								'ok' => null,
632								'okTime' => null,
633								'problemTime' => null,
634								'downtimeTime' => null
635							];
636						}
637
638						$rs[$service['serviceid']]['sla'][] = [
639							'from' => $interval['from'],
640							'to' => $interval['to'],
641							'sla' => $intervalSla['ok'],
642							'okTime' => $intervalSla['okTime'],
643							'problemTime' => $intervalSla['problemTime'],
644							'downtimeTime' => $intervalSla['downtimeTime']
645						];
646					}
647				}
648			}
649		}
650
651		return $rs;
652	}
653
654	/**
655	 * Deletes all service times for the given services.
656	 *
657	 * @param array $serviceIds
658	 *
659	 * @return boolean
660	 */
661	public function deleteTimes($serviceIds) {
662		$serviceIds = zbx_toArray($serviceIds);
663		$this->validateDeleteTimes($serviceIds);
664
665		DB::delete('services_times', [
666			'serviceid' =>  $serviceIds
667		]);
668
669		return ['serviceids' => $serviceIds];
670	}
671
672	/**
673	 * Returns true if all of the given objects are available for reading.
674	 *
675	 * @param $ids
676	 *
677	 * @return bool
678	 */
679	public function isReadable(array $ids) {
680		if (empty($ids)) {
681			return true;
682		}
683		$ids = array_unique($ids);
684
685		$count = $this->get([
686			'serviceids' => $ids,
687			'countOutput' => true
688		]);
689		return count($ids) == $count;
690	}
691
692	/**
693	 * Returns true if all of the given objects are available for writing.
694	 *
695	 * @param $ids
696	 *
697	 * @return bool
698	 */
699	public function isWritable(array $ids) {
700		return $this->isReadable($ids);
701	}
702
703	/**
704	 * Deletes the dependencies of the parent services on the given services.
705	 *
706	 * @param $serviceIds
707	 *
708	 * @return void
709	 */
710	protected function deleteParentDependencies($serviceIds) {
711		DB::delete('services_links', [
712			'servicedownid' => $serviceIds,
713			'soft' => 0
714		]);
715	}
716
717
718	/**
719	 * Returns an array of triggers which are in a problem state and are linked to the given services.
720	 *
721	 * @param array $serviceIds
722	 *
723	 * @return array    in the form of array(serviceId1 => array(triggerId => trigger), ...)
724	 */
725	protected function fetchProblemTriggers(array $serviceIds) {
726		$sql = 'SELECT s.serviceid,t.triggerid'.
727				' FROM services s,triggers t'.
728				' WHERE s.status>0'.
729					' AND t.triggerid=s.triggerid'.
730					' AND '.dbConditionInt('s.serviceid', $serviceIds).
731				' ORDER BY s.status DESC,t.description';
732
733		// get service reason
734		$triggers = DBfetchArray(DBSelect($sql));
735
736		$rs = [];
737		foreach ($triggers as $trigger) {
738			$serviceId = $trigger['serviceid'];
739			unset($trigger['serviceid']);
740
741			$rs[$serviceId] = [$trigger['triggerid'] => $trigger];
742		}
743
744		return $rs;
745	}
746
747	/**
748	 * Escalates the problem triggers from the child services to their parents and adds them to $slaData.
749	 * The escalation will stop if a service has status calculation disabled or is in OK state.
750	 *
751	 * @param array $services
752	 * @param array $serviceProblems    an array of service triggers defines as
753	 *                                  array(serviceId1 => array(triggerId => trigger), ...)
754	 * @param array $slaData
755	 *
756	 * @return array
757	 */
758	protected function escalateProblems(array $services, array $serviceProblems, array $slaData) {
759		$parentProblems = [];
760		foreach ($serviceProblems as $serviceId => $problemTriggers) {
761			$service = $services[$serviceId];
762
763			// add the problem trigger of the current service to the data
764			$slaData[$serviceId]['problems'] = zbx_array_merge($slaData[$serviceId]['problems'], $problemTriggers);
765
766			// add the same trigger to the parent services
767			foreach ($service['parentDependencies'] as $dependency) {
768				$parentServiceId = $dependency['serviceupid'];
769
770				if (isset($services[$parentServiceId])) {
771					$parentService = $services[$parentServiceId];
772
773					// escalate only if status calculation is enabled for the parent service and it's in problem state
774					if ($this->isStatusEnabled($parentService) && $parentService['status']) {
775						if (!isset($parentProblems[$parentServiceId])) {
776							$parentProblems[$parentServiceId] = [];
777						}
778						$parentProblems[$parentServiceId] = zbx_array_merge($parentProblems[$parentServiceId], $problemTriggers);
779					}
780				}
781			}
782		}
783
784		// propagate the problems to the parents
785		if ($parentProblems) {
786			$slaData = $this->escalateProblems($services, $parentProblems, $slaData);
787		}
788
789		return $slaData;
790	}
791
792	/**
793	 * Returns the value of the latest service alarm before the given time.
794	 *
795	 * @param array $serviceIds
796	 * @param int $beforeTime
797	 *
798	 * @return array
799	 */
800	protected function fetchLatestValues(array $serviceIds, $beforeTime) {
801		// The query will return the alarms with the latest servicealarmid for each service, before $beforeTime.
802		$query = DBSelect(
803			'SELECT sa.serviceid,sa.value'.
804			' FROM (SELECT sa2.serviceid,MAX(sa2.servicealarmid) AS servicealarmid'.
805					' FROM service_alarms sa2'.
806					' WHERE sa2.clock<'.zbx_dbstr($beforeTime).
807						' AND '.dbConditionInt('sa2.serviceid', $serviceIds).
808					'GROUP BY sa2.serviceid) ss2 '.
809			' JOIN service_alarms sa ON sa.servicealarmid = ss2.servicealarmid'
810		);
811		$rs = [];
812		while ($alarm = DBfetch($query)) {
813			$rs[$alarm['serviceid']] = $alarm['value'];
814		}
815
816		return $rs;
817	}
818
819	/**
820	 * Returns an array of dependencies that are children of the given services. Performs permission checks.
821	 *
822	 * @param array $parentServiceIds
823	 * @param $output
824	 *
825	 * @return array    an array of service links sorted by "sortorder" in ascending order
826	 */
827	protected function fetchChildDependencies(array $parentServiceIds, $output) {
828		$sqlParts = API::getApiService()->createSelectQueryParts('services_links', 'sl', [
829			'output' => $output,
830			'filter' => ['serviceupid' => $parentServiceIds]
831		]);
832
833		// sort by sortorder
834		$sqlParts['from'][] = $this->tableName().' '.$this->tableAlias();
835		$sqlParts['where'][] = 'sl.servicedownid='.$this->fieldId('serviceid');
836		$sqlParts = $this->addQueryOrder($this->fieldId('sortorder'), $sqlParts);
837		$sqlParts = $this->addQueryOrder($this->fieldId('serviceid'), $sqlParts);
838
839		// add permission filter
840		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
841			$sqlParts = $this->addPermissionFilter($sqlParts);
842		}
843
844		$sql = $this->createSelectQueryFromParts($sqlParts);
845
846		return DBfetchArray(DBselect($sql));
847	}
848
849	/**
850	 * Returns an array of dependencies from the parent services to the given services.
851	 * Performs permission checks.
852	 *
853	 * @param array $childServiceIds
854	 * @param $output
855	 * @param boolean $soft             if set to true, will return only soft-linked dependencies
856	 *
857	 * @return array    an array of service links sorted by "sortorder" in ascending order
858	 */
859	protected function fetchParentDependencies(array $childServiceIds, $output, $soft = null) {
860		$sqlParts = API::getApiService()->createSelectQueryParts('services_links', 'sl', [
861			'output' => $output,
862			'filter' => ['servicedownid' => $childServiceIds]
863		]);
864
865		$sqlParts['from'][] = $this->tableName().' '.$this->tableAlias();
866		$sqlParts['where'][] = 'sl.serviceupid='.$this->fieldId('serviceid');
867		if ($soft !== null) {
868			$sqlParts['where'][] = 'sl.soft='.($soft ? 1 : 0);
869		}
870		$sqlParts = $this->addQueryOrder($this->fieldId('sortorder'), $sqlParts);
871		$sqlParts = $this->addQueryOrder($this->fieldId('serviceid'), $sqlParts);
872
873		// add permission filter
874		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
875			$sqlParts = $this->addPermissionFilter($sqlParts);
876		}
877
878		$sql = $this->createSelectQueryFromParts($sqlParts);
879
880		return DBfetchArray(DBselect($sql));
881	}
882
883	/**
884	 * Returns true if status calculation is enabled for the given service.
885	 *
886	 * @param array $service
887	 *
888	 * @return bool
889	 */
890	protected function isStatusEnabled(array $service) {
891		return ($service['algorithm'] != SERVICE_ALGORITHM_NONE);
892	}
893
894	/**
895	 * Validates the "name" field.
896	 *
897	 * @throws APIException if the name is missing
898	 *
899	 * @param array $service
900	 *
901	 * @return void
902	 */
903	protected function checkName(array $service) {
904		if (!isset($service['name']) || zbx_empty($service['name'])) {
905			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty name.'));
906		}
907	}
908
909	/**
910	 * Validates the "algorithm" field. Assumes the "name" field is valid.
911	 *
912	 * @throws APIException if the name is missing or invalid
913	 *
914	 * @param array $service
915	 *
916	 * @return void
917	 */
918	protected function checkAlgorithm(array $service) {
919		if (!isset($service['algorithm']) || !serviceAlgorithm($service['algorithm'])) {
920			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect algorithm for service "%1$s".', $service['name']));
921		}
922	}
923
924	/**
925	 * Validates the "showsla" field. Assumes the "name" field is valid.
926	 *
927	 * @throws APIException if the name is missing or is not a boolean value
928	 *
929	 * @param array $service
930	 *
931	 * @return void
932	 */
933	protected function checkShowSla(array $service) {
934		$showSlaValues = [
935			SERVICE_SHOW_SLA_OFF => true,
936			SERVICE_SHOW_SLA_ON => true
937		];
938		if (!isset($service['showsla']) || !isset($showSlaValues[$service['showsla']])) {
939			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect calculate SLA value for service "%1$s".', $service['name']));
940		}
941	}
942
943	/**
944	 * Validates the "showsla" field. Assumes the "name" field is valid.
945	 *
946	 * @throws APIException if the value is missing, or is out of bounds
947	 *
948	 * @param array $service
949	 *
950	 * @return void
951	 */
952	protected function checkGoodSla(array $service) {
953		if ((!empty($service['showsla']) && empty($service['goodsla']))
954				|| (isset($service['goodsla'])
955					&& (!is_numeric($service['goodsla']) || $service['goodsla'] < 0 || $service['goodsla'] > 100))) {
956
957			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect acceptable SLA for service "%1$s".', $service['name']));
958		}
959	}
960
961	/**
962	 * Validates the "sortorder" field. Assumes the "name" field is valid.
963	 *
964	 * @throws APIException if the value is missing, or is out of bounds
965	 *
966	 * @param array $service
967	 *
968	 * @return void
969	 */
970	protected function checkSortOrder(array $service) {
971		if (!isset($service['sortorder']) || !zbx_is_int($service['sortorder'])
972			|| $service['sortorder'] < 0 || $service['sortorder'] > 999) {
973
974			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect sorder order for service "%1$s".', $service['name']));
975		}
976	}
977
978	/**
979	 * Validates the "triggerid" field. Assumes the "name" field is valid.
980	 *
981	 * @throws APIException if the value is incorrect
982	 *
983	 * @param array $service
984	 *
985	 * @return void
986	 */
987	protected function checkTriggerId(array $service) {
988		if (!empty($service['triggerid']) && !zbx_is_int($service['triggerid'])) {
989			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect trigger ID for service "%1$s".', $service['name']));
990		}
991	}
992
993	/**
994	 * Validates the "parentid" field. Assumes the "name" field is valid.
995	 *
996	 * @throws APIException if the value is incorrect
997	 *
998	 * @param array $service
999	 *
1000	 * @return void
1001	 */
1002	protected function checkParentId(array $service) {
1003		if (!empty($service['parentid']) && !zbx_is_int($service['parentid'])) {
1004			if (isset($service['name'])) {
1005				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect parent for service "%1$s".', $service['name']));
1006			}
1007			else {
1008				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect parent service.'));
1009			}
1010		}
1011
1012		if (isset($service['serviceid']) && idcmp($service['serviceid'], $service['parentid'])) {
1013			self::exception(ZBX_API_ERROR_PARAMETERS, _('Service cannot be parent and child at the same time.'));
1014		}
1015	}
1016
1017	/**
1018	 * Validates the "status" field. Assumes the "name" field is valid.
1019	 *
1020	 * @throws APIException if the value is incorrect
1021	 *
1022	 * @param array $service
1023	 *
1024	 * @return void
1025	 */
1026	protected function checkStatus(array $service) {
1027		if (!empty($service['status']) && !zbx_is_int($service['status'])) {
1028			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect status for service "%1$s".', $service['name']));
1029		}
1030	}
1031
1032	/**
1033	 * Checks that the user has read access to the given triggers.
1034	 *
1035	 * @throws APIException if the user doesn't have permission to access any of the triggers
1036	 *
1037	 * @param array $services
1038	 *
1039	 * @return void
1040	 */
1041	protected function checkTriggerPermissions(array $services) {
1042		$triggerIds = [];
1043		foreach ($services as $service) {
1044			if (!empty($service['triggerid'])) {
1045				$triggerIds[] = $service['triggerid'];
1046			}
1047		}
1048		if (!API::Trigger()->isReadable($triggerIds)) {
1049			self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
1050		}
1051	}
1052
1053	/**
1054	 * Checks that all of the given services are readable.
1055	 *
1056	 * @throws APIException if at least one of the services doesn't exist
1057	 *
1058	 * @param array $serviceIds
1059	 *
1060	 * @return void
1061	 */
1062	protected function checkServicePermissions(array $serviceIds) {
1063		if (!$this->isReadable($serviceIds)) {
1064			self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
1065		}
1066	}
1067
1068	/**
1069	 * Checks that none of the given services have any children.
1070	 *
1071	 * @throws APIException if at least one of the services has a child service
1072	 *
1073	 * @param array $serviceIds
1074	 *
1075	 * @return void
1076	 */
1077	protected function checkThatServicesDontHaveChildren(array $serviceIds) {
1078		$child = API::getApiService()->select('services_links', [
1079			'output' => ['serviceupid'],
1080			'filter' => [
1081				'serviceupid' => $serviceIds,
1082				'soft' => 0
1083			],
1084			'limit' => 1
1085		]);
1086		$child = reset($child);
1087		if ($child) {
1088			$service = API::getApiService()->select($this->tableName(), [
1089				'output' => ['name'],
1090				'serviceids' => $child['serviceupid'],
1091				'limit' => 1
1092			]);
1093			$service = reset($service);
1094			self::exception(ZBX_API_ERROR_PERMISSIONS,
1095				_s('Service "%1$s" cannot be deleted, because it is dependent on another service.', $service['name'])
1096			);
1097		}
1098	}
1099
1100	/**
1101	 * Checks that the given dependency is valid.
1102	 *
1103	 * @throws APIException if the dependency is invalid
1104	 *
1105	 * @param array $dependency
1106	 *
1107	 * @return void
1108	 */
1109	protected function checkDependency(array $dependency) {
1110		if (idcmp($dependency['serviceid'], $dependency['dependsOnServiceid'])) {
1111			$service = API::getApiService()->select($this->tableName(), [
1112				'output' => ['name'],
1113				'serviceids' => $dependency['serviceid']
1114			]);
1115			$service = reset($service);
1116			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Service "%1$s" cannot be dependent on itself.', $service['name']));
1117		}
1118
1119		// check 'soft' field value
1120		if (!isset($dependency['soft']) || !in_array((int) $dependency['soft'], [0, 1], true)) {
1121			$service = API::getApiService()->select($this->tableName(), [
1122				'output' => ['name'],
1123				'serviceids' => $dependency['serviceid']
1124			]);
1125			$service = reset($service);
1126			self::exception(ZBX_API_ERROR_PARAMETERS,
1127				_s('Incorrect "soft" field value for dependency for service "%1$s".', $service['name'])
1128			);
1129		}
1130	}
1131
1132	/**
1133	 * Checks that that none of the given services are hard linked to a different service.
1134	 * Assumes the dependencies are valid.
1135	 *
1136	 * @throws APIException if at a least one service is hard linked to another service
1137	 *
1138	 * @param array $dependencies
1139	 *
1140	 * @return void
1141	 */
1142	protected function checkForHardlinkedDependencies(array $dependencies) {
1143		// only check hard dependencies
1144		$hardDepServiceIds = [];
1145		foreach ($dependencies as $dependency) {
1146			if (!$dependency['soft']) {
1147				$hardDepServiceIds[] = $dependency['dependsOnServiceid'];
1148			}
1149		}
1150
1151		if ($hardDepServiceIds) {
1152			// look for at least one hardlinked service among the given
1153			$hardDepServiceIds = array_unique($hardDepServiceIds);
1154			$dep = API::getApiService()->select('services_links', [
1155				'output' => ['servicedownid'],
1156				'filter' => [
1157					'soft' => 0,
1158					'servicedownid' => $hardDepServiceIds
1159				],
1160				'limit' => 1
1161			]);
1162			if ($dep) {
1163				$dep = reset($dep);
1164				$service = API::getApiService()->select($this->tableName(), [
1165					'output' => ['name'],
1166					'serviceids' => $dep['servicedownid']
1167				]);
1168				$service = reset($service);
1169				self::exception(ZBX_API_ERROR_PARAMETERS,
1170					_s('Service "%1$s" is already hardlinked to a different service.', $service['name'])
1171				);
1172			}
1173		}
1174	}
1175
1176	/**
1177	 * Checks that none of the parent services are linked to a trigger. Assumes the dependencies are valid.
1178	 *
1179	 * @throws APIException if at least one of the parent services is linked to a trigger
1180	 *
1181	 * @param array $dependencies
1182	 *
1183	 * @return void
1184	 */
1185	protected function checkThatParentsDontHaveTriggers(array $dependencies) {
1186		$parentServiceIds = array_unique(zbx_objectValues($dependencies, 'serviceid'));
1187		if ($parentServiceIds) {
1188			$query = DBselect(
1189				'SELECT s.triggerid,s.name'.
1190					' FROM services s '.
1191					' WHERE '.dbConditionInt('s.serviceid', $parentServiceIds).
1192					' AND s.triggerid IS NOT NULL', 1);
1193			if ($parentService = DBfetch($query)) {
1194				self::exception(ZBX_API_ERROR_PARAMETERS,
1195					_s('Service "%1$s" cannot be linked to a trigger and have children at the same time.', $parentService['name']));
1196			}
1197		}
1198	}
1199
1200	/**
1201	 * Checks that dependencies will not create cycles in service dependencies.
1202	 *
1203	 * @throws APIException if at least one cycle is possible
1204	 *
1205	 * @param array $depsToValid	dependency list to be validated
1206	 *
1207	 * @return void
1208	 */
1209	protected function checkForCircularityInDependencies($depsToValid) {
1210		$dbDeps = API::getApiService()->select('services_links', [
1211			'output' => ['serviceupid', 'servicedownid']
1212		]);
1213
1214		// create existing dependency acyclic graph
1215		$arr = [];
1216		foreach ($dbDeps as $dbDep) {
1217			if (!isset($arr[$dbDep['serviceupid']])) {
1218				$arr[$dbDep['serviceupid']] = [];
1219			}
1220			$arr[$dbDep['serviceupid']][$dbDep['servicedownid']] = $dbDep['servicedownid'];
1221		}
1222
1223		// check for circularity and add dependencies to the graph
1224		foreach ($depsToValid as $dep) {
1225			$this->DFCircularitySearch($dep['serviceid'], $dep['dependsOnServiceid'], $arr);
1226			$arr[$dep['serviceid']][$dep['dependsOnServiceid']] = $dep['dependsOnServiceid'];
1227		}
1228
1229	}
1230
1231	/**
1232	 * Depth First Search recursive function to find circularity and rise exception.
1233	 *
1234	 * @throws APIException if cycle is possible
1235	 *
1236	 * @param int $id	dependency from id
1237	 * @param int $depId	dependency to id
1238	 * @param ref $arr	reference to graph structure. Structure is associative array with keys as "from id"
1239	 *			and values as arrays with keys and values as "to id".
1240	 *
1241	 * @return void
1242	 */
1243	protected function dfCircularitySearch($id, $depId, &$arr) {
1244		if ($id == $depId) {
1245			// cycle found
1246			self::exception(ZBX_API_ERROR_PARAMETERS, _('Services form a circular dependency.'));
1247		}
1248		if (isset($arr[$depId])) {
1249			foreach ($arr[$depId] as $dep) {
1250				$this->DFCircularitySearch($id, $dep, $arr);
1251			}
1252		}
1253	}
1254
1255	/**
1256	 * Checks that the given service time is valid.
1257	 *
1258	 * @throws APIException if the service time is invalid
1259	 *
1260	 * @param array $serviceTime
1261	 *
1262	 * @return void
1263	 */
1264	protected function checkTime(array $serviceTime) {
1265		if (empty($serviceTime['serviceid'])) {
1266			self::exception(ZBX_API_ERROR_PARAMETERS, _('Invalid method parameters.'));
1267		}
1268
1269		checkServiceTime($serviceTime);
1270	}
1271
1272	protected function applyQueryFilterOptions($tableName, $tableAlias, array $options, array $sqlParts) {
1273		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
1274			// if services with specific trigger IDs were requested, return only the ones accessible to the current user.
1275			if ($options['filter']['triggerid']) {
1276				$accessibleTriggers = API::Trigger()->get([
1277					'output' => ['triggerid'],
1278					'triggerids' => $options['filter']['triggerid']
1279				]);
1280				$options['filter']['triggerid'] = zbx_objectValues($accessibleTriggers, 'triggerid');
1281			}
1282			// otherwise return services with either no triggers, or any trigger accessible to the current user
1283			else {
1284				$sqlParts = $this->addPermissionFilter($sqlParts);
1285			}
1286		}
1287
1288		$sqlParts = parent::applyQueryFilterOptions($tableName, $tableAlias, $options, $sqlParts);
1289
1290		// parentids
1291		if ($options['parentids'] !== null) {
1292			$sqlParts['from'][] = 'services_links slp';
1293			$sqlParts['where'][] = $this->fieldId('serviceid').'=slp.servicedownid AND slp.soft=0';
1294			$sqlParts['where'][] = dbConditionInt('slp.serviceupid', (array) $options['parentids']);
1295		}
1296		// childids
1297		if ($options['childids'] !== null) {
1298			$sqlParts['from'][] = 'services_links slc';
1299			$sqlParts['where'][] = $this->fieldId('serviceid').'=slc.serviceupid AND slc.soft=0';
1300			$sqlParts['where'][] = dbConditionInt('slc.servicedownid', (array) $options['childids']);
1301		}
1302
1303		return $sqlParts;
1304	}
1305
1306	protected function addRelatedObjects(array $options, array $result) {
1307		$result = parent::addRelatedObjects($options, $result);
1308
1309		$serviceIds = array_keys($result);
1310
1311		// selectDependencies
1312		if ($options['selectDependencies'] !== null && $options['selectDependencies'] != API_OUTPUT_COUNT) {
1313			$dependencies = $this->fetchChildDependencies($serviceIds,
1314				$this->outputExtend($options['selectDependencies'], ['serviceupid', 'linkid'])
1315			);
1316			$dependencies = zbx_toHash($dependencies, 'linkid');
1317			$relationMap = $this->createRelationMap($dependencies, 'serviceupid', 'linkid');
1318
1319			$dependencies = $this->unsetExtraFields($dependencies, ['serviceupid', 'linkid'], $options['selectDependencies']);
1320			$result = $relationMap->mapMany($result, $dependencies, 'dependencies');
1321		}
1322
1323		// selectParentDependencies
1324		if ($options['selectParentDependencies'] !== null && $options['selectParentDependencies'] != API_OUTPUT_COUNT) {
1325			$dependencies = $this->fetchParentDependencies($serviceIds,
1326				$this->outputExtend($options['selectParentDependencies'], ['servicedownid', 'linkid'])
1327			);
1328			$dependencies = zbx_toHash($dependencies, 'linkid');
1329			$relationMap = $this->createRelationMap($dependencies, 'servicedownid', 'linkid');
1330
1331			$dependencies = $this->unsetExtraFields($dependencies, ['servicedownid', 'linkid'],
1332				$options['selectParentDependencies']
1333			);
1334			$result = $relationMap->mapMany($result, $dependencies, 'parentDependencies');
1335		}
1336
1337		// selectParent
1338		if ($options['selectParent'] !== null && $options['selectParent'] != API_OUTPUT_COUNT) {
1339			$dependencies = $this->fetchParentDependencies($serviceIds, ['servicedownid', 'serviceupid'], false);
1340			$relationMap = $this->createRelationMap($dependencies, 'servicedownid', 'serviceupid');
1341			$parents = $this->get([
1342				'output' => $options['selectParent'],
1343				'serviceids' => $relationMap->getRelatedIds(),
1344				'preservekeys' => true
1345			]);
1346			$result = $relationMap->mapOne($result, $parents, 'parent');
1347		}
1348
1349		// selectTimes
1350		if ($options['selectTimes'] !== null && $options['selectTimes'] != API_OUTPUT_COUNT) {
1351			$serviceTimes = API::getApiService()->select('services_times', [
1352				'output' => $this->outputExtend($options['selectTimes'], ['serviceid', 'timeid']),
1353				'filter' => ['serviceid' => $serviceIds],
1354				'preservekeys' => true
1355			]);
1356			$relationMap = $this->createRelationMap($serviceTimes, 'serviceid', 'timeid');
1357
1358			$serviceTimes = $this->unsetExtraFields($serviceTimes, ['serviceid', 'timeid'], $options['selectTimes']);
1359			$result = $relationMap->mapMany($result, $serviceTimes, 'times');
1360		}
1361
1362		// selectAlarms
1363		if ($options['selectAlarms'] !== null && $options['selectAlarms'] != API_OUTPUT_COUNT) {
1364			$serviceAlarms = API::getApiService()->select('service_alarms', [
1365				'output' => $this->outputExtend($options['selectAlarms'], ['serviceid', 'servicealarmid']),
1366				'filter' => ['serviceid' => $serviceIds],
1367				'preservekeys' => true
1368			]);
1369			$relationMap = $this->createRelationMap($serviceAlarms, 'serviceid', 'servicealarmid');
1370
1371			$serviceAlarms = $this->unsetExtraFields($serviceAlarms, ['serviceid', 'servicealarmid'],
1372				$options['selectAlarms']
1373			);
1374			$result = $relationMap->mapMany($result, $serviceAlarms, 'alarms');
1375		}
1376
1377		// selectTrigger
1378		if ($options['selectTrigger'] !== null && $options['selectTrigger'] != API_OUTPUT_COUNT) {
1379			$relationMap = $this->createRelationMap($result, 'serviceid', 'triggerid');
1380			$triggers = API::getApiService()->select('triggers', [
1381				'output' => $options['selectTrigger'],
1382				'triggerids' => $relationMap->getRelatedIds(),
1383				'preservekeys' => true
1384			]);
1385			$result = $relationMap->mapOne($result, $triggers, 'trigger');
1386		}
1387
1388		return $result;
1389	}
1390
1391	protected function applyQueryOutputOptions($tableName, $tableAlias, array $options, array $sqlParts) {
1392		$sqlParts = parent::applyQueryOutputOptions($tableName, $tableAlias, $options, $sqlParts);
1393
1394		if ($options['countOutput'] === null) {
1395			if ($options['selectTrigger'] !== null) {
1396				$sqlParts = $this->addQuerySelect($this->fieldId('triggerid'), $sqlParts);
1397			}
1398		}
1399
1400		return $sqlParts;
1401	}
1402
1403	/**
1404	 * Add permission filter SQL query part
1405	 *
1406	 * @param array $sqlParts
1407	 *
1408	 * @return string
1409	 */
1410	protected function addPermissionFilter($sqlParts) {
1411		$userGroups = getUserGroupsByUserId(self::$userData['userid']);
1412
1413		$sqlParts['where'][] = '(EXISTS ('.
1414									'SELECT NULL'.
1415									' FROM functions f,items i,hosts_groups hgg'.
1416									' JOIN rights r'.
1417										' ON r.id=hgg.groupid'.
1418										' AND '.dbConditionInt('r.groupid', $userGroups).
1419									' WHERE s.triggerid=f.triggerid'.
1420										' AND f.itemid=i.itemid'.
1421										' AND i.hostid=hgg.hostid'.
1422									' GROUP BY f.triggerid'.
1423									' HAVING MIN(r.permission)>'.PERM_DENY.
1424									')'.
1425								' OR s.triggerid IS NULL)';
1426
1427		return $sqlParts;
1428	}
1429}
1430