Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
1.3.47 (2026-06-26)
- fix: remove double-escaping in evalform.php (add_slashes + mpform_escape_string)
- fix: remove add_slashes() before htmlspecialchars() in save_field.php
- fix: case 'date'; -> case 'date': in modify.php (deprecated PHP 8.5)
- fix: email field value obfuscated by output filter on form re-display (@ and . encoded as HTML entities in session)

mpForm wbCMS Content Module
###########################

Expand Down
17 changes: 13 additions & 4 deletions evalform.php
Original file line number Diff line number Diff line change
Expand Up @@ -538,13 +538,22 @@ function eval_form($section_id) {
);
} else {
// make sure user does see what he entered:
$session_val = htmlspecialchars(
stripslashes($post_field) ?? '', ENT_QUOTES);
// protect @ and . from output filter obfuscation in email fields
if($field['type'] == 'email') {
$session_val = str_replace(
array('@', '.'),
array('@', '.'),
$session_val
);
}
$_SESSION['mpf']['field'.$field_id]
= str_replace(
array("[[", "]]"),
array("[[", "]]"),
htmlspecialchars(
stripslashes($post_field) ?? '', ENT_QUOTES)
);
$session_val
);
}

// no injections, please
Expand Down Expand Up @@ -763,7 +772,7 @@ function eval_form($section_id) {
array("[[", "]]"),
array("[[", "]]"),
htmlspecialchars(
$admin->add_slashes($v) ?? '', ENT_QUOTES
$v ?? '', ENT_QUOTES
)
);
$curr_field .= mpform_escape_string($field_value) . ", ";
Expand Down
257 changes: 135 additions & 122 deletions export.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@
*
**/
/* This file exports the whole section (excluding the submissions) to an xml file.
The code was taken form the export_section module and integrated into mpform now */
The code was taken form the export_section module and integrated into mpform now.

2026-07: rewritten to use information_schema for table/column discovery
instead of SHOW TABLES / SHOW COLUMNS ... LIKE, and to use parameterized
queries throughout. This is more reliable under the PDO-based Database
class and avoids relying on driver-specific numeric-index quirks of
fetchRow(). */

unset($_GET['page_id']);
unset($_GET['section_id']);

// manually include the config.php file (defines the required constants)
require('../../config.php');

// Include WB admin wrapper script
//require(WB_PATH.'/modules/admin.php');

// include core functions of WB 2.7 to edit the optional module CSS files (frontend.css, backend.css)
@include_once(WB_PATH .'/framework/module.functions.php');

Expand All @@ -35,11 +38,9 @@
// obtain module directory
$mod_dir = basename(dirname(__FILE__));


// include the module language file depending on the backend language of the current user
if (!@include(get_module_language_file($mod_dir))) return;


// include WB admin wrapper script to check permissions
$admin_header = false;
require(WB_PATH . '/modules/admin.php');
Expand All @@ -53,14 +54,16 @@
exit();
}

// protect from cross site scripting
// load the section row once; it's needed both for the id check below
// and for the export itself, so there is no need to query it twice
$query_content = $database->query(
"SELECT *"
. " FROM ".TABLE_PREFIX."sections"
. " WHERE section_id = '$section_id'");
"SELECT * FROM ".TABLE_PREFIX."sections WHERE section_id = ?",
[$section_id]
);
$section_row = $query_content ? $query_content->fetchRow(MYSQLI_ASSOC) : null;

$res = $query_content->fetchRow();
if (($res['page_id'] != $page_id)
// protect from cross site scripting
if ((!$section_row || $section_row['page_id'] != $page_id)
&& (!(defined('MPFORM_SKIP_ID_CHECK')&&(MPFORM_SKIP_ID_CHECK)))) {
$sUrlToGo = ADMIN_URL."/pages/index.php";
if(headers_sent())
Expand All @@ -72,124 +75,134 @@
exit(0);
}

// obtain module directory
$curr_dir = dirname(__FILE__);

/* code originally in config.inc.php, not needed anymore here:

// groups of known modules:
$smooth_modules = array('wysiwyg', 'guestbook');
$warn_modules = array(
'code' => 'Make sure to check whether you need to change '
. 'variable names used in the code of this section!',
'bakery' => 'Make sure to move and rename all image files used '
. 'for the articles of this section!',
'form' => 'Submissions have been omitted from export!',
'formx' => 'Submissions have been omitted from export!<br />'
. 'It is highly recommended to use the module '
. '"Migrate formx" to migrate the page to mpform!',
'mpform' => 'Submissions and results have been omitted from export!'
);
$blocked_modules = array('section_picker', 'foldergallery');

// extract path separator and detect this module name
$path_sep = strtoupper(substr(PHP_OS, 0, 3) == 'WIN') ? '\\' : '/';
$module_folder
= str_replace(
WB_PATH
. $path_sep
. 'modules'
. $path_sep,
'',
dirname(__FILE__)
);
$url_admintools = ADMIN_URL . '/admintools/tool.php?tool=' . $module_folder;

*/
/**
* Escapes a value for safe placement inside a CDATA section.
* CDATA blocks may contain anything except the literal sequence "]]>",
* so that sequence has to be broken up across two adjacent CDATA blocks.
*/
function mpform_export_cdata(?string $value): string
{
$value = $value ?? '';
return str_replace(']]>', ']]]]><![CDATA[>', $value);
}

$lines = array();
$lines[] = '<?xml version="1.0" encoding="'. DEFAULT_CHARSET .'" ?>';

$sql = "SELECT * FROM ". TABLE_PREFIX ."sections where section_id = '$section_id'";
$results = $database->query($sql);
if ($results && $row = $results->fetchRow()) {
// if ( in_array($row['module'], $blocked_modules))
// after integrating into mpform we restrict this to mpform sections instead
// we should not arrive here anyway...
if ($row['module'] != 'mpform'){
$admin->print_header();
$admin->print_error("Export of sections of type ".$row['module']." is not possible",
ADMIN_URL.'/pages/modify.php?page_id='.(int)$page_id);
$admin->print_footer();
exit;
} else {
$lines[] = "<export_section>";
$lines[] = "\t<module>";
$lines[] = "\t\t<name>".$row['module']."</name>";
$sql = "SELECT * FROM ".TABLE_PREFIX ."addons where directory = '".$row['module']."'";
$results = $database->query($sql);
if ($results && $row2 = $results->fetchRow()) {
$lines[] = "\t\t<version>".$row2['version']."</version>";
}
/* we don't include the warning anymore
// look for known issues and warn:
if (array_key_exists($row['module'], $warn_modules)) {
$lines[] = "\t\t<warning><![CDATA[". $warn_modules[$row['module']] ."]]></warning>";
if (!$section_row) {
// section no longer exists - nothing to export
$admin->print_header();
$admin->print_error("Section $section_id not found",
ADMIN_URL.'/pages/modify.php?page_id='.(int)$page_id);
$admin->print_footer();
exit;
}

if ($section_row['module'] != 'mpform') {
$admin->print_header();
$admin->print_error("Export of sections of type ".$section_row['module']." is not possible",
ADMIN_URL.'/pages/modify.php?page_id='.(int)$page_id);
$admin->print_footer();
exit;
}

$lines[] = "<export_section>";
$lines[] = "\t<module>";
$lines[] = "\t\t<name>".$section_row['module']."</name>";

$results = $database->query(
"SELECT * FROM ".TABLE_PREFIX."addons WHERE directory = ?",
[$section_row['module']]
);
if ($results && $addon_row = $results->fetchRow(MYSQLI_ASSOC)) {
$lines[] = "\t\t<version>".$addon_row['version']."</version>";
}
$lines[] = "\t</module>";

// ── Discover all module tables that carry a section_id column ──────────────
// Instead of SHOW TABLES + SHOW COLUMNS ... LIKE (which turned out to behave
// unreliably through the PDO wrapper), ask information_schema directly for
// every table in the current database that starts with "<prefix>mod_" and
// has a `section_id` column. This is a single, portable, MySQL/MariaDB-
// standard query.
$prefix_pattern = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], TABLE_PREFIX.'mod_') . '%';
$tables_result = $database->query(
"SELECT DISTINCT c.TABLE_NAME"
. " FROM information_schema.COLUMNS c"
. " WHERE c.TABLE_SCHEMA = DATABASE()"
. " AND c.COLUMN_NAME = 'section_id'"
. " AND c.TABLE_NAME LIKE ? ESCAPE '\\\\'"
. " ORDER BY c.TABLE_NAME",
[$prefix_pattern]
);

$export_tables = [];
if ($tables_result) {
while ($t = $tables_result->fetchRow(MYSQLI_ASSOC)) {
$tablename = $t['TABLE_NAME'];
// skip submissions from form / formx / mpform modules
if (strpos($tablename, TABLE_PREFIX.'mod_form_submissions') === 0) continue;
if (strpos($tablename, TABLE_PREFIX.'mod_formx_submissions') === 0) continue;
if (strpos($tablename, TP_MPFORM.'submissions') === 0) continue;
$export_tables[] = $tablename;
}
}

foreach ($export_tables as $tablename) {
// find the auto_increment column of this table (if any), so it can be
// excluded from the export - a new value is assigned again on import.
// IMPORTANT: this must only exclude AUTO_INCREMENT columns, not every
// primary key column - some tables (e.g. mod_mpform_settings) use
// section_id itself as a non-autoincrement primary key, and that value
// is required on import (import.php re-injects the current section_id
// only for fields that are actually present in the export).
// This mirrors import.php's own detection query exactly:
// SHOW COLUMNS FROM `$tn` WHERE extra LIKE 'auto_increment'
$pk_result = $database->query(
"SELECT COLUMN_NAME FROM information_schema.COLUMNS"
. " WHERE TABLE_SCHEMA = DATABASE()"
. " AND TABLE_NAME = ?"
. " AND EXTRA LIKE '%auto_increment%'"
. " LIMIT 1",
[$tablename]
);
$pk_row = $pk_result ? $pk_result->fetchRow(MYSQLI_ASSOC) : null;
$pk_column = $pk_row['COLUMN_NAME'] ?? null;

// table name comes from information_schema (trusted), not user input,
// so it's safe to interpolate into the identifier position here
$results2 = $database->query(
"SELECT * FROM `" . $tablename . "` WHERE section_id = ?",
[$section_id]
);

$inside_tab = false;
while ($results2 && $row2 = $results2->fetchRow(MYSQLI_ASSOC)) {
if (!$inside_tab) {
$tn = substr($tablename, strlen(TABLE_PREFIX));
$lines[] = "\t<export_section_table>";
$lines[] = "\t\t<tablename>$tn</tablename>";
$inside_tab = true;
}
*/
$lines[] = "\t</module>";

$sql = "SHOW TABLES";
$result = $database->query($sql);
while ($row = $result->fetchRow()) {
// skip non-module tables:
if (strpos($row[0], TABLE_PREFIX.'mod_') !== 0) continue;
// skip submissions from form module:
if (strpos($row[0], TABLE_PREFIX.'mod_form_submissions') === 0) continue;
// skip submissions from formx module:
if (strpos($row[0], TABLE_PREFIX.'mod_formx_submissions') === 0) continue;
// skip submissions from mpform module:
if (strpos($row[0], TP_MPFORM.'submissions') === 0) continue;
$sql = "SHOW COLUMNS FROM `" . $row[0] . "` LIKE 'section_id'";
$results = $database->query($sql);
if ($results && $exists = $results->fetchRow()) {
$sql2 = "SELECT * FROM `" . $row[0] . "` WHERE section_id = '$section_id'";
$results2 = $database->query($sql2);
$inside_tab = false;
while ($results2 && $row2 = $results2->fetchRow()) {
if (!$inside_tab) {
$tn = substr($row[0], strlen(TABLE_PREFIX));
$lines[] = "\t<export_section_table>";
$lines[] = "\t\t<tablename>$tn</tablename>";
$inside_tab = true;
}
$lines[] = "\t\t<export_section_row>";
$i = 0;
foreach ($row2 as $k => $v) {
$i++;
if ($i > 1) {
if ($i % 2 == 0) {
$cv = addslashes($v);
$lines[]
= "\t\t\t<export_section_field>"
. "<fieldn>$k</fieldn>"
. "<fieldv><![CDATA[" .$cv. "]]></fieldv>"
. "</export_section_field>";
}
}
}
$lines[] = "\t\t</export_section_row>";
}
if ($inside_tab) {
$lines[] = "\t</export_section_table>";
$inside_tab = false;
}
}
$lines[] = "\t\t<export_section_row>";
foreach ($row2 as $k => $v) {
if ($pk_column !== null && $k === $pk_column) continue;
$cv = mpform_export_cdata($v);
$lines[]
= "\t\t\t<export_section_field>"
. "<fieldn>$k</fieldn>"
. "<fieldv><![CDATA[" . $cv . "]]></fieldv>"
. "</export_section_field>";
}

$lines[] = "</export_section>";
$lines[] = "\t\t</export_section_row>";
}
if ($inside_tab) {
$lines[] = "\t</export_section_table>";
}
}

$lines[] = "</export_section>";

header("Content-Type: text/plain");
header("Content-Disposition: attachment; filename=section_$section_id.xml");
foreach ($lines as $l) echo "$l\r\n";
2 changes: 1 addition & 1 deletion info.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
$module_directory = 'mpform';
$module_name = 'mpForm';
$module_function = 'page';
$module_version = '1.3.44';
$module_version = '1.3.47';
$module_platform = '2.8.x';
$module_status = 'stable';
$module_author = 'Frank Heyne, NorHei(heimsath.org), Christian M. Stefan (Stefek), Martin Hecht (mrbaseman) and others';
Expand Down
2 changes: 1 addition & 1 deletion modify.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@
case 'filename':
$rt = $TEXT['UPLOAD_FILES'];
break;
case 'date';
case 'date':
$rt = $TEXT['DATE'];
break;
case 'email':
Expand Down
4 changes: 2 additions & 2 deletions save_field.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ function int_not0($s) {
$values[] = preg_replace("/&amp;(#?[a-zA-Z0-9]+);/","&\\1;",
str_replace(array(",", "[[", "]]"),
array("&#44;", '', ''),
htmlspecialchars($admin->add_slashes(
$admin->get_post('value'.$i)),
htmlspecialchars(
$admin->get_post('value'.$i),
ENT_QUOTES)
)
) . $defcode;
Expand Down