From 172f088fd97d012a73748790fabee5d2ed6ac7a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=B4me=20Chilliet?= <come.chilliet@fusiondirectory.org>
Date: Wed, 3 Jun 2020 17:02:53 +0200
Subject: [PATCH] :sparkles: feat(core) Start at revamping error system

Adds FusionDirectoryError base class and one child class for check
 failures.
Use this new class for IntAttribute as first test.
Build HTML dialog from error class using attribute data.

issue #6071
---
 include/class_msg_dialog.inc                  |  6 +-
 include/errors/class_FusionDirectoryError.inc | 33 ++++++++
 .../errors/class_SimplePluginCheckError.inc   | 77 +++++++++++++++++++
 include/functions.inc                         | 22 +++---
 .../attributes/class_IntAttribute.inc         | 45 ++++++-----
 .../attributes/class_StringAttribute.inc      |  5 ++
 include/simpleplugin/class_Attribute.inc      | 10 +++
 7 files changed, 167 insertions(+), 31 deletions(-)
 create mode 100644 include/errors/class_FusionDirectoryError.inc
 create mode 100644 include/errors/class_SimplePluginCheckError.inc

diff --git a/include/class_msg_dialog.inc b/include/class_msg_dialog.inc
index 4da11d9e8..e6b0b6c28 100644
--- a/include/class_msg_dialog.inc
+++ b/include/class_msg_dialog.inc
@@ -133,7 +133,11 @@ class msg_dialog
   public static function displayChecks ($messages)
   {
     foreach ($messages as $error) {
-      static::display(_('Error'), $error, ERROR_DIALOG);
+      if ($error instanceof FusionDirectoryError) {
+        static::display(...$error->computeMsgDialogParameters());
+      } else {
+        static::display(_('Error'), $error, ERROR_DIALOG);
+      }
     }
   }
 
diff --git a/include/errors/class_FusionDirectoryError.inc b/include/errors/class_FusionDirectoryError.inc
new file mode 100644
index 000000000..4fcd6a586
--- /dev/null
+++ b/include/errors/class_FusionDirectoryError.inc
@@ -0,0 +1,33 @@
+<?php
+/*
+  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
+  Copyright (C) 2019-2020  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 FusionDirectoryError
+    \brief Parent class for all errors in FusionDirectory
+*/
+class FusionDirectoryError extends Error
+{
+  protected $htmlMessage;
+
+  public function __construct (string $htmlMessage = '', int $code = 0, Throwable $previous = NULL)
+  {
+    $this->htmlMessage = $htmlMessage;
+    parent::__construct(htmlunescape(strip_tags($htmlMessage)), $code, $previous);
+  }
+}
diff --git a/include/errors/class_SimplePluginCheckError.inc b/include/errors/class_SimplePluginCheckError.inc
new file mode 100644
index 000000000..35899f1b1
--- /dev/null
+++ b/include/errors/class_SimplePluginCheckError.inc
@@ -0,0 +1,77 @@
+<?php
+/*
+  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
+  Copyright (C) 2019-2020  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 SimplePluginCheckError
+    \brief Error returned by check method of SimplePlugin
+*/
+class SimplePluginCheckError extends FusionDirectoryError
+{
+  protected $tab;
+  protected $attribute;
+
+  public function __construct (?object $origin, string $htmlMessage = '', int $code = 0, Throwable $previous = NULL)
+  {
+    if ($origin instanceof Attribute) {
+      $this->attribute  = $origin;
+      $this->tab        = $origin->getParent();
+    } else {
+      $this->attribute = NULL;
+      if ($origin instanceof SimpleTab) {
+        $this->tab = $origin;
+      } else {
+        $this->tab = NULL;
+      }
+    }
+
+    parent::__construct($htmlMessage, $code, $previous);
+  }
+
+  public function computeMsgDialogParameters (): array
+  {
+    $html = '';
+
+    if (isset($this->tab)) {
+      $html .= htmlescape($this->tab->parent->getBaseObject()->dn.' > ');
+      $html .= htmlescape($this->tab->parent->by_name[get_class($this->tab)].' > ');
+    }
+
+    if (isset($this->attribute)) {
+      $label = $this->attribute->getLabel();
+      if (empty($label)) {
+        $html .= '<i>'.htmlescape($this->attribute->getLdapName()).'</i>';
+      } else {
+        $html .= htmlescape($label);
+      }
+      $example = $this->attribute->getExample();
+    }
+
+    $html .= '<br/><br/>';
+
+    $html .= $this->htmlMessage;
+
+    /* Stylize example */
+    if (!empty($example)) {
+      $html .= '<br/><br/><i>'.sprintf(_('Example: %s'), htmlescape($example)).'</i> ';
+    }
+
+
+    return [_('Error'), $html, ERROR_DIALOG];
+  }
+}
diff --git a/include/functions.inc b/include/functions.inc
index 4a97774bf..a756a6ff8 100644
--- a/include/functions.inc
+++ b/include/functions.inc
@@ -1722,19 +1722,21 @@ function send_binary_content ($data, $name, $type = "application/octet-stream")
   exit();
 }
 
-
-function reverse_html_entities ($str, $type = ENT_QUOTES, $charset = "UTF-8")
+/*!
+ * \brief Escape string for HTML output
+ */
+function htmlescape (string $str): string
 {
-  if (is_string($str)) {
-    return htmlentities($str, $type, $charset);
-  } elseif (is_array($str)) {
-    foreach ($str as $name => $value) {
-      $str[$name] = reverse_html_entities($value, $type, $charset);
-    }
-  }
-  return $str;
+  return htmlentities($str, ENT_COMPAT | ENT_HTML5, 'UTF-8');
 }
 
+/*!
+ * \brief Unescape string for HTML output, reverse of htmlescape
+ */
+function htmlunescape (string $html): string
+{
+  return html_entity_decode($html, ENT_COMPAT | ENT_HTML5, 'UTF-8');
+}
 
 /*!
  * \brief Encode special string characters
diff --git a/include/simpleplugin/attributes/class_IntAttribute.inc b/include/simpleplugin/attributes/class_IntAttribute.inc
index 496c7cd10..bcbfc7008 100644
--- a/include/simpleplugin/attributes/class_IntAttribute.inc
+++ b/include/simpleplugin/attributes/class_IntAttribute.inc
@@ -26,7 +26,6 @@ class IntAttribute extends Attribute
   protected $min;
   protected $max;
   protected $step = 1;
-  protected $example;
 
   /*! \brief The constructor of IntAttribute
    *
@@ -44,14 +43,16 @@ class IntAttribute extends Attribute
     parent::__construct($label, $description, $ldapName, $required, $defaultValue, $acl);
     $this->min      = ($min === FALSE ? FALSE : $this->inputValue($min));
     $this->max      = ($max === FALSE ? FALSE : $this->inputValue($max));
-    $this->example  = '';
-
-    if (($min !== FALSE) && ($max !== FALSE)) {
-      $this->example = sprintf(_('An integer between %d and %d'), $min, $max);
-    } elseif ($min !== FALSE) {
-      $this->example = sprintf(_('An integer larger than %d'),    $min);
-    } elseif ($max !== FALSE) {
-      $this->example = sprintf(_('An integer smaller than %d'),   $max);
+  }
+
+  public function getExample (): ?string
+  {
+    if (($this->min !== FALSE) && ($this->max !== FALSE)) {
+      return sprintf(_('An integer between %d and %d'), $this->min, $this->max);
+    } elseif ($this->min !== FALSE) {
+      return sprintf(_('An integer larger than %d'),    $this->min);
+    } elseif ($this->max !== FALSE) {
+      return sprintf(_('An integer smaller than %d'),   $this->max);
     }
   }
 
@@ -80,11 +81,13 @@ class IntAttribute extends Attribute
       return $error;
     } elseif ($this->value !== '') {
       if (!is_numeric($this->value)) {
-        return msgPool::invalid($this->getLabel(), $this->value, $this->example);
+        return new SimplePluginCheckError($this, sprintf(_('"%s" is not an number'), $this->getValue()));
       }
-      if ((($this->min !== FALSE) && ($this->value < $this->min))
-      || (($this->max !== FALSE) && ($this->value > $this->max))) {
-        return msgPool::invalid($this->getLabel(), $this->value, $this->example);
+      if (($this->min !== FALSE) && ($this->value < $this->min)) {
+        return new SimplePluginCheckError($this, sprintf(_('%s is smaller than %s'), $this->getValue(), $this->min));
+      }
+      if (($this->max !== FALSE) && ($this->value > $this->max)) {
+        return new SimplePluginCheckError($this, sprintf(_('%s is larger than %s'), $this->getValue(), $this->max));
       }
     }
   }
@@ -156,14 +159,16 @@ class FloatAttribute extends IntAttribute
     parent::__construct($label, $description, $ldapName, $required, $min, $max, $defaultValue, $acl);
 
     $this->step = 0.01;
+  }
 
-    $this->example  = '';
-    if (($min !== FALSE) && ($max !== FALSE)) {
-      $this->example = sprintf(_('A float between %f and %f'), $min, $max);
-    } elseif ($min !== FALSE) {
-      $this->example = sprintf(_('A float larger than %f'),    $min);
-    } elseif ($max !== FALSE) {
-      $this->example = sprintf(_('A float smaller than %f'),   $max);
+  public function getExample (): ?string
+  {
+    if (($this->min !== FALSE) && ($this->max !== FALSE)) {
+      return sprintf(_('A float between %f and %f'),  $this->min, $this->max);
+    } elseif ($this->min !== FALSE) {
+      return sprintf(_('A float larger than %f'),     $this->min);
+    } elseif ($this->max !== FALSE) {
+      return sprintf(_('A float smaller than %f'),    $this->max);
     }
   }
 
diff --git a/include/simpleplugin/attributes/class_StringAttribute.inc b/include/simpleplugin/attributes/class_StringAttribute.inc
index 635de25fd..857710e30 100644
--- a/include/simpleplugin/attributes/class_StringAttribute.inc
+++ b/include/simpleplugin/attributes/class_StringAttribute.inc
@@ -53,6 +53,11 @@ class StringAttribute extends Attribute
     $this->example = $example;
   }
 
+  public function getExample (): ?string
+  {
+    return $this->example;
+  }
+
   function setPattern ($pattern)
   {
     $this->pattern = $pattern;
diff --git a/include/simpleplugin/class_Attribute.inc b/include/simpleplugin/class_Attribute.inc
index 8c1e6878f..747f7becc 100644
--- a/include/simpleplugin/class_Attribute.inc
+++ b/include/simpleplugin/class_Attribute.inc
@@ -121,6 +121,11 @@ class Attribute
     $this->manageAttributes($this->getValue());
   }
 
+  function getParent(): ?simplePlugin
+  {
+    return $this->plugin;
+  }
+
   function setIsSubAttribute (bool $bool)
   {
     $this->isSubAttribute = $bool;
@@ -167,6 +172,11 @@ class Attribute
     return $this->inLdap;
   }
 
+  public function getExample (): ?string
+  {
+    return NULL;
+  }
+
   function checkValue ($value)
   {
     /* Should throw InvalidValueException if needed */
-- 
GitLab