1<?php
2
3/**
4 * Observium
5 *
6 *   This file is part of Observium.
7 *
8 * @package    observium
9 * @subpackage functions
10 * @copyright  (C) 2006-2013 Adam Armstrong, (C) 2013-2019 Observium Limited
11 *
12 */
13
14// Notifications and alerts in bottom navbar
15$notifications = array();
16$alerts        = array();
17
18// Enable caching, currently only for WUI
19include_once($config['install_dir'] .'/includes/cache.inc.php');
20
21include_once($config['html_dir'].'/includes/graphs/functions.inc.php');
22
23$print_functions = array('addresses', 'events', 'mac_addresses', 'rows',
24                         'status', 'arptable', 'fdbtable', 'navbar',
25                         'search', 'syslogs', 'inventory', 'alert',
26                         'authlog', 'dot1xtable', 'alert_log', 'logalert',
27                         'common', 'routing', 'neighbours', 'billing');
28
29foreach ($print_functions as $item)
30{
31  $print_path = $config['html_dir'].'/includes/print/'.$item.'.inc.php';
32  if (is_file($print_path)) { include($print_path); }
33}
34
35// Load generic entity include
36include($config['html_dir'].'/includes/entities/generic.inc.php');
37
38// Load all per-entity includes
39foreach ($config['entities'] as $entity_type => $item)
40{
41  $path = $config['html_dir'].'/includes/entities/'.$entity_type.'.inc.php';
42  if (is_file($path)) { include($path); }
43}
44
45/**
46 * Used for replace some strings at end of run all html scripts
47 *
48 * @param string $buffer HTML buffer from ob_start()
49 * @return string Changed buffer
50 */
51function html_callback($buffer)
52{
53  global $config;
54
55  // Install registered CSS/JS links
56  $types = array(
57    'css'    => '  <link href="%%STRING%%?v=' . OBSERVIUM_VERSION . '" rel="stylesheet" type="text/css" />' . PHP_EOL,
58    'style'  => '  <style type="text/css">' . PHP_EOL . '%%STRING%%' . PHP_EOL . '  </style>' . PHP_EOL,
59    'js'     => '  <script type="text/javascript" src="%%STRING%%?v=' . OBSERVIUM_VERSION . '"></script>' . PHP_EOL,
60    'script' => '  <script type="text/javascript">' . PHP_EOL .
61                '  <!-- Begin' . PHP_EOL . '%%STRING%%' . PHP_EOL .
62                '  // End -->' . PHP_EOL . '  </script>' . PHP_EOL,
63    'meta'   => '  <meta http-equiv="%%STRING_http-equiv%%" content="%%STRING_content%%" />' . PHP_EOL,
64  );
65
66  foreach ($types as $type => $string)
67  {
68    $uptype = strtoupper($type);
69    if (isset($GLOBALS['cache_html']['resources'][$type]))
70    {
71      $$type = '<!-- ' . $uptype . ' BEGIN -->' . PHP_EOL;
72      foreach (array_unique($GLOBALS['cache_html']['resources'][$type]) as $content) // Do not use global $cache variable, because it is reset before flush ob_cache
73      {
74        if (is_array($content))
75        {
76          // for meta
77          foreach($content as $param => $value)
78          {
79            $string = str_replace('%%STRING_'.$param.'%%', $value, $string);
80          }
81          $$type .= $string;
82        } else {
83          $$type .= str_replace('%%STRING%%', $content, $string);
84        }
85      }
86      $$type .= '  <!-- ' . $uptype . ' END -->' . PHP_EOL;
87      $buffer = str_replace('<!-- ##' . $uptype . '_CACHE## -->' . PHP_EOL, $$type, $buffer);
88    } else {
89      // Clean template string
90      $buffer = str_replace('<!-- ##' . $uptype . '_CACHE## -->', '', $buffer);
91    }
92  }
93
94  // Replace page title as specified by the page modules
95  if (!is_array($GLOBALS['cache_html']['title']))
96  {
97    // Title not set by any page, fall back to nicecase'd page name:
98    if ($GLOBALS['vars']['page'] && $_SESSION['authenticated'])
99    {
100      $GLOBALS['cache_html']['title'] = array(nicecase($GLOBALS['vars']['page']));
101    } else {
102      // HALP. Likely main page, doesn't need anything else...
103      $GLOBALS['cache_html']['title'] = array();
104    }
105  }
106
107  // If suffix is set, put it in the back
108  if ($config['page_title_suffix']) { $GLOBALS['cache_html']['title'][] = $config['page_title_suffix']; }
109
110  // If prefix is set, put it in front
111  if ($config['page_title_prefix']) { array_unshift($GLOBALS['cache_html']['title'], $config['page_title_prefix']); }
112
113  // Build title with separators
114  $title = implode($config['page_title_separator'], $GLOBALS['cache_html']['title']);
115
116  // Replace title placeholder by actual title
117  $buffer = str_replace('##TITLE##', escape_html($title), $buffer);
118
119  // Page panel
120  $buffer = str_replace('##PAGE_PANEL##', $GLOBALS['cache_html']['page_panel'], $buffer);
121
122  // UI Alerts
123  $buffer = str_replace('##UI_ALERTS##', $GLOBALS['ui_alerts'], $buffer);
124
125  // Return modified HTML page source
126  return $buffer;
127}
128
129/**
130 * Parse $_GET, $_POST and REQUEST_URI into $vars array
131 *
132 * @param array $vars_order Request variables order (POST, URI, GET)
133 * @param boolean $auth this var or ($_SESSION['authenticated']) used for allow to use var_decode()
134 * @return array array of vars
135 */
136function get_vars($vars_order = array(), $auth = FALSE)
137{
138  if (is_string($vars_order))
139  {
140    $vars_order = explode(' ', $vars_order);
141  }
142  else if (empty($vars_order) || !is_array($vars_order))
143  {
144    $vars_order = array('POST', 'URI', 'GET'); // Default order
145  }
146
147  // XSS script regex
148  // <sCrIpT> < / s c r i p t >
149  // javascript:alert("Hello world");/
150  $prevent_xss = '!(^\s*(J\s*A\s*V\s*A\s*)?S\s*C\s*R\s*I\s*P\s*T\s*:|<\s*/?\s*S\s*C\s*R\s*I\s*P\s*T\s*>)!i';
151
152  // Allow to use var_decode(), this prevent to use potentially unsafe serialize functions
153  $auth = $auth || $_SESSION['authenticated'];
154
155  $vars = array();
156  foreach ($vars_order as $order)
157  {
158    $order = strtoupper($order);
159    switch ($order)
160    {
161      case 'POST':
162        // Parse POST variables into $vars
163        foreach ($_POST as $name => $value)
164        {
165          if (!isset($vars[$name]))
166          {
167            $vars[$name] = $auth ? var_decode($value) : $value;
168            if (preg_match($prevent_xss, $vars[$name]))
169            {
170              // Prevent any <script> html tag inside vars, exclude any possible XSS with scripts
171              unset($vars[$name]);
172            }
173          }
174        }
175        break;
176      case 'URI':
177      case 'URL':
178        // Parse URI into $vars
179        $segments = explode('/', trim($_SERVER['REQUEST_URI'], '/'));
180        foreach ($segments as $pos => $segment)
181        {
182          //$segment = urldecode($segment);
183          if ($pos == "0" && !str_contains($segment, '='))
184          {
185            if (!preg_match($prevent_xss, $segment))
186            {
187              // Prevent any <script> html tag inside vars, exclude any possible XSS with scripts
188              $segment = urldecode($segment);
189              $vars['page'] = $segment;
190            }
191          } else {
192            list($name, $value) = explode('=', $segment, 2);
193            if (!isset($vars[$name]))
194            {
195              if (!isset($value) || $value === '')
196              {
197                $vars[$name] = 'yes';
198              } else {
199                $value = str_replace('%7F', '/', urldecode($value)); // %7F (DEL, delete) - not defined in HTML 4 standard
200                if (preg_match($prevent_xss, $value))
201                {
202                  // Prevent any <script> html tag inside vars, exclude any possible XSS with scripts
203                  continue;
204                }
205                if (strpos($value, ','))
206                {
207                  // Here commas list (convert to array)
208                  $vars[$name] = explode(',', $value);
209                } else {
210                  // Here can be string as encoded array
211                  $vars[$name] = $auth ? var_decode($value) : $value;
212                  if (is_string($vars[$name]) && preg_match($prevent_xss, $vars[$name]))
213                  {
214                    // Prevent any <script> html tag inside vars, exclude any possible XSS with scripts
215                    unset($vars[$name]);
216                  }
217                }
218                if (strpos($vars[$name], '%1F') !== FALSE)
219                {
220                  $vars[$name] = str_replace('%1F', ',', $vars[$name]); // %1F (US, unit separator) - not defined in HTML 4 standard
221                }
222              }
223            }
224          }
225        }
226        break;
227      case 'GET':
228        // Parse GET variable into $vars
229        foreach ($_GET as $name => $value)
230        {
231          if (!isset($vars[$name]))
232          {
233            $value = str_replace('%7F', '/', urldecode($value)); // %7F (DEL, delete) - not defined in HTML 4 standard
234            if (preg_match($prevent_xss, $value))
235            {
236              // Prevent any <script> html tag inside vars, exclude any possible XSS with scripts
237              continue;
238            }
239            if (strpos($value, ','))
240            {
241              // Here commas list (convert to array)
242              $vars[$name] = explode(',', $value);
243            } else {
244              // Here can be string as encoded array
245              $vars[$name] = $auth ? var_decode($value) : $value;
246              if (is_string($vars[$name]) && preg_match($prevent_xss, $vars[$name]))
247              {
248                // Prevent any <script> html tag inside vars, exclude any possible XSS with scripts
249                unset($vars[$name]);
250              }
251            }
252            if (strpos($vars[$name], '%1F') !== FALSE)
253            {
254              $vars[$name] = str_replace('%1F', ',', $vars[$name]); // %1F (US, unit separator) - not defined in HTML 4 standard
255            }
256          }
257        }
258        break;
259    }
260  }
261
262  // Always convert location to array
263  if (isset($vars['location']))
264  {
265    if ($vars['location'] === '')
266    {
267      // Unset location if is empty string
268      unset($vars['location']);
269    }
270    else if (is_array($vars['location']))
271    {
272      // Additionally decode locations if array entries encoded
273      foreach ($vars['location'] as $k => $location)
274      {
275        $vars['location'][$k] = $auth ? var_decode($location) : $location;
276      }
277    } else {
278       // All other location strings covert to array
279      $vars['location'] = array($vars['location']);
280    }
281  }
282
283  //r($vars);
284  return($vars);
285}
286
287/**
288 * Validate requests by compare session and request tokens.
289 * This prevents a CSRF attacks
290 *
291 * @param string|array $token Passed from request token or array with 'requesttoken' param inside.
292 * @return boolean TRUE if session requesttoken same as passed from request
293 */
294function request_token_valid($token = NULL)
295{
296  if (is_array($token))
297  {
298    // If $vars array passed, fetch our default 'requesttoken' param
299    $token = $token['requesttoken'];
300  }
301
302  //print_vars($_SESSION['requesttoken']);
303  //print_vars($token);
304
305  // See: https://stackoverflow.com/questions/6287903/how-to-properly-add-csrf-token-using-php
306  // Session token generated after valid user auth in html/includes/authenticate.inc.php
307  if (empty($_SESSION['requesttoken']))
308  {
309    // User not authenticated
310    //print_warning("Request passed by unauthorized user.");
311    return FALSE;
312  }
313  else if (empty($token))
314  {
315    // Token not passed, WARNING seems as CSRF attack
316    print_error("WARNING. Possible CSRF attack with EMPTY request token.");
317    ///FIXME. need an user actions log
318    return FALSE;
319  }
320  else if (hash_equals($_SESSION['requesttoken'], $token))
321  {
322    // Correct session and request tokens, all good
323    return TRUE;
324  } else {
325    // Passed incorrect request token,
326    // WARNING seems as CSRF attack
327    print_error("WARNING. Possible CSRF attack with INCORRECT request token.");
328    ///FIXME. need an user actions log
329    return FALSE;
330  }
331}
332
333/**
334 * Detect if current URI is link to graph
335 *
336 * @return boolean TRUE if current script is graph
337 */
338// TESTME needs unit testing
339function is_graph()
340{
341  if (!defined('OBS_GRAPH'))
342  {
343    // defined in html/graph.php
344    define('OBS_GRAPH', FALSE);
345  }
346
347  return OBS_GRAPH;
348  //return (realpath($_SERVER['SCRIPT_FILENAME']) === realpath($GLOBALS['config']['html_dir'].'/graph.php'));
349}
350
351// TESTME needs unit testing
352/**
353 * Generates base64 data uri with alert graph
354 *
355 * @return string
356 */
357function generate_alert_graph($graph_array)
358{
359  global $config;
360
361  $vars = $graph_array;
362  $auth = (is_cli() ? TRUE : $GLOBALS['auth']); // Always set $auth to true for cli
363  $vars['image_data_uri'] = TRUE;
364  $vars['height'] = '150';
365  $vars['width']  = '400';
366  $vars['legend'] = 'no';
367  $vars['from']   = $config['time']['twoday'];
368  $vars['to']     = $config['time']['now'];
369
370  include($config['html_dir'].'/includes/graphs/graph.inc.php');
371
372  return $image_data_uri;
373}
374
375// TESTME needs unit testing
376// DOCME needs phpdoc block
377function datetime_preset($preset)
378{
379  $begin_fmt = 'Y-m-d 00:00:00';
380  $end_fmt   = 'Y-m-d 23:59:59';
381
382  switch($preset)
383  {
384    case 'sixhours':
385      $from = date('Y-m-d H:i:00', strtotime('-6 hours'));
386      $to   = date('Y-m-d H:i:59');
387      break;
388    case 'today':
389      $from = date($begin_fmt);
390      $to   = date($end_fmt);
391      break;
392    case 'yesterday':
393      $from = date($begin_fmt, strtotime('-1 day'));
394      $to   = date($end_fmt,   strtotime('-1 day'));
395      break;
396    case 'tweek':
397      $from = (date('l') == 'Monday') ? date($begin_fmt) : date($begin_fmt, strtotime('last Monday'));
398      $to   = (date('l') == 'Sunday') ? date($end_fmt)   : date($end_fmt,   strtotime('next Sunday'));
399      break;
400    case 'lweek':
401      $from = date($begin_fmt, strtotime('-6 days'));
402      $to   = date($end_fmt);
403      break;
404    case 'tmonth':
405      $tmonth = date('Y-m');
406      $from = $tmonth.'-01 00:00:00';
407      $to   = date($end_fmt, strtotime($tmonth.' next month - 1 hour'));
408      break;
409    case 'lmonth':
410      $from = date($begin_fmt, strtotime('previous month + 1 day'));
411      $to   = date($end_fmt);
412      break;
413    case 'tquarter':
414    case 'lquarter':
415      $quarter = ceil(date('m') / 3); // Current quarter
416      if ($preset == 'lquarter')
417      {
418        $quarter = $quarter - 1; // Previous quarter
419      }
420      $year = date('Y');
421      if ($quarter < 1)
422      {
423        $year   -= 1;
424        $quarter = 4;
425      }
426      $tmonth = $quarter * 3;
427      $fmonth = $tmonth - 2;
428
429      $from = $year.'-'.zeropad($fmonth).'-01 00:00:00';
430      $to   = date('Y-m-t 23:59:59', strtotime($year.'-'.$tmonth.'-01'));
431      break;
432    case 'tyear':
433      $from = date('Y-01-01 00:00:00');
434      $to   = date('Y-12-31 23:59:59');
435      break;
436    case 'lyear':
437      $from = date($begin_fmt, strtotime('previous year + 1 day'));
438      $to   = date($end_fmt);
439      break;
440  }
441
442  return array('from' => $from, 'to' => $to);
443}
444
445// TESTME needs unit testing
446// DOCME needs phpdoc block
447function bug()
448{
449  echo('<div class="alert alert-error">
450  <button type="button" class="close" data-dismiss="alert">&times;</button>
451  <strong>Bug!</strong> Please report this to the Observium development team.
452</div>');
453}
454
455/**
456 * This function determines type of web browser for current User-Agent (mobile/tablet/generic).
457 * For more detailed browser info and custom User-Agent use detect_browser()
458 *
459 * @return string Return type of browser (generic/mobile/tablet)
460 */
461function detect_browser_type()
462{
463  $ua_info = detect_browser();
464
465  return $ua_info['type'];
466}
467
468/**
469 * This function determines detailed info of web browser by User-Agent agent string.
470 * If User-Agent not passed, used current from $_SERVER['HTTP_USER_AGENT']
471 *
472 * @param string $user_agent Custom User-Agent string, by default, the value of HTTP User-Agent header is used
473 *
474 * @return array Return detected browser info: user_agent, type, icon, platform, browser, version,
475 *                                             browser_full - full browser name (ie: Chrome 43.0)
476 *                                             svg          - supported or not svg images (TRUE|FALSE),
477 *                                             screen_ratio - for HiDPI screens it more that 1,
478 *                                             screen_resolution - full resolution of client screen (if exist),
479 *                                             screen_size  - initial size of browser window (if exist)
480 */
481// TESTME! needs unit testing
482function detect_browser($user_agent = NULL)
483{
484  $ua_custom = !is_null($user_agent); // Used custom user agent?
485
486  if (!$ua_custom && isset($GLOBALS['cache']['detect_browser']))
487  {
488    //if (isset($_COOKIE['observium_screen_ratio']) && !isset($GLOBALS['cache']['detect_browser']['screen_resolution']))
489    //{
490    //  r($_COOKIE);
491    //}
492    // Return cached info
493    return $GLOBALS['cache']['detect_browser'];
494  }
495
496  $detect = new Mobile_Detect;
497
498  if ($ua_custom)
499  {
500    // Set custom User-Agent
501    $detect->setUserAgent($user_agent);
502  } else {
503    $user_agent = $_SERVER['HTTP_USER_AGENT'];
504  }
505
506  // Default type and icon
507  $type = 'generic';
508  $icon = 'icon-laptop';
509  if ($detect->isMobile())
510  {
511    // Any phone device (exclude tablets).
512    $type = 'mobile';
513    $icon = 'glyphicon glyphicon-phone';
514    if ($detect->isTablet())
515    {
516      // Any tablet device.
517      $type = 'tablet';
518      $icon = 'icon-tablet';
519    }
520  }
521
522  // Load additional function
523  if (!function_exists('parse_user_agent'))
524  {
525    include_once($GLOBALS['config']['install_dir'].'/libs/UserAgentParser.php');
526  }
527
528  // Detect Browser name, version and platform
529  if (!empty($user_agent))
530  {
531    $ua_info = parse_user_agent($user_agent);
532  } else {
533    $ua_info = array();
534  }
535
536  $detect_browser = array('user_agent' => $user_agent,
537                          'type'       => $type,
538                          'icon'       => $icon,
539                          'browser_full' => $ua_info['browser'] . ' ' . preg_replace('/^([^\.]+(?:\.[^\.]+)?).*$/', '\1', $ua_info['version']),
540                          'browser'    => $ua_info['browser'],
541                          'version'    => $ua_info['version'],
542                          'platform'   => $ua_info['platform']);
543
544   // For custom UA, do not cache and return only base User-Agent info
545  if ($ua_custom)
546  {
547    return $detect_browser;
548  }
549
550  // Load screen and DPI detector. This set cookies with:
551  //  $_COOKIE['observium_screen_ratio'] - if ratio >= 2, than HiDPI screen is used
552  //  $_COOKIE['observium_screen_resolution'] - screen resolution 'width x height', ie: 1920x1080
553  //  $_COOKIE['observium_screen_size'] - current window size (less than resolution) 'width x height', ie: 1097x456
554  register_html_resource('js', 'observium-screen.js');
555
556  // Additional browser info (screen_ratio, screen_size, svg)
557  if ($ua_info['browser'] == 'Firefox' && version_compare($ua_info['version'], '47.0') < 0)
558  {
559    // Do not use srcset in FF, while issue open:
560    // https://bugzilla.mozilla.org/show_bug.cgi?id=1149357
561    // Update, seems as in 47.0 partially fixed
562    $zoom = 1;
563  }
564  else if (isset($_COOKIE['observium_screen_ratio']))
565  {
566    // Note, Opera uses ratio 1.5
567    $zoom = round($_COOKIE['observium_screen_ratio']); // Use int zoom
568  } else {
569    // If JS not supported or cookie not set, use default zoom 2 (for allow srcset)
570    $zoom = 2;
571  }
572  $detect_browser['screen_ratio'] = $zoom;
573  //$detect_browser['svg']          = ($ua_info['browser'] == 'Firefox'); // SVG supported or allowed
574  if (isset($_COOKIE['observium_screen_resolution']))
575  {
576    $detect_browser['screen_resolution'] = $_COOKIE['observium_screen_resolution'];
577    //$detect_browser['screen_size']       = $_COOKIE['observium_screen_size'];
578  }
579
580  $GLOBALS['cache']['detect_browser'] = $detect_browser; // Store to cache
581
582  //r($GLOBALS['cache']['detect_browser']);
583  return $GLOBALS['cache']['detect_browser'];
584}
585
586// TESTME needs unit testing
587// DOCME needs phpdoc block
588function data_uri($file, $mime)
589{
590  $contents = file_get_contents($file);
591  $base64   = base64_encode($contents);
592
593  return ('data:' . $mime . ';base64,' . $base64);
594}
595
596// TESTME needs unit testing
597// DOCME needs phpdoc block
598function toner_map($descr, $colour)
599{
600  foreach ($GLOBALS['config']['toner'][$colour] as $str)
601  {
602    if (stripos($descr, $str) !== FALSE) { return TRUE; }
603  }
604
605  return FALSE;
606}
607
608// TESTME needs unit testing
609// DOCME needs phpdoc block
610function toner_to_colour($descr, $percent)
611{
612  if (substr($descr, -1) == 'C' || toner_map($descr, "cyan"   )) { $colour['left'] = "B6F6F6"; $colour['right'] = "33B4B1"; }
613  if (substr($descr, -1) == 'M' || toner_map($descr, "magenta")) { $colour['left'] = "FBA8E6"; $colour['right'] = "D028A6"; }
614  if (substr($descr, -1) == 'Y' || toner_map($descr, "yellow" )) { $colour['left'] = "FFF764"; $colour['right'] = "DDD000"; }
615  if (substr($descr, -1) == 'K' || toner_map($descr, "black"  )) { $colour['left'] = "888787"; $colour['right'] = "555555"; }
616  if (substr($descr, -1) == 'R' || toner_map($descr, "red"    )) { $colour['left'] = "FB6A4A"; $colour['right'] = "CB181D"; }
617
618  if (!isset($colour['left'])) { $colour = get_percentage_colours(100-$percent); $colour['found'] = FALSE; } else { $colour['found'] = TRUE; }
619
620  return $colour;
621}
622
623// TESTME needs unit testing
624// DOCME needs phpdoc block
625function generate_link($text, $vars, $new_vars = array(), $escape = TRUE)
626{
627  if ($escape) { $text = escape_html($text); }
628  return '<a href="'.generate_url($vars, $new_vars).'">'.$text.'</a>';
629}
630
631// TESTME needs unit testing
632// DOCME needs phpdoc block
633function pagination(&$vars, $total, $return_vars = FALSE)
634{
635  $pagesizes = array(10,20,50,100,500,1000,10000,50000); // Permitted pagesizes
636  if (is_numeric($vars['pagesize']))
637  {
638    $per_page = (int)$vars['pagesize'];
639  }
640  else if (isset($_SESSION['pagesize']))
641  {
642    $per_page = $_SESSION['pagesize'];
643  } else {
644    $per_page = $GLOBALS['config']['web_pagesize'];
645  }
646  if (!$vars['short'])
647  {
648    // Permit fixed pagesizes only (except $vars['short'] == TRUE)
649    foreach ($pagesizes as $pagesize)
650    {
651      if ($per_page <= $pagesize) { $per_page = $pagesize; break; }
652    }
653    if (isset($vars['pagesize']) && $vars['pagesize'] != $_SESSION['pagesize'])
654    {
655      if ($vars['pagesize'] != $GLOBALS['config']['web_pagesize'])
656      {
657        session_set_var('pagesize', $per_page); // Store pagesize in session only if changed default
658      }
659      else if (isset($_SESSION['pagesize']))
660      {
661        session_unset_var('pagesize');          // Reset pagesize from session
662      }
663    }
664  }
665  $vars['pagesize'] = $per_page;       // Return back current pagesize
666
667  $page     = (int)$vars['pageno'];
668  $lastpage = ceil($total/$per_page);
669  if ($page < 1) { $page = 1; }
670  else if (!$return_vars && $lastpage < $page) { $page = (int)$lastpage; }
671  $vars['pageno'] = $page; // Return back current pageno
672
673  if ($return_vars) { return ''; } // Silent exit (needed for detect default pagesize/pageno)
674
675  $start = ($page - 1) * $per_page;
676  $prev  = $page - 1;
677  $next  = $page + 1;
678  $lpm1  = $lastpage - 1;
679
680  $adjacents = 3;
681  $pagination = '';
682
683  if ($total > 99 || $total > $per_page)
684  {
685
686    if($total > 9999) { $total_text = format_si($total); } else { $total_text = $total; }
687
688
689    $pagination .= '<div class="row">' . PHP_EOL .
690                   '  <div class="col-lg-1 col-md-2 col-sm-2" style="display: inline-block;">' . PHP_EOL .
691                   //'    <span class="btn disabled" style="line-height: 20px;">'.$total.'&nbsp;Items</span>' . PHP_EOL .
692                   '    <div class="box box-solid" style="padding: 4px 12px;">'.$total_text.'&nbsp;Items</div>' . PHP_EOL .
693                   '  </div>' . PHP_EOL .
694                   '  <div class="col-lg-10 col-md-8 col-sm-8">' . PHP_EOL .
695                   '    <div class="pagination pagination-centered"><ul>' . PHP_EOL;
696
697    if ($prev)
698    {
699      //$pagination .= '      <li><a href="'.generate_url($vars, array('pageno' => 1)).'">First</a></li>' . PHP_EOL;
700      $pagination .= '      <li><a href="'.generate_url($vars, array('pageno' => $prev)).'">Prev</a></li>' . PHP_EOL;
701    }
702
703    if ($lastpage < 7 + ($adjacents * 2))
704    {
705      for ($counter = 1; $counter <= $lastpage; $counter++)
706      {
707        if ($counter == $page)
708        {
709          $pagination.= "<li class='active'><a>$counter</a></li>";
710        } else {
711          $pagination.= "<li><a href='".generate_url($vars, array('pageno' => $counter))."'>$counter</a></li>";
712        }
713      }
714    }
715    elseif ($lastpage > 5 + ($adjacents * 2))
716    {
717      if ($page < 1 + ($adjacents * 2))
718      {
719        for ($counter = 1; $counter < 4 + ($adjacents * 2); $counter++)
720        {
721          if ($counter == $page)
722          {
723            $pagination .= "<li class='active'><a>$counter</a></li>";
724          } else {
725            $class = '';
726            //if ($counter > 9)
727            //{
728            //  $class = ' class="hidden-md hidden-sm hidden-xs"';
729            //}
730            //else if ($counter > 6)
731            //{
732            //  $class = ' class="hidden-sm hidden-xs"';
733            //}
734            $pagination .= "<li$class><a href='".generate_url($vars, array('pageno' => $counter))."'>$counter</a></li>";
735          }
736        }
737
738        $pagination.= "<li><a href='".generate_url($vars, array('pageno' => $lpm1))."'>$lpm1</a></li>";
739        $pagination.= "<li><a href='".generate_url($vars, array('pageno' => $lastpage))."'>$lastpage</a></li>";
740      }
741      elseif ($lastpage - ($adjacents * 2) > $page && $page > ($adjacents * 2))
742      {
743        $pagination.= "<li><a href='".generate_url($vars, array('pageno' => '1'))."'>1</a></li>";
744        $pagination.= "<li><a href='".generate_url($vars, array('pageno' => '2'))."'>2</a></li>";
745
746        for ($counter = $page - $adjacents; $counter <= $page + $adjacents; $counter++)
747        {
748          if ($counter == $page)
749          {
750            $pagination.= "<li class='active'><a>$counter</a></li>";
751          } else {
752            $pagination.= "<li><a href='".generate_url($vars, array('pageno' => $counter))."'>$counter</a></li>";
753          }
754        }
755
756        $pagination.= "<li><a href='".generate_url($vars, array('pageno' => $lpm1))."'>$lpm1</a></li>";
757        $pagination.= "<li><a href='".generate_url($vars, array('pageno' => $lastpage))."'>$lastpage</a></li>";
758      } else {
759        $pagination.= "<li><a href='".generate_url($vars, array('pageno' => '1'))."'>1</a></li>";
760        $pagination.= "<li><a href='".generate_url($vars, array('pageno' => '2'))."'>2</a></li>";
761        for ($counter = $lastpage - (2 + ($adjacents * 2)); $counter <= $lastpage; $counter++)
762        {
763          if ($counter == $page)
764          {
765            $pagination.= "<li class='active'><a>$counter</a></li>";
766          } else {
767            $class = '';
768            //if ($lastpage - $counter > 9)
769            //{
770            //  $class = ' class="hidden-md hidden-sm hidden-xs"';
771            //}
772            //else if ($lastpage - $counter > 6)
773            //{
774            //  $class = ' class="hidden-sm hidden-xs"';
775            //}
776            $pagination.= "<li$class><a href='".generate_url($vars, array('pageno' => $counter))."'>$counter</a></li>";
777          }
778        }
779      }
780    }
781
782    if ($page < $counter - 1)
783    {
784      $pagination.= "<li><a href='".generate_url($vars, array('pageno' => $next))."'>Next</a></li>";
785      # No need for "Last" as we don't have "First", 1, 2 and the 2 last pages are always in the list.
786      #$pagination.= "<li><a href='".generate_url($vars, array('pageno' => $lastpage))."'>Last</a></li>";
787    }
788    else if ($lastpage > 1)
789    {
790      $pagination.= "<li class='active'><a>Next</a></li>";
791      #$pagination.= "<li class='active'><a>Last</a></li>";
792    }
793
794    $pagination.= "</ul></div></div>";
795
796    //$values = array('' => array('name'))
797    foreach ($pagesizes as $pagesize)
798    {
799      $value = generate_url($vars, array('pagesize' => $pagesize, 'pageno' => floor($start / $pagesize)));
800      $name  = ($pagesize == $GLOBALS['config']['web_pagesize'] ? "[ $pagesize ]" : $pagesize);
801      $values[$value] = array('name' => $name, 'class' => 'text-center');
802    }
803    $element = array('type'     => 'select',
804                     'class'    => 'pagination',
805                     'id'       => 'pagesize',
806                     'name'     => '# '.$per_page,
807                     'width'    => '90px',
808                     'onchange' => "window.open(this.options[this.selectedIndex].value,'_top')",
809                     'value'    => $per_page,
810                     'data-style' => 'box',
811                     'values'   => $values);
812
813    $pagination.= '
814       <div class="col-lg-1 col-md-2 col-sm-2">
815       <form class="pull-right pagination" action="#">';
816
817    $pagination .= generate_form_element($element);
818
819    $pagination .= '</form></div></div>';
820  }
821
822  return $pagination;
823}
824
825// TESTME needs unit testing
826// DOCME needs phpdoc block
827function generate_url($vars, $new_vars = array())
828{
829  $vars = ($vars) ? array_merge($vars, $new_vars) : $new_vars;
830
831  $url = $vars['page'];
832  if ($url[strlen($url)-1] !== '/') { $url .= '/'; }
833  unset($vars['page']);
834
835  foreach ($vars as $var => $value)
836  {
837    if ($var == "username" || $var == "password")
838    {
839      // Ignore these vars. They shouldn't end up in URLs.
840    }
841    else if (is_array($value))
842    {
843      $url .= urlencode($var) . '=' . var_encode($value) . '/';
844    }
845    else if ($value == "0" || $value != "" && strstr($var, "opt") === FALSE && is_numeric($var) === FALSE)
846    {
847      $url .= urlencode($var) . '=' . urlencode(str_replace('/', '%7F', $value)).'/'; // %7F converted back to / in get_vars()
848    }
849  }
850
851  // If we're being generated outside of the web interface, prefix the generated URL to make it work properly.
852  if (is_cli())
853  {
854    if ($GLOBALS['config']['web_url'] == 'http://localhost:80/')
855    {
856      // override default web_url by http://localhost/
857      $url = 'http://'.get_localhost().'/'.$url;
858    } else {
859      $url = $GLOBALS['config']['web_url'] . $url;
860    }
861  }
862
863  return($url);
864}
865
866// TESTME needs unit testing
867// DOCME needs phpdoc block
868function generate_feed_url($vars)
869{
870  $url = FALSE;
871  if (!class_exists('SimpleXMLElement')) { return $url; } // Break if class SimpleXMLElement is not available.
872
873  if (is_numeric($_SESSION['user_id']) && is_numeric($_SESSION['userlevel']))
874  {
875    $key = get_user_pref($_SESSION['user_id'], 'atom_key');
876  }
877  if ($key)
878  {
879    $param   = array(rtrim($GLOBALS['config']['base_url'], '/').'/feed.php?id='.$_SESSION['user_id']);
880    $param[] = 'hash='.encrypt($_SESSION['user_id'].'|'.$_SESSION['userlevel'].'|'.$_SESSION['auth_mechanism'], $key);
881
882    $feed_type = 'atom';
883    foreach ($vars as $var => $value)
884    {
885      if ($value != '')
886      {
887        switch ($var)
888        {
889          case 'v':
890            if ($value == 'rss')
891            {
892              $param[] = "$var=rss";
893              $feed_type = 'rss';
894            }
895            break;
896          case 'feed':
897            $title = "Observium :: ".ucfirst($value)." Feed";
898            $param[] = 'size='.$GLOBALS['config']['frontpage']['eventlog']['items'];
899            // no break here
900          case 'size':
901            $param[] = "$var=$value";
902            break;
903        }
904      }
905    }
906
907    $baseurl = implode('&amp;', $param);
908
909    $url = '<link href="'.$baseurl.'" rel="alternate" title="'.escape_html($title).'" type="application/'.$feed_type.'+xml" />';
910  }
911
912  return $url;
913}
914
915// TESTME needs unit testing
916// DOCME needs phpdoc block
917function generate_location_url($location, $vars = array())
918{
919  if ($location === '') { $location = OBS_VAR_UNSET; }
920  $value = var_encode($location);
921  return generate_url(array('page' => 'devices', 'location' => $value), $vars);
922}
923
924// TESTME needs unit testing
925// DOCME needs phpdoc block
926function generate_overlib_content($graph_array, $text = NULL, $escape = TRUE)
927{
928  global $config;
929
930  $graph_array['height'] = "100";
931  $graph_array['width']  = "210";
932
933  if ($escape) { $text = escape_html($text); }
934
935  $content = '<div style="width: 590px;"><span style="font-weight: bold; font-size: 16px;">'.$text.'</span><br />';
936  /*
937  $box_args = array('body-style' => 'width: 590px;');
938  if (strlen($text))
939  {
940    $box_args['title'] = $text;
941  }
942  $content = generate_box_open($box_args);
943  */
944  foreach (array('day', 'week', 'month', 'year') as $period)
945  {
946    $graph_array['from'] = $config['time'][$period];
947    $content .= generate_graph_tag($graph_array);
948  }
949  $content .= "</div>";
950  //$content .= generate_box_close();
951
952  return $content;
953}
954
955// TESTME needs unit testing
956// DOCME needs phpdoc block
957function get_percentage_colours($percentage)
958{
959
960  if     ($percentage > '90') { $background['left']='cb181d'; $background['right']='fb6a4a'; $background['class'] = 'error'; }
961  elseif ($percentage > '80') { $background['left']='cc4c02'; $background['right']='fe9929'; $background['class'] = 'warning'; }
962  elseif ($percentage > '60') { $background['left']='6a51a3'; $background['right']='9e9ac8'; $background['class'] = 'information'; }
963  elseif ($percentage > '30') { $background['left']='045a8d'; $background['right']='74a9cf'; $background['class'] = 'information'; }
964  else                        { $background['left']='4d9221'; $background['right']='7fbc41'; $background['class'] = 'information'; }
965
966  return($background);
967}
968
969/**
970 * Generate common popup links which uses ajax/entitypopup.php
971 *
972 * @param string $type Popup type, see possible types in html/ajax/entitypopup.php
973 * @param string $text Text used as link name and ajax data
974 * @param array $vars Array for generate url
975 * @param string Additional css classes for link
976 * @param boolean $escape Escape or not text in url
977 * @return string Returns string with link, when hover on this link show popup message based on type
978 */
979function generate_popup_link($type, $text = NULL, $vars = array(), $class = NULL, $escape = TRUE)
980{
981  if (!is_string($type) || !is_string($text)) { return ''; }
982
983  if ($type == 'ip')
984  {
985    list($ip, $mask) = explode('/', $text, 2);
986    $ip_version = get_ip_version($ip);
987    if ($ip_version === 6)
988    {
989      // Autocompress IPv6 addresses
990      $ip   = Net_IPv6::compress($ip);
991      $text = $ip;
992      if (strlen($mask))
993      {
994        $text .= '/' . $mask;
995      }
996    }
997    if (!$ip_version || in_array($ip, array('0.0.0.0', '127.0.0.1', '::', '::1')))
998    {
999      return $text;
1000    }
1001  }
1002  $url  = (count($vars) ? generate_url($vars) : 'javascript:void(0)'); // If vars empty, set link not clickable
1003  $data = $text;
1004  if ($escape) { $text = escape_html($text); }
1005
1006  return '<a href="'.$url.'" class="entity-popup'.($class ? " $class" : '').'" data-eid="'.$data.'" data-etype="'.$type.'">'.$text.'</a>';
1007}
1008
1009/**
1010 * Generate mouseover links with static tooltip from URL, link text, contents and a class.
1011 *
1012 * Tooltips with static position and linked to current object.
1013 * Note, mostly same as overlib_link(), except tooltip position.
1014 * Always display tooltip if content not empty
1015 *
1016 * @param string $url URL string
1017 * @param string $text Text displayed as link
1018 * @param string $contents Text content displayed in mouseover tooltip (only for non-mobile devices)
1019 * @param string $class Css class name used for link
1020 * @param boolean $escape Escape or not link text
1021 * @return string
1022 */
1023// TESTME needs unit testing
1024function generate_tooltip_link($url, $text, $contents = '', $class = NULL, $escape = FALSE)
1025{
1026  global $config, $link_iter;
1027
1028  $link_iter++;
1029
1030  $href = (strlen($url) ? 'href="' . $url . '"' : '');
1031  if ($escape) { $text = escape_html($text); }
1032
1033  // Allow the Grinch to disable popups and destroy Christmas.
1034  $allow_mobile = (in_array(detect_browser_type(), array('mobile', 'tablet')) ? $config['web_mouseover_mobile'] : TRUE);
1035  if ($config['web_mouseover'] && strlen($contents) && $allow_mobile)
1036  {
1037    $output  = '<a '.$href.' class="'.$class.'" style="cursor: pointer;" data-rel="tooltip" data-tooltip="'.escape_html($contents).'">'.$text.'</a>';
1038    //$output  = '<a '.$href.' class="'.$class.'" data-toggle="tooltip" title="'.escape_html($contents).'">'.$text.'</a>';
1039  } else {
1040    $output  = '<a '.$href.' class="'.$class.'">'.$text.'</a>';
1041  }
1042
1043  return $output;
1044}
1045
1046/**
1047 * Generate mouseover links from URL, link text, contents and a class.
1048 *
1049 * Tooltips followed by mouse cursor.
1050 * Note, by default text NOT escaped for compatability with many old magic code usage.
1051 *
1052 * @param string $url URL string
1053 * @param string $text Text displayed as link
1054 * @param string $contents Text content displayed in mouseover tooltip (only for non-mobile devices)
1055 * @param string $class Css class name used for link
1056 * @param boolean $escape Escape or not link text
1057 */
1058// TESTME needs unit testing
1059// RENAMEME to generate_mouseover_link() or something similar
1060function overlib_link($url, $text, $contents, $class = NULL, $escape = FALSE)
1061{
1062  global $config, $link_iter;
1063
1064  $link_iter++;
1065
1066  $href = (strlen($url) ? 'href="' . $url . '"' : '');
1067  if ($escape) { $text = escape_html($text); }
1068
1069  // Allow the Grinch to disable popups and destroy Christmas.
1070  $allow_mobile = (in_array(detect_browser_type(), array('mobile', 'tablet')) ? $config['web_mouseover_mobile'] : TRUE);
1071  if ($config['web_mouseover'] && strlen($contents) && $allow_mobile)
1072  {
1073    $output  = '<a '.$href.' class="tooltip-from-data '.$class.'" style="cursor: pointer;" data-tooltip="'.escape_html($contents).'">'.$text.'</a>';
1074  } else {
1075    $output  = '<a '.$href.' class="'.$class.'">'.$text.'</a>';
1076  }
1077
1078  return $output;
1079}
1080
1081/**
1082 * Generate menu links with item counts from URL, link text, contents and a class.
1083 *
1084 * Tooltips with static position and linked to current object.
1085 * Note, mostly same as overlib_link(), except tooltip position.
1086 * Always display tooltip if content not empty
1087 *
1088 * @param string $url URL string
1089 * @param string $text Text displayed as link
1090 * @param string $count Counts displayed at right
1091 * @param string $class Css class name used for count (default is 'label')
1092 * @param boolean $escape Escape or not link text
1093 */
1094// TESTME needs unit testing
1095function generate_menu_link($url, $text, $count = NULL, $class = 'label', $escape = FALSE, $alert_count = NULL)
1096{
1097  $href = (strlen($url) ? 'href="' . $url . '"' : '');
1098  if ($escape) { $text = escape_html($text); }
1099
1100  $output = '<a role="menuitem" ' . $href . '><span>' . $text . '</span>';
1101
1102  if (is_numeric($alert_count))
1103  {
1104    $output .= '<span class="label label-danger">' . $alert_count . '</span>';
1105  }
1106
1107  if (is_numeric($count))
1108  {
1109    $output .= '<span class="' . $class . '">' . $count . '</span>';
1110  }
1111
1112  $output .= '</a>';
1113
1114  return $output;
1115}
1116
1117
1118/**
1119 * Generate menu links with item counts from URL, link text, contents and a class.
1120 *
1121 * Replaces previous function with multiple arguments. Should be used for all navbar menus
1122 *
1123 * @param string $array Array of options
1124 */
1125// TESTME needs unit testing
1126function generate_menu_link_new($array)
1127{
1128
1129  $array = array_merge(array(
1130                           'count' => NULL,
1131                           'escape' => FALSE,
1132                           'class' => 'label'
1133                         ), $array);
1134
1135  $link_opts = '';
1136  if (isset($array['link_opts'])) { $link_opts .= ' ' . $array['link_opts']; }
1137  if (isset($array['alt']))       { $link_opts .= ' data-rel="tooltip" data-tooltip="'.$array['alt'].'"'; }
1138  if (isset($array['id']))        { $link_opts .= ' id="'.$array['id'].'"'; }
1139
1140  if (empty($array['url']) || $array['url'] == '#') { $array['url'] = 'javascript:void(0)'; }
1141
1142  if ($array['escape']) { $array['text'] = escape_html($array['text']); }
1143
1144  $output  = '<a role="menuitem" href="'.$array['url'].'" '.$link_opts.'>';
1145
1146  $output .= '<span>';
1147  if (isset($array['icon']))
1148  {
1149    $output .= '<i class="' . $array['icon'] . '"></i>&nbsp;';
1150  }
1151  $output .= $array['text'] . '</span>';
1152
1153  // Counter label(s) in navbar menu
1154  if (isset($array['count_array']) && count($array['count_array'])) {
1155    // Multiple counts as group
1156    $count_items = [];
1157    // Ok/Up
1158    if      ($array['count_array']['ok'])       { $count_items[] = ['event' => 'success', 'text' => $array['count_array']['ok']]; }
1159    else if ($array['count_array']['up'])       { $count_items[] = ['event' => 'success', 'text' => $array['count_array']['up']]; }
1160    // Warning
1161    if      ($array['count_array']['warning'])  { $count_items[] = ['event' => 'warning', 'text' => $array['count_array']['warning']]; }
1162    // Alert/Down
1163    if      ($array['count_array']['alert'])    { $count_items[] = ['event' => 'danger',  'text' => $array['count_array']['alert']]; }
1164    else if ($array['count_array']['down'])     { $count_items[] = ['event' => 'danger',  'text' => $array['count_array']['down']]; }
1165    // Ignored
1166    if      ($array['count_array']['ignored'])  { $count_items[] = ['event' => 'default', 'text' => $array['count_array']['ignored']]; }
1167    // Disabled
1168    if      ($array['count_array']['disabled']) { $count_items[] = ['event' => 'inverse', 'text' => $array['count_array']['disabled']]; }
1169    // Fallback to just count
1170    if (!count($count_items) && strlen($array['count_array']['count'])) {
1171      $count_items[] = ['event' => 'default', 'text' => $array['count_array']['count']];
1172    }
1173
1174    //r(get_label_group($count_items));
1175    $output .= get_label_group($count_items);
1176  } else {
1177    // single counts
1178    if (is_numeric($array['alert_count']))
1179    {
1180      $output .= ' <span class="label label-danger">' . $array['alert_count'] . '</span> ';
1181    }
1182
1183    if (is_numeric($array['count']))
1184    {
1185      $output .= ' <span class="' . $array['class'] . '">' . $array['count'] . '</span>';
1186    }
1187  }
1188
1189  $output .= '</a>';
1190
1191  return $output;
1192}
1193
1194
1195// Generate a typical 4-graph popup using $graph_array
1196// TESTME needs unit testing
1197// DOCME needs phpdoc block
1198function generate_graph_popup($graph_array)
1199{
1200  global $config;
1201
1202  // Todo - this should have entity headers where appropriate, too.
1203
1204  // Take $graph_array and print day,week,month,year graps in overlib, hovered over graph
1205
1206  $original_from = $graph_array['from'];
1207
1208  $graph = generate_graph_tag($graph_array);
1209
1210  /*
1211  $box_args = array('body-style' => 'width: 850px;');
1212  if (strlen($graph_array['popup_title']))
1213  {
1214    $box_args['title'] = $graph_array['popup_title'];
1215  }
1216  $content = generate_box_open($box_args);
1217  */
1218  unset($graph_array['style']);
1219  $content =  '<div class=entity-title><h4>'.$graph_array['popup_title'].'</h4></div>';
1220  $content .= '<div style="width: 850px">';
1221  $graph_array['legend']   = "yes";
1222  $graph_array['height']   = "100";
1223  $graph_array['width']    = "340";
1224  $graph_array['from']     = $config['time']['day'];
1225  $content .= generate_graph_tag($graph_array);
1226  $graph_array['from']     = $config['time']['week'];
1227  $content .= generate_graph_tag($graph_array);
1228  $graph_array['from']     = $config['time']['month'];
1229  $content .= generate_graph_tag($graph_array);
1230  $graph_array['from']     = $config['time']['year'];
1231  $content .= generate_graph_tag($graph_array);
1232  $content .= "</div>";
1233  //$content .= generate_box_close();
1234
1235  $graph_array['from'] = $original_from;
1236
1237  $graph_array['link'] = generate_url($graph_array, array('page' => 'graphs', 'height' => NULL, 'width' => NULL, 'bg' => NULL));
1238
1239  return overlib_link($graph_array['link'], $graph, $content, NULL);
1240}
1241
1242// output the popup generated in generate_graph_popup();
1243// TESTME needs unit testing
1244// DOCME needs phpdoc block
1245function print_graph_popup($graph_array)
1246{
1247  echo(generate_graph_popup($graph_array));
1248}
1249
1250// TESTME needs unit testing
1251// DOCME needs phpdoc block
1252function permissions_cache($user_id)
1253{
1254  $permissions = array();
1255
1256  foreach (dbFetchRows("SELECT * FROM `entity_permissions` WHERE `user_id` = ?", array($user_id)) as $entity)
1257  {
1258    switch($entity['entity_type'])
1259    {
1260      case "group": // this is a group, so expand it's members into an array
1261        $group = get_group_by_id($entity['entity_id']);
1262        foreach(get_group_entities($entity['entity_id']) as $group_entity_id)
1263        {
1264          $permissions[$group['entity_type']][$group_entity_id] = TRUE;
1265        }
1266        //break; // And also store self group permission in cache
1267      default:
1268        $permissions[$entity['entity_type']][$entity['entity_id']] = TRUE;
1269        break;
1270    }
1271  }
1272
1273  // Alerts
1274  $alert = array();
1275  foreach (dbFetchRows('SELECT `alert_table_id`, `device_id`, `entity_id`, `entity_type` FROM `alert_table`') as $alert_table_entry)
1276  {
1277    //r($alert_table_entry);
1278    if (is_entity_permitted($alert_table_entry['entity_id'], $alert_table_entry['entity_type'], $alert_table_entry['device_id'], $permissions))
1279    {
1280      $alert[$alert_table_entry['alert_table_id']] = TRUE;
1281    }
1282  }
1283  if (count($alert))
1284  {
1285    $permissions['alert'] = $alert;
1286  }
1287
1288  return $permissions;
1289}
1290
1291/**
1292 * Return WEB client remote IP address.
1293 * In mostly cases (also by default) this is just $_SERVER['REMOTE_ADDR'],
1294 * but if config options ($config['web_remote_addr_header']) set, this can use specified HTTP headers
1295 *
1296 * @param boolean Use or not HTTP header specified in $config['web_remote_addr_header']
1297 * @return string IP address of remote client
1298 */
1299function get_remote_addr($use_http_header = FALSE)
1300{
1301  if ($use_http_header)
1302  {
1303    // Note, this headers is very dangerous for use as auth!
1304    switch ($config['web_remote_addr_header'])
1305    {
1306      case 'CF-Connecting-IP': // CloudFlare network
1307      case 'X-Real-IP':
1308      case 'Client-IP':
1309      case 'X-Forwarded-For':
1310        $header = 'HTTP_' . strtoupper(str_replace('-', '_', $config['web_remote_addr_header']));
1311        if (!empty($_SERVER[$header]) && preg_match(OBS_PATTERN_IP_FULL, $_SERVER[$header], $matches))
1312        {
1313          // HTTP header founded and it contains valid IP address
1314          return $matches[1];
1315        }
1316        break;
1317    }
1318  }
1319
1320  // By default just use server remote address
1321  return $_SERVER['REMOTE_ADDR'];
1322}
1323
1324/**
1325 * Every time you call session_start(), PHP adds another
1326 * identical session cookie to the response header. Do this
1327 * enough times, and your response header becomes big enough
1328 * to choke the web server.
1329 *
1330 * This method clears out the duplicate session cookies. You can
1331 * call it after each time you've called session_start(), or call it
1332 * just before you send your headers.
1333 */
1334function clear_duplicate_cookies() {
1335  // If headers have already been sent, there's nothing we can do
1336  if (headers_sent()) {
1337    return;
1338  }
1339
1340  $cookies = array();
1341  foreach (headers_list() as $header) {
1342    // Identify cookie headers
1343    if (strpos($header, 'Set-Cookie:') === 0) {
1344      $cookies[] = $header;
1345    }
1346  }
1347  // Removes all cookie headers, including duplicates
1348  header_remove('Set-Cookie');
1349
1350  // Restore one copy of each cookie
1351  foreach(array_unique($cookies) as $cookie) {
1352    header($cookie, false);
1353  }
1354}
1355
1356/**
1357 * Store cached device/port/etc permitted IDs into $_SESSION['cache']
1358 *
1359 * IDs collected in html/includes/cache-data.inc.php
1360 * This function used mostly in print_search() or print_form(), see html/includes/print/search.inc.php
1361 * Cached IDs from $_SESSION used in ajax forms by generate_query_permitted()
1362 *
1363 * @return null
1364 */
1365function permissions_cache_session()
1366{
1367  if (!$_SESSION['authenticated']) { return; }
1368
1369  if (isset($GLOBALS['permissions_cached_session'])) { return; } // skip if this function already run. FIXME?
1370
1371  @session_start(); // Re-enable write to session
1372
1373  // Store device IDs in SESSION var for use to check permissions with ajax queries
1374  foreach (array('permitted', 'disabled', 'ignored') as $key)
1375  {
1376    $_SESSION['cache']['devices'][$key] = $GLOBALS['cache']['devices'][$key];
1377  }
1378
1379  // Store port IDs in SESSION var for use to check permissions with ajax queries
1380  foreach (array('permitted', 'deleted', 'errored', 'ignored', 'poll_disabled', 'device_disabled', 'device_ignored') as $key)
1381  {
1382    $_SESSION['cache']['ports'][$key] = $GLOBALS['cache']['ports'][$key];
1383  }
1384
1385  $GLOBALS['permissions_cached_session'] = TRUE;
1386
1387  session_commit(); // Write and close session
1388}
1389
1390// TESTME needs unit testing
1391// DOCME needs phpdoc block
1392function bill_permitted($bill_id)
1393{
1394  global $permissions;
1395
1396  if ($_SESSION['userlevel'] >= "5")
1397  {
1398    $allowed = TRUE;
1399  } elseif ($permissions['bill'][$bill_id]) {
1400    $allowed = TRUE;
1401  } else {
1402    $allowed = FALSE;
1403  }
1404
1405  return $allowed;
1406}
1407
1408
1409
1410// TESTME needs unit testing
1411// MOVEME to includes/functions.inc.php
1412/**
1413 * Returns a device_id when given an entity_id and an entity_type. Returns FALSE if the device isn't found.
1414 *
1415 * @param $entity_id
1416 * @param $entity_type
1417 *
1418 * @return bool|integer
1419 */
1420function get_device_id_by_entity_id($entity_id, $entity_type)
1421{
1422  // $entity = get_entity_by_id_cache($entity_type, $entity_id);
1423  $translate = entity_type_translate_array($entity_type);
1424
1425  if (isset($translate['device_id_field']) && $translate['device_id_field'] &&
1426      is_numeric($entity_id) && $entity_type)
1427  {
1428    $device_id = dbFetchCell('SELECT `' . $translate['device_id_field'] . '` FROM `' . $translate['table']. '` WHERE `' . $translate['id_field'] . '` = ?', array($entity_id));
1429  }
1430  if (is_numeric($device_id))
1431  {
1432    return $device_id;
1433  } else {
1434    return FALSE;
1435  }
1436}
1437
1438// TESTME needs unit testing
1439// DOCME needs phpdoc block
1440function port_permitted($port_id, $device_id = NULL)
1441{
1442  return is_entity_permitted($port_id, 'port', $device_id);
1443}
1444
1445// TESTME needs unit testing
1446// DOCME needs phpdoc block
1447function port_permitted_array(&$ports)
1448{
1449  // Strip out the ports the user isn't allowed to see, if they don't have global rights
1450  if ($_SESSION['userlevel'] < '7')
1451  {
1452    foreach ($ports as $key => $port)
1453    {
1454      if (!port_permitted($port['port_id'], $port['device_id']))
1455      {
1456        unset($ports[$key]);
1457      }
1458    }
1459  }
1460}
1461
1462function entity_permitted_array(&$entities, $entity_type)
1463{
1464
1465  $entity_type_data = entity_type_translate_array($entity_type);
1466
1467  // Strip out the entities the user isn't allowed to see, if they don't have global view rights
1468  if (!isset($_SESSION['user_limited']) || $_SESSION['user_limited'])
1469  {
1470    foreach ($entities as $key => $entity)
1471    {
1472      if (!is_entity_permitted($entity[$entity_type_data['id_field']], $entity_type, $entity['device_id']))
1473      {
1474        unset($entities[$key]);
1475      }
1476    }
1477  }
1478}
1479
1480
1481// TESTME needs unit testing
1482// DOCME needs phpdoc block
1483function application_permitted($app_id, $device_id = NULL)
1484{
1485  global $permissions;
1486
1487  if (is_numeric($app_id))
1488  {
1489    if (!$device_id) { $device_id = get_device_id_by_app_id ($app_id); }
1490    if ($_SESSION['userlevel'] >= "5") {
1491      $allowed = TRUE;
1492    } elseif (device_permitted($device_id)) {
1493      $allowed = TRUE;
1494    } elseif ($permissions['application'][$app_id]) {
1495      $allowed = TRUE;
1496    } else {
1497      $allowed = FALSE;
1498    }
1499  } else {
1500    $allowed = FALSE;
1501  }
1502
1503  return $allowed;
1504}
1505
1506// TESTME needs unit testing
1507// DOCME needs phpdoc block
1508function device_permitted($device_id)
1509{
1510  global $permissions;
1511
1512  if ($_SESSION['userlevel'] >= "5")
1513  {
1514    $allowed = true;
1515  } elseif ($permissions['device'][$device_id]) {
1516    $allowed = true;
1517  } else {
1518    $allowed = false;
1519  }
1520
1521  return $allowed;
1522}
1523
1524// TESTME needs unit testing
1525// DOCME needs phpdoc block
1526function print_graph_tag($args)
1527{
1528  echo(generate_graph_tag($args));
1529}
1530
1531// TESTME needs unit testing
1532// DOCME needs phpdoc block
1533function generate_graph_tag($args, $return_array = FALSE)
1534{
1535
1536  if (empty($args)) { return ''; } // Quick return if passed empty array
1537
1538  $style = 'max-width: 100%; width: auto; vertical-align: top;';
1539  if (isset($args['style']))
1540  {
1541    if (is_array($args['style']))
1542    {
1543      $style .= implode("; ", $args['style']) . ';';
1544    } else {
1545      $style .= $args['style'] . ';';
1546    }
1547    unset($args['style']);
1548  }
1549
1550  if (isset($args['img_id']))
1551  {
1552      $i['img_id'] = $args['img_id'];
1553  } else {
1554      $i['img_id'] = generate_random_string(8);
1555  }
1556
1557  // Detect allowed screen ratio for current browser
1558  $ua_info = detect_browser();
1559  $zoom = $ua_info['screen_ratio'];
1560
1561  if ($zoom >= 2)
1562  {
1563    // Add img srcset for HiDPI screens
1564    $args_x = $args;
1565    $args_x['zoom'] = $zoom;
1566    $srcset = ' srcset="'.generate_graph_url($args_x).' '.$args_x['zoom'].'x"';
1567    $i['srcset'] = $attribs;
1568  } else{
1569    $srcset = '';
1570  }
1571
1572  if(isset($args['class']))
1573  { $attribs .= ' class="'.$args['class'].'"'; unset($args['class']); }
1574
1575
1576  $img_url = generate_graph_url($args);
1577
1578  $i['img_url'] = $img_url;
1579  $i['img_tag'] = '<img id="' . $i['img_id'] . '" src="' . $img_url . '"' . $srcset . $attribs.' style="' . $style . '" alt="" />';
1580
1581
1582  if($return_array === TRUE)
1583  {
1584      return $i;
1585  } else {
1586      return $i['img_tag'];
1587  }
1588}
1589
1590
1591function generate_graph_url($args)
1592{
1593
1594  foreach ($args as $key => $arg)
1595  {
1596    if (is_array($arg)) { $arg = var_encode($arg); } // Encode arrays
1597    $urlargs[] = $key."=".$arg;
1598  }
1599
1600  $url = 'graph.php?' . implode('&amp;',$urlargs);
1601
1602  if (is_cli())
1603  {
1604    if ($GLOBALS['config']['web_url'] == 'http://localhost:80/')
1605    {
1606      // override default web_url by http://localhost/
1607      $url = 'http://'.get_localhost().'/'.$url;
1608    } else {
1609      $url = $GLOBALS['config']['web_url'] . $url;
1610    }
1611  }
1612
1613  return $url;
1614
1615}
1616
1617// TESTME needs unit testing
1618// DOCME needs phpdoc block
1619function generate_graph_js_state($args)
1620{
1621  // we are going to assume we know roughly what the graph url looks like here.
1622  // TODO: Add sensible defaults
1623  $from   = (is_numeric($args['from'])   ? $args['from']   : 0);
1624  $to     = (is_numeric($args['to'])     ? $args['to']     : 0);
1625  $width  = (is_numeric($args['width'])  ? $args['width']  : 0);
1626  $height = (is_numeric($args['height']) ? $args['height'] : 0);
1627  $legend = str_replace("'", "", $args['legend']);
1628
1629  $state = <<<STATE
1630<script type="text/javascript">
1631document.graphFrom = $from;
1632document.graphTo = $to;
1633document.graphWidth = $width;
1634document.graphHeight = $height;
1635document.graphLegend = '$legend';
1636</script>
1637STATE;
1638
1639  return $state;
1640}
1641
1642/**
1643 * Generate Percentage Bar
1644 *
1645 * This function generates an Observium percentage bar from a supplied array of arguments.
1646 * It is possible to draw a bar that does not work at all,
1647 * So care should be taken to make sure values are valid.
1648 *
1649 * @param array $args
1650 * @return string
1651 */
1652
1653// TESTME needs unit testing
1654function percentage_bar($args)
1655{
1656  if (strlen($args['bg']))     { $style .= 'background-color:'.$args['bg'].';'; }
1657  if (strlen($args['border'])) { $style .= 'border-color:'.$args['border'].';'; }
1658  if (strlen($args['width']))  { $style .= 'width:'.$args['width'].';'; }
1659  if (strlen($args['text_c'])) { $style_b .= 'color:'.$args['text_c'].';'; }
1660
1661  $total = '0';
1662  $output = '<div class="percbar" style="'.$style.'">';
1663  foreach ($args['bars'] as $bar)
1664  {
1665    $output .= '<div class="bar" style="width:'.$bar['percent'].'%; background-color:'.$bar['colour'].';"></div>';
1666    $total += $bar['percent'];
1667  }
1668  $left = '100' - $total;
1669  if ($left > 0) { $output .= '<div class="bar" style="width:'.$left.'%;"></div>'; }
1670
1671  if ($left >= 0) { $output .= '<div class="bar-text" style="margin-left: -100px; margin-top: 0px; float: right; text-align: right; '.$style_b.'">'.$args['text'].'</div>'; }
1672
1673  foreach ($args['bars'] as $bar)
1674  {
1675    $output .= '<div class="bar-text" style="width:'.$bar['percent'].'%; max-width:'.$bar['percent'].'%; padding-left: 4px;">'.$bar['text'].'</div>';
1676  }
1677#  if ($left > '0') { $output .= '<div class="bar-text" style="margin-left: -100px; margin-top: -16px; float: right; text-align: right; '.$style_b.'">'.$args['text'].'</div>'; }
1678
1679  $output .= '</div>';
1680
1681  return $output;
1682}
1683
1684// Legacy function
1685// DO NOT USE THIS. Please replace instances of it with percentage_bar from above.
1686// TESTME needs unit testing
1687// DOCME needs phpdoc block
1688function print_percentage_bar($width, $height, $percent, $left_text, $left_colour, $left_background, $right_text, $right_colour, $right_background)
1689{
1690
1691  if ($percent > "100") { $size_percent = "100"; } else { $size_percent = $percent; }
1692
1693  $percentage_bar['border']  = "#".$left_background;
1694  $percentage_bar['bg']      = "#".$right_background;
1695  $percentage_bar['width']   = $width;
1696  $percentage_bar['text']    = $right_text;
1697  $percentage_bar['bars'][0] = array('percent' => $size_percent, 'colour' => '#'.$left_background, 'text' => $left_text);
1698
1699  $output = percentage_bar($percentage_bar);
1700
1701  return $output;
1702}
1703
1704// DOCME needs phpdoc block
1705function print_optionbar_start($height = 0, $width = 0, $marginbottom = 5)
1706{
1707   echo(PHP_EOL . '<div class="box box-solid well-shaded">' . PHP_EOL);
1708}
1709
1710// DOCME needs phpdoc block
1711function print_optionbar_end()
1712{
1713  echo(PHP_EOL . '  </div>' . PHP_EOL);
1714}
1715
1716// TESTME needs unit testing
1717// DOCME needs phpdoc block
1718function geteventicon($message)
1719{
1720  if ($message == "Device status changed to Down") { $icon = "server_connect.png"; }
1721  if ($message == "Device status changed to Up") { $icon = "server_go.png"; }
1722  if ($message == "Interface went down" || $message == "Interface changed state to Down") { $icon = "if-disconnect.png"; }
1723  if ($message == "Interface went up" || $message == "Interface changed state to Up") { $icon = "if-connect.png"; }
1724  if ($message == "Interface disabled") { $icon = "if-disable.png"; }
1725  if ($message == "Interface enabled") { $icon = "if-enable.png"; }
1726  if (isset($icon)) { return $icon; } else { return false; }
1727}
1728
1729function get_entity_icon($entity)
1730{
1731
1732  $config['entities'][$entity['type']]['icon'];
1733
1734}
1735
1736// TESTME needs unit testing
1737// DOCME needs phpdoc block
1738function overlibprint($text)
1739{
1740  return "onmouseover=\"return overlib('" . $text . "');\" onmouseout=\"return nd();\"";
1741}
1742
1743// TESTME needs unit testing
1744// DOCME needs phpdoc block
1745function device_link_class($device)
1746{
1747  if (isset($device['status']) && $device['status'] == '0') { $class = "red"; } else { $class = ""; }
1748  if (isset($device['ignore']) && $device['ignore'] == '1')
1749  {
1750     $class = "grey";
1751     if (isset($device['status']) && $device['status'] == '1') { $class = "green"; }
1752  }
1753  if (isset($device['disabled']) && $device['disabled'] == '1') { $class = "grey"; }
1754
1755  return $class;
1756}
1757
1758/**
1759 * Return cached locations list
1760 *
1761 * If filter used, return locations avialable only for specified params.
1762 * Without filter return all avialable locations (cached)
1763 *
1764 * @param array $filter
1765 * @return array
1766 */
1767// TESTME needs unit testing
1768function get_locations($filter = array())
1769{
1770  foreach ($filter as $var => $value)
1771  {
1772    switch ($var)
1773    {
1774      case 'location_lat':
1775      case 'location_lon':
1776      case 'location_country':
1777      case 'location_state':
1778      case 'location_county':
1779      case 'location_city':
1780        // Check geo params only when GEO enabled globally
1781        if (!$GLOBALS['config']['geocoding']['enable']) { break; }
1782      case 'location':
1783        $where_array[$var] = generate_query_values($value, $var);
1784        break;
1785    }
1786  }
1787
1788  if (count($where_array))
1789  {
1790    // Return only founded locations
1791    $where = implode('', $where_array) . $GLOBALS['cache']['where']['devices_permitted'];
1792    $locations = dbFetchColumn("SELECT DISTINCT `location` FROM `devices_locations` WHERE 1 $where;");
1793  } else {
1794    $locations = array();
1795    foreach ($GLOBALS['cache']['device_locations'] as $location => $count)
1796    {
1797      $locations[] = $location;
1798    }
1799  }
1800  sort($locations);
1801
1802  return $locations;
1803}
1804
1805// TESTME needs unit testing
1806// DOCME needs phpdoc block
1807function foldersize($path)
1808{
1809  $total_size = 0;
1810  $files = scandir($path);
1811  $total_files = 0;
1812
1813  foreach ($files as $t)
1814  {
1815    if (is_dir(rtrim($path, '/') . '/' . $t))
1816    {
1817      if ($t<>"." && $t<>"..")
1818      {
1819        $size = foldersize(rtrim($path, '/') . '/' . $t);
1820        $total_size += $size;
1821      }
1822    } else {
1823      $size = filesize(rtrim($path, '/') . '/' . $t);
1824      $total_size += $size;
1825      $total_files++;
1826    }
1827  }
1828
1829  return array($total_size, $total_files);
1830}
1831
1832// return the filename of the device RANCID config file
1833// TESTME needs unit testing
1834// DOCME needs phpdoc block
1835function get_rancid_filename($hostname, $rdebug = FALSE)
1836{
1837  global $config;
1838
1839  $hostnames = array($hostname);
1840
1841  if ($rdebug) { echo("Hostname: $hostname<br />"); }
1842
1843  // Also check non-FQDN hostname.
1844  list($shortname) = explode('.', $hostname);
1845
1846  if ($rdebug) { echo("Short hostname: $shortname<br />"); }
1847
1848  if ($shortname != $hostname)
1849  {
1850    $hostnames[] = $shortname;
1851    if ($rdebug) { echo("Hostname different from short hostname, looking for both<br />"); }
1852  }
1853
1854  // Addition of a domain suffix for non-FQDN device names.
1855  if (isset($config['rancid_suffix']) && $config['rancid_suffix'] !== '')
1856  {
1857    $hostnames[] = $hostname . '.' . trim($config['rancid_suffix'], ' .');
1858    if ($rdebug) { echo("RANCID suffix configured, also looking for " . $hostname . '.' . trim($config['rancid_suffix'], ' .') . "<br />"); }
1859  }
1860
1861  foreach ($config['rancid_configs'] as $config_path)
1862  {
1863    if ($config_path[strlen($config_path)-1] != '/') { $config_path .= '/'; }
1864    if ($rdebug) { echo("Looking in configured directory: <b>$config_path</b><br />"); }
1865
1866    foreach ($hostnames as $host)
1867    {
1868      if (is_file($config_path . $host))
1869      {
1870        if ($rdebug) { echo("File <b>" . $config_path . $host . "</b> found.<br />"); }
1871        return $config_path . $host;
1872      } else {
1873        if ($rdebug) { echo("File <b>" . $config_path . $host . "</b> not found.<br />"); }
1874      }
1875    }
1876  }
1877
1878  return FALSE;
1879}
1880
1881// return the filename of the device NFSEN rrd file
1882// TESTME needs unit testing
1883// DOCME needs phpdoc block
1884function get_nfsen_filename($hostname)
1885{
1886  global $config;
1887
1888  $nfsen_rrds = (is_array($config['nfsen_rrds']) ? $config['nfsen_rrds'] : array($config['nfsen_rrds']));
1889  foreach ($nfsen_rrds as $nfsen_rrd)
1890  {
1891    if ($nfsen_rrd[strlen($nfsen_rrd)-1] != '/') { $nfsen_rrd .= '/'; }
1892    $basefilename_underscored = preg_replace('/\./', $config['nfsen_split_char'], $hostname);
1893
1894    // Remove suffix and prefix from basename
1895    $nfsen_filename = $basefilename_underscored;
1896    if (isset($config['nfsen_suffix']) && strlen($config['nfsen_suffix']))
1897    {
1898      $nfsen_filename = (strstr($nfsen_filename, $config['nfsen_suffix'], TRUE));
1899    }
1900    if (isset($config['nfsen_prefix']) && strlen($config['nfsen_prefix']))
1901    {
1902      $nfsen_filename = (strstr($nfsen_filename, $config['nfsen_prefix']));
1903    }
1904
1905    $nfsen_rrd_file = $nfsen_rrd . $nfsen_filename . '.rrd';
1906    if (is_file($nfsen_rrd_file))
1907    {
1908      return $nfsen_rrd_file;
1909    }
1910  }
1911
1912  return FALSE;
1913}
1914
1915// Note, by default text NOT escaped.
1916// TESTME needs unit testing
1917// DOCME needs phpdoc block
1918function generate_ap_link($args, $text = NULL, $type = NULL, $escape = FALSE)
1919{
1920  global $config;
1921
1922  humanize_port($args);
1923
1924  if (!$text) { $text = escape_html($args['port_label']); }
1925  if ($type) { $args['graph_type'] = $type; }
1926  if (!isset($args['graph_type'])) { $args['graph_type'] = 'port_bits'; }
1927
1928  if (!isset($args['hostname'])) { $args = array_merge($args, device_by_id_cache($args['device_id'])); }
1929
1930  $content = "<div class=entity-title>". $args['text'] . " - " . escape_html($args['port_label']) . "</div>";
1931  if ($args['ifAlias']) { $content .= escape_html($args['ifAlias']) . "<br />"; }
1932  $content .= "<div style=\'width: 850px\'>";
1933  $graph_array['type']     = $args['graph_type'];
1934  $graph_array['legend']   = "yes";
1935  $graph_array['height']   = "100";
1936  $graph_array['width']    = "340";
1937  $graph_array['to']       = $config['time']['now'];
1938  $graph_array['from']     = $config['time']['day'];
1939  $graph_array['id']       = $args['accesspoint_id'];
1940  $content .= generate_graph_tag($graph_array);
1941  $graph_array['from']     = $config['time']['week'];
1942  $content .= generate_graph_tag($graph_array);
1943  $graph_array['from']     = $config['time']['month'];
1944  $content .= generate_graph_tag($graph_array);
1945  $graph_array['from']     = $config['time']['year'];
1946  $content .= generate_graph_tag($graph_array);
1947  $content .= "</div>";
1948
1949  $url = generate_ap_url($args);
1950  if (port_permitted($args['interface_id'], $args['device_id']))
1951  {
1952    return overlib_link($url, $text, $content, $class, $escape);
1953  } else {
1954    return $text;
1955  }
1956}
1957
1958// TESTME needs unit testing
1959// DOCME needs phpdoc block
1960function generate_ap_url($ap, $vars=array())
1961{
1962  return generate_url(array('page' => 'device', 'device' => $ap['device_id'], 'tab' => 'accesspoint', 'ap' => $ap['accesspoint_id']), $vars);
1963}
1964
1965/**
1966 * Generate SQL WHERE string with check permissions and ignores for device_id, port_id and other
1967 *
1968 * Note, this function uses comparison operator IN. Max number of values in the IN list
1969 * is limited by the 'max_allowed_packet' option (default: 1048576)
1970 *
1971 * Usage examples:
1972 *  generate_query_permitted()
1973 *   ' AND `device_id` IN (1,4,8,33) AND `device_id` NOT IN (66) AND (`device_id` != '' AND `device_id` IS NOT NULL) '
1974 *  generate_query_permitted(array('device'), array('device_table' => 'D'))
1975 *   ' AND `D`.`device_id` IN (1,4,8,33) AND `D`.`device_id` NOT IN (66) AND (`D`.`device_id` != '' AND `D`.`device_id` IS NOT NULL) '
1976 *  generate_query_permitted(array('device', 'port'), array('port_table' => 'I')) ==
1977 *   ' AND `device_id` IN (1,4,8,33) AND `device_id` NOT IN (66) AND (`device_id` != '' AND `device_id` IS NOT NULL)
1978 *     AND `I`.`port_id` IN (1,4,8,33) AND `I`.`port_id` NOT IN (66) AND (`I`.`port_id` != '' AND `I`.`port_id` IS NOT NULL) '
1979 *  generate_query_permitted(array('device', 'port'), array('port_table' => 'I', 'hide_ignored' => TRUE))
1980 *    This additionaly exclude all ignored devices and ports
1981 *
1982 * @uses html/includes/cache-data.inc.php
1983 * @global integer $_SESSION['userlevel']
1984 * @global boolean $GLOBALS['config']['web_show_disabled']
1985 * @global array $GLOBALS['permissions']
1986 * @global array $GLOBALS['cache']['devices']
1987 * @global array $GLOBALS['cache']['ports']
1988 * @global string $GLOBALS['vars']['page']
1989 * @param array|string $type_array Array with permission types, currently allowed 'devices', 'ports'
1990 * @param array $options Options for each permission type: device_table, port_table, hide_ignored, hide_disabled
1991 * @return string
1992 */
1993// TESTME needs unit testing
1994function generate_query_permitted($type_array = array('device'), $options = array())
1995{
1996  if (!is_array($type_array)) { $type_array = array($type_array); }
1997  $user_limited = ($_SESSION['userlevel'] < 5 ? TRUE : FALSE);
1998  $page = $GLOBALS['vars']['page'];
1999
2000  // If device IDs stored in SESSION use it (used in ajax)
2001  //if (!isset($GLOBALS['cache']['devices']) && isset($_SESSION['cache']['devices']))
2002  //{
2003  //  $GLOBALS['cache']['devices'] = $_SESSION['cache']['devices'];
2004  //}
2005
2006  if (!isset($GLOBALS['permissions']))
2007  {
2008    // Note, this function must used after load permissions list!
2009    print_error("Function ".__FUNCTION__."() on page '$page' called before include cache-data.inc.php or something wrong with caching permissions. Please report this to developers!");
2010  }
2011  // Use option hide_disabled if passed or use config
2012  $options['hide_disabled'] = (isset($options['hide_disabled']) ? (bool)$options['hide_disabled'] : !$GLOBALS['config']['web_show_disabled']);
2013
2014  //$query_permitted = '';
2015
2016  foreach ($type_array as $type)
2017  {
2018    switch ($type)
2019    {
2020      // Devices permission query
2021      case 'device':
2022      case 'devices':
2023        $column = '`device_id`';
2024        $query_permitted = array();
2025        if (isset($options['device_table'])) { $column = '`'.$options['device_table'].'`.'.$column; }
2026
2027        // Show only permitted devices
2028        if ($user_limited)
2029        {
2030          if (count($GLOBALS['permissions']['device']))
2031          {
2032            $query_permitted[] = " $column IN (".
2033                                 implode(',', array_keys($GLOBALS['permissions']['device'])).
2034                                 ')';
2035
2036          } else {
2037            // Exclude all entries, because there is no permitted devices
2038            $query_permitted[] = ' 0';
2039          }
2040        }
2041
2042        // Also don't show ignored and disabled devices (except on 'device' and 'devices' pages)
2043        $devices_excluded = array();
2044        if (strpos($page, 'device') !== 0)
2045        {
2046          if ($options['hide_ignored'] && count($GLOBALS['cache']['devices']['ignored']))
2047          {
2048            $devices_excluded = array_merge($devices_excluded, $GLOBALS['cache']['devices']['ignored']);
2049          }
2050          if ($options['hide_disabled'] && count($GLOBALS['cache']['devices']['disabled']))
2051          {
2052            $devices_excluded = array_merge($devices_excluded, $GLOBALS['cache']['devices']['disabled']);
2053          }
2054        }
2055        if (count($devices_excluded))
2056        {
2057          // Set query with excluded devices
2058          $query_permitted[] = " $column NOT IN (".
2059                               implode(',', array_unique($devices_excluded)).
2060                               ')';
2061        }
2062
2063        // At the end excluded entries with empty/null device_id (wrong entries)
2064        //$query_permitted[] = " ($column != '' AND $column IS NOT NULL)";
2065        $query_permitted[] = " $column IS NOT NULL"; // Note: SELECT '' = 0; is TRUE
2066        $query_part[] = implode(" AND ", $query_permitted);
2067        unset($query_permitted);
2068        break;
2069      // Ports permission query
2070      case 'port':
2071      case 'ports':
2072        $column = '`port_id`';
2073        if (isset($options['port_table'])) { $column = '`'.$options['port_table'].'`.'.$column; }
2074
2075        // If port IDs stored in SESSION use it (used in ajax)
2076        //if (!isset($GLOBALS['cache']['ports']) && isset($_SESSION['cache']['ports']))
2077        //{
2078        //  $GLOBALS['cache']['ports'] = $_SESSION['cache']['ports'];
2079        //}
2080
2081        // Show only permitted ports
2082        if ($user_limited)
2083        {
2084          if (count($GLOBALS['permissions']['port']))
2085          {
2086            $query_permitted[] = " $column IN (" .
2087                                 implode(',', array_keys($GLOBALS['permissions']['port'])) .
2088                                 ')';
2089          } else {
2090            // Exclude all entries, because there is no permitted ports
2091            $query_permitted[] = '0';
2092          }
2093        }
2094
2095        $ports_excluded = array();
2096        // Don't show ports with disabled polling.
2097        if (count($GLOBALS['cache']['ports']['poll_disabled']))
2098        {
2099          $ports_excluded = array_merge($ports_excluded, $GLOBALS['cache']['ports']['poll_disabled']);
2100          //foreach ($GLOBALS['cache']['ports']['poll_disabled'] as $entry)
2101          //{
2102          //  $ports_excluded[] = $entry;
2103          //}
2104          //$ports_excluded = array_unique($ports_excluded);
2105        }
2106        // Don't show deleted ports (except on 'deleted-ports' page)
2107        if ($page != 'deleted-ports' && count($GLOBALS['cache']['ports']['deleted']))
2108        {
2109          $ports_excluded = array_merge($ports_excluded, $GLOBALS['cache']['ports']['deleted']);
2110          //foreach ($GLOBALS['cache']['ports']['deleted'] as $entry)
2111          //{
2112          //  $ports_excluded[] = $entry;
2113          //}
2114          //$ports_excluded = array_unique($ports_excluded);
2115        }
2116        if ($page != 'device' && !in_array('device', $type_array))
2117        {
2118          // Don't show ports for disabled devices (except on 'device' page or if 'device' permissions already queried)
2119          if ($options['hide_disabled'] && !$user_limited && count($GLOBALS['cache']['ports']['device_disabled']))
2120          {
2121            $ports_excluded = array_merge($ports_excluded, $GLOBALS['cache']['ports']['device_disabled']);
2122            //foreach ($GLOBALS['cache']['ports']['device_disabled'] as $entry)
2123            //{
2124            //  $ports_excluded[] = $entry;
2125            //}
2126            //$ports_excluded = array_unique($ports_excluded);
2127          }
2128          // Don't show ports for ignored devices (except on 'device' page)
2129          if ($options['hide_ignored'] && count($GLOBALS['cache']['ports']['device_ignored']))
2130          {
2131            $ports_excluded = array_merge($ports_excluded, $GLOBALS['cache']['ports']['device_ignored']);
2132            //foreach ($GLOBALS['cache']['ports']['device_ignored'] as $entry)
2133            //{
2134            //  $ports_excluded[] = $entry;
2135            //}
2136            //$ports_excluded = array_unique($ports_excluded);
2137          }
2138        }
2139        // Don't show ignored ports (only on some pages!)
2140        if (($page == 'overview' || $options['hide_ignored']) && count($GLOBALS['cache']['ports']['ignored']))
2141        {
2142          $ports_excluded = array_merge($ports_excluded, $GLOBALS['cache']['ports']['ignored']);
2143          //foreach ($GLOBALS['cache']['ports']['ignored'] as $entry)
2144          //{
2145          //  $ports_excluded[] = $entry;
2146          //}
2147          //$ports_excluded = array_unique($ports_excluded);
2148        }
2149        unset($entry);
2150        if (count($ports_excluded))
2151        {
2152          // Set query with excluded ports
2153          $query_permitted[] = $column . " NOT IN (".
2154                             implode(',', array_unique($ports_excluded)).
2155                             ')';
2156
2157        }
2158
2159        // At the end excluded entries with empty/null port_id (wrong entries)
2160        //$query_permitted[] = "($column != '' AND $column IS NOT NULL)";
2161        $query_permitted[] = "$column IS NOT NULL";
2162
2163        $query_part[] = implode(" AND ", $query_permitted);
2164        unset($query_permitted);
2165
2166        break;
2167      case 'sensor':
2168      case 'sensors':
2169        // For sensors
2170        // FIXME -- this is easily generifyable, just use translate_table_array()
2171
2172        $column = '`sensor_id`';
2173
2174        if (isset($options['sensor_table'])) { $column = '`'.$options['sensor_table'].'`.'.$column; }
2175
2176        // If IDs stored in SESSION use it (used in ajax)
2177        //if (!isset($GLOBALS['cache']['sensors']) && isset($_SESSION['cache']['sensors']))
2178        //{
2179        //  $GLOBALS['cache']['sensors'] = $_SESSION['cache']['sensors'];
2180        //}
2181
2182        // Show only permitted entities
2183        if ($user_limited)
2184        {
2185          if (count($GLOBALS['permissions']['sensor']))
2186          {
2187            $query_permitted .= " $column IN (";
2188            $query_permitted .= implode(',', array_keys($GLOBALS['permissions']['sensor']));
2189            $query_permitted .= ')';
2190          } else {
2191            // Exclude all entries, because there are no permitted entities
2192            $query_permitted .= '0';
2193          }
2194          $query_part[] = $query_permitted;
2195          unset($query_permitted);
2196        }
2197
2198        break;
2199
2200      case 'alert':
2201      case 'alerts':
2202        // For generic alert
2203
2204        $column = '`alert_table_id`';
2205
2206        // Show only permitted entities
2207        if ($user_limited)
2208        {
2209          if (count($GLOBALS['permissions']['alert']))
2210          {
2211            $query_permitted .= " $column IN (";
2212            $query_permitted .= implode(',', array_keys($GLOBALS['permissions']['alert']));
2213            $query_permitted .= ')';
2214          } else {
2215            // Exclude all entries, because there are no permitted entities
2216            $query_permitted .= '0';
2217          }
2218          $query_part[] = $query_permitted;
2219          unset($query_permitted);
2220        }
2221
2222        break;
2223      case 'bill':
2224      case 'bills':
2225        // For bills
2226        break;
2227    }
2228  }
2229  if (count($query_part))
2230  {
2231    //r($query_part);
2232    if ($user_limited)
2233    {
2234      // Limited user must use OR for include multiple entities
2235      $query_permitted = " AND ((".implode(") OR (", $query_part)."))";
2236    } else {
2237      // Unlimited used must use AND for exclude multiple hidden entities
2238      $query_permitted = " AND ((".implode(") AND (", $query_part)."))";
2239    }
2240  }
2241
2242  $query_permitted .= ' ';
2243
2244  //r($query_permitted);
2245
2246  return $query_permitted;
2247}
2248
2249// TESTME needs unit testing
2250// DOCME needs phpdoc block
2251function get_user_prefs($user_id)
2252{
2253  $prefs = array();
2254  foreach (dbFetchRows("SELECT * FROM `users_prefs` WHERE `user_id` = ?", array($user_id)) as $entry)
2255  {
2256    $prefs[$entry['pref']] = $entry;
2257  }
2258  return $prefs;
2259}
2260
2261// TESTME needs unit testing
2262// DOCME needs phpdoc block
2263function get_user_pref($user_id, $pref)
2264{
2265  if ($entry = dbFetchRow("SELECT `value` FROM `users_prefs` WHERE `user_id` = ? AND `pref` = ?", array($user_id, $pref)))
2266  {
2267    return $entry['value'];
2268  }
2269  else
2270  {
2271    return NULL;
2272  }
2273}
2274
2275// TESTME needs unit testing
2276// DOCME needs phpdoc block
2277function set_user_pref($user_id, $pref, $value)
2278{
2279  //if (dbFetchCell("SELECT COUNT(*) FROM `users_prefs` WHERE `user_id` = ? AND `pref` = ?", array($user_id, $pref)))
2280  if (dbExist('users_prefs', '`user_id` = ? AND `pref` = ?', array($user_id, $pref)))
2281  {
2282    $id = dbUpdate(array('value' => $value), 'users_prefs', '`user_id` = ? AND `pref` = ?', array($user_id, $pref));
2283  } else {
2284    $id = dbInsert(array('user_id' => $user_id, 'pref' => $pref, 'value' => $value), 'users_prefs');
2285  }
2286  return $id;
2287}
2288
2289// TESTME needs unit testing
2290// DOCME needs phpdoc block
2291function del_user_pref($user_id, $pref)
2292{
2293  return dbDelete('users_prefs', "`user_id` = ? AND `pref` = ?", array($user_id, $pref));
2294}
2295
2296// TESTME needs unit testing
2297// DOCME needs phpdoc block
2298function get_smokeping_files($rdebug = 0)
2299{
2300  global $config;
2301
2302  $smokeping_files = array();
2303
2304  if ($rdebug) { echo('- Recursing through ' . $config['smokeping']['dir'] . '<br />'); }
2305
2306  if (isset($config['smokeping']['master_hostname']))
2307  {
2308    $master_hostname = $config['smokeping']['master_hostname'];
2309  } else {
2310    $master_hostname = $config['own_hostname'];
2311  }
2312
2313  if (is_dir($config['smokeping']['dir']))
2314  {
2315    foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($config['smokeping']['dir'])) as $file)
2316    {
2317      if (basename($file) != "." && basename($file) != ".." && strstr($file, ".rrd"))
2318      {
2319        if ($rdebug) { echo('- Found file ending in ".rrd": ' . basename($file) . '<br />'); }
2320
2321        if (strstr($file, "~"))
2322        {
2323          list($target,$slave) = explode("~", basename($file,".rrd"));
2324          if ($rdebug) { echo('- Determined to be a slave file for target <b>' . $target . '</b><br />'); }
2325          $target = str_replace($config['smokeping']['split_char'], ".", $target);
2326          if ($config['smokeping']['suffix']) { $target = $target.$config['smokeping']['suffix']; if ($rdebug) { echo('- Suffix is configured, target is now <b>' . $target . '</b><br />'); } }
2327          $smokeping_files['incoming'][$target][$slave] = $file;
2328          $smokeping_files['outgoing'][$slave][$target] = $file;
2329        } else {
2330          $target = basename($file,".rrd");
2331          if ($rdebug) { echo('- Determined to be a local file, for target <b>' . $target . '</b><br />'); }
2332          $target = str_replace($config['smokeping']['split_char'], ".", $target);
2333          if ($rdebug) { echo('- After replacing configured split_char ' . $config['smokeping']['split_char'] . ' by . target is <b>' . $target . '</b><br />'); }
2334          if ($config['smokeping']['suffix']) { $target = $target.$config['smokeping']['suffix']; if ($rdebug) { echo('- Suffix is configured, target is now <b>' . $target . '</b><br />'); } }
2335          $smokeping_files['incoming'][$target][$master_hostname] = $file;
2336          $smokeping_files['outgoing'][$master_hostname][$target] = $file;
2337        }
2338      }
2339    }
2340  } else {
2341    if ($rdebug) { echo("- Smokeping RRD directory not found: " . $config['smokeping']['dir']); }
2342  }
2343
2344  return $smokeping_files;
2345}
2346
2347/**
2348 * Darkens or lightens a colour
2349 * Found via http://codepad.org/MTGLWVd0
2350 *
2351 * First argument is the colour in hex, second argument is how dark it should be 1=same, 2=50%
2352 *
2353 * @return string
2354 * @param string $rgb
2355 * @param int $darker
2356 */
2357function darken_color($rgb, $darker=2)
2358{
2359  if (strpos($rgb, '#') !== FALSE)
2360  {
2361    $hash = '#';
2362    $rgb  = str_replace('#', '', $rgb);
2363  } else {
2364    $hash = '';
2365  }
2366  $len  = strlen($rgb);
2367  if ($len == 6) {} // Passed RGB
2368  else if ($len == 8)
2369  {
2370    // Passed RGBA, remove alpha channel
2371    $rgb = substr($rgb, 0, 6);
2372  } else {
2373    $rgb = FALSE;
2374  }
2375
2376  if ($rgb === FALSE) { return $hash.'000000'; }
2377
2378  $darker = ($darker > 1) ? $darker : 1;
2379
2380  list($R16, $G16, $B16) = str_split($rgb, 2);
2381
2382  $R = sprintf("%02X", floor(hexdec($R16) / $darker));
2383  $G = sprintf("%02X", floor(hexdec($G16) / $darker));
2384  $B = sprintf("%02X", floor(hexdec($B16) / $darker));
2385
2386  return $hash.$R.$G.$B;
2387}
2388
2389// Originally from http://stackoverflow.com/questions/6054033/pretty-printing-json-with-php/21162086#21162086
2390// FIXME : This is only required for PHP < 5.4, remove this when our requirements are >= 5.4
2391if (!defined('JSON_UNESCAPED_SLASHES')) { define('JSON_UNESCAPED_SLASHES', 64); }
2392if (!defined('JSON_PRETTY_PRINT'))      { define('JSON_PRETTY_PRINT', 128); }
2393if (!defined('JSON_UNESCAPED_UNICODE')) { define('JSON_UNESCAPED_UNICODE', 256); }
2394
2395function json_output($status, $message)
2396{
2397  header("Content-type: application/json; charset=utf-8");
2398  echo json_encode(array("status" => $status, "message" => $message));
2399
2400  exit();
2401}
2402
2403function _json_encode($data, $options = 448)
2404{
2405  if (version_compare(PHP_VERSION, '5.4', '>='))
2406  {
2407    return json_encode($data, $options);
2408  } else {
2409    return _json_format(json_encode($data), $options);
2410  }
2411}
2412
2413function _json_format($json, $options = 448)
2414{
2415  $prettyPrint = (bool) ($options & JSON_PRETTY_PRINT);
2416  $unescapeUnicode = (bool) ($options & JSON_UNESCAPED_UNICODE);
2417  $unescapeSlashes = (bool) ($options & JSON_UNESCAPED_SLASHES);
2418  if (!$prettyPrint && !$unescapeUnicode && !$unescapeSlashes)
2419  {
2420    return $json;
2421  }
2422  $result = '';
2423  $pos = 0;
2424  $strLen = strlen($json);
2425  $indentStr = ' ';
2426  $newLine = "\n";
2427  $outOfQuotes = true;
2428  $buffer = '';
2429  $noescape = true;
2430  for ($i = 0; $i < $strLen; $i++)
2431  {
2432    // Grab the next character in the string
2433    $char = substr($json, $i, 1);
2434    // Are we inside a quoted string?
2435    if ('"' === $char && $noescape)
2436    {
2437      $outOfQuotes = !$outOfQuotes;
2438    }
2439    if (!$outOfQuotes)
2440    {
2441      $buffer .= $char;
2442      $noescape = '\\' === $char ? !$noescape : true;
2443      continue;
2444    }
2445    elseif ('' !== $buffer)
2446    {
2447      if ($unescapeSlashes)
2448      {
2449        $buffer = str_replace('\\/', '/', $buffer);
2450      }
2451      if ($unescapeUnicode && function_exists('mb_convert_encoding'))
2452      {
2453        // http://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha
2454        $buffer = preg_replace_callback('/\\\\u([0-9a-f]{4})/i',
2455          function ($match)
2456          {
2457            return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE');
2458          }, $buffer);
2459      }
2460      $result .= $buffer . $char;
2461      $buffer = '';
2462      continue;
2463    }
2464    elseif(false !== strpos(" \t\r\n", $char))
2465    {
2466      continue;
2467    }
2468    if (':' === $char)
2469    {
2470      // Add a space after the : character
2471      $char .= ' ';
2472    }
2473    elseif (('}' === $char || ']' === $char))
2474    {
2475      $pos--;
2476      $prevChar = substr($json, $i - 1, 1);
2477      if ('{' !== $prevChar && '[' !== $prevChar)
2478      {
2479        // If this character is the end of an element,
2480        // output a new line and indent the next line
2481        $result .= $newLine;
2482        for ($j = 0; $j < $pos; $j++)
2483        {
2484          $result .= $indentStr;
2485        }
2486      }
2487      else
2488      {
2489        // Collapse empty {} and []
2490        $result = rtrim($result) . "\n\n" . $indentStr;
2491      }
2492    }
2493    $result .= $char;
2494    // If the last character was the beginning of an element,
2495    // output a new line and indent the next line
2496    if (',' === $char || '{' === $char || '[' === $char)
2497    {
2498      $result .= $newLine;
2499      if ('{' === $char || '[' === $char)
2500      {
2501        $pos++;
2502      }
2503      for ($j = 0; $j < $pos; $j++)
2504      {
2505        $result .= $indentStr;
2506      }
2507    }
2508  }
2509  // If buffer not empty after formating we have an unclosed quote
2510  if (strlen($buffer) > 0)
2511  {
2512    //json is incorrectly formatted
2513    $result = false;
2514  }
2515  return $result;
2516}
2517
2518/**
2519 * Register an HTML resource
2520 *
2521 * Registers resource for use later (will be re-inserted via output buffer handler)
2522 * CSS and JS files default to the css/ and js/ directories respectively.
2523 * Scripts are inserted literally as passed in $name.
2524 *
2525 * @param string $type Type of resource (css/js/script)
2526 * @param string $content Filename or script content or array (for meta)
2527 */
2528// TESTME needs unit testing
2529function register_html_resource($type, $content)
2530{
2531  // If no path specified, default to subdirectory of resource type (for CSS and JS only)
2532  $type = strtolower($type);
2533  if (in_array($type, array('css', 'js')) && strpos($content, '/') === FALSE)
2534  {
2535    $content = $type . '/' . $content;
2536  }
2537
2538  // Insert into global variable, used in html callback function
2539  $GLOBALS['cache_html']['resources'][$type][] = $content;
2540}
2541
2542/**
2543 * Register an HTML title section
2544 *
2545 * Registers title section for use in the html <title> tag.
2546 * Calls can be stacked, and will be concatenated later by the HTML callback function.
2547 *
2548 * @param string $title Section title content
2549 */
2550// TESTME needs unit testing
2551function register_html_title($title)
2552{
2553  $GLOBALS['cache_html']['title'][] = $title;
2554}
2555
2556
2557/**
2558 * Register an HTML panel section
2559 *
2560 * Registers left panel section.
2561 * Calls can be stacked, and will be concatenated later by the HTML callback function.
2562 *
2563 * @param string $html Section panel content
2564 */
2565// TESTME needs unit testing
2566function register_html_panel($html = '')
2567{
2568  $GLOBALS['cache_html']['page_panel'] = $html;
2569}
2570
2571/**
2572 * Redirect to specified URL
2573 *
2574 * @param string $url Redirecting URL
2575 */
2576function redirect_to_url($url)
2577{
2578  if (!strlen($url) || $url == '#') { return; } // Empty url, do not redirect
2579
2580  $parse = parse_url($url);
2581  if (!isset($parse['scheme']) && !str_starts($url, '/'))
2582  {
2583    // When this is not full url or not started with /
2584    $url = '/' . $url;
2585  }
2586
2587  if (headers_sent())
2588  {
2589    // HTML headers already sent, use JS than
2590    register_html_resource('script', "location.href='$url'");
2591  } else {
2592    // Just use headers
2593    header('Location: '.$url);
2594  }
2595}
2596
2597function generate_colour_gradient($start_colour, $end_colour, $steps) {
2598
2599  if($steps < 4) { $steps = 4; }
2600
2601  $FromRGB['r'] = hexdec(substr($start_colour, 0, 2));
2602  $FromRGB['g'] = hexdec(substr($start_colour, 2, 2));
2603  $FromRGB['b'] = hexdec(substr($start_colour, 4, 2));
2604
2605  $ToRGB['r'] = hexdec(substr($end_colour, 0, 2));
2606  $ToRGB['g'] = hexdec(substr($end_colour, 2, 2));
2607  $ToRGB['b'] = hexdec(substr($end_colour, 4, 2));
2608
2609  $StepRGB['r'] = ($FromRGB['r'] - $ToRGB['r']) / ($steps - 1);
2610  $StepRGB['g'] = ($FromRGB['g'] - $ToRGB['g']) / ($steps - 1);
2611  $StepRGB['b'] = ($FromRGB['b'] - $ToRGB['b']) / ($steps - 1);
2612
2613  $GradientColors = array();
2614
2615  for($i = 0; $i < $steps; $i++) {
2616    $RGB['r'] = floor($FromRGB['r'] - ($StepRGB['r'] * $i));
2617    $RGB['g'] = floor($FromRGB['g'] - ($StepRGB['g'] * $i));
2618    $RGB['b'] = floor($FromRGB['b'] - ($StepRGB['b'] * $i));
2619
2620    $HexRGB['r'] = sprintf('%02x', ($RGB['r']));
2621    $HexRGB['g'] = sprintf('%02x', ($RGB['g']));
2622    $HexRGB['b'] = sprintf('%02x', ($RGB['b']));
2623
2624    $GradientColors[] = implode(NULL, $HexRGB);
2625  }
2626  $GradientColors = array_filter($GradientColors, "c_len");
2627  return $GradientColors;
2628}
2629
2630function c_len($val){
2631  return (strlen($val) == 6 ? true : false );
2632}
2633
2634function adjust_colour_brightness($hex, $steps) {
2635    // Steps should be between -255 and 255. Negative = darker, positive = lighter
2636    $steps = max(-255, min(255, $steps));
2637
2638    // Normalize into a six character long hex string
2639    $hex = str_replace('#', '', $hex);
2640    if (strlen($hex) == 3) {
2641        $hex = str_repeat(substr($hex,0,1), 2).str_repeat(substr($hex,1,1), 2).str_repeat(substr($hex,2,1), 2);
2642    }
2643
2644    // Split into three parts: R, G and B
2645    $color_parts = str_split($hex, 2);
2646
2647    $return ='';
2648    foreach ($color_parts as $color) {
2649        $color   = hexdec($color); // Convert to decimal
2650        $color   = max(0,min(255,$color + $steps)); // Adjust color
2651        $return  .= str_pad(dechex($color), 2, '0', STR_PAD_LEFT); // Make two char hex code
2652    }
2653
2654    return $return;
2655}
2656
2657// EOF
2658