]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarHtml/SugarHtml.php
Release 6.5.0
[Github/sugarcrm.git] / include / SugarHtml / SugarHtml.php
1 <?php
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.
6  * 
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.
13  * 
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
17  * details.
18  * 
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
22  * 02110-1301 USA.
23  * 
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.
26  * 
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.
30  * 
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  ********************************************************************************/
37
38 /**
39  * SugarHtml is a static class that provides a collection of helper methods for creating HTML DOM elements.
40  *
41  * @auther Justin Park(jpark@sugarcrm.com)
42  *
43  */
44 class SugarHtml {
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 = ")";
54
55     /**
56      * @var integer the counter for generating automatic input field names.
57      */
58     public static $count=0;
59
60     /**
61      * @static
62      * Create an open html element
63      *
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
68      */
69     public static function createOpenTag($tagName, $params = array(), $self_closing = false) {
70
71         $self_closing_tag = ($self_closing) ? "/" : "";
72         if(empty($params))
73             return "<{$tagName}{$self_closing_tag}>";
74
75         $options = self::createAttributes($params);
76         return "<{$tagName} {$options}{$self_closing_tag}>";
77     }
78     /**
79      * @static
80      * Create a close html element
81      *
82      * @param String $tagName - the tag name
83      * @return string - generated html string
84      */
85     public static function createCloseTag($tagName) {
86         return "</{$tagName}>";
87     }
88     /**
89      * @static
90      * Create a html block given a cascade array tree.
91      * Resemble the array form generated by parseHtmlTag
92      * @see SugarHtml::parseHtmlTag
93      *
94      * @param array $dom_tree - Cascade array form
95      * <pre>
96      * 1. Simple html format
97      * array(
98      *      'tag' => 'div',
99      *      // all attributes are assigned in key-value form
100      *      'id' => 'div_id',
101      *      'class' => 'wrapper',
102      *      ...
103      * )
104      *
105      * 2. Cascade html format
106      * array(
107      *      'tag' => 'div',
108      *      //all subitems are assigned in container
109      *      'container' => array(
110      *          array(
111      *              'tag' => 'input',
112      *              'type' => 'hidden',
113      *          )
114      *      )
115      * )
116      *
117      * 3. Siblings
118      * array(
119      *      //all siblings are assigned in a parallel array form
120      *      array('tag' => 'input', 'type' => 'button', ... ),
121      *      array('tag' => 'input', 'type' => 'submit', ... )
122      * )
123      *
124      * 4. Smarty
125      * array(
126      *      'smarty' => array(
127      *          array(
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
133      *              )
134      *              '[CONTENT2]' => array(...)
135      *          ),
136      *      )
137      * )
138      * </pre>
139      *
140      * @return string - generated html string
141      */
142     public static function createHtml($dom_tree = array()) {
143
144         $out = "";
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;
148             }
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']);
157             }
158             $out .= self::HTML_TAG_BEGIN."{$tagName} ";
159             if(isset($dom_tree['smarty'])) {
160                 $out .= self::createHtml(array(
161                     'smarty' => $dom_tree['smarty']
162                 )).' ';
163                 unset($dom_tree['smarty']);
164             }
165             $out .= self::createHtml($dom_tree);
166             if($self_closing) {
167                 $out .= '/>';
168             } else{
169                 $out .= self::HTML_TAG_END;
170                 $out .= (is_array($container)) ? self::createHtml($container) : $container;
171                 $out .= self::createCloseTag($tagName);
172             }
173         } else if( isset($dom_tree['smarty']) ) { //dom contains smarty function
174             $count = 0;
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;
181                 }
182                 if($count++ > 0) $out .= ' ';
183                 $out .= strtr($template, $replacement);
184             }
185         } else {
186             $count = 0;
187             foreach($dom_tree as $attr => $value) {
188                 if($count++ > 0) $out .= ' ';
189                 $out .= (empty($value)) ? $attr : $attr.'="'.$value.'"';
190             }
191         }
192
193         return $out;
194     }
195
196     public static function parseSugarHtml($sugar_html = array()) {
197         $output = array();
198         $input_types = array(
199             'submit', 'button', 'hidden', 'checkbox', 'input'
200         );
201
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'];
205
206             $output = array_merge(array(
207                 'tag' => 'input',
208                 'self_closing' => true,
209             ), $sugar_html['htmlOptions']);
210         }
211         if(isset($sugar_html['template'])) {
212             $output = array(
213                 'smarty' => array(
214                     array(
215                         'template' => $sugar_html['template'],
216                         '[CONTENT]' => $output
217                     )
218                 )
219             );
220         }
221
222         return $output;
223     }
224
225     /**
226      * @static
227      * Disassemble an html string into a cascaded array form elements
228      *
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
233      */
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);
237         $output = array();
238         if(substr($code, 0, 1) != self::HTML_TAG_BEGIN || $start_pos === false) {
239             $offset = 0;
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);
245             }
246         } else {
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;
251
252             if($end_pos === false) {
253                 $output['self_closing'] = true;
254                 $code = substr($code, $start_pos + 1);
255             } else {
256                 $output['self_closing'] = false;
257                 $code = substr($code, $start_pos + 1, $end_pos - $start_pos - 1);
258             }
259             $remainder = self::extractAttributes($code, $output);
260
261             if(!empty($remainder)) {
262                 array_push($appendTo, $output);
263                 return self::parseHtmlTag($remainder, $appendTo);
264             }
265         }
266         return (empty($appendTo)) ? $output : array_merge($appendTo, array($output));
267     }
268
269     /**
270      * @static
271      * Disassemble a smarty string into a cascaded array form elements
272      *
273      * @param String $code - smarty encoded string
274      * @param Array $output - parsed attribute
275      * @param int $offset
276      * @param bool $is_attr - whether current smarty block is inside html attributes or not
277      */
278     public static function parseSmartyTag($code, &$output, &$offset = 0, $is_attr = false) {
279         if(empty($output['smarty']))
280             $output['smarty'] = array();
281
282         $_str = ltrim(substr($code, $offset + 1));
283
284         preg_match("/^[$\w]+/", $_str, $statement);
285         $_smarty_closing = self::SMARTY_TAG_BEGIN.'/'.$statement[0];
286         $_left = strlen($statement[0]);
287
288         $_right = strpos($code, $_smarty_closing, $offset);
289         if($_right === false) { //smarty closed itself
290             $_right = strpos($code, self::SMARTY_TAG_END, $offset);
291         } else {
292             preg_match_all('/\{( |)+'.substr($_str, 0, $_left).'/', substr($_str, 0, $_right), $matches);
293
294             $match_count = count($matches[0]);
295             while($match_count-- > 0) {
296                 $_right = strpos($code, $_smarty_closing, $_right + strlen($_smarty_closing));
297             }
298
299             $_right = strpos($code, self::SMARTY_TAG_END, $_right);
300         }
301         $smarty_string = substr($code, $offset, $_right + 1 - $offset);
302
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.
307         );
308         $smarty_template = array(
309             'template' => '',
310         );
311         //Concatenate smarty variables
312         $reserved_strings = array(
313             '$', 'ldelim', 'rdelim'
314         );
315         $reserved_functions = array(
316             'literal' => false,
317             'nocache' => false,
318         );
319         $queue = 0;
320         $is_literal = false;
321         $current_literal_string = '';
322         for($seq = 0; $seq < count($clauses); $seq++) {
323             $is_reserved = false;
324
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);
327
328             foreach($reserved_strings as $str) {
329                 if(substr(ltrim($clauses[$seq]), 0, strlen($str)) == $str) {
330                     $is_reserved = true;
331                     break;
332                 }
333             }
334             if($is_literal || ($seq > 0 && $is_reserved)) {
335                 if($queue == 0)
336                     $clauses[$queue] = self::SMARTY_TAG_BEGIN.$clauses[$seq].self::SMARTY_TAG_END;
337                 else
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];
343                 } else {
344                     $queue++;
345                 }
346             } else {
347                 $clauses[$queue++] = $clauses[$seq];
348             }
349         }
350         array_splice($clauses, $queue);
351         //Split phrases for the conditional statement
352         $count = 0;
353         $queue = 0;
354         for($seq = 0; $seq < count($clauses); $seq++) {
355
356             if ($seq > 0 && substr(ltrim($clauses[$seq]), 0, 2) == 'if') {
357                 $count++;
358             }
359             if($count > 0) {
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];
363                 }
364             } else {
365                 $clauses[$queue++] = $clauses[$seq];
366             }
367
368             if ( $seq > 0 && substr(ltrim($clauses[$seq - 1]), 0, 3) == '/if') {
369                 $count--;
370             }
371         }
372         array_splice($clauses, $queue);
373
374         //resemble the smarty phases
375         $seq = 0;
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;
380                 else
381                     $smarty_template['template'] .= '{'.$clause.'}';
382             } else if( !empty($clause) ){
383                 $key = '[CONTENT'.($seq++).']';
384                 $smarty_template['template'] .= $key;
385                 $params = array();
386                 if($is_attr)
387                     self::extractAttributes($clause, $params);
388                 else
389                     $params = self::parseHtmlTag($clause);
390                 $smarty_template[$key] = $params;
391             }
392         }
393         $output['smarty'][] = $smarty_template;
394         $offset = $_right + 1;
395     }
396
397     /**
398      * @static
399      * Disassemble an html attributes into a key-value array form
400      *
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 ">"
404      */
405     public static function extractAttributes($code, &$output) {
406         $var_assign = false;
407         $quote_encoded = false;
408         $smarty_encoded = false;
409         $cache = array();
410         $code = rtrim($code);
411         for($i = 0; $i < strlen($code) ; $i ++) {
412             $char = $code[$i];
413             if( !$smarty_encoded && ($char == self::SINGLE_QUOTE || $char == self::DOUBLE_QUOTE) ) {
414                 if(empty($quote_type)) {
415                     $quote_encoded = true;
416                     $quote_type = $char;
417                 } else if ($quote_type == $char) {
418                     if(!empty($cache)) {
419                         $string = implode('', $cache);
420                         if(empty($var_name)) {
421                             $var_name = $string;
422                         } else if($var_assign) {
423                             $output[trim($var_name)] = $string;
424                             unset($var_name);
425                         }
426                     }
427                     $quote_type = '';
428                     $var_assign = false;
429                     $cache = array();
430                     $quote_encoded = false;
431                 } else {
432                     array_push($cache, $char);
433                 }
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 == ' ' ) {
441                 if(!empty($cache)) {
442                     $string = implode('', $cache);
443                     if(empty($var_name)) {
444                         $var_name = $string;
445                     } else if($var_assign) {
446                         $output[trim($var_name)] = $string;
447                         unset($var_name);
448                     }
449                     $quote_encoded = false;
450                     $var_assign = false;
451                     $cache = array();
452                 }
453             } else if ( !$quote_encoded && $char == self::ASSIGN_SIGN ) {
454
455                 if(!empty($var_name)) {
456                     $output[$var_name] = '';
457                 }
458                 $string = implode('', $cache);
459                 if(trim($string) != "") {
460                     $var_name = $string;
461                 }
462                 $var_assign = true;
463                 $cache = array();
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 ) {
467                 break;
468             } else {
469                 array_push($cache, $char);
470             }
471         }
472         if(!empty($cache)) {
473             $var_name = implode('', $cache);
474             $output[$var_name] = '';
475         }
476
477         if(isset($output['self_closing']) && $output['self_closing'] === false) {
478             $output['container'] = self::parseHtmlTag(substr($code, $i + 1));
479             return '';
480         }
481
482         return substr($code, $i + 1);
483     }
484
485     /**
486      * @static
487      * Creates HTML attribute elements corresponding key-value pair.
488      *
489      * @param Array $params - Attributes (key-value pair)
490      * @return string - Generated html attribute string
491      */
492     public static function createAttributes($params) {
493         $options = "";
494         foreach($params as $attr => $value) {
495             if(is_numeric($attr) === false) {
496                 $attr = trim($attr);
497                 if($value)
498                     $options .= $attr.'="'.$value.'" ';
499                 elseif(!empty($attr))
500                     $options .= $attr.' ';
501             }
502         }
503         return $options;
504     }
505
506 }