2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
39 * SugarHtml is a static class that provides a collection of helper methods for creating HTML DOM elements.
41 * @auther Justin Park(jpark@sugarcrm.com)
45 const SINGLE_QUOTE = "'";
46 const DOUBLE_QUOTE = '"';
47 const ASSIGN_SIGN = "=";
48 const HTML_TAG_BEGIN = "<";
49 const HTML_TAG_END = ">";
50 const SMARTY_TAG_BEGIN = "{";
51 const SMARTY_TAG_END = "}";
52 const CLAUSE_TAG_BEGIN = "(";
53 const CLAUSE_TAG_END = ")";
56 * @var integer the counter for generating automatic input field names.
58 public static $count=0;
62 * Create an open html element
64 * @param String $tagName - the tag name
65 * @param array $params - the element attributes
66 * @param bool $self_closing - whether the element is self-closing or not
67 * @return string - generated html string
69 public static function createOpenTag($tagName, $params = array(), $self_closing = false) {
71 $self_closing_tag = ($self_closing) ? "/" : "";
73 return "<{$tagName}{$self_closing_tag}>";
75 $options = self::createAttributes($params);
76 return "<{$tagName} {$options}{$self_closing_tag}>";
80 * Create a close html element
82 * @param String $tagName - the tag name
83 * @return string - generated html string
85 public static function createCloseTag($tagName) {
86 return "</{$tagName}>";
90 * Create a html block given a cascade array tree.
91 * Resemble the array form generated by parseHtmlTag
92 * @see SugarHtml::parseHtmlTag
94 * @param array $dom_tree - Cascade array form
96 * 1. Simple html format
99 * // all attributes are assigned in key-value form
101 * 'class' => 'wrapper',
105 * 2. Cascade html format
108 * //all subitems are assigned in container
109 * 'container' => array(
112 * 'type' => 'hidden',
119 * //all siblings are assigned in a parallel array form
120 * array('tag' => 'input', 'type' => 'button', ... ),
121 * array('tag' => 'input', 'type' => 'submit', ... )
128 * //smarty must contain 'template'. Container will replace with the same key value
129 * 'template' => '{if}[CONTENT1]{else}[CONTENT2]{/if}',
130 * //content1 will be assign on the lefthand side, and content2 will be on righthand side
131 * '[CONTENT1]' => array(
132 * //cascade valid array form
134 * '[CONTENT2]' => array(...)
140 * @return string - generated html string
142 public static function createHtml($dom_tree = array()) {
145 if(isset($dom_tree[0])) { //dom contains sibling items
146 foreach($dom_tree as $dom) {
147 $out .= is_array($dom) ? self::createHtml($dom) : $dom;
149 } else if( isset($dom_tree['tag']) ) {
150 $tagName = $dom_tree['tag'];
151 $self_closing = $dom_tree['self_closing'];
152 unset($dom_tree['tag']);
153 unset($dom_tree['self_closing']);
154 if(isset($dom_tree['container'])) {
155 $container = $dom_tree['container'];
156 unset($dom_tree['container']);
158 $out .= self::HTML_TAG_BEGIN."{$tagName} ";
159 if(isset($dom_tree['smarty'])) {
160 $out .= self::createHtml(array(
161 'smarty' => $dom_tree['smarty']
163 unset($dom_tree['smarty']);
165 $out .= self::createHtml($dom_tree);
169 $out .= self::HTML_TAG_END;
170 $out .= (is_array($container)) ? self::createHtml($container) : $container;
171 $out .= self::createCloseTag($tagName);
173 } else if( isset($dom_tree['smarty']) ) { //dom contains smarty function
175 foreach($dom_tree['smarty'] as $blocks) {
176 $template = $blocks['template'];
177 unset($blocks['template']);
178 $replacement = array();
179 foreach($blocks as $key => $value) {
180 $replacement[$key] = is_array($value) ? self::createHtml($value) : $value;
182 if($count++ > 0) $out .= ' ';
183 $out .= strtr($template, $replacement);
187 foreach($dom_tree as $attr => $value) {
188 if($count++ > 0) $out .= ' ';
189 $out .= (empty($value)) ? $attr : $attr.'="'.$value.'"';
196 public static function parseSugarHtml($sugar_html = array()) {
198 $input_types = array(
199 'submit', 'button', 'hidden', 'checkbox', 'input'
202 if(in_array($sugar_html['type'], $input_types)) {
203 $sugar_html['htmlOptions']['type'] = (empty($sugar_html['htmlOptions']['type'])) ? $sugar_html['type'] : $sugar_html['htmlOptions']['type'];
204 $sugar_html['htmlOptions']['value'] = $sugar_html['value'];
206 $output = array_merge(array(
208 'self_closing' => true,
209 ), $sugar_html['htmlOptions']);
211 if(isset($sugar_html['template'])) {
215 'template' => $sugar_html['template'],
216 '[CONTENT]' => $output
227 * Disassemble an html string into a cascaded array form elements
229 * @param String $code - html string (support the mixed html string with smarty blocks)
230 * @param array $appendTo - Precedent siblings
231 * @return array - structual array form, can be restored in a html code by createHtml
232 * @see SugarHtml::createHtml
234 public static function parseHtmlTag($code, $appendTo = array()) {
235 $code = ltrim($code);
236 $start_pos = strpos($code, ' ') ? strpos($code, ' ') : strpos($code, self::HTML_TAG_END);
238 if(substr($code, 0, 1) != self::HTML_TAG_BEGIN || $start_pos === false) {
240 self::parseSmartyTag($code, $output, $offset);
241 $remainder = ltrim(substr($code, $offset));
242 if(!empty($remainder)) {
243 array_push($appendTo, $output);
244 return self::parseHtmlTag($remainder, $appendTo);
247 $tag = substr($code, 1, $start_pos - 1);
248 $closing_tag = '</'.$tag;
249 $end_pos = strpos($code, $closing_tag, $start_pos + 1);
250 $output['tag'] = $tag;
252 if($end_pos === false) {
253 $output['self_closing'] = true;
254 $code = substr($code, $start_pos + 1);
256 $output['self_closing'] = false;
257 $code = substr($code, $start_pos + 1, $end_pos - $start_pos - 1);
259 $remainder = self::extractAttributes($code, $output);
261 if(!empty($remainder)) {
262 array_push($appendTo, $output);
263 return self::parseHtmlTag($remainder, $appendTo);
266 return (empty($appendTo)) ? $output : array_merge($appendTo, array($output));
271 * Disassemble a smarty string into a cascaded array form elements
273 * @param String $code - smarty encoded string
274 * @param Array $output - parsed attribute
276 * @param bool $is_attr - whether current smarty block is inside html attributes or not
278 public static function parseSmartyTag($code, &$output, &$offset = 0, $is_attr = false) {
279 if(empty($output['smarty']))
280 $output['smarty'] = array();
282 $_str = ltrim(substr($code, $offset + 1));
284 preg_match("/^[$\w]+/", $_str, $statement);
285 $_smarty_closing = self::SMARTY_TAG_BEGIN.'/'.$statement[0];
286 $_left = strlen($statement[0]);
288 $_right = strpos($code, $_smarty_closing, $offset);
289 if($_right === false) { //smarty closed itself
290 $_right = strpos($code, self::SMARTY_TAG_END, $offset);
292 preg_match_all('/\{( |)+'.substr($_str, 0, $_left).'/', substr($_str, 0, $_right), $matches);
294 $match_count = count($matches[0]);
295 while($match_count-- > 0) {
296 $_right = strpos($code, $_smarty_closing, $_right + strlen($_smarty_closing));
299 $_right = strpos($code, self::SMARTY_TAG_END, $_right);
301 $smarty_string = substr($code, $offset, $_right + 1 - $offset);
303 $clauses = array_slice(
304 //split into each clause
305 preg_split("/[\{\}]/i", $smarty_string),
306 1, -1 //slice out the first and last items, which is empty.
308 $smarty_template = array(
311 //Concatenate smarty variables
312 $reserved_strings = array(
313 '$', 'ldelim', 'rdelim'
315 $reserved_functions = array(
321 $current_literal_string = '';
322 for($seq = 0; $seq < count($clauses); $seq++) {
323 $is_reserved = false;
325 $current_literal_string = !empty($current_literal_string) ? $current_literal_string : (isset($reserved_functions[trim($clauses[$seq])]) ? trim($clauses[$seq]) : '');
326 $is_literal = $is_literal || !empty($current_literal_string);
328 foreach($reserved_strings as $str) {
329 if(substr(ltrim($clauses[$seq]), 0, strlen($str)) == $str) {
334 if($is_literal || ($seq > 0 && $is_reserved)) {
336 $clauses[$queue] = self::SMARTY_TAG_BEGIN.$clauses[$seq].self::SMARTY_TAG_END;
338 $clauses[--$queue] .= self::SMARTY_TAG_BEGIN.$clauses[$seq].self::SMARTY_TAG_END;
339 $is_literal = $is_literal && (substr(ltrim($clauses[$seq]), 0, strlen("/".$current_literal_string)) != "/".$current_literal_string);
340 $current_literal_string = ($is_literal) ? $current_literal_string : '';
341 if($seq < count($clauses) - 1) {
342 $clauses[$queue++] .= $clauses[++$seq];
347 $clauses[$queue++] = $clauses[$seq];
350 array_splice($clauses, $queue);
351 //Split phrases for the conditional statement
354 for($seq = 0; $seq < count($clauses); $seq++) {
356 if ($seq > 0 && substr(ltrim($clauses[$seq]), 0, 2) == 'if') {
360 $clauses[--$queue] .= ($seq % 2 == 0) ? self::SMARTY_TAG_BEGIN.$clauses[$seq].self::SMARTY_TAG_END : $clauses[$seq];
361 if($seq < count($clauses)) {
362 $clauses[$queue++] .= $clauses[++$seq];
365 $clauses[$queue++] = $clauses[$seq];
368 if ( $seq > 0 && substr(ltrim($clauses[$seq - 1]), 0, 3) == '/if') {
372 array_splice($clauses, $queue);
374 //resemble the smarty phases
376 foreach($clauses as $index => $clause) {
377 if($index % 2 == 0) {
378 if(self::SMARTY_TAG_BEGIN == substr($clause, 0, 1) && self::SMARTY_TAG_END == substr($clause, -1, 1))
379 $smarty_template['template'] .= $clause;
381 $smarty_template['template'] .= '{'.$clause.'}';
382 } else if( !empty($clause) ){
383 $key = '[CONTENT'.($seq++).']';
384 $smarty_template['template'] .= $key;
387 self::extractAttributes($clause, $params);
389 $params = self::parseHtmlTag($clause);
390 $smarty_template[$key] = $params;
393 $output['smarty'][] = $smarty_template;
394 $offset = $_right + 1;
399 * Disassemble an html attributes into a key-value array form
401 * @param String $code - attribute string (i.e. - id='form_id' name='button' value='View Detail' ...)
402 * @param Array $output - Parsed the attribute into key-value form
403 * @return string - Remainder string by spliting with ">"
405 public static function extractAttributes($code, &$output) {
407 $quote_encoded = false;
408 $smarty_encoded = false;
410 $code = rtrim($code);
411 for($i = 0; $i < strlen($code) ; $i ++) {
413 if( !$smarty_encoded && ($char == self::SINGLE_QUOTE || $char == self::DOUBLE_QUOTE) ) {
414 if(empty($quote_type)) {
415 $quote_encoded = true;
417 } else if ($quote_type == $char) {
419 $string = implode('', $cache);
420 if(empty($var_name)) {
422 } else if($var_assign) {
423 $output[trim($var_name)] = $string;
430 $quote_encoded = false;
432 array_push($cache, $char);
434 } else if ( $quote_encoded && $char == self::SMARTY_TAG_BEGIN ) {
435 $smarty_encoded = true;
436 array_push($cache, $char);
437 } else if ( $quote_encoded && $char == self::SMARTY_TAG_END ) {
438 $smarty_encoded = false;
439 array_push($cache, $char);
440 } else if ( !$quote_encoded && $char == ' ' ) {
442 $string = implode('', $cache);
443 if(empty($var_name)) {
445 } else if($var_assign) {
446 $output[trim($var_name)] = $string;
449 $quote_encoded = false;
453 } else if ( !$quote_encoded && $char == self::ASSIGN_SIGN ) {
455 if(!empty($var_name)) {
456 $output[$var_name] = '';
458 $string = implode('', $cache);
459 if(trim($string) != "") {
464 } else if ( !$quote_encoded && $char == self::SMARTY_TAG_BEGIN) {
465 self::parseSmartyTag($code, $output, $i, true);
466 } else if ( !$quote_encoded && $char == self::HTML_TAG_END ) {
469 array_push($cache, $char);
473 $var_name = implode('', $cache);
474 $output[$var_name] = '';
477 if(isset($output['self_closing']) && $output['self_closing'] === false) {
478 $output['container'] = self::parseHtmlTag(substr($code, $i + 1));
482 return substr($code, $i + 1);
487 * Creates HTML attribute elements corresponding key-value pair.
489 * @param Array $params - Attributes (key-value pair)
490 * @return string - Generated html attribute string
492 public static function createAttributes($params) {
494 foreach($params as $attr => $value) {
495 if(is_numeric($attr) === false) {
498 $options .= $attr.'="'.$value.'" ';
499 elseif(!empty($attr))
500 $options .= $attr.' ';