| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- <?php
- /**
- * The formdom class file of ZenTaoPMS.
- *
- * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
- * @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
- * @author Zhu Jinyong<zhujinyong@easycorp.ltd>
- * @package form
- * @link https://www.zentao.net
- */
- /**
- * 解析form dom为数组数据。
- * Parse Form dom to array data.
- */
- class formdom
- {
- /**
- * Errors
- *
- * @var array
- * @access private
- */
- private $errors = array();
- /**
- * Options
- *
- * @var array
- * @access private
- */
- private $options = array(
- 'include_disabled' => false,
- 'include_empty_checkboxes' => false,
- 'default_radio_value' => null,
- 'debug' => false
- );
- /**
- * Constructor
- *
- * @param array $options
- * @access public
- * @return void
- */
- public function __construct($options = array())
- {
- $this->options = array_merge($this->options, $options);
- }
- /**
- * Parse html dom
- *
- * @param string $html
- * @param string $formSelector
- * @access public
- * @return array
- */
- public function parse($html, $formSelector = null)
- {
- $this->errors = array();
- if(empty($html))
- {
- $this->errors[] = "HTML is empty";
- return array();
- }
- $dom = new DOMDocument();
- libxml_use_internal_errors(true);
- @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
- libxml_clear_errors();
- $xpath = new DOMXPath($dom);
- /* 查找表单 */
- $form = null;
- if($formSelector)
- {
- // 通过 id 查找
- $forms = $xpath->query("//form[@id='$formSelector']");
- if($forms->length > 0)
- {
- $form = $forms->item(0);
- }
- else
- {
- /* 通过 name 查找 */
- $forms = $xpath->query("//form[@name='$formSelector']");
- if($forms->length > 0)
- {
- $form = $forms->item(0);
- }
- }
- } else {
- /* 获取第一个表单 */
- $forms = $xpath->query("//form");
- if ($forms->length > 0) {
- $form = $forms->item(0);
- }
- }
- if(!$form)
- {
- $this->errors[] = "Form not found";
- return array();
- }
- /* 提取表单数据 */
- $result = $this->extractFormData($xpath, $form);
- return $result;
- }
- /**
- * Extract form data
- *
- * @param object $xpath
- * @param object $form
- * @access private
- * @return array
- */
- private function extractFormData($xpath, $form)
- {
- $data = [];
- /* 处理所有表单元素 */
- $this->processInputs($xpath, $form, $data);
- $this->processSelects($xpath, $form, $data);
- $this->processTextareas($xpath, $form, $data);
- $this->processZinPickers($xpath, $form, $data);
- $this->processZinEditors($xpath, $form, $data);
- return $data;
- }
- /**
- * Process inputs
- *
- * @param object $xpath
- * @param object $form
- * @param array $data
- * @access private
- * @return void
- */
- private function processInputs($xpath, $form, &$data)
- {
- // 查找所有 input 元素(包括嵌套的)
- $inputs = $xpath->query(".//input[@name]", $form);
- $radioGroups = array();
- foreach($inputs as $input)
- {
- $name = $input->getAttribute('name');
- $type = strtolower($input->getAttribute('type') ?: 'text');
- $value = $input->getAttribute('value');
- $disabled = $input->hasAttribute('disabled');
- /* 跳过禁用的元素(除非配置允许) */
- if(!$this->options['include_disabled'] && $disabled) continue;
- switch($type)
- {
- case 'checkbox':
- if($input->hasAttribute('checked'))
- {
- $this->addValue($data, $name, $value ?: 'on');
- }
- elseif($this->options['include_empty_checkboxes'])
- {
- $this->addValue($data, $name, '');
- }
- break;
- case 'radio':
- $baseName = $this->getBaseName($name);
- if(!isset($radioGroups[$baseName]))
- {
- $radioGroups[$baseName] = array(
- 'selected' => null,
- 'hasSelection' => false
- );
- }
- if($input->hasAttribute('checked'))
- {
- $radioGroups[$baseName]['selected'] = $value;
- $radioGroups[$baseName]['hasSelection'] = true;
- }
- break;
- case 'file':
- $this->addValue($data, $name, '');
- break;
- case 'submit':
- case 'button':
- case 'image':
- case 'reset':
- break;
- default:
- /* 包括 hidden, text, password, email 等所有其他类型 */
- $this->addValue($data, $name, $value ?: '');
- break;
- }
- }
- /* 处理 radio 组 */
- foreach ($radioGroups as $name => $group) {
- if ($group['hasSelection']) {
- $data[$name] = $group['selected'];
- } elseif ($this->options['default_radio_value'] !== null) {
- $data[$name] = $this->options['default_radio_value'];
- }
- }
- }
- /**
- * Process selects
- *
- * @param object $xpath
- * @param object $form
- * @param array $data
- * @access private
- * @return void
- */
- private function processSelects($xpath, $form, &$data)
- {
- $selects = $xpath->query(".//select[@name]", $form);
- foreach($selects as $select)
- {
- if(!$this->options['include_disabled'] && $select->hasAttribute('disabled')) continue;
- $name = $select->getAttribute('name');
- $multiple = $select->hasAttribute('multiple');
- $options = $xpath->query(".//option", $select);
- if($multiple)
- {
- $selectedValues = [];
- foreach($options as $option)
- {
- if($option->hasAttribute('selected'))
- {
- $value = $option->hasAttribute('value') ? $option->getAttribute('value') : trim($option->textContent);
- $selectedValues[] = $value;
- }
- }
- /* 对于多选,即使没有选中项,也要处理默认值 */
- foreach($options as $option)
- {
- $value = $option->hasAttribute('value') ? $option->getAttribute('value') : trim($option->textContent);
- if(!empty($value))
- {
- $selectedValues[] = $value;
- break; // 只取第一个有值的选项作为默认
- }
- }
- if(!empty($selectedValues))
- {
- foreach($selectedValues as $val) $this->addValue($data, $name, $val);
- }
- }
- else
- {
- $selectedValue = null;
- $firstValue = null;
- foreach($options as $index => $option)
- {
- $value = $option->hasAttribute('value')
- ? $option->getAttribute('value')
- : trim($option->textContent);
- if($index === 0) $firstValue = $value;
- if($option->hasAttribute('selected'))
- {
- $selectedValue = $value;
- break;
- }
- }
- $finalValue = $selectedValue !== null ? $selectedValue : $firstValue;
- if($finalValue !== null) $this->addValue($data, $name, $finalValue);
- }
- }
- }
- /**
- * Process textarea
- *
- * @param object $xpath
- * @param object $form
- * @param array $data
- * @access private
- * @return void
- */
- private function processTextareas($xpath, $form, &$data)
- {
- $textareas = $xpath->query(".//textarea[@name]", $form);
- foreach($textareas as $textarea)
- {
- if(!$this->options['include_disabled'] && $textarea->hasAttribute('disabled')) continue;
- $name = $textarea->getAttribute('name');
- $value = $textarea->textContent;
- $this->addValue($data, $name, $value);
- }
- }
- /**
- * Process zin pickers and date pickers.
- *
- * @param object $xpath
- * @param object $form
- * @param array $data
- * @access private
- * @return void
- */
- private function processZinPickers($xpath, $form, &$data)
- {
- /* 查找所有包含 zui-create-picker 属性的元素(通常是 div.picker-box) */
- $pickers = $xpath->query(".//*[@zui-create]", $form);
- foreach ($pickers as $picker) {
- /* 1. 跳过禁用的picker(如果配置不允许) */
- $pickerDisabled = $picker->hasAttribute('disabled') || $picker->getAttribute('disabled') === 'true';
- if(!$this->options['include_disabled'] && $pickerDisabled) continue;
- /* 2. 获取 zui-create-picker 属性的JSON值 */
- if($picker->hasAttribute('zui-create-picker'))
- {
- $pickerConfig = $picker->getAttribute('zui-create-picker');
- }
- elseif($picker->hasAttribute('zui-create-datepicker'))
- {
- $pickerConfig = $picker->getAttribute('zui-create-datepicker');
- }
- if(empty($pickerConfig)) continue;
- /* 3. 提取name(关键:用正则匹配name字段)*/
- $name = null;
- if(preg_match('/"name"\s*:\s*"([^"]+)"/i', $pickerConfig, $nameMatches))
- {
- $name = $nameMatches[1]; // 捕获引号中的name值
- }
- elseif(preg_match("/'name'\s*:\s*'([^']+)'/i", $pickerConfig, $nameMatches))
- {
- $name = $nameMatches[1]; // 兼容单引号
- }
- if(empty($name)) continue;
- /* 4. 提取defaultValue(核心:兼容JS函数和非JSON格式)*/
- $defaultValue = '';
- /* 正则规则:匹配 "defaultValue": 值 或 'defaultValue': 值 */
- /* 支持的值类型:字符串(单/双引号)、数字、true/false/null */
- $valuePattern = '/"defaultValue"\s*:\s*(?:"([^"]+)"|\'([^\']+)\'|(\d+\.?\d*)|(true|false|null)|\[([^\]]+)\])/i';
- if(preg_match($valuePattern, $pickerConfig, $valueMatches))
- {
- /* 匹配优先级:数组 > 双引号字符串 > 单引号字符串 > 数字 > 布尔/null */
- $defaultValue = !empty($valueMatches[5]) ? $valueMatches[5] // 优先级1:数组内容
- : (!empty($valueMatches[1]) ? $valueMatches[1] // 优先级2:双引号字符串
- : (!empty($valueMatches[2]) ? $valueMatches[2] // 优先级3:单引号字符串
- : (!empty($valueMatches[3]) || $valueMatches[3] === '0' ? $valueMatches[3] // 优先级4:数字
- : $valueMatches[4] ?? null))); // 优先级5:布尔/null
- /* 转换类型(如"true"→true,"123"→123)*/
- if ($defaultValue === 'true') $defaultValue = true;
- elseif ($defaultValue === 'false') $defaultValue = false;
- elseif ($defaultValue === 'null') $defaultValue = null;
- elseif (is_numeric($defaultValue)) $defaultValue = (float)$defaultValue;
- }
- /* 5. 写入数据 */
- $this->addValue($data, $name, $defaultValue);
- }
- }
- /**
- * Process zin editors.
- *
- * @param object $xpath
- * @param object $form
- * @param array $data
- * @access private
- * @return void
- */
- private function processZinEditors($xpath, $form, &$data)
- {
- $editors = $xpath->query("//zen-editor", $form);
- foreach($editors as $editor)
- {
- $name = $editor->getAttribute('name');
- $value = '';
- $contentNode = $xpath->query(".//article[@slot='content']", $editor)->item(0);
- if($contentNode)
- {
- /* 获取内部HTML内容 */
- $dom = $contentNode->ownerDocument;
- foreach($contentNode->childNodes as $child) $value .= $dom->saveHTML($child);
- }
- $this->addValue($data, $name, $value);
- }
- }
- /**
- * Add value to data.
- *
- * @param array $data
- * @param string $name
- * @param mixed $value
- * @access private
- * @return void
- */
- private function addValue(&$data, $name, $value)
- {
- /* 使用 parse_str 来处理名称 */
- if($value === null) $value = '';
- parse_str($name . '=' . urlencode($value), $temp);
- /* 合并到数据中 */
- foreach($temp as $key => $val)
- {
- if(!isset($data[$key]))
- {
- $data[$key] = $val;
- }
- elseif(is_array($data[$key]) && is_array($val))
- {
- /* 递归合并数组 */
- foreach($val as $v)
- {
- if(!in_array($v, $data[$key])) $data[$key][] = $v;
- }
- }
- else
- {
- /* 覆盖(处理重复的非数组字段) */
- $data[$key] = $val;
- }
- }
- }
- /**
- * Get base name of field.
- *
- * @param string $name
- * @access private
- * @return string
- */
- private function getBaseName($name)
- {
- if(preg_match('/^([^\[]+)/', $name, $matches)) return $matches[1];
- return $name;
- }
- /**
- * Get errors.
- *
- * @access public
- * @return array
- */
- public function getErrors()
- {
- return $this->errors;
- }
- }
|