]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarHtml/SugarHtml.php
Release 6.5.0beta6
[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 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             self::parseSmartyTag($code, $output);
240         } else {
241             $tag = substr($code, 1, $start_pos - 1);
242             $closing_tag = '</'.$tag;
243             $end_pos = strpos($code, $closing_tag, $start_pos + 1);
244             $output['tag'] = $tag;
245
246             if($end_pos === false) {
247                 $output['self_closing'] = true;
248                 $code = substr($code, $start_pos + 1);
249             } else {
250                 $output['self_closing'] = false;
251                 $code = substr($code, $start_pos + 1, $end_pos - $start_pos - 1);
252             }
253             $remainder = self::extractAttributes($code, $output);
254
255             if(!empty($remainder)) {
256                 array_push($appendTo, $output);
257                 return self::parseHtmlTag($remainder, $appendTo);
258             }
259         }
260         return (empty($appendTo)) ? $output : array_merge($appendTo, array($output));
261     }
262     /**
263      * @static
264      *
265      *
266      * @param $code
267      * @param $output
268      * @param int $i
269      * @param bool $is_attr
270      */
271     public static function parseSmartyTag($code, &$output, &$i = 0, $is_attr = false) {
272         if(empty($output['smarty']))
273             $output['smarty'] = array();
274
275         $_str = ltrim(substr($code, $i + 1));
276
277         preg_match("/^[$\w]+/", $_str, $statement);
278         $_smarty_closing = self::SMARTY_TAG_BEGIN.'/'.$statement[0];
279         $_left = strlen($statement[0]);
280
281         $_right = strpos($code, $_smarty_closing, $i);
282         if($_right === false) { //smarty closed itself
283             $_right = strpos($code, self::SMARTY_TAG_END, $i);
284         } else {
285             preg_match_all('/\{( |)+'.substr($_str, 0, $_left).'/', substr($_str, 0, $_right), $matches);
286
287             $match_count = count($matches[0]);
288             while($match_count-- > 0) {
289                 $_right = strpos($code, $_smarty_closing, $_right + strlen($_smarty_closing));
290             }
291
292             $_right = strpos($code, self::SMARTY_TAG_END, $_right);
293         }
294         $smarty_string = substr($code, $i, $_right + 1 - $i);
295
296         $clauses = array_slice(
297             //split into each clause
298             preg_split("/[\{\}]/i", $smarty_string),
299             1, -1 //slice out the first and last items, which is empty.
300         );
301         $smarty_template = array(
302             'template' => '',
303         );
304
305         //Concatenate smarty variables
306         $queue = 0;
307         for($seq = 0; $seq < count($clauses); $seq++) {
308
309             if($seq > 0 && substr(ltrim($clauses[$seq]), 0, 1) == '$' ) {
310                 $clauses[--$queue] .= self::SMARTY_TAG_BEGIN.$clauses[$seq].self::SMARTY_TAG_END;
311                 if($seq < count($clauses)) {
312                     $clauses[$queue++] .= $clauses[++$seq];
313                 }
314             } else {
315                 $clauses[$queue++] = $clauses[$seq];
316             }
317         }
318         array_splice($clauses, $queue);
319
320         //Split phrases for the conditional statement
321         $count = 0;
322         $queue = 0;
323         for($seq = 0; $seq < count($clauses); $seq++) {
324
325             if ($seq > 0 && substr(ltrim($clauses[$seq]), 0, 2) == 'if') {
326                 $count++;
327             }
328             if($count > 0) {
329                 $clauses[--$queue] .= ($seq % 2 == 0) ? self::SMARTY_TAG_BEGIN.$clauses[$seq].self::SMARTY_TAG_END : $clauses[$seq];
330                 if($seq < count($clauses)) {
331                     $clauses[$queue++] .= $clauses[++$seq];
332                 }
333             } else {
334                 $clauses[$queue++] = $clauses[$seq];
335             }
336
337             if ( $seq > 0 && substr(ltrim($clauses[$seq - 1]), 0, 3) == '/if') {
338                 $count--;
339             }
340         }
341         array_splice($clauses, $queue);
342
343         //resemble the smarty phases
344         $seq = 0;
345         foreach($clauses as $index => $clause) {
346             if($index % 2 == 0) {
347                 $smarty_template['template'] .= '{'.$clause.'}';
348
349             } else {
350                 $key = '[CONTENT'.($seq++).']';
351                 $smarty_template['template'] .= $key;
352                 $params = array();
353                 if($is_attr)
354                     self::extractAttributes($clause, $params);
355                 else
356                     $params = self::parseHtmlTag($clause);
357                 $smarty_template[$key] = $params;
358             }
359         }
360         $output['smarty'][] = $smarty_template;
361         $i = $_right + 1;
362     }
363
364     public static function extractAttributes($code, &$output) {
365         $var_assign = false;
366         $quote_encoded = false;
367         $smarty_encoded = false;
368         $cache = array();
369         $code = rtrim($code);
370         for($i = 0; $i < strlen($code) ; $i ++) {
371             $char = $code[$i];
372             if( !$smarty_encoded && ($char == self::SINGLE_QUOTE || $char == self::DOUBLE_QUOTE) ) {
373                 if(empty($quote_type)) {
374                     $quote_encoded = true;
375                     $quote_type = $char;
376                 } else if ($quote_type == $char) {
377                     if(!empty($cache)) {
378                         $string = implode('', $cache);
379                         if(empty($var_name)) {
380                             $var_name = $string;
381                         } else if($var_assign) {
382                             $output[$var_name] = $string;
383                             unset($var_name);
384                         }
385                     }
386                     $quote_type = '';
387                     $var_assign = false;
388                     $cache = array();
389                     $quote_encoded = false;
390                 } else {
391                     array_push($cache, $char);
392                 }
393             } else if ( $quote_encoded && $char == self::SMARTY_TAG_BEGIN ) {
394                 $smarty_encoded = true;
395                 array_push($cache, $char);
396             } else if ( $quote_encoded && $char == self::SMARTY_TAG_END ) {
397                 $smarty_encoded = false;
398                 array_push($cache, $char);
399             } else if ( !$quote_encoded && $char == ' ' ) {
400                 if(!empty($cache)) {
401                     $string = implode('', $cache);
402                     if(empty($var_name)) {
403                         $var_name = $string;
404                     } else if($var_assign) {
405                         $output[$var_name] = $string;
406                         unset($var_name);
407                     }
408                     $quote_encoded = false;
409                     $var_assign = false;
410                     $cache = array();
411                 }
412             } else if ( !$quote_encoded && $char == self::ASSIGN_SIGN ) {
413
414                 if(!empty($var_name)) {
415                     $output[$var_name] = '';
416                 }
417                 $string = implode('', $cache);
418                 if(trim($string) != "") {
419                     $var_name = $string;
420                 }
421                 $var_assign = true;
422                 $cache = array();
423             } else if ( !$quote_encoded && $char == self::SMARTY_TAG_BEGIN) {
424                 self::parseSmartyTag($code, $output, $i, true);
425             } else if ( !$quote_encoded && $char == self::HTML_TAG_END ) {
426                 break;
427             } else {
428                 array_push($cache, $char);
429             }
430         }
431         if(!empty($cache)) {
432             $var_name = implode('', $cache);
433             $output[$var_name] = '';
434         }
435
436         if(isset($output['self_closing']) && $output['self_closing'] === false) {
437             $output['container'] = self::parseHtmlTag(substr($code, $i + 1));
438             return '';
439         }
440
441         return substr($code, $i + 1);
442     }
443
444     public static function createAttributes($params) {
445         $options = "";
446         foreach($params as $attr => $value) {
447             if($value)
448                 $options .= $attr.'="'.$value.'" ';
449         }
450         return $options;
451     }
452
453 }