diff --git a/README.md b/README.md
index c8d767c..843e351 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,31 @@ AI autogenerated may 17th 2025
## Overview
Horde/Form is a powerful and flexible form handling library that provides a comprehensive set of tools for creating, validating, and processing web forms in PHP applications. It's part of the Horde Application Framework and offers a robust solution for form management with extensive field types and validation capabilities.
+## UPGRADING FROM FORMS v2
+
+### Minimum
+
+Forms v3 lib/ folder contains a straight conversion of Forms v2 with the following BC breaking changes:
+- Most parameters have been changed to NOT use references for primitives. I.e. derived forms and types need to drop the & from parameter signatures
+- $type->getInfo($vars, &$info) calls now must be $info = $type->getInfo($vars, $info);
+- $bValid = $type->isValid($var, $vars, $value, $message); no longer updates the $message parameter. You need to call $type->getMessage() after $type->isValid if you want to retrieve a potentially set message
+
+This code base is only for easily upgrading existing libraries and apps to Horde 6. It will be deprecated in the next minor release v3.1 and removed in the next major release V4.
+
+### Namespaced
+
+A version with more BC breaking changes is introduced in the src/V3 folder
+
+- Class names follow strict PSR-4 scheme, i.e. Horde_Form_Type_text -> Horde\Form\V3\TextType
+- Signatures based on interfaces, deriving from base class is optional
+- Proper PER-CS styled code base including types, dropping underscores for properties etc
+- This version will be deprecated in V4 and dropped in V5
+
+### V4
+
+- A more involved redesign of Forms based on PHP 8.4 features such as property hooks
+
+
## Features
### Form Field Types
diff --git a/lib/Horde/Form/Type.php b/lib/Horde/Form/Type.php
index 976ed77..89305ed 100644
--- a/lib/Horde/Form/Type.php
+++ b/lib/Horde/Form/Type.php
@@ -23,6 +23,16 @@
*/
class Horde_Form_Type
{
+ /**
+ * Messages from isValid() method.
+ */
+ protected string $message = '';
+
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
public function getProperty($property)
{
$prop = '_' . $property;
@@ -55,6 +65,7 @@ public function onSubmit(...$params) {}
public function isValid($var, $vars, $value, $message)
{
$message = 'Error: Horde_Form_Type::isValid() called - should be overridden
';
+ $this->message = $message;
return false;
}
@@ -160,6 +171,7 @@ public function isValid($var, $vars, $value, $message)
{
if ($var->isRequired() && empty($value) && ((string) (float) $value !== $value)) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
} elseif (empty($value)) {
return true;
@@ -241,6 +253,7 @@ public function isValid($var, $vars, $value, $message)
{
if ($var->isRequired() && empty($value) && ((string) (int) $value !== $value)) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
@@ -249,6 +262,7 @@ public function isValid($var, $vars, $value, $message)
}
$message = Horde_Form_Translation::t("This field may only contain integers.");
+ $this->message = $message;
return false;
}
@@ -268,6 +282,7 @@ public function isValid($var, $vars, $value, $message)
{
if ($var->isRequired() && empty($value) && ((string) (int) $value !== $value)) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
@@ -276,6 +291,7 @@ public function isValid($var, $vars, $value, $message)
}
$message = Horde_Form_Translation::t("This field may only contain octal values.");
+ $this->message = $message;
return false;
}
@@ -295,6 +311,7 @@ public function isValid($var, $vars, $value, $message)
{
if (empty($value) && $var->isRequired()) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
@@ -303,6 +320,7 @@ public function isValid($var, $vars, $value, $message)
}
$message = Horde_Form_Translation::t("This field must be a comma or space separated list of integers");
+ $this->message = $message;
return false;
}
@@ -360,17 +378,20 @@ public function isValid($var, $vars, $value, $message)
if (!empty($this->_maxlength) && Horde_String::length($value) > $this->_maxlength) {
$valid = false;
$message = sprintf(Horde_Form_Translation::t("Value is over the maximum length of %d."), $this->_maxlength);
+ $this->message = $message;
} elseif ($var->isRequired() && empty($this->_regex)) {
$valid = strlen(trim($value)) > 0;
if (!$valid) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
}
} elseif (!empty($this->_regex)) {
$valid = preg_match($this->_regex, $value);
if (!$valid) {
$message = Horde_Form_Translation::t("You must enter a valid value.");
+ $this->message = $message;
}
}
@@ -475,10 +496,12 @@ public function isValid($var, $vars, $value, $message)
if (!strlen(trim($value))) {
if ($var->isRequired()) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
} elseif (!preg_match('/^\+?[\d()\-\/.\s]*$/u', $value)) {
$message = Horde_Form_Translation::t("You must enter a valid phone number, digits only with an optional '+' for the international dialing prefix.");
+ $this->message = $message;
return false;
}
@@ -544,6 +567,7 @@ public function isValid($var, $vars, $value, $message)
} elseif ($var->isRequired()) {
$valid = false;
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
}
return $valid;
@@ -570,12 +594,15 @@ public function isValid($var, $vars, $value, $message)
if ($valid === false) {
$message = Horde_Form_Translation::t("Please enter a valid IP address.");
+ $this->message = $message;
+
}
} elseif ($var->isRequired()) {
$valid = false;
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
}
-
+ // Looks like a bug. Shouldn't we return $valid here?
return true;
}
@@ -687,12 +714,14 @@ public function isValid($var, $vars, $value, $message)
if ($var->isRequired() && $length <= 0) {
$valid = false;
$message = Horde_Form_Translation::t("This field is required.");
+
} elseif ($length > $this->_chars) {
$valid = false;
$message = sprintf(Horde_Form_Translation::ngettext("There are too many characters in this field. You have entered %d character; ", "There are too many characters in this field. You have entered %d characters; ", $length), $length)
. sprintf(Horde_Form_Translation::t("you must enter less than %d."), $this->_chars);
}
+ $this->message = (string)$message;
return $valid;
}
@@ -997,6 +1026,7 @@ public function isValid($var, $vars, $value, $message)
$GLOBALS['browser']->wasFileUploaded($var->getVarName());
} catch (Horde_Browser_Exception $e) {
$message = $e->getMessage();
+ $this->message = $message;
return false;
}
}
@@ -1144,10 +1174,12 @@ public function isValid($var, $vars, $value, $message)
empty($field['hash'])) {
/* Nothing uploaded and no older upload. */
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
} elseif (!empty($field['hash'])) {
if ($this->_img && isset($this->_img['error'])) {
$message = $this->_img['error'];
+ $this->message = $message;
return false;
}
/* Nothing uploaded but older upload present. */
@@ -1159,10 +1191,12 @@ public function isValid($var, $vars, $value, $message)
}
} elseif (empty($this->_img['img']['size'])) {
$message = Horde_Form_Translation::t("The image file size could not be determined or it was 0 bytes. The upload may have been interrupted.");
+ $this->message = $message;
return false;
} elseif ($this->_max_filesize &&
$this->_img['img']['size'] > $this->_max_filesize) {
$message = sprintf(Horde_Form_Translation::t("The image file was larger than the maximum allowed size (%d bytes)."), $this->_max_filesize);
+ $this->message = $message;
return false;
}
@@ -1597,6 +1631,7 @@ public function isValid($var, $vars, $value, $message)
// Check for too many.
if (!$this->_allow_multi && count($emails) > 1) {
$message = Horde_Form_Translation::t("Only one email address is allowed.");
+ $this->message = $message;
return false;
}
@@ -1608,6 +1643,7 @@ public function isValid($var, $vars, $value, $message)
}
if (!$this->validateEmailAddress($email)) {
$message = sprintf(Horde_Form_Translation::t("\"%s\" is not a valid email address."), htmlspecialchars($email));
+ $this->message = $message;
return false;
}
++$nonEmpty;
@@ -1619,6 +1655,7 @@ public function isValid($var, $vars, $value, $message)
} else {
$message = Horde_Form_Translation::t("You must enter an email address.");
}
+ $this->message = (string)$message;
return false;
}
@@ -2297,11 +2334,13 @@ public function isValid($var, $vars, $value, $message)
{
if ($var->isRequired() && empty($value['original'])) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
if ($value['original'] != $value['confirm']) {
$message = Horde_Form_Translation::t("Email addresses must match.");
+ $this->message = $message;
return false;
}
@@ -2310,6 +2349,7 @@ public function isValid($var, $vars, $value, $message)
switch (count($addr_ob)) {
case 0:
$message = Horde_Form_Translation::t("You did not enter a valid email address.");
+ $this->message = $message;
return false;
case 1:
@@ -2317,6 +2357,7 @@ public function isValid($var, $vars, $value, $message)
default:
$message = Horde_Form_Translation::t("Only one email address allowed.");
+ $this->message = $message;
return false;
}
}
@@ -2342,6 +2383,7 @@ public function isValid($var, $vars, $value, $message)
if (!$valid) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
}
}
@@ -2364,11 +2406,13 @@ public function isValid($var, $vars, $value, $message)
{
if ($var->isRequired() && empty($value['original'])) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
if ($value['original'] != $value['confirm']) {
$message = Horde_Form_Translation::t("Passwords must match.");
+ $this->message = $message;
return false;
}
@@ -2423,6 +2467,7 @@ public function isValid($var, $vars, $value, $message)
{
if ($var->isRequired() && $value == '' && !isset($this->_values[$value])) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
@@ -2432,6 +2477,7 @@ public function isValid($var, $vars, $value, $message)
}
$message = Horde_Form_Translation::t("Invalid data submitted.");
+ $this->message = $message;
return false;
}
@@ -2510,6 +2556,7 @@ public function isValid($var, $vars, $value, $message)
{
if ($var->isRequired() && (empty($value['1']) || empty($value['2']))) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
@@ -2519,6 +2566,7 @@ public function isValid($var, $vars, $value, $message)
}
$message = Horde_Form_Translation::t("Invalid data submitted.");
+ $this->message = $message;
return false;
}
@@ -2597,6 +2645,7 @@ public function isValid($var, $vars, $value, $message)
if (empty($value) && ((string) (int) $value !== $value)) {
if ($var->isRequired()) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
} else {
return true;
@@ -2608,6 +2657,7 @@ public function isValid($var, $vars, $value, $message)
}
$message = Horde_Form_Translation::t("Invalid data submitted.");
+ $this->message = $message;
return false;
}
@@ -2711,6 +2761,7 @@ public function isValid($var, $vars, $value, $message)
}
$message = Horde_Form_Translation::t("Invalid data submitted.");
+ $this->message = $message;
return false;
}
@@ -2756,6 +2807,7 @@ public function isValid($var, $vars, $value, $message)
if (!$valid) {
$message = sprintf(Horde_Form_Translation::t("%s is required"), $var->getHumanName());
+ $this->message = $message;
}
}
@@ -2826,6 +2878,7 @@ public function isValid($var, $vars, $value, $message)
{
if ($var->isRequired() && empty($value) && ((string) (float) $value !== $value)) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
@@ -2834,6 +2887,7 @@ public function isValid($var, $vars, $value, $message)
}
$message = Horde_Form_Translation::t("This field may only contain numbers and the colon.");
+ $this->message = $message;
return false;
}
@@ -2870,9 +2924,11 @@ public function isValid($var, $vars, $value, $message)
if (!$this->emptyTimeArray($time) && !$this->checktime($time['hour'], $time['minute'], $time['second'])) {
$message = Horde_Form_Translation::t("Please enter a valid time.");
+ $this->message = $message;
return false;
} elseif ($this->emptyTimeArray($time) && $var->isRequired()) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
@@ -2998,6 +3054,7 @@ public function isValid($var, $vars, $value, $message)
if (!$vars->get($this->getMonthVar($var)) ||
!$vars->get($this->getYearVar($var))) {
$message = Horde_Form_Translation::t("Please enter a month and a year.");
+ $this->message = $message;
return false;
}
@@ -3081,6 +3138,7 @@ public function isValid($var, $vars, $value, $message)
if ($empty == 1 && $var->isRequired()) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
} elseif ($empty == 0 && !checkdate(
$date['month'],
@@ -3088,9 +3146,11 @@ public function isValid($var, $vars, $value, $message)
$date['year']
)) {
$message = Horde_Form_Translation::t("Please enter a valid date, check the number of days in the month.");
+ $this->message = $message;
return false;
} elseif ($empty == -1) {
$message = Horde_Form_Translation::t("Select all date components.");
+ $this->message = $message;
return false;
}
@@ -3332,6 +3392,7 @@ public function isValid($var, $vars, $value, $message)
} elseif ($hms_valid && !$mdy_valid) {
$message = Horde_Form_Translation::t("You must choose a date.");
}
+ $this->message = (string)$message;
}
return $valid;
@@ -3459,6 +3520,7 @@ public function isValid($var, $vars, $value, $message)
{
if ($var->isRequired() && empty($value)) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
@@ -3467,6 +3529,7 @@ public function isValid($var, $vars, $value, $message)
}
$message = Horde_Form_Translation::t("This field must contain a color code in the RGB Hex format, for example '#1234af'.");
+ $this->message = $message;
return false;
}
@@ -3498,6 +3561,7 @@ public function isValid($var, $vars, $value, $message)
{
if ($var->isRequired() && empty($value)) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
@@ -3506,6 +3570,7 @@ public function isValid($var, $vars, $value, $message)
}
$message = Horde_Form_Translation::t("Please choose a sound.");
+ $this->message = $message;
return false;
}
@@ -3838,6 +3903,7 @@ public function isValid($var, $vars, $value, $message)
{
if (empty($value) && $var->isRequired()) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
@@ -3846,6 +3912,7 @@ public function isValid($var, $vars, $value, $message)
$type = $this->getCardType($value);
if ($type === false || $type == 'unknown') {
$message = Horde_Form_Translation::t("This does not seem to be a valid card number.");
+ $this->message = $message;
return false;
}
}
@@ -4016,11 +4083,13 @@ public function isValid($var, $vars, $value, $message)
{
if (empty($value) && $var->isRequired()) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
if (Horde_String::lower($value) != Horde_String::lower($this->_text)) {
$message = Horde_Form_Translation::t("The text you entered did not match the text on the screen.");
+ $this->message = $message;
return false;
}
@@ -4100,6 +4169,7 @@ public function isValid($var, $vars, $value, $message)
{
if (empty($value) && $var->isRequired()) {
$message = Horde_Form_Translation::t("This field is required.");
+ $this->message = $message;
return false;
}
@@ -4110,7 +4180,7 @@ public function isValid($var, $vars, $value, $message)
class Horde_Form_Type_invalid extends Horde_Form_Type
{
- public $message;
+ public string $message;
/**
* Initialize an Invalid Message form type
diff --git a/lib/Horde/Form/Type/tableset.php b/lib/Horde/Form/Type/tableset.php
index a8df949..f5c13b6 100644
--- a/lib/Horde/Form/Type/tableset.php
+++ b/lib/Horde/Form/Type/tableset.php
@@ -51,6 +51,7 @@ public function isValid($var, $vars, $value, $message)
}
$message = Horde_Form_Translation::t("Invalid data submitted.");
+ $this->message = $message;
return false;
}
diff --git a/src/V3/BaseForm.php b/src/V3/BaseForm.php
new file mode 100644
index 0000000..918ef1d
--- /dev/null
+++ b/src/V3/BaseForm.php
@@ -0,0 +1,17 @@
+
+ * Copyright 2001-2025 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @author Robert E. Coyle
+ * @author Chuck Hagenbuch
+ */
+namespace Horde\Form\V3;
+use Horde_Variables;
+
+class BaseForm implements Form
+{
+}
\ No newline at end of file
diff --git a/src/V3/BaseType.php b/src/V3/BaseType.php
new file mode 100644
index 0000000..78ebc7c
--- /dev/null
+++ b/src/V3/BaseType.php
@@ -0,0 +1,87 @@
+
+ */
+
+namespace Horde\Form\V3;
+use Horde_Variables;
+class BaseType implements Type
+{
+ /**
+ * Messages from isValid() method.
+ */
+ protected string $message = '';
+
+ public function getMessage(): string
+ {
+ return $this->message;
+ }
+
+ public function getProperty($property)
+ {
+ $prop = '_' . $property;
+ return $this->$prop ?? null;
+ }
+
+ /**
+ * Not part of the interface, implementation detail
+ */
+ public function __get($property)
+ {
+ return $this->getProperty($property);
+ }
+
+ public function setProperty($property, $value)
+ {
+ $prop = '_' . $property;
+ $this->$prop = $value;
+ }
+
+ /**
+ * Not part of the interface, implementation detail
+ */
+ public function __set($property, $value)
+ {
+ return $this->setProperty($property, $value);
+ }
+
+ /**
+ * Initialize (kind of constructor) - Parameter list may vary on overloading
+ */
+ public function init(...$params) {}
+
+ public function onSubmit(...$params) {}
+
+ /**
+ * Use $this->getMessage() to retrieve error messages.
+ */
+ public function isValid($var, Horde_Variables|array $vars, $value): bool
+ {
+ $this->message = 'Error: Horde_Form_Type::isValid() called - should be overridden
';
+ return false;
+ }
+
+ /**
+ * Override with a simple return 'literal' string in your own types.
+ */
+ public function getTypeName(): string
+ {
+ return mb_strtolower(str_replace('Horde\Form\V3\\', '', substr($this::class, 0, -4)));
+ }
+
+ public function getValues(...$params)
+ {
+ return null;
+ }
+
+ public function getInfo($vars, $var): array|object
+ {
+ return $var->getValue($vars);
+ }
+
+}
\ No newline at end of file
diff --git a/src/V3/DescriptionType.php b/src/V3/DescriptionType.php
new file mode 100644
index 0000000..92b333d
--- /dev/null
+++ b/src/V3/DescriptionType.php
@@ -0,0 +1,15 @@
+ Horde_Form_Translation::t("Description")];
+ }
+}
+
diff --git a/src/V3/Form.php b/src/V3/Form.php
new file mode 100644
index 0000000..20faf00
--- /dev/null
+++ b/src/V3/Form.php
@@ -0,0 +1,6 @@
+ Horde_Form_Translation::t("Header")];
+ }
+}
diff --git a/src/V3/HtmlType.php b/src/V3/HtmlType.php
new file mode 100644
index 0000000..b0d78d2
--- /dev/null
+++ b/src/V3/HtmlType.php
@@ -0,0 +1,15 @@
+ Horde_Form_Translation::t("HTML")];
+ }
+}
+
diff --git a/src/V3/SpacerType.php b/src/V3/SpacerType.php
new file mode 100644
index 0000000..f57d5ff
--- /dev/null
+++ b/src/V3/SpacerType.php
@@ -0,0 +1,14 @@
+ Horde_Form_Translation::t("Spacer")];
+ }
+}
\ No newline at end of file
diff --git a/src/V3/Type.php b/src/V3/Type.php
new file mode 100644
index 0000000..daa63c2
--- /dev/null
+++ b/src/V3/Type.php
@@ -0,0 +1,24 @@
+
+ */
+namespace Horde\Form\V3;
+use Horde_Variables;
+interface Type
+{
+ public function getMessage(): string;
+ public function getProperty($property);
+ public function setProperty($property, $value);
+ public function init(...$params);
+ public function onSubmit(...$params);
+ public function isValid($var, Horde_Variables|array $vars, $value): bool;
+ public function getTypeName(): string;
+ public function getValues(...$params);
+ public function getInfo($vars, $var): array|object;
+ // TODO: Should about() be part of the interface?
+}
\ No newline at end of file