<?php
/*
  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
  Copyright (C) 2011-2016  FusionDirectory

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/

class ThemeFileParsingException extends Exception
{
}

/*!
 * \brief Icon theme directory
 */

class IconThemeDir
{
  /* Nominal (unscaled) size of the icons in this directory.
   * Required. */
  private $Size;

  /* Specifies the minimum (unscaled) size that the icons in this directory can be scaled to.
   * Defaults to the value of Size if not present. */
  private $MinSize;

  /* Specifies the maximum (unscaled) size that the icons in this directory can be scaled to.
   * Defaults to the value of Size if not present. */
  private $MaxSize;

  /* The type of icon sizes for the icons in this directory.
   * Valid types are Fixed, Scalable and Threshold.
   * The type decides what other keys in the section are used.
   * If not specified, the default is Threshold. */
  private $Type = 'Threshold';

  /* The icons in this directory can be used if the size differ at most this much from the desired (unscaled) size.
   * Defaults to 2 if not present. */
  private $Threshold = 2;

  function __construct ($infos)
  {
    $this->Size    = $infos['Size'];
    $this->MinSize = $infos['Size'];
    $this->MaxSize = $infos['Size'];
    foreach (['Type', 'MaxSize', 'MinSize', 'Threshold'] as $key) {
      if (isset($infos[$key])) {
        $this->$key = $infos[$key];
      }
    }
    /* Thanks to this Threshold and Scaled are the same */
    if ($this->Type == 'Threshold') {
      $this->MinSize = $this->Size - $this->Threshold;
      $this->MaxSize = $this->Size + $this->Threshold;
    }
  }

  function MatchesSize ($size)
  {
    switch ($this->Type) {
      case 'Fixed':
        return ($this->Size == $size);
      case 'Threshold':
      case 'Scalable':
      default:
        return (($this->MinSize <= $size) && ($size <= $this->MaxSize));
    }
  }

  function SizeDistance ($size)
  {
    switch ($this->Type) {
      case 'Fixed':
        return abs($this->Size - $size);
      case 'Threshold':
      case 'Scalable':
      default:
        if ($size < $this->MinSize) {
          return $this->MinSize - $size;
        }
        if ($size > $this->MaxSize) {
          return $size - $this->MaxSize;
        }
        return 0;
    }
  }
}

/*!
 * \brief Icon theme
 */

class IconTheme
{
  private $subdirs = [];
  private $path;
  private $parent;

  function __construct ($folder, $default_parent)
  {
    $this->path = $folder;
    $datas      = @parse_ini_file($folder . '/index.theme', TRUE, INI_SCANNER_RAW);
    if ($datas === FALSE) {
      throw new ThemeFileParsingException('Error while parsing theme file');
    }
    if (isset($datas['Icon Theme']['Directories']) && !empty($datas['Icon Theme']['Directories'])) {
      $dirs = preg_split('/,/', $datas['Icon Theme']['Directories']);
      foreach ($dirs as $name) {
        if (isset($datas[$name])) {
          $this->subdirs[strtolower($datas[$name]['Context'])][$name] = new IconThemeDir($datas[$name]);
        }
      }
    }

    if (isset($datas['Icon Theme']['Inherits'])) {
      $this->parent = $datas['Icon Theme']['Inherits'];
    } else {
      $this->parent = $default_parent;
    }
  }

  function FindIcon ($context, $icon, $size)
  {
    $context = strtolower($context);
    return $this->FindIconHelper($context, $icon, $size);
  }

  function FindIconHelper ($context, $icon, $size)
  {
    $filename = $this->LookupIcon($context, $icon, $size);
    if ($filename != NULL) {
      return $filename;
    }
    if (isset(static::$fallbacks[$context . '/' . $icon])) {
      foreach (static::$fallbacks[$context . '/' . $icon] as $fallback) {
        $filename = $this->LookupIcon($fallback[0], $fallback[1], $size);
        if ($filename != NULL) {
          return $filename;
        }
      }
    }

    if ($this->parent !== NULL) {
      $parentTheme = $this->findTheme($this->parent);
      if ($parentTheme === NULL) {
        $parentTheme = $this->findTheme(static::$default_theme);
      }
      return $parentTheme->FindIconHelper($context, $icon, $size);
    }

    return NULL;
  }

  function LookupIcon ($context, $iconname, $size)
  {
    if (!isset($this->subdirs[$context])) {
      return NULL;
    }
    foreach ($this->subdirs[$context] as $path => &$subdir) {
      if ($subdir->MatchesSize($size)) {
        foreach (static::$extensions as $extension) {
          $filename = $this->path . '/' . $path . '/' . $iconname . '.' . $extension;
          if (file_exists($filename)) {
            return $filename;
          }
        }
      }
    }
    unset($subdir);
    if (static::$find_closest) {
      $minimal_size = PHP_INT_MAX;
      foreach ($this->subdirs[$context] as $path => &$subdir) {
        if (($sizedistance = $subdir->SizeDistance($size)) < $minimal_size) {
          foreach (static::$extensions as $extension) {
            $filename = $this->path . '/' . $path . '/' . $iconname . '.' . $extension;
            if (file_exists($filename)) {
              $closest_filename = $filename;
              $minimal_size     = $sizedistance;
            }
          }
        }
      }
      unset($subdir);
      if (isset($closest_filename)) {
        return $closest_filename;
      }
    }
    return NULL;
  }

  static public $default_theme = 'breezy';
  static public $extensions = ['png', 'xpm', 'svg'];
  static public $find_closest = FALSE;

  /* We store themes in the session. To do otherwise, override these methods. */
  static public $session_var = 'IconThemes';

  static public function loadThemes ($path)
  {
    $themes = [];
    if ($dir = opendir("$path")) {
      while (($file = readdir($dir)) !== FALSE) {
        if (file_exists("$path/$file/index.theme") && !preg_match("/^\./", $file)) {
          try {
            if ($file == static::$default_theme) {
              $themes[$file] = new IconTheme("$path/$file", NULL);
            } else {
              $themes[$file] = new IconTheme("$path/$file", static::$default_theme);
            }
          } catch (ThemeFileParsingException $e) {
            continue;
          }
        }
      }
    }
    $_SESSION[static::$session_var] = $themes;
  }

  static public function findThemeIcon ($theme, $context, $icon, $size)
  {
    // We have to sanitize the $icon received from $_GET['icon']. Fixing vulnerability : CWE-35
    if (!preg_match('/^[a-zA-Z0-9_\-]+$/', $icon)) {
      trigger_error('Error: Wrong icon name received');
      die('Error: wrong icon name received');
    }
    if (!isset($_SESSION[static::$session_var])) {
      trigger_error('Error: no theme found in session');
      die('Error: no theme found in session');
    }
    if (isset($_SESSION[static::$session_var][$theme])) {
      return $_SESSION[static::$session_var][$theme]->FindIcon($context, $icon, $size);
    }
    return $_SESSION[static::$session_var][static::$default_theme]->FindIcon($context, $icon, $size);
  }

  public function findTheme ($theme)
  {
    if (isset($_SESSION[static::$session_var][$theme])) {
      return $_SESSION[static::$session_var][$theme];
    }
    return NULL;
  }

  /* Fallback system */
  static public $fallbacks = [
    'types/user-group' => [
      ['applications', 'system-users']
    ],
    'types/resource-group' => [
      ['actions', 'resource-group']
    ],
    'types/user' => [
      ['places', 'user-identity'],
      ['status', 'avatar-default'],
    ],
    'types/contact' => [
      ['mimetypes', 'x-office-contact'],
    ],
    'types/certificate' => [
      ['mimetypes', 'stock_certificate'],
      ['mimetypes', 'application-certificate'],
      ['actions', 'view-certificate'],
    ],
    'applications/user-info' => [
      ['actions', 'user-properties'],
      ['types', 'contact'],
      ['mimetypes', 'x-office-contact'],
      ['types', 'user'],
      ['places', 'user-identity'],
      ['status', 'avatar-default'],
    ],
    'applications/office-calendar' => [
      ['mimetypes', 'x-office-calendar'],
    ],
    'applications/os-linux' => [
      ['applications', 'linux'],
    ],
    'applications/os-windows' => [
      ['applications', 'windows'],
    ],
    'applications/samba' => [
      ['applications', 'os-windows'],
      ['applications', 'windows'],
    ],
    'applications/config-language' => [
      ['applications', 'locale'],
      ['applications', 'preferences-desktop-locale'],
    ],
    'mimetypes/text-csv' => [
      ['mimetypes', 'x-office-spreadsheet'],
      ['mimetypes', 'text-x-generic'],
    ],
    'mimetypes/application-pdf' => [
      ['mimetypes', 'x-office-document'],
    ],
    'actions/application-exit' => [
      ['actions', 'system-log-out'],
    ],
    'actions/archive' => [
      ['mimetypes', 'application-x-archive'],
    ],
    'actions/document-export' => [
      ['actions', 'document-send'],
    ],
    'actions/download' => [
      ['actions', 'document-save'],
    ],
    'actions/document-restore' => [
      ['actions', 'document-import'],
      ['actions', 'document-open'],
    ],
    'actions/document-edit' => [
      ['actions', 'edit'],
      ['applications', 'text-editor'],
      ['applications', 'accessories-text-editor'],
      ['actions', 'document-open'],
    ],
    'actions/snapshot' => [
      ['actions', 'document-save'],
    ],
    'actions/system-reboot' => [
      ['actions', 'view-refresh'],
    ],
    'actions/system-update' => [
      ['applications', 'system-software-update'],
    ],
    'actions/system-reinstall' => [
      ['applications', 'system-installer'],
    ],
    'actions/task-start' => [
      ['actions', 'media-playback-start'],
    ],
    'actions/task-stop' => [
      ['actions', 'media-playback-stop'],
    ],
    'actions/task-schedule' => [
      ['actions', 'chronometer'],
      ['actions', 'smallclock'],
    ],
    'actions/up' => [
      ['actions', 'go-up'],
      ['actions', 'arrow-up'],
    ],
    'actions/upload' => [
      ['actions', 'document-import'],
      ['actions', 'up'],
    ],
    'actions/down' => [
      ['actions', 'go-down'],
      ['actions', 'arrow-down'],
    ],
    'actions/previous' => [
      ['actions', 'go-previous'],
      ['actions', 'arrow-left'],
    ],
    'actions/next' => [
      ['actions', 'go-next'],
      ['actions', 'arrow-right'],
    ],
    'actions/submit' => [
      ['actions', 'go-jump'],
    ],
    'categories/settings' => [
      ['categories', 'gnome-settings'],
      ['categories', 'preferences-other'],
      ['categories', 'preferences-system'],
    ],
    'categories/checks' => [
      ['actions', 'view-task'],
      ['actions', 'view-calendar-tasks'],
      ['actions', 'checkbox'],
      ['status', 'task-complete'],
    ],
    'devices/server' => [
      ['places', 'server'],
      ['places', 'network-server'],
    ],
    'devices/media-cdrom' => [
      ['devices', 'media-optical'],
    ],
    'devices/terminal' => [
      ['applications', 'utilities-terminal'],
    ],
    'devices/computer-windows' => [
      ['applications', 'os-windows'],
      ['applications', 'windows'],
    ],
    'devices/template' => [
      ['actions', 'document-new'],
    ],
    'status/object-locked' => [
      ['status', 'changes-prevent'],
    ],
    'status/object-unlocked' => [
      ['status', 'changes-allow'],
    ],
    'status/task-waiting' => [
      ['actions', 'task-schedule'],
    ],
    'places/folder-network' => [
      ['places', 'folder-remote'],
    ],
  ];
}