1<?php
2
3/**
4 * phpIPAM class with common functions, used in all other classes
5 *
6 * @author: Miha Petkovsek <miha.petkovsek@gmail.com>
7 */
8class Common_functions  {
9
10	/**
11     * from api flag
12     *
13     * (default value: false)
14     *
15     * @var bool
16     */
17    public $api = false;
18
19	/**
20	 * settings
21	 *
22	 * (default value: null)
23	 *
24	 * @var mixed
25	 * @access public
26	 */
27	public $settings = null;
28
29	/**
30	 * If Jdon validation error occurs it will be saved here
31	 *
32	 * (default value: false)
33	 *
34	 * @var bool
35	 * @access public
36	 */
37	public $json_error = false;
38
39    /**
40     * Default font
41     *
42     * (default value: "<font face='Helvetica, Verdana, Arial, sans-serif' style='font-size:12px)
43     *
44     * @var string
45     * @access public
46     */
47    public $mail_font_style = "<font face='Helvetica, Verdana, Arial, sans-serif' style='font-size:12px;color:#333;'>";
48
49    /**
50     * Default font
51     *
52     * (default value: "<font face='Helvetica, Verdana, Arial, sans-serif' style='font-size:12px)
53     *
54     * @var string
55     * @access public
56     */
57    public $mail_font_style_light = "<font face='Helvetica, Verdana, Arial, sans-serif' style='font-size:11px;color:#777;'>";
58
59    /**
60     * Default font for links
61     *
62     * (default value: "<font face='Helvetica, Verdana, Arial, sans-serif' style='font-size:12px)
63     *
64     * @var string
65     * @access public
66     */
67    public $mail_font_style_href = "<font face='Helvetica, Verdana, Arial, sans-serif' style='font-size:12px;color:#a0ce4e;'>";
68
69	/**
70	 * Database
71	 *
72	 * @var mixed
73	 * @access protected
74	 */
75	protected $Database;
76
77	/**
78	 * Result
79	 *
80	 * @var mixed
81	 * @access public
82	 */
83	public $Result;
84
85	/**
86	 * Log
87	 *
88	 * @var mixed
89	 * @access public
90	 */
91	public $Log;
92
93	/**
94	 * Net_IPv4
95	 *
96	 * @var mixed
97	 * @access protected
98	 */
99	protected $Net_IPv4;
100
101	/**
102	 * Net_IPv6
103	 *
104	 * @var mixed
105	 * @access protected
106	 */
107	protected $Net_IPv6;
108
109	/**
110	 * NET_DNS object
111	 *
112	 * @var mixed
113	 * @access protected
114	 */
115	protected $DNS2;
116
117	/**
118	 * debugging flag
119	 *
120	 * @var mixed
121	 * @access protected
122	 */
123	protected $debugging;
124
125	/**
126	 * Cache mac vendor objects
127	 * @var array|null
128	 */
129	private $mac_address_vendors = null;
130
131
132
133	/**
134	 * __construct function
135	 *
136	 * @access public
137	 */
138	public function __construct () {
139		# debugging
140		$this->set_debugging( Config::get('debugging') );
141	}
142
143	/**
144	 *	@version handling
145	 *	--------------------------------
146	 */
147
148	 /**
149	 * Compare dotted version numbers 1.21.0 <=> 1.4.10
150	 *
151	 * @access public
152	 * @param string $verA
153	 * @param mixed $verB
154	 * @return int
155	 */
156	public function cmp_version_strings($verA, $verB) {
157		$a = explode('.', $verA);
158		$b = explode('.', $verB);
159
160		if ($a[0] != $b[0]) return $a[0] < $b[0] ? -1 : 1;			// 1.x.y is less than 2.x.y
161		if (strcmp($a[1], $b[1]) != 0) return strcmp($a[1], $b[1]);	// 1.21.y is less than 1.3.y
162		if ($a[2] != $b[2]) return $a[2] < $b[2] ? -1 : 1;			// 1.4.9 is less than 1.4.10
163		return 0;
164	}
165
166
167
168
169
170
171
172
173	/**
174	 *	@general fetch methods
175	 *	--------------------------------
176	 */
177
178
179	/**
180	 * Fetch all objects from specified table in database
181	 *
182	 * @access public
183	 * @param mixed $table
184	 * @param mixed $sortField (default:id)
185	 * @param mixed bool (default:true)
186	 * @return false|array
187	 */
188	public function fetch_all_objects ($table=null, $sortField="id", $sortAsc=true) {
189		# null table
190		if(is_null($table)||strlen($table)==0) return false;
191
192		$cached_item = $this->cache_check("fetch_all_objects", "t=$table f=$sortField o=$sortAsc");
193		if(is_object($cached_item)) return $cached_item->result;
194
195		# fetch
196		try { $res = $this->Database->getObjects($table, $sortField, $sortAsc); }
197		catch (Exception $e) {
198			$this->Result->show("danger", _("Error: ").$e->getMessage());
199			return false;
200		}
201		# save
202		if (is_array($res)) {
203			foreach ($res as $r) {
204				$this->cache_write ($table, $r);
205			}
206		}
207		# result
208		$result = (is_array($res) && sizeof($res)>0) ? $res : false;
209		$this->cache_write ("fetch_all_objects", (object) ["id"=>"t=$table f=$sortField o=$sortAsc", "result" => $result]);
210		return $result;
211	}
212
213	/**
214	 * Fetches specified object specified table in database
215	 *
216	 * @access public
217	 * @param mixed $table
218	 * @param mixed $method (default: null)
219	 * @param mixed $value
220	 * @return false|object
221	 */
222	public function fetch_object ($table=null, $method=null, $value) {
223		// checks
224		if(!is_string($table)) return false;
225		if(strlen($table)==0)  return false;
226		if(is_null($method))   return false;
227		if(is_null($value))    return false;
228		if($value===0)         return false;
229
230		# check cache
231		$cached_item = $this->cache_check($table, $value);
232		if(is_object($cached_item))
233			return $cached_item;
234
235		# null method
236		$method = is_null($method) ? "id" : $this->Database->escape($method);
237
238		try { $res = $this->Database->getObjectQuery("SELECT * from `$table` where `$method` = ? limit 1;", array($value)); }
239		catch (Exception $e) {
240			$this->Result->show("danger", _("Error: ").$e->getMessage());
241			return false;
242		}
243
244		# save to cache array
245		$this->cache_write ($table, $res);
246
247		return is_object($res) ? $res : false;
248	}
249
250	/**
251	 * Fetches multiple objects in specified table in database
252	 *
253	 *	doesnt cache
254	 *
255	 * @access public
256	 * @param mixed $table
257	 * @param mixed $field
258	 * @param mixed $value
259	 * @param string $sortField (default: 'id')
260	 * @param bool $sortAsc (default: true)
261	 * @param bool $like (default: false)
262	 * @param array|mixed $result_fields (default: *)
263	 * @return bool|array
264	 */
265	public function fetch_multiple_objects ($table, $field, $value, $sortField = 'id', $sortAsc = true, $like = false, $result_fields = "*") {
266		# null table
267		if(is_null($table)||strlen($table)==0) return false;
268		else {
269			try { $res = $this->Database->findObjects($table, $field, $value, $sortField, $sortAsc, $like, false, $result_fields); }
270			catch (Exception $e) {
271				$this->Result->show("danger", _("Error: ").$e->getMessage());
272				return false;
273			}
274			# save to cache
275			if ($result_fields==="*" && is_array($res)) { // Only cache objects containing all fields
276				foreach ($res as $r) {
277					$this->cache_write ($table, $r);
278				}
279			}
280			# result
281			return (is_array($res) && sizeof($res)>0) ? $res : false;
282		}
283	}
284
285	/**
286	 * Count objects in database.
287	 *
288	 * @access public
289	 * @param mixed $table
290	 * @param mixed $field
291	 * @param mixed $val (default: null)
292	 * @param bool $like (default: false)
293	 * @return int
294	 */
295	public function count_database_objects ($table, $field, $val=null, $like = false) {
296		# if null
297		try { $cnt = $this->Database->numObjectsFilter($table, $field, $val, $like); }
298		catch (Exception $e) {
299			$this->Result->show("danger", _("Error: ").$e->getMessage());
300			return false;
301		}
302		return $cnt;
303	}
304
305	/**
306	 * Count all objects in database.
307	 *
308	 * @param  string $table
309	 * @param  string $field
310	 * @return array|false
311	 */
312	public function count_all_database_objects ($table, $field) {
313		try { $cnt = $this->Database->getGroupBy($table, $field); }
314		catch (Exception $e) {
315			$this->Result->show("danger", _("Error: ").$e->getMessage());
316			return false;
317		}
318		return $cnt;
319	}
320
321	/**
322	 * Returns table schema information
323	 *
324	 * @param  string $tableName
325	 * @return array
326	 */
327	public function getTableSchemaByField($tableName) {
328		$results = [];
329
330		if (!is_string($tableName)) return $results;
331
332		$tableName = $this->Database->escape($tableName);
333
334		$cached_item = $this->cache_check("getTableSchemaByField", "t=$tableName");
335		if(is_object($cached_item)) return $cached_item->result;
336
337		try {
338			$query = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?;";
339			$schema = $this->Database->getObjectsQuery($query, [$this->Database->dbname, $tableName]);
340		} catch (\Exception $e) {
341			$this->Result->show("danger", _("Error: ").$e->getMessage());
342			return $results;
343		}
344
345		if (is_array($schema)) {
346			foreach ($schema as $col) {
347				$results[$col->COLUMN_NAME] = $col;
348			}
349		}
350		$this->cache_write("getTableSchemaByField", (object) ["id"=>"t=$tableName", "result" => $results]);
351		return $results;
352	}
353
354	/**
355	 * Get all admins that are set to receive changelog
356	 *
357	 * @access public
358	 * @param bool|mixed $subnetId
359	 * @return bool|array
360	 */
361	public function changelog_mail_get_recipients ($subnetId = false) {
362    	// fetch all users with mailNotify
363        $notification_users = $this->fetch_multiple_objects ("users", "mailChangelog", "Yes", "id", true);
364        // recipients array
365        $recipients = array();
366        // any ?
367        if (is_array($notification_users)) {
368        	if(sizeof($notification_users)>0) {
369	         	// if subnetId is set check who has permissions
370	        	if (isset($subnetId)) {
371	             	foreach ($notification_users as $u) {
372	                	// inti object
373	                	$Subnets = new Subnets ($this->Database);
374	                	//check permissions
375	                	$subnet_permission = $Subnets->check_permission($u, $subnetId);
376	                	// if 3 than add
377	                	if ($subnet_permission==3) {
378	                    	$recipients[] = $u;
379	                	}
380	            	}
381	        	}
382	        	else {
383	            	foreach ($notification_users as $u) {
384	                	if($u->role=="Administrator") {
385	                    	$recipients[] = $u;
386	                	}
387	            	}
388	        	}
389	        }
390        	return sizeof($recipients)>0 ? $recipients : false;
391        }
392        else {
393            return false;
394        }
395	}
396
397
398
399
400	/**
401	 * fetches settings from database
402	 *
403	 * @access private
404	 * @return mixed
405	 */
406	public function get_settings () {
407		if (is_object($this->settings))
408			return $this->settings;
409
410		// fetch_object results are cached in $Database->cache.
411		$settings = $this->fetch_object("settings", "id", 1);
412
413		if (!is_object($settings))
414			return false;
415
416		#save
417		$this->settings = $settings;
418
419		return $this->settings;
420	}
421
422
423    /**
424     * Write result to cache.
425     *
426     * @access protected
427     * @param string $table
428     * @param mixed $object
429     * @return void
430     */
431    protected function cache_write ($table, $object) {
432        if (!is_object($object))
433            return;
434
435        // Exclude exceptions from caching
436        if ($this->cache_check_exceptions($table))
437            return;
438
439        // get and check id property
440        $identifier = $this->cache_set_identifier ($table);
441
442        if (!property_exists($object, $identifier))
443            return;
444
445        $id = $object->{$identifier};
446
447        // already set
448        if (isset($this->Database->cache[$table][$identifier][$id]))
449            return;
450
451        // add ip ?
452        $ip_check = $this->cache_check_add_ip($table);
453        if ($ip_check!==false) {
454            $object->ip = $this->transform_address ($object->{$ip_check}, "dotted");
455        }
456
457        // save
458        $this->Database->cache[$table][$identifier][$id] = clone $object;
459    }
460
461    /**
462     * Check if caching is not needed
463     *
464     * @access protected
465     * @param mixed $table
466     * @return bool
467     */
468    protected function cache_check_exceptions ($table) {
469        $exceptions = [
470            "firewallZoneSubnet"=>1,
471            "circuitsLogicalMapping" =>1,
472            "php_sessions"=>1];
473
474        // check
475        return isset($exceptions[$table]) ? true : false;
476    }
477
478    /**
479     * Check if ip is to be added to result
480     *
481     * @access protected
482     * @param mixed $table
483     * @return bool|mixed
484     */
485    protected function cache_check_add_ip ($table) {
486        $ip_tables = ["subnets"=>"subnet", "ipaddresses"=>"ip_addr"];
487
488        // check
489        return array_key_exists ($table, $ip_tables) ? $ip_tables[$table] : false;
490    }
491
492    /**
493     * Set identifier for table - exceptions.
494     *
495     * @access protected
496     * @param string $table
497     * @return string
498     */
499    protected function cache_set_identifier ($table) {
500        // Tables with different primary keys
501        $mapings = [
502            'userGroups'=>'g_id',
503            'lang'=>'l_id',
504            'vlans'=>'vlanId',
505            'vrf'=>'vrfId',
506            'changelog'=>'cid',
507            'widgets'=>'wid',
508            'deviceTypes'=>'tid'];
509
510        return isset($mapings[$table]) ? $mapings[$table] : 'id';
511    }
512
513    /**
514     * Checks if object alreay exists in cache..
515     *
516     * @access protected
517     * @param mixed $table
518     * @param mixed $id
519     * @return bool|array
520     */
521    protected function cache_check ($table, $id) {
522        // get identifier
523        $identifier = $this->cache_set_identifier ($table);
524
525        // check if cache is already set, otherwise return false
526        if (isset($this->Database->cache[$table][$identifier][$id]))
527            return clone $this->Database->cache[$table][$identifier][$id];
528
529        return false;
530    }
531
532	/**
533	 * Sets debugging
534	 *
535	 * @access public
536	 * @param bool $debug (default: false)
537	 * @return void
538	 */
539	public function set_debugging ($debug = false) {
540		$this->debugging = $debug==true ? true : false;
541	}
542
543	/**
544	 * Gets debugging
545	 *
546	 * @access public
547	 * @return bool
548	 */
549	public function get_debugging () {
550		return $this->debugging;
551	}
552
553	/**
554	 * Initializes PEAR Net IPv4 object
555	 *
556	 * @access public
557	 * @return void
558	 */
559	public function initialize_pear_net_IPv4 () {
560		//initialize NET object
561		if(!is_object($this->Net_IPv4)) {
562			require_once( dirname(__FILE__) . '/../../functions/PEAR/Net/IPv4.php' );
563			//initialize object
564			$this->Net_IPv4 = new Net_IPv4();
565		}
566	}
567
568	/**
569	 * Initializes PEAR Net IPv6 object
570	 *
571	 * @access public
572	 * @return void
573	 */
574	public function initialize_pear_net_IPv6 () {
575		//initialize NET object
576		if(!is_object($this->Net_IPv6)) {
577			require_once( dirname(__FILE__) . '/../../functions/PEAR/Net/IPv6.php' );
578			//initialize object
579			$this->Net_IPv6 = new Net_IPv6();
580		}
581	}
582
583	/**
584	 * Initializes PEAR Net IPv6 object
585	 *
586	 * @access public
587	 * @return void
588	 */
589	public function initialize_pear_net_DNS2 () {
590		//initialize NET object
591		if(!is_object($this->DNS2)) {
592			require_once( dirname(__FILE__) . '/../../functions/PEAR/Net/DNS2.php' );
593			//initialize object
594			$this->DNS2 = new Net_DNS2_Resolver(array("timeout"=>2));
595		}
596	}
597
598	/**
599	 * Strip tags from array or field to protect from XSS
600	 *
601	 * @access public
602	 * @param array|string $input
603	 * @return array|string
604	 */
605	public function strip_input_tags ($input) {
606		if(is_array($input)) {
607			foreach($input as $k=>$v) {
608				if(is_array($v)) {
609					$input[$k] = $this->strip_input_tags($v);
610					continue;
611				}
612				$input[$k] = is_null($v) ? NULL : strip_tags($v);
613			}
614			# stripped array
615			return $input;
616		}
617
618		// not array
619		return is_null($input) ? NULL : strip_tags($input);
620	}
621
622	/**
623	 * Remove <script>, <iframe> and JS HTML event attributes from HTML to protect from XSS
624	 *
625	 * @param   string  $html
626	 * @return  string
627	 */
628	public function noxss_html($html) {
629		if (!is_string($html) || strlen($html)==0)
630			return "";
631
632		// Convert encoding to UTF-8
633		$html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
634
635		// Throw loadHTML() parsing errors
636		$err_mode = libxml_use_internal_errors(false);
637
638		try {
639			$dom = new \DOMDocument();
640
641			if ($dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOBLANKS | LIBXML_NOWARNING | LIBXML_NOERROR) === false)
642				return "";
643
644			$banned_elements = ['script', 'iframe', 'embed'];
645			$remove_elements = [];
646
647			$elements = $dom->getElementsByTagName('*');
648
649			if (is_object($elements) && $elements->length>0) {
650				foreach($elements as $e) {
651					if (in_array($e->nodeName, $banned_elements)) {
652						$remove_elements[] = $e;
653						continue;
654					}
655
656					if (!$e->hasAttributes())
657						continue;
658
659					// remove on* HTML event attributes
660					foreach ($e->attributes as $attr) {
661						if (substr($attr->nodeName,0,2) == "on")
662							$e->removeAttribute($attr->nodeName);
663					}
664				}
665
666				// Remove banned elements
667				foreach($remove_elements as $e)
668					$e->parentNode->removeChild($e);
669
670				// Return sanitised HTML
671				$html = $dom->saveHTML();
672			}
673		} catch (Exception $e) {
674			$html = "";
675		}
676
677		// restore error mode
678		libxml_use_internal_errors($err_mode);
679
680		return is_string($html) ? $html : "";
681	}
682
683	/**
684	 * Changes empty array fields to specified character
685	 *
686	 * @access public
687	 * @param array|object $fields
688	 * @param string $char (default: "/")
689	 * @return array
690	 */
691	public function reformat_empty_array_fields ($fields, $char = "/") {
692    	$out = array();
693    	// loop
694		foreach($fields as $k=>$v) {
695    		if(is_array($v)) {
696        		$out[$k] = $v;
697    		}
698    		else {
699    			if(is_null($v) || strlen($v)==0) {
700    				$out[$k] = 	$char;
701    			} else {
702    				$out[$k] = $v;
703    			}
704    		}
705		}
706		# result
707		return $out;
708	}
709
710	/**
711	 * Removes empty array fields
712	 *
713	 * @access public
714	 * @param array $fields
715	 * @return array
716	 */
717	public function remove_empty_array_fields ($fields) {
718    	// init
719    	$out = array();
720    	// loop
721    	if(is_array($fields)) {
722			foreach($fields as $k=>$v) {
723				if(is_null($v) || strlen($v)==0) {
724				}
725				else {
726					$out[$k] = $v;
727				}
728			}
729		}
730		# result
731		return $out;
732	}
733
734	/**
735	 * Trim whitespace form array objects
736	 *
737	 * @method trim_array_objects
738	 * @param  string|array $fields
739	 * @return string|array
740	 */
741	public function trim_array_objects ($fields) {
742		if(is_array($fields)) {
743	    	// init
744	    	$out = array();
745	    	// loop
746			foreach($fields as $k=>$v) {
747				$out[$k] = trim($v);
748			}
749		}
750		else {
751			$out = trim($fields);
752		}
753		# result
754		return $out;
755	}
756
757	/**
758	 * Strip XSS on value print
759	 *
760	 * @method strip_xss
761	 *
762	 * @param  string $input
763	 *
764	 * @return string
765	 */
766	public function strip_xss ($input) {
767		return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
768	}
769
770	/**
771	 * Detect the encoding used for a string and convert to UTF-8
772	 *
773	 * @method convert_encoding_to_UTF8
774	 * @param  string $string
775	 * @return string
776	 */
777	public function convert_encoding_to_UTF8($string) {
778		//convert encoding if necessary
779		return mb_convert_encoding($string, 'UTF-8', mb_detect_encoding($string, 'ASCII, UTF-8, ISO-8859-1, auto', true));
780	}
781
782	/**
783	 * Function to verify checkbox if 0 length
784	 *
785	 * @access public
786	 * @param mixed $field
787	 * @return int|mixed
788	 */
789	public function verify_checkbox ($field) {
790		return (!isset($field) || strlen($field)==0) ? 0 : escape_input($field);
791	}
792
793	/**
794	 * identify ip address type - ipv4 or ipv6
795	 *
796	 * @access public
797	 * @param mixed $address
798	 * @return mixed IP version
799	 */
800	public function identify_address ($address) {
801		# dotted representation
802		if (strpos($address, ':') !== false) return 'IPv6';
803		if (strpos($address, '.') !== false) return 'IPv4';
804		# numeric representation
805		if (is_numeric($address)) {
806			if($address <= 4294967295) return 'IPv4'; // 4294967295 = '255.255.255.255'
807			return 'IPv6';
808		} else {
809			# decimal representation
810			if(strlen($address) < 12) return 'IPv4';
811			return 'IPv6';
812		}
813	}
814
815	/**
816	 * Alias of identify_address_format function
817	 *
818	 * @access public
819	 * @param mixed $address
820	 * @return mixed
821	 */
822	public function get_ip_version ($address) {
823		return $this->identify_address ($address);
824	}
825
826	/**
827	 * Transforms array to log format
828	 *
829	 * @access public
830	 * @param mixed $logs
831	 * @param bool $changelog
832	 * @return mixed
833	 */
834	public function array_to_log ($logs, $changelog = false) {
835		$result = "";
836
837		if(!is_array($logs))
838			return $result;
839
840		foreach($logs as $key=>$req) {
841			# ignore __ and PHPSESSID
842			if( substr($key,0,2)=='__' || substr($key,0,9)=='PHPSESSID' || substr($key,0,4)=='pass' || $key=='plainpass' )
843				continue;
844
845			// NOTE The colon character ":" is reserved as it used in array_to_log for implode/explode.
846			// Replace colon (U+003A) with alternative characters.
847			// Using JSON encode/decode would be more appropiate but we need to maintain backwards compatibility with historical changelog/logs data in the database.
848			if ($req == "mac")
849				$req = strtr($req, ':', '-'); # Mac-addresses, replace Colon U+003A with hyphen U+002D
850
851			if (strpos($req, ':')!==false)
852				$req = strtr($req, ':', '.'); # Default, replace Colon U+003A with Full Stop U+002E.
853
854			$result .= ($changelog===true) ? "[$key]: $req<br>" : " ". $key . ": " . $req . "<br>";
855		}
856		return $result;
857	}
858
859	/**
860	 * Transforms seconds to hms
861	 *
862	 * @access public
863	 * @param mixed $sec
864	 * @param bool $padHours (default: false)
865	 * @return mixed
866	 */
867	public function sec2hms($sec, $padHours = false) {
868	    // holds formatted string
869	    $hms = "";
870
871	    // get the number of hours
872	    $hours = intval(intval($sec) / 3600);
873
874	    // add to $hms, with a leading 0 if asked for
875	    $hms .= ($padHours)
876	          ? str_pad($hours, 2, "0", STR_PAD_LEFT). ':'
877	          : $hours. ':';
878
879	    // get the seconds
880	    $minutes = intval(($sec / 60) % 60);
881
882	    // then add to $hms (with a leading 0 if needed)
883	    $hms .= str_pad($minutes, 2, "0", STR_PAD_LEFT). ':';
884
885	    // seconds
886	    $seconds = intval($sec % 60);
887
888	    // add to $hms, again with a leading 0 if needed
889	    $hms .= str_pad($seconds, 2, "0", STR_PAD_LEFT);
890
891	    // return hms
892	    return $hms;
893	}
894
895	/**
896	 * Shortens text to max chars
897	 *
898	 * @access public
899	 * @param mixed $text
900	 * @param int $chars (default: 25)
901	 * @return mixed
902	 */
903	public function shorten_text($text, $chars = 25) {
904		// minimum length = 8
905		if ($chars < 8) $chars = 8;
906		// count input text size
907		$origLen = mb_strlen($text);
908		// cut unwanted chars
909		if ($origLen > $chars) {
910			$text = mb_substr($text, 0, $chars-3) . '...';
911		}
912		return $text;
913	}
914
915	/**
916	 * Reformats MAC address to requested format
917	 *
918	 * @access public
919	 * @param mixed $mac
920	 * @param string $format (default: 1)
921	 *      1 : 00:66:23:33:55:66
922	 *      2 : 00-66-23-33-55-66
923	 *      3 : 0066.2333.5566
924	 *      4 : 006623335566
925	 * @return mixed
926	 */
927	public function reformat_mac_address ($mac, $format = 1) {
928    	// strip al tags first
929    	$mac = strtolower(str_replace(array(":",".","-"), "", $mac));
930    	// format 4
931    	if ($format==4) {
932        	return $mac;
933    	}
934    	// format 3
935    	if ($format==3) {
936        	$mac = str_split($mac, 4);
937        	$mac = implode(".", $mac);
938    	}
939    	// format 2
940    	elseif ($format==2) {
941        	$mac = str_split($mac, 2);
942        	$mac = implode("-", $mac);
943    	}
944    	// format 1
945    	else {
946        	$mac = str_split($mac, 2);
947        	$mac = implode(":", $mac);
948    	}
949    	// return
950    	return $mac;
951	}
952
953	/**
954	* Returns true if site is accessed with https
955	*
956	* @access public
957	* @return bool
958	*/
959	public function isHttps() {
960		if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
961			return true;
962		}
963		if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
964			return true;
965		}
966		if (!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') {
967			return true;
968		}
969		return false;
970	}
971
972	/**
973	 * Create URL for base
974	 *
975	 * @access public
976	 * @return mixed
977	 */
978	public function createURL () {
979		// SSL on standard port
980		if(($_SERVER['HTTPS'] == 'on') || ($_SERVER['SERVER_PORT'] == 443)) {
981			$url = "https://".$_SERVER['HTTP_HOST'];
982		}
983		// reverse proxy doing SSL offloading
984		elseif(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
985			if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
986				$url = "https://".$_SERVER['HTTP_X_FORWARDED_HOST'];
987			}
988			else {
989				$url = "https://".$_SERVER['HTTP_HOST'];
990			}
991		}
992		elseif(isset($_SERVER['HTTP_X_SECURE_REQUEST'])  && $_SERVER['HTTP_X_SECURE_REQUEST'] == 'true') {
993			$url = "https://".$_SERVER['SERVER_NAME'];
994		}
995		// custom port
996		elseif($_SERVER['SERVER_PORT']!="80" && (isset($_SERVER['HTTP_X_FORWARDED_PORT']) && $_SERVER['HTTP_X_FORWARDED_PORT']!="80")) {
997			$url = "http://".$_SERVER['SERVER_NAME'].":".$_SERVER['SERVER_PORT'];
998		}
999		// normal http
1000		else {
1001			$url = "http://".$_SERVER['HTTP_HOST'];
1002		}
1003
1004		//result
1005		return $url;
1006	}
1007
1008	/**
1009	 * Creates links from text fields if link is present
1010	 *
1011	 *	source: https://css-tricks.com/snippets/php/find-urls-in-text-make-links/
1012	 *
1013	 * @access public
1014	 * @param mixed $field_type
1015	 * @param mixed $text
1016	 * @return mixed
1017	 */
1018	public function create_links ($text, $field_type = "varchar") {
1019		// create links only for varchar fields
1020		if (strpos($field_type, "varchar")!==false) {
1021			// regular expression
1022			$reg_exUrl = "#((http|https|ftp|ftps|telnet|ssh)://\S+[^\s.,>)\];'\"!?])#";
1023
1024			// Check if there is a url in the text, make the urls hyper links
1025			$text = preg_replace($reg_exUrl, "<a href='$0' target='_blank'>$0</a>", $text);
1026		}
1027		// return text
1028		return $text;
1029	}
1030
1031	/**
1032	 * Sets valid actions
1033	 *
1034	 * @access private
1035	 * @return string[]
1036	 */
1037	private function get_valid_actions () {
1038		return array(
1039		        "add",
1040		        "all-add",
1041		        "edit",
1042		        "all-edit",
1043		        "delete",
1044		        "truncate",
1045		        "split",
1046		        "resize",
1047		        "move",
1048		        "remove",
1049		        "assign"
1050		      );
1051	}
1052
1053	/**
1054	 * Validate posted action on scripts
1055	 *
1056	 * @access public
1057	 * @param mixed $action
1058	 * @param bool $popup
1059	 * @return mixed|bool
1060	 */
1061	public function validate_action ($action, $popup = false) {
1062		# get valid actions
1063		$valid_actions = $this->get_valid_actions ();
1064		# check
1065		in_array($action, $valid_actions) ?: $this->Result->show("danger", _("Invalid action!"), true, $popup);
1066	}
1067
1068	/**
1069	 * Validates email address.
1070	 *
1071	 * @access public
1072	 * @param mixed $email
1073	 * @return bool
1074	 */
1075	public function validate_email($email) {
1076		return filter_var($email, FILTER_VALIDATE_EMAIL);
1077	}
1078
1079	/**
1080	 * Validate hostname
1081	 *
1082	 * @access public
1083	 * @param mixed $hostname
1084	 * @param bool $permit_root_domain
1085	 * @return bool|mixed
1086	 */
1087	public function validate_hostname($hostname, $permit_root_domain=true) {
1088    	// first validate hostname
1089    	$valid =  (preg_match("/^([a-z_\d](-*[a-z_\d])*)(\.([a-z_\d](-*[a-z_\d])*))*$/i", $hostname) 	//valid chars check
1090	            && preg_match("/^.{1,253}$/", $hostname) 										//overall length check
1091	            && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $hostname)   ); 				//length of each label
1092	    // if it fails return immediately
1093	    if (!$valid) {
1094    	    return $valid;
1095	    }
1096	    // than validate root_domain if requested
1097	    elseif ($permit_root_domain)    {
1098    	    return $valid;
1099	    }
1100	    else {
1101    	    if(strpos($hostname, ".")!==false)  { return $valid; }
1102    	    else                                { return false; }
1103	    }
1104	}
1105
1106	/**
1107	 * Validates IP address
1108	 *
1109	 * @access public
1110	 * @param mixed $ip
1111	 * @return bool
1112	 */
1113	public function validate_ip ($ip) {
1114    	if(filter_var($ip, FILTER_VALIDATE_IP)===false) { return false; }
1115    	else                                            { return true; }
1116	}
1117
1118	/**
1119	 * Validates MAC address
1120	 *
1121	 * @access public
1122	 * @param mixed $mac
1123	 * @return bool
1124	 */
1125	public function validate_mac ($mac) {
1126    	// first put it to common format (1)
1127    	$mac = $this->reformat_mac_address ($mac);
1128    	// we permit empty
1129        if (strlen($mac)==0)                                                            { return true; }
1130    	elseif (preg_match('/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/', $mac) != 1)   { return false; }
1131    	else                                                                            { return true; }
1132	}
1133
1134    /**
1135     * Validates json from provided string.
1136     *
1137     * @access public
1138     * @param mixed $string
1139     * @return mixed
1140     */
1141    public function validate_json_string($string) {
1142        // try to decode
1143        json_decode($string);
1144        // check for error
1145        $parse_result = json_last_error_msg();
1146        // save possible error
1147        if($parse_result!=="No error") {
1148            $this->json_error = $parse_result;
1149        }
1150        // return true / false
1151        return (json_last_error() == JSON_ERROR_NONE);
1152    }
1153
1154	/**
1155	 * Transforms ipv6 to nt
1156	 *
1157	 * @access public
1158	 * @param mixed $ipv6
1159	 * @return mixed
1160	 */
1161	public function ip2long6 ($ipv6) {
1162		if($ipv6 == ".255.255.255") {
1163			return false;
1164		}
1165	    $ip_n = inet_pton($ipv6);
1166	    $bits = 15; // 16 x 8 bit = 128bit
1167	    $ipv6long = "";
1168
1169	    while ($bits >= 0)
1170	    {
1171	        $bin = sprintf("%08b",(ord($ip_n[$bits])));
1172	        $ipv6long = $bin.$ipv6long;
1173	        $bits--;
1174	    }
1175	    return gmp_strval(gmp_init($ipv6long,2),10);
1176	}
1177
1178	/**
1179	 * Transforms int to ipv4
1180	 *
1181	 * @access public
1182	 * @param mixed $ipv4long
1183	 * @return mixed
1184	 */
1185	public function long2ip4($ipv4long) {
1186		if (PHP_INT_SIZE==4) {
1187			// As of php7.1 long2ip() no longer accepts strings.
1188			// Convert unsigned int IPv4 to signed integer.
1189			$ipv4long = (int) ($ipv4long + 0);
1190		}
1191		return long2ip($ipv4long);
1192	}
1193
1194	/**
1195	 * Transforms int to ipv6
1196	 *
1197	 * @access public
1198	 * @param mixed $ipv6long
1199	 * @return mixed
1200	 */
1201	public function long2ip6($ipv6long) {
1202		$hex = sprintf('%032s', gmp_strval(gmp_init($ipv6long, 10), 16));
1203		$ipv6 = implode(':', str_split($hex, 4));
1204		// compress result
1205		return inet_ntop(inet_pton($ipv6));
1206	}
1207
1208	/**
1209	 * Identifies IP address format
1210	 *
1211	 *	0 = decimal
1212	 *	1 = dotted
1213	 *
1214	 * @access public
1215	 * @param mixed $address
1216	 * @return mixed decimal or dotted
1217	 */
1218	public function identify_address_format ($address) {
1219		return is_numeric($address) ? "decimal" : "dotted";
1220	}
1221
1222	/**
1223	 * Transforms IP address to required format
1224	 *
1225	 *	format can be decimal (1678323323) or dotted (10.10.0.0)
1226	 *
1227	 * @access public
1228	 * @param mixed $address
1229	 * @param string $format (default: "dotted")
1230	 * @return mixed requested format
1231	 */
1232	public function transform_address ($address, $format = "dotted") {
1233		# no change
1234		if($this->identify_address_format ($address) == $format)		{ return $address; }
1235		else {
1236			if($this->identify_address_format ($address) == "dotted")	{ return $this->transform_to_decimal ($address); }
1237			else														{ return $this->transform_to_dotted ($address); }
1238		}
1239	}
1240
1241	/**
1242	 * Transform IP address from decimal to dotted (167903488 -> 10.2.1.0)
1243	 *
1244	 * @access public
1245	 * @param mixed $address
1246	 * @return mixed dotted format
1247	 */
1248	public function transform_to_dotted ($address) {
1249	    if ($this->identify_address ($address) == "IPv4" ) 				{ return($this->long2ip4($address)); }
1250	    else 								 			  				{ return($this->long2ip6($address)); }
1251	}
1252
1253	/**
1254	 * Transform IP address from dotted to decimal (10.2.1.0 -> 167903488)
1255	 *
1256	 * @access public
1257	 * @param mixed $address
1258	 * @return int IP address
1259	 */
1260	public function transform_to_decimal ($address) {
1261	    if ($this->identify_address ($address) == "IPv4" ) 				{ return( sprintf("%u", ip2long($address)) ); }
1262	    else 								 							{ return($this->ip2long6($address)); }
1263	}
1264
1265	/**
1266	 * Returns text representation of json errors
1267	 *
1268	 * @access public
1269	 * @param mixed $error_int
1270	 * @return mixed
1271	 */
1272	public function json_error_decode ($error_int) {
1273    	// init
1274    	$error = array();
1275		// error definitions
1276		$error[0] = "JSON_ERROR_NONE";
1277		$error[1] = "JSON_ERROR_DEPTH";
1278		$error[2] = "JSON_ERROR_STATE_MISMATCH";
1279		$error[3] = "JSON_ERROR_CTRL_CHAR";
1280		$error[4] = "JSON_ERROR_SYNTAX";
1281		$error[5] = "JSON_ERROR_UTF8";
1282		$error[6] = "JSON_ERROR_RECURSION";
1283		$error[7] = "JSON_ERROR_INF_OR_NAN";
1284		$error[8] = "JSON_ERROR_UNSUPPORTED_TYPE";
1285		$error[9] = "JSON_ERROR_INVALID_PROPERTY_NAME";
1286		$error[10] = "JSON_ERROR_UTF16";
1287		// return def
1288		if (isset($error[$error_int]))	{ return $error[$error_int]; }
1289		else							{ return "JSON_ERROR_UNKNOWN"; }
1290	}
1291
1292	/**
1293	 * Download URL via CURL
1294	 * @param  string $url
1295	 * @param  array|boolean $headers (default:false)
1296	 * @param  integer $timeout (default:30)
1297	 */
1298	public function curl_fetch_url($url, $headers=false, $timeout=30) {
1299		$result = ['result'=>false, 'result_code'=>503, 'error_msg'=>''];
1300
1301		try {
1302			$curl = curl_init();
1303			curl_setopt($curl, CURLOPT_URL, $url);
1304			curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
1305			curl_setopt($curl, CURLOPT_MAXREDIRS, 4);
1306			curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1307			curl_setopt($curl, CURLOPT_FAILONERROR, true);
1308			curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
1309			curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $timeout);
1310			if (is_array($headers))
1311			curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1312
1313			// configure proxy settings
1314			if (Config::get('proxy_enabled') == true) {
1315				curl_setopt($curl, CURLOPT_PROXY, Config::get('proxy_server'));
1316				curl_setopt($curl, CURLOPT_PROXYPORT, Config::get('proxy_port'));
1317				if (Config::get('proxy_use_auth') == true) {
1318				curl_setopt($curl, CURLOPT_PROXYUSERPWD, Config::get('proxy_user').':'.Config::get('proxy_pass'));
1319				}
1320			}
1321
1322			$result['result']      = curl_exec($curl);
1323			$result['result_code'] = curl_getinfo($curl, CURLINFO_HTTP_CODE);
1324			$result['error_msg']   = curl_error($curl);
1325
1326			// close
1327			curl_close ($curl);
1328
1329		} catch (Exception $e) {
1330		}
1331
1332		return $result;
1333	}
1334
1335	/**
1336	 * Fetches latlng from googlemaps by provided address
1337	 *
1338	 * @access public
1339	 * @param mixed $address
1340	 * @return array
1341	 */
1342	public function get_latlng_from_address ($address) {
1343		$results = array('lat' => null, 'lng' => null, 'error' => null);
1344
1345		// get geocode API key
1346		$gmaps_api_geocode_key = Config::get('gmaps_api_geocode_key');
1347
1348		if(empty($gmaps_api_geocode_key)) {
1349			$results['info'] = _("Geocode API key not set");
1350			return $results;
1351		}
1352
1353		# Geocode address
1354		$curl = $this->curl_fetch_url('https://maps.google.com/maps/api/geocode/json?address='.rawurlencode($address).'&sensor=false&key='.rawurlencode($gmaps_api_geocode_key), ["Accept: application/json"]);
1355
1356		if ($curl['result'] === false) {
1357			$results['error'] = _("Geocode lookup failed. Check Internet connectivity.");
1358			return $results;
1359		}
1360
1361		$output= json_decode($curl['result']);
1362
1363		if (isset($output->results[0]->geometry->location->lat))
1364			$results['lat'] = str_replace(",", ".", $output->results[0]->geometry->location->lat);
1365
1366		if (isset($output->results[0]->geometry->location->lng))
1367			$results['lng'] = str_replace(",", ".", $output->results[0]->geometry->location->lng);
1368
1369		if (isset($output->error_message))
1370			$results['error'] = $output->error_message;
1371
1372		return $results;
1373	}
1374
1375    /**
1376     * Updates location to latlng from address
1377     *
1378     * @access public
1379     * @param mixed $id
1380     * @param mixed $lat
1381     * @param mixed $lng
1382     * @return bool
1383     */
1384    public function update_latlng ($id, $lat, $lng) {
1385		# execute
1386		try { $this->Database->updateObject("locations", array("id"=>$id, "lat"=>$lat, "long"=>$lng), "id"); }
1387		catch (Exception $e) {
1388			return false;
1389		}
1390		return true;
1391    }
1392
1393    /**
1394     * Creates form input field for custom fields.
1395     *
1396     * @access public
1397     * @param mixed $field
1398     * @param mixed $object
1399     * @param string $action
1400     * @param mixed $timepicker_index
1401     * @param bool $disabled
1402     * @param string $set_delimiter
1403     * @return array
1404     */
1405    public function create_custom_field_input ($field, $object, $action, $timepicker_index, $disabled = false, $set_delimiter = "") {
1406        # make sure it is array
1407		$field  = (array) $field;
1408		$object = (object) $object;
1409
1410        // disabled
1411        $disabled_text = $disabled ? "readonly" : "";
1412        // replace spaces with |
1413        $field['nameNew'] = str_replace(" ", "___", $field['name']);
1414        // required
1415        $required = $field['Null']=="NO" ? "*" : "";
1416        // set default value if adding new object
1417        if ($action=="add")	{ $object->{$field['name']} = $field['Default']; }
1418
1419        //set, enum
1420        if(substr($field['type'], 0,3) == "set" || substr($field['type'], 0,4) == "enum") {
1421        	$html = $this->create_custom_field_input_set_enum ($field, $object, $disabled_text, $set_delimiter);
1422        }
1423        //date and time picker
1424        elseif($field['type'] == "date" || $field['type'] == "datetime") {
1425        	$res = $this->create_custom_field_input_date ($field, $object, $timepicker_index, $disabled_text);
1426			$timepicker_index = $res['timepicker_index'];
1427			$html             = $res ['html'];
1428        }
1429        //boolean
1430        elseif($field['type'] == "tinyint(1)") {
1431        	$html = $this->create_custom_field_input_boolean ($field, $object, $disabled_text);
1432        }
1433        //text
1434        elseif($field['type'] == "text") {
1435        	$html = $this->create_custom_field_input_textarea ($field, $object, $disabled_text);
1436        }
1437		//default - input field
1438		else {
1439            $html = $this->create_custom_field_input_input ($field, $object, $disabled_text);
1440		}
1441
1442        # result
1443        return array(
1444			"required"         => $required,
1445			"field"            => implode("\n", $html),
1446			"timepicker_index" => $timepicker_index
1447        );
1448	}
1449
1450    /**
1451     * Creates form input field for set and enum values
1452     *
1453     * @access public
1454     * @param mixed $field
1455     * @param mixed $object
1456     * @param string $disabled_text
1457     * @param string $set_delimiter
1458     * @return array
1459     */
1460    public function create_custom_field_input_set_enum ($field, $object, $disabled_text, $set_delimiter = "") {
1461		$html = array();
1462    	//parse values
1463    	$field['type'] = trim(substr($field['type'],0,-1));
1464    	$tmp = substr($field['type'], 0,3)=="set" ? explode(",", str_replace(array("set(", "'"), "", $field['type'])) : explode(",", str_replace(array("enum(", "'"), "", $field['type']));
1465    	//null
1466    	if($field['Null']!="NO") { array_unshift($tmp, ""); }
1467
1468    	$html[] = "<select name='$field[nameNew]' class='form-control input-sm input-w-auto' rel='tooltip' data-placement='right' title='$field[Comment]' $disabled_text>";
1469    	foreach($tmp as $v) {
1470    		// set selected
1471			$selected = $v==$object->{$field['name']} ? "selected='selected'" : "";
1472			// parse delimiter
1473			if(strlen($set_delimiter)==0) {
1474				// save
1475		        $html[] = "<option value='$v' $selected>$v</option>";
1476			}
1477			else {
1478				// explode by delimiter
1479				$tmp2 = explode ($set_delimiter, $v);
1480	    		// reset selected
1481				$selected = $tmp2[0]==$object->{$field['name']} ? "selected='selected'" : "";
1482				// save
1483		        $html[] = "<option value='$tmp2[0]' $selected>$tmp2[1]</option>";
1484			}
1485
1486    	}
1487    	$html[] = "</select>";
1488
1489    	// result
1490    	return $html;
1491	}
1492
1493    /**
1494     * Creates form input field for date fields.
1495     *
1496     * @access public
1497     * @param mixed $field
1498     * @param mixed $object
1499     * @param mixed $timepicker_index
1500     * @param string $disabled_text
1501     * @return array
1502     */
1503    public function create_custom_field_input_date ($field, $object, $timepicker_index, $disabled_text) {
1504   		$html = array ();
1505    	// just for first
1506    	if($timepicker_index==0) {
1507    		$html[] =  '<link rel="stylesheet" type="text/css" href="css/bootstrap/bootstrap-datetimepicker.min.css?v='.SCRIPT_PREFIX.'">';
1508    		$html[] =  '<script src="js/bootstrap-datetimepicker.min.js?v='.SCRIPT_PREFIX.'"></script>';
1509    		$html[] =  '<script>';
1510    		$html[] =  '$(document).ready(function() {';
1511    		//date only
1512    		$html[] =  '	$(".datepicker").datetimepicker( {pickDate: true, pickTime: false, pickSeconds: false });';
1513    		//date + time
1514    		$html[] =  '	$(".datetimepicker").datetimepicker( { pickDate: true, pickTime: true } );';
1515    		$html[] =  '})';
1516    		$html[] =  '</script>';
1517    	}
1518    	$timepicker_index++;
1519
1520    	//set size
1521    	if($field['type'] == "date")	{ $size = 10; $class='datepicker';		$format = "yyyy-MM-dd"; }
1522    	else							{ $size = 19; $class='datetimepicker';	$format = "yyyy-MM-dd"; }
1523
1524    	//field
1525    	if(!isset($object->{$field['name']}))	{ $html[] = ' <input type="text" class="'.$class.' form-control input-sm input-w-auto" data-format="'.$format.'" name="'. $field['nameNew'] .'" maxlength="'.$size.'" rel="tooltip" data-placement="right" title="'.$field['Comment'].'" '.$disabled_text.'>'. "\n"; }
1526    	else								    { $html[] = ' <input type="text" class="'.$class.' form-control input-sm input-w-auto" data-format="'.$format.'" name="'. $field['nameNew'] .'" maxlength="'.$size.'" value="'. $this->strip_xss($object->{$field['name']}). '" rel="tooltip" data-placement="right" title="'.$field['Comment'].'" '.$disabled_text.'>'. "\n"; }
1527
1528    	// result
1529    	return array (
1530					"html"             => $html,
1531					"timepicker_index" => $timepicker_index
1532    	              );
1533	}
1534
1535    /**
1536     * Creates form input field for boolean fields.
1537     *
1538     * @access public
1539     * @param mixed $field
1540     * @param mixed $object
1541     * @param string $disabled_text
1542     * @return array
1543     */
1544    public function create_custom_field_input_boolean ($field, $object, $disabled_text) {
1545    	$html = array ();
1546    	$html[] =  "<select name='$field[nameNew]' class='form-control input-sm input-w-auto' rel='tooltip' data-placement='right' title='$field[Comment]' $disabled_text>";
1547    	$tmp = array(0=>"No",1=>"Yes");
1548    	//null
1549    	if($field['Null']!="NO") { $tmp[2] = ""; }
1550
1551    	foreach($tmp as $k=>$v) {
1552    		if(strlen($object->{$field['name']})==0 && $k==2)	{ $html[] = "<option value='$k' selected='selected'>"._($v)."</option>"; }
1553    		elseif($k==$object->{$field['name']})				{ $html[] = "<option value='$k' selected='selected'>"._($v)."</option>"; }
1554    		else											    { $html[] = "<option value='$k'>"._($v)."</option>"; }
1555    	}
1556    	$html[] = "</select>";
1557    	// result
1558    	return $html;
1559	}
1560
1561    /**
1562     * Creates form input field for text fields.
1563     *
1564     * @access public
1565     * @param mixed $field
1566     * @param mixed $object
1567     * @param string $disabled_text
1568     * @return array
1569     */
1570    public function create_custom_field_input_textarea ($field, $object, $disabled_text) {
1571    	$html = array ();
1572    	$html[] = ' <textarea class="form-control input-sm" name="'. $field['nameNew'] .'" placeholder="'. $this->print_custom_field_name ($field['name']) .'" rowspan=3 rel="tooltip" data-placement="right" title="'.$field['Comment'].'" '.$disabled_text.'>'. $object->{$field['name']}. '</textarea>'. "\n";
1573    	// result
1574    	return $html;
1575	}
1576
1577    /**
1578     * Creates form input field for date fields.
1579     *
1580     * @access public
1581     * @param mixed $field
1582     * @param mixed $object
1583     * @param string $disabled_text
1584     * @return array
1585     */
1586    public function create_custom_field_input_input ($field, $object, $disabled_text) {
1587        $html = array ();
1588        // max length
1589        $maxlength = 100;
1590        if(strpos($field['type'],"varchar")!==false) {
1591            $maxlength = str_replace(array("varchar","(",")"),"", $field['type']);
1592        }
1593        if(strpos($field['type'],"int")!==false) {
1594            $maxlength = str_replace(array("int","(",")"),"", $field['type']);
1595        }
1596        // print
1597		$html[] = ' <input type="text" class="ip_addr form-control input-sm" name="'. $field['nameNew'] .'" placeholder="'. $this->print_custom_field_name ($field['name']) .'" value="'. $this->strip_xss($object->{$field['name']}). '" size="30" rel="tooltip" data-placement="right" maxlength="'.$maxlength.'" title="'.$field['Comment'].'" '.$disabled_text.'>'. "\n";
1598    	// result
1599    	return $html;
1600	}
1601
1602	/**
1603	 * Prints custom field
1604	 *
1605	 * @method print_custom_field
1606	 *
1607	 * @param  string $type
1608	 * @param  string $value
1609	 *
1610	 * @return void
1611	 */
1612	public function print_custom_field ($type, $value) {
1613		// escape
1614		$value = str_replace("'", "&#39;", $value);
1615		// create links
1616		$value = $this->create_links ($value, $type);
1617
1618		//booleans
1619		if($type=="tinyint(1)")	{
1620			if($value == "1")			{ print _("Yes"); }
1621			elseif(strlen($value)==0) 	{ print "/"; }
1622			else						{ print _("No"); }
1623		}
1624		//text
1625		elseif($type=="text") {
1626			if(strlen($value)>0)	{ print "<i class='fa fa-gray fa-comment' rel='tooltip' data-container='body' data-html='true' title='".str_replace("\n", "<br>", $value)."'>"; }
1627			else					{ print ""; }
1628		}
1629		else {
1630			print $value;
1631		}
1632	}
1633
1634	/**
1635	 * Print custom field name, strip out custom_ prefix
1636	 *
1637	 * @method print_custom_field_name
1638	 *
1639	 * @param  string $name
1640	 *
1641	 * @return string
1642	 */
1643	public function print_custom_field_name ($name, $return = true) {
1644		return strpos($name, "custom_")===0 ? substr($name, 7) : $name;
1645	}
1646
1647	/**
1648	 * Creates image link to rack.
1649	 *
1650	 * @method create_rack_link
1651	 *
1652	 * @param  bool|int $rackId
1653	 * @param  bool|int $deviceId
1654	 * @param  bool $is_back
1655	 *
1656	 * @return [type]
1657	 */
1658	public function create_rack_link ($rackId = false, $deviceId = false, $is_back = false) {
1659    	if($rackId===false) {
1660        	    return false;
1661    	}
1662    	else {
1663        	//device ?
1664        	if ($deviceId!==false) {
1665            	return $this->createURL ().BASE."app/tools/racks/draw_rack.php?rackId=$rackId&deviceId=$deviceId&is_back=$is_back";
1666        	}
1667        	else {
1668            	return $this->createURL ().BASE."app/tools/racks/draw_rack.php?rackId=$rackId&is_back=$is_back";
1669        	}
1670    	}
1671	}
1672
1673	/**
1674	 * Get MAC address vendor details
1675	 *
1676	 * https://www.macvendorlookup.com/vendormacs-xml-download
1677	 *
1678	 * @method get_mac_address_vendor
1679	 * @param  mixed $mac
1680	 * @return string
1681	 */
1682	public function get_mac_address_vendor_details ($mac) {
1683		// set default arrays
1684		$matches = array();
1685		// validate mac
1686		if(strlen($mac)<4)				{ return ""; }
1687		if(!$this->validate_mac ($mac))	{ return ""; }
1688		// reformat mac address
1689		$mac = strtoupper($this->reformat_mac_address ($mac, 1));
1690		$mac_partial = explode(":", $mac);
1691		// get mac XML database
1692
1693		if (is_null($this->mac_address_vendors)) {
1694			//populate mac vendors array
1695			$this->mac_address_vendors = array();
1696
1697			$data = file_get_contents(dirname(__FILE__)."/../vendormacs.xml");
1698
1699			if (preg_match_all('/\<VendorMapping\smac_prefix="([0-9a-fA-F]{2})[:-]([0-9a-fA-F]{2})[:-]([0-9a-fA-F]{2})"\svendor_name="(.*)"\/\>/', $data, $matches, PREG_SET_ORDER)) {
1700				if (is_array($matches)) {
1701					foreach ($matches as $match) {
1702						$mac_vendor = strtoupper($match[1] . ':' . $match[2] . ':' . $match[3]);
1703						$this->mac_address_vendors[$mac_vendor] = $match[4];
1704					}
1705				}
1706			}
1707		}
1708
1709		$mac_vendor = strtoupper($mac_partial[0] . ':' . $mac_partial[1] . ':' . $mac_partial[2]);
1710
1711		if (isset($this->mac_address_vendors[$mac_vendor])) {
1712			return $this->mac_address_vendors[$mac_vendor];
1713		} else {
1714			return "";
1715		}
1716	}
1717
1718	/**
1719	 * Read user supplied permissions ($_POST) and calculate deltas from old_permissions
1720	 *
1721	 * @access public
1722	 * @param  array $post_permissions
1723	 * @param  array $old_permissions
1724	 * @return array
1725	 */
1726	public function get_permission_changes ($post_permissions, $old_permissions) {
1727		$new_permissions = array();
1728		$removed_permissions = array();
1729		$changed_permissions = array();
1730
1731		# set new posted permissions
1732		foreach($post_permissions as $key=>$val) {
1733			if(substr($key, 0,5) == "group") {
1734				if($val != "0") $new_permissions[substr($key,5)] = $val;
1735			}
1736		}
1737
1738		// calculate diff
1739		if(is_array($old_permissions)) {
1740			foreach ($old_permissions as $k1=>$p1) {
1741				// if there is not permisison in new that remove old
1742				// if change than save
1743				if (!array_key_exists($k1, $new_permissions)) {
1744					$removed_permissions[$k1] = 0;
1745				} elseif ($old_permissions[$k1]!==$new_permissions[$k1]) {
1746					$changed_permissions[$k1] = $new_permissions[$k1];
1747				}
1748			}
1749		} else {
1750			$old_permissions = array();  // fix for adding
1751		}
1752		// add also new groups if available
1753		if(is_array($new_permissions)) {
1754			foreach ($new_permissions as $k1=>$p1) {
1755				if(!array_key_exists($k1, $old_permissions)) {
1756					$changed_permissions[$k1] = $new_permissions[$k1];
1757				}
1758			}
1759		}
1760
1761		return array($removed_permissions, $changed_permissions, $new_permissions);
1762	}
1763
1764	/**
1765	 * Parse subnet permissions to user readable format
1766	 *
1767	 * @access public
1768	 * @param mixed $permissions
1769	 * @return string
1770	 */
1771	public function parse_permissions ($permissions) {
1772		switch($permissions) {
1773			case 0: 	$r = _("No access");	break;
1774			case 1: 	$r = _("Read");			break;
1775			case 2: 	$r = _("Write");		break;
1776			case 3: 	$r = _("Admin");		break;
1777			default:	$r = _("error");
1778		}
1779		return $r;
1780	}
1781
1782
1783
1784
1785
1786
1787	/**
1788	 *	@breadcrumbs functions
1789	 * ------------------------
1790	 */
1791
1792	/**
1793	 * print_breadcrumbs function.
1794	 *
1795	 * @access public
1796	 * @param mixed $Section
1797	 * @param mixed $Subnet
1798	 * @param mixed $req
1799	 * @param mixed $Address (default: null)
1800	 * @return void
1801	 */
1802	public function print_breadcrumbs ($Section, $Subnet, $req, $Address=null) {
1803		# subnets
1804		if($req['page'] == "subnets")		{ $this->print_subnet_breadcrumbs ($Subnet, $req, $Address); }
1805		# folders
1806		elseif($req['page'] == "folder")	{ $this->print_folder_breadcrumbs ($Section, $Subnet, $req); }
1807		# tools
1808		elseif ($req['page'] == "tools") 	{ $this->print_tools_breadcrumbs ($req); }
1809	}
1810
1811	/**
1812	 * Print address breadcrumbs
1813	 *
1814	 * @access private
1815	 * @param mixed $Subnet
1816	 * @param mixed $req
1817	 * @param mixed $Address
1818	 * @return void
1819	 */
1820	private function print_subnet_breadcrumbs ($Subnet, $req, $Address) {
1821		if(isset($req['subnetId'])) {
1822			# get all parents
1823			$parents = $Subnet->fetch_parents_recursive ($req['subnetId']);
1824
1825			print "<ul class='breadcrumb'>";
1826			# remove root - 0
1827			//array_shift($parents);
1828
1829			# section details
1830			$section = (array) $this->fetch_object ("sections", "id", $req['section']);
1831
1832			# section name
1833			print "	<li><a href='".create_link("subnets",$section['id'])."'>$section[name]</a> <span class='divider'></span></li>";
1834
1835			# all parents
1836			foreach($parents as $parent) {
1837				$subnet = (array) $Subnet->fetch_subnet("id",$parent);
1838				if($subnet['isFolder']==1) {
1839					print "	<li><a href='".create_link("folder",$section['id'],$parent)."'><i class='icon-folder-open icon-gray'></i> $subnet[description]</a> <span class='divider'></span></li>";
1840				} else {
1841					print "	<li><a href='".create_link("subnets",$section['id'],$parent)."'>$subnet[description] ($subnet[ip]/$subnet[mask])</a> <span class='divider'></span></li>";
1842				}
1843			}
1844			# parent subnet
1845			$subnet = (array) $Subnet->fetch_subnet("id",$req['subnetId']);
1846			# ip set
1847			if(isset($req['ipaddrid'])) {
1848				$ip = (array) $Address->fetch_address ("id", $req['ipaddrid']);
1849				print "	<li><a href='".create_link("subnets",$section['id'],$subnet['id'])."'>$subnet[description] ($subnet[ip]/$subnet[mask])</a> <span class='divider'></span></li>";
1850				print "	<li class='active'>$ip[ip]</li>";			//IP address
1851			}
1852			else {
1853				print "	<li class='active'>$subnet[description] ($subnet[ip]/$subnet[mask])</li>";		//active subnet
1854
1855			}
1856			print "</ul>";
1857		}
1858	}
1859
1860	/**
1861	 * Print folder breadcrumbs
1862	 *
1863	 * @access private
1864	 * @param obj $Section
1865	 * @param obj $Subnet
1866	 * @param mixed $req
1867	 * @return void
1868	 */
1869	private function print_folder_breadcrumbs ($Section, $Subnet, $req) {
1870		if(isset($req['subnetId'])) {
1871			# get all parents
1872			$parents = $Subnet->fetch_parents_recursive ($req['subnetId']);
1873			print "<ul class='breadcrumb'>";
1874			# remove root - 0
1875			array_shift($parents);
1876
1877			# section details
1878			$section = (array) $Section->fetch_section(null, $req['section']);
1879
1880			# section name
1881			print "	<li><a href='".create_link("subnets",$section['id'])."'>$section[name]</a> <span class='divider'></span></li>";
1882
1883			# all parents
1884			foreach($parents as $parent) {
1885				$parent = (array) $parent;
1886				$subnet = (array) $Subnet->fetch_subnet(null,$parent[0]);
1887				if ($subnet['isFolder']=="1")
1888				print "	<li><a href='".create_link("folder",$section['id'],$parent[0])."'><i class='icon-folder-open icon-gray'></i> $subnet[description]</a> <span class='divider'></span></li>";
1889				else
1890				print "	<li><a href='".create_link("subnets",$section['id'],$parent[0])."'><i class='icon-folder-open icon-gray'></i> $subnet[description]</a> <span class='divider'></span></li>";
1891			}
1892			# parent subnet
1893			$subnet = (array) $Subnet->fetch_subnet(null,$req['subnetId']);
1894			print "	<li>$subnet[description]</li>";																		# active subnet
1895			print "</ul>";
1896		}
1897	}
1898
1899	/**
1900	 * Prints tools breadcrumbs
1901	 *
1902	 * @access public
1903	 * @param mixed $req
1904	 * @return void
1905	 */
1906	private function print_tools_breadcrumbs ($req) {
1907		print "<ul class='breadcrumb'>";
1908		print "	<li><a href='".create_link("tools")."'>"._('Tools')."</a> <span class='divider'></span></li>";
1909		if(!isset($req['subnetId'])) {
1910		    print "	<li class='active'>$req[section]</li>";
1911		}
1912		else {
1913		    print "	<li class='active'><a href='".create_link("tools", $req['section'])."'>$req[section]</a> <span class='divider'></span></li>";
1914
1915		    # pstn
1916		    if ($_GET['section']=="pstn-prefixes") {
1917    			# get all parents
1918    			$Tools = new Tools ($this->Database);
1919    			$parents = $Tools->fetch_prefix_parents_recursive ($req['subnetId']);
1920    			# all parents
1921    			foreach($parents as $parent) {
1922    				$prefix = $this->fetch_object("pstnPrefixes", "id", $parent[0]);
1923    				print "	<li><a href='".create_link("tools",$req['section'],$parent[0])."'><i class='icon-folder-open icon-gray'></i> $prefix->name</a> <span class='divider'></span></li>";
1924    			}
1925
1926		    }
1927		    $prefix = $this->fetch_object("pstnPrefixes", "id", $req['subnetId']);
1928		    print "	<li class='active'>$prefix->name</li>";
1929		}
1930		print "</ul>";
1931	}
1932
1933	/**
1934	 * Prints site title
1935	 *
1936	 * @access public
1937	 * @param mixed $get
1938	 * @return void
1939	 */
1940	public function get_site_title ($get) {
1941    	// remove html tags
1942    	$get = $this->strip_input_tags ($get);
1943    	// init
1944    	$title = array ();
1945    	$title[] = $this->settings->siteTitle;
1946
1947    	// page
1948    	if (isset($get['page'])) {
1949        	// dashboard
1950        	if ($get['page']=="dashboard") {
1951            	return $this->settings->siteTitle." Dashboard";
1952        	}
1953        	// install, upgrade
1954        	elseif ($get['page']=="temp_share" || $get['page']=="request_ip" || $get['page']=="opensearch") {
1955            	$title[] = ucwords(escape_input($get['page']));
1956        	}
1957        	// sections, subnets
1958        	elseif ($get['page']=="subnets" || $get['page']=="folder") {
1959            	// subnets
1960            	$title[] = _("Subnets");
1961
1962            	// section
1963            	if (isset($get['section'])) {
1964                 	$se = $this->fetch_object ("sections", "id", escape_input($get['section']));
1965                	if($se!==false) {
1966                    	$title[] = $se->name;
1967                	}
1968            	}
1969            	// subnet
1970            	if (isset($get['subnetId'])) {
1971                 	$sn = $this->fetch_object ("subnets", "id", escape_input($get['subnetId']));
1972                	if($sn!==false) {
1973                    	if($sn->isFolder) {
1974                        	$title[] = $sn->description;
1975                    	}
1976                    	else {
1977                        	$sn->description = strlen($sn->description)>0 ? " (".$sn->description.")" : "";
1978                        	$title[] = $this->transform_address($sn->subnet, "dotted")."/".$sn->mask.$sn->description;
1979                        }
1980                	}
1981            	}
1982            	// ip address
1983            	if (isset($get['ipaddrid'])) {
1984                    $ip = $this->fetch_object ("ipaddresses", "id", escape_input($get['ipaddrid']));
1985                    if($ip!==false) {
1986                        $title[] = $this->transform_address($ip->ip_addr, "dotted");
1987                    }
1988            	}
1989        	}
1990        	// tools, admin
1991        	elseif ($get['page']=="tools" || $get['page']=="administration") {
1992            	$title[] = ucwords(escape_input($get['page']));
1993            	// subpage
1994            	if (isset($get['section'])) {
1995                	$title[] = ucwords(escape_input($get['section']));
1996            	}
1997            	if (isset($get['subnetId'])) {
1998                	// vland domain
1999                	if($get['section']=="vlan") {
2000                     	$se = $this->fetch_object ("vlanDomains", "id", escape_input($get['subnetId']));
2001                    	if($se!==false) {
2002                        	$title[] = $se->name." domain";
2003                    	}
2004                	}
2005                	else {
2006                    	$title[] = ucwords(escape_input($get['subnetId']));
2007                    }
2008            	}
2009        	}
2010        	else {
2011            	$title[] = ucwords(escape_input($get['page']));
2012            }
2013    	}
2014        // return title
2015    	return implode(" / ", $title);
2016	}
2017
2018
2019
2020
2021	/**
2022	 * Print action wrapper
2023	 *
2024	 * Provided items can have following items:
2025	 *     type: link, divider, header
2026	 *     text: text to print
2027	 *     href: ''
2028	 *     class: classes to be added to item
2029	 *     dataparams: params to be added (e.g. data-deviceid='0')
2030	 *     icon: name for icon
2031	 *     visible: where it should be visible
2032	 *
2033	 *
2034	 * @method print_actions
2035	 * @param  string $type
2036	 * @param  array $items [array of items]
2037	 * @param  bool $left_align
2038	 * @param  bool $print_text
2039	 * @return [type]
2040	 */
2041	public function print_actions ($compress = "1", $items = [], $left_align = false, $print_text = false) {
2042	    if (sizeof($items)>0) {
2043	        return $compress=="1" ? $this->print_actions_dropdown($items, $left_align, $print_text) : $this->print_actions_buttons ($items);
2044	    }
2045	    else {
2046	        return "";
2047	    }
2048	}
2049
2050	/**
2051	 * Prints action dropdown
2052	 *
2053	 * @method print_actions_buttons
2054	 * @param  array $items [array of items]
2055	 * @param  bool $left_align
2056	 * @param  bool $print_text
2057	 * @return string
2058	 */
2059	private function print_actions_dropdown ($items = [], $left_align = false, $print_text = false) {
2060	    // init
2061	    $html   = [];
2062	    // alignment
2063	    $alignment = $left_align ? "dropdown-menu-left" : "dropdown-menu-right";
2064	    // text
2065	    $action_text = $print_text ? " <i class='fa fa-cogs'></i> Actions " : " <i class='fa fa-cogs'></i> ";
2066
2067	    $html[] = "<div class='dropdown'>";
2068	    $html[] = "  <button class='btn btn-xs btn-default dropdown-toggle ' type='button' id='dropdownMenu' data-toggle='dropdown' aria-haspopup='true' aria-expanded='true' rel='tooltip' title='"._("Actions")."'> "._($action_text)." <span class='caret'></span></button>";
2069	    $html[] = "  <ul class='dropdown-menu $alignment' aria-labelledby='dropdownMenu'>";
2070
2071	    // loop items
2072	    foreach ($items as $i) {
2073	        // visible
2074	        if (isset($i['visible'])) {
2075	            if ($i['visible']!="dropdown") {
2076	                continue;
2077	            }
2078	        }
2079	        // title
2080	        if ($i['type']=="header") {
2081	            $html[] = "   <li class='dropdown-header'>".($i['text'])."</li>";
2082
2083	        }
2084	        // separator
2085	        elseif ($i['type']=="divider") {
2086	            $html[] = "   <li role='separator' class='divider'></li>";
2087	        }
2088	        // item
2089	        else {
2090	            $html[] = "   <li><a class='$i[class]' href='$i[href]' $i[dataparams]><i class='fa fa-$i[icon]'></i> "._($i['text'])."</a></li>";
2091	        }
2092	    }
2093	    // remove last divider if present
2094	    if (strpos(end($html),"divider")!==false) {
2095	        array_pop($html);
2096	    }
2097	    // end
2098	    $html[] = " </ul>";
2099	    $html[] = "</div>";
2100	    // result
2101	    return implode("\n", $html);
2102	}
2103
2104
2105	/**
2106	 * Prints icons btn-group
2107	 *
2108	 * @method print_actions_buttons
2109	 * @param  array $items [array of items]
2110	 * @return string
2111	 */
2112	private function print_actions_buttons ($items = []) {
2113	    // init
2114	    $html   = [];
2115	    // structure
2116	    $html[] = " <div class='btn-group'>";
2117	    // irems
2118	    foreach ($items as $i) {
2119	        // visible
2120	        if (isset($i['visible'])) {
2121	            if ($i['visible']!="buttons") {
2122	                continue;
2123	            }
2124	        }
2125	        // save only links
2126	        if($i['type']=="link") {
2127	            $html[] = " <a href='$i[href]' class='btn btn-xs btn-default $i[class]' $i[dataparams] rel='tooltip' title='"._($i['text'])."'><i class='fa fa-$i[icon]'></i></a>";
2128	        }
2129	    }
2130	    // end
2131	    $html[] =  " </div>";
2132	    // result
2133	    return implode("\n", $html);
2134	}
2135}