salesagility_SuiteCRM/include/SugarHtml/SugarHtml.php

552 lines
21 KiB
PHP
Executable File

<?php
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
/**
*
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* 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 Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
*/
/**
* SugarHtml is a static class that provides a collection of helper methods for creating HTML DOM elements.
*
* @auther Justin Park(jpark@sugarcrm.com)
*
*/
class SugarHtml
{
const SINGLE_QUOTE = "'";
const DOUBLE_QUOTE = '"';
const ASSIGN_SIGN = "=";
const HTML_TAG_BEGIN = "<";
const HTML_TAG_END = ">";
const SMARTY_TAG_BEGIN = "{";
const SMARTY_TAG_END = "}";
const CLAUSE_TAG_BEGIN = "(";
const CLAUSE_TAG_END = ")";
/**
* @var integer the counter for generating automatic input field names.
*/
public static $count=0;
/**
* @static
* Create an open html element
*
* @param String $tagName - the tag name
* @param array $params - the element attributes
* @param bool $self_closing - whether the element is self-closing or not
* @return string - generated html string
*/
public static function createOpenTag($tagName, $params = array(), $self_closing = false)
{
$self_closing_tag = ($self_closing) ? "/" : "";
if (empty($params)) {
return "<{$tagName}{$self_closing_tag}>";
}
$options = self::createAttributes($params);
return "<{$tagName} {$options}{$self_closing_tag}>";
}
/**
* @static
* Create a close html element
*
* @param String $tagName - the tag name
* @return string - generated html string
*/
public static function createCloseTag($tagName)
{
return "</{$tagName}>";
}
/**
* @static
* Create a html block given a cascade array tree.
* Resemble the array form generated by parseHtmlTag
* @see SugarHtml::parseHtmlTag
*
* @param array $dom_tree - Cascade array form
* <pre>
* 1. Simple html format
* array(
* 'tag' => 'div',
* // all attributes are assigned in key-value form
* 'id' => 'div_id',
* 'class' => 'wrapper',
* ...
* )
*
* 2. Cascade html format
* array(
* 'tag' => 'div',
* //all subitems are assigned in container
* 'container' => array(
* array(
* 'tag' => 'input',
* 'type' => 'hidden',
* )
* )
* )
*
* 3. Siblings
* array(
* //all siblings are assigned in a parallel array form
* array('tag' => 'input', 'type' => 'button', ... ),
* array('tag' => 'input', 'type' => 'submit', ... )
* )
*
* 4. Smarty
* array(
* 'smarty' => array(
* array(
* //smarty must contain 'template'. Container will replace with the same key value
* 'template' => '{if}[CONTENT1]{else}[CONTENT2]{/if}',
* //content1 will be assign on the lefthand side, and content2 will be on righthand side
* '[CONTENT1]' => array(
* //cascade valid array form
* )
* '[CONTENT2]' => array(...)
* ),
* )
* )
* </pre>
*
* @return string - generated html string
*/
public static function createHtml($dom_tree = array())
{
$out = "";
if (isset($dom_tree[0])) { //dom contains sibling items
foreach ($dom_tree as $dom) {
$out .= is_array($dom) ? self::createHtml($dom) : $dom;
}
} else {
if (isset($dom_tree['tag'])) {
$tagName = $dom_tree['tag'];
$self_closing = $dom_tree['self_closing'];
unset($dom_tree['tag']);
unset($dom_tree['self_closing']);
if (isset($dom_tree['container'])) {
$container = $dom_tree['container'];
unset($dom_tree['container']);
}
$out .= self::HTML_TAG_BEGIN."{$tagName} ";
if (isset($dom_tree['smarty'])) {
$out .= self::createHtml(array(
'smarty' => $dom_tree['smarty']
)).' ';
unset($dom_tree['smarty']);
}
$out .= self::createHtml($dom_tree);
if ($self_closing) {
$out .= '/>';
} else {
$out .= self::HTML_TAG_END;
$out .= (is_array($container)) ? self::createHtml($container) : $container;
$out .= self::createCloseTag($tagName);
}
} else {
if (isset($dom_tree['smarty'])) { //dom contains smarty function
$count = 0;
foreach ($dom_tree['smarty'] as $blocks) {
$template = $blocks['template'];
unset($blocks['template']);
$replacement = array();
foreach ($blocks as $key => $value) {
$replacement[$key] = is_array($value) ? self::createHtml($value) : $value;
}
if ($count++ > 0) {
$out .= ' ';
}
$out .= strtr($template, $replacement);
}
} else {
$count = 0;
foreach ($dom_tree as $attr => $value) {
if ($count++ > 0) {
$out .= ' ';
}
$out .= (empty($value)) ? $attr : $attr.'="'.$value.'"';
}
}
}
}
return $out;
}
public static function parseSugarHtml($sugar_html = array())
{
$output = array();
$input_types = array(
'submit', 'button', 'hidden', 'checkbox', 'input'
);
if (in_array($sugar_html['type'], $input_types)) {
$sugar_html['htmlOptions']['type'] = (empty($sugar_html['htmlOptions']['type'])) ? $sugar_html['type'] : $sugar_html['htmlOptions']['type'];
$sugar_html['htmlOptions']['value'] = $sugar_html['value'];
$output = array_merge(array(
'tag' => 'input',
'self_closing' => true,
), $sugar_html['htmlOptions']);
}
if (isset($sugar_html['template'])) {
$output = array(
'smarty' => array(
array(
'template' => $sugar_html['template'],
'[CONTENT]' => $output
)
)
);
}
return $output;
}
/**
* @static
* Disassemble an html string into a cascaded array form elements
*
* @param String $code - html string (support the mixed html string with smarty blocks)
* @param array $appendTo - Precedent siblings
* @return array - structual array form, can be restored in a html code by createHtml
* @see SugarHtml::createHtml
*/
public static function parseHtmlTag($code, $appendTo = array())
{
$code = ltrim($code);
$start_pos = strpos($code, ' ') ? strpos($code, ' ') : strpos($code, self::HTML_TAG_END);
$output = array();
if (substr($code, 0, 1) != self::HTML_TAG_BEGIN || $start_pos === false) {
$offset = 0;
self::parseSmartyTag($code, $output, $offset);
$remainder = ltrim(substr($code, $offset));
if (!empty($remainder)) {
array_push($appendTo, $output);
return self::parseHtmlTag($remainder, $appendTo);
}
} else {
$tag = substr($code, 1, $start_pos - 1);
$closing_tag = '</'.$tag;
$end_pos = strpos($code, $closing_tag, $start_pos + 1);
$output['tag'] = $tag;
if ($end_pos === false) {
$output['self_closing'] = true;
$code = substr($code, $start_pos + 1);
} else {
$output['self_closing'] = false;
$code = substr($code, $start_pos + 1, $end_pos - $start_pos - 1);
}
$remainder = self::extractAttributes($code, $output);
if (!empty($remainder)) {
array_push($appendTo, $output);
return self::parseHtmlTag($remainder, $appendTo);
}
}
return (empty($appendTo)) ? $output : array_merge($appendTo, array($output));
}
/**
* @static
* Disassemble a smarty string into a cascaded array form elements
*
* @param String $code - smarty encoded string
* @param Array $output - parsed attribute
* @param int $offset
* @param bool $is_attr - whether current smarty block is inside html attributes or not
*/
public static function parseSmartyTag($code, &$output, &$offset = 0, $is_attr = false)
{
if (empty($output['smarty'])) {
$output['smarty'] = array();
}
$_str = ltrim(substr($code, $offset + 1));
preg_match("/^[$\w]+/", $_str, $statement);
$_smarty_closing = self::SMARTY_TAG_BEGIN.'/'.$statement[0];
$_left = strlen($statement[0]);
$_right = strpos($code, $_smarty_closing, $offset);
if ($_right === false) { //smarty closed itself
$_right = strpos($code, self::SMARTY_TAG_END, $offset);
} else {
preg_match_all('/\{( |)+'.substr($_str, 0, $_left).'/', substr($_str, 0, $_right), $matches);
$match_count = count($matches[0]);
while ($match_count-- > 0) {
$_right = strpos($code, $_smarty_closing, $_right + strlen($_smarty_closing));
}
$_right = strpos($code, self::SMARTY_TAG_END, $_right);
}
$smarty_string = substr($code, $offset, $_right + 1 - $offset);
$clauses = array_slice(
//split into each clause
preg_split("/[\{\}]/i", $smarty_string),
1,
-1 //slice out the first and last items, which is empty.
);
$smarty_template = array(
'template' => '',
);
//Concatenate smarty variables
$reserved_strings = array(
'$', 'ldelim', 'rdelim'
);
$reserved_functions = array(
'literal' => false,
'nocache' => false,
);
$queue = 0;
$is_literal = false;
$current_literal_string = '';
for ($seq = 0; $seq < count($clauses); $seq++) {
$is_reserved = false;
$current_literal_string = !empty($current_literal_string) ? $current_literal_string : (isset($reserved_functions[trim($clauses[$seq])]) ? trim($clauses[$seq]) : '');
$is_literal = $is_literal || !empty($current_literal_string);
foreach ($reserved_strings as $str) {
if (substr(ltrim($clauses[$seq]), 0, strlen($str)) == $str) {
$is_reserved = true;
break;
}
}
if ($is_literal || ($seq > 0 && $is_reserved)) {
if ($queue == 0) {
$clauses[$queue] = self::SMARTY_TAG_BEGIN.$clauses[$seq].self::SMARTY_TAG_END;
} else {
$clauses[--$queue] .= self::SMARTY_TAG_BEGIN.$clauses[$seq].self::SMARTY_TAG_END;
}
$is_literal = $is_literal && (substr(ltrim($clauses[$seq]), 0, strlen("/".$current_literal_string)) != "/".$current_literal_string);
$current_literal_string = ($is_literal) ? $current_literal_string : '';
if ($seq < count($clauses) - 1) {
$clauses[$queue++] .= $clauses[++$seq];
} else {
$queue++;
}
} else {
$clauses[$queue++] = $clauses[$seq];
}
}
array_splice($clauses, $queue);
//Split phrases for the conditional statement
$count = 0;
$queue = 0;
for ($seq = 0; $seq < count($clauses); $seq++) {
if ($seq > 0 && substr(ltrim($clauses[$seq]), 0, 2) == 'if') {
$count++;
}
if ($count > 0) {
$clauses[--$queue] .= ($seq % 2 == 0) ? self::SMARTY_TAG_BEGIN.$clauses[$seq].self::SMARTY_TAG_END : $clauses[$seq];
if ($seq < count($clauses)) {
$clauses[$queue++] .= $clauses[++$seq];
}
} else {
$clauses[$queue++] = $clauses[$seq];
}
if ($seq > 0 && substr(ltrim($clauses[$seq - 1]), 0, 3) == '/if') {
$count--;
}
}
array_splice($clauses, $queue);
//resemble the smarty phases
$seq = 0;
foreach ($clauses as $index => $clause) {
if ($index % 2 == 0) {
if (self::SMARTY_TAG_BEGIN == substr($clause, 0, 1) && self::SMARTY_TAG_END == substr($clause, -1, 1)) {
$smarty_template['template'] .= $clause;
} else {
$smarty_template['template'] .= '{'.$clause.'}';
}
} else {
if (!empty($clause)) {
$key = '[CONTENT'.($seq++).']';
$smarty_template['template'] .= $key;
$params = array();
if ($is_attr) {
self::extractAttributes($clause, $params);
} else {
$params = self::parseHtmlTag($clause);
}
$smarty_template[$key] = $params;
}
}
}
$output['smarty'][] = $smarty_template;
$offset = $_right + 1;
}
/**
* @static
* Disassemble an html attributes into a key-value array form
*
* @param String $code - attribute string (i.e. - id='form_id' name='button' value='View Detail' ...)
* @param Array $output - Parsed the attribute into key-value form
* @return string - Remainder string by spliting with ">"
*/
public static function extractAttributes($code, &$output)
{
$var_assign = false;
$quote_encoded = false;
$smarty_encoded = false;
$cache = array();
$code = rtrim($code);
for ($i = 0; $i < strlen($code) ; $i ++) {
$char = $code[$i];
if (!$smarty_encoded && ($char == self::SINGLE_QUOTE || $char == self::DOUBLE_QUOTE)) {
if (empty($quote_type)) {
$quote_encoded = true;
$quote_type = $char;
} else {
if ($quote_type == $char) {
if (!empty($cache)) {
$string = implode('', $cache);
if (empty($var_name)) {
$var_name = $string;
} else {
if ($var_assign) {
$output[trim($var_name)] = $string;
unset($var_name);
}
}
}
$quote_type = '';
$var_assign = false;
$cache = array();
$quote_encoded = false;
} else {
array_push($cache, $char);
}
}
} else {
if ($quote_encoded && $char == self::SMARTY_TAG_BEGIN) {
$smarty_encoded = true;
array_push($cache, $char);
} else {
if ($quote_encoded && $char == self::SMARTY_TAG_END) {
$smarty_encoded = false;
array_push($cache, $char);
} else {
if (!$quote_encoded && $char == ' ') {
if (!empty($cache)) {
$string = implode('', $cache);
if (empty($var_name)) {
$var_name = $string;
} else {
if ($var_assign) {
$output[trim($var_name)] = $string;
unset($var_name);
}
}
$quote_encoded = false;
$var_assign = false;
$cache = array();
}
} else {
if (!$quote_encoded && $char == self::ASSIGN_SIGN) {
if (!empty($var_name)) {
$output[$var_name] = '';
}
$string = implode('', $cache);
if (trim($string) != "") {
$var_name = $string;
}
$var_assign = true;
$cache = array();
} else {
if (!$quote_encoded && $char == self::SMARTY_TAG_BEGIN) {
self::parseSmartyTag($code, $output, $i, true);
} else {
if (!$quote_encoded && $char == self::HTML_TAG_END) {
break;
} else {
array_push($cache, $char);
}
}
}
}
}
}
}
}
if (!empty($cache)) {
$var_name = implode('', $cache);
$output[$var_name] = '';
}
if (isset($output['self_closing']) && $output['self_closing'] === false) {
$output['container'] = self::parseHtmlTag(substr($code, $i + 1));
return '';
}
return substr($code, $i + 1);
}
/**
* @static
* Creates HTML attribute elements corresponding key-value pair.
*
* @param Array $params - Attributes (key-value pair)
* @return string - Generated html attribute string
*/
public static function createAttributes($params)
{
$options = "";
foreach ($params as $attr => $value) {
if (is_numeric($attr) === false) {
$attr = trim($attr);
if ($value) {
$options .= $attr.'="'.$value.'" ';
} elseif (!empty($attr)) {
$options .= $attr.' ';
}
}
}
return $options;
}
}