Open-Source PHP Framework - Designed for rapid development of performance-oriented scalable applications

/mvc/helpers/ugc

[return to app]
1 <?php
2
/**
3  * UGC (User Generated Contend) helper class used to componentize Vork tools in a manner that is safe for public
 
access
4  
*/
5 class 
ugcHelper {
6     
/**
7      * Tags that are allowed within UGC - anything else will be htmlencoded - FYI: all tag-properties get stripped
 
out
8      
* @var array
9      */
10     public 
$allowedTags = array('hr''em''strong''tt''dl''dt''dd');
11
12     
/**
13      * Display text with vorkCode-embedded
14      *
15      * @param string $content
16      * @return string
17      */
18     
public function text($content) {
19         if (
$content !== '') {
20             
$allowedTags implode('|'$this->allowedTags);
21             
$content get::htmlentities($content);
22             
$content preg_replace('/&lt;(\/?(' $allowedTags ')(\s.*)?\/?)&gt;/isU''<$2>'$content);
23             
$content preg_replace('/(\r?\n){2,}/'PHP_EOL PHP_EOL$content); //compact excessive line
 breaks
24             
$content preg_replace('/(\r?\n){2}=/'PHP_EOL '='$content); //reduce line break preceding
 header
25             
$content $this->customTags($content);
26             
$content $this->tagShortcuts($content);
27             
$content $this->autolink($content);
28             
$content get::component('filter')->cleanHtml($content);
29         }
30         return 
$content;
31     }
32
33     
/**
34      * Support method for autolink emails
35      *
36      * @param array $array
37      * @return string
38      */
39     
protected function _email(array $array) {
40         return (isset(
$array[1]) ? get::helper('html')->email($array[1]) : current($array));
41     }
42
43     
/**
44      * Support method for autolink URLs
45      *
46      * @param array $array
47      * @return string
48      */
49     
protected function _autolink(array $array) {
50         if (!isset(
$array[1])) {
51             return 
current($array);
52         }
53         
$url $array[1];
54         if (!isset(
$array[2])) { // protocol is not prefixed
55             
$url get::helper('html')->link('http://' $url$url);
56         } else {
57             
$url get::helper('html')->link($url);
58         }
59         return 
$url;
60     }
61
62     
/**
63      * Automatically converts URLs to links and emails to spam-resistant email-links
64      *
65      * @param string $str
66      * @return string
67      */
68     
public function autoLink($str) {
69         
$end '[^<>\(\)\s\"\']';
70         
$emailRegex '/(?:[^mailto:])\b(([\w\.!#$%"*+\/=?`{}|~^-]+)@((?:[-\w]+\.)+[A-Za-z]{2,}))\b/';
71         
//$urlRegex = '/(?<=[^"\'\/])\b(((https?|ftp):\/\/|www\.)([-\w]+\.)+[A-Za-z]{2,}(:\d+)?([\\\\\/]\S'
72         //. $end . '+)*[\\\\\/]?(\?\S*' . $end . ')?)\b/i';
73         
$urlRegex '/(?:[^\s])?(?<=[^"\'\/])\b((?:(https?|ftp):\/\/|www\.)'
74                   
'(?:[-\w]+\.)+[A-Za-z]{2,}(?:\:\d+)?(?:[\\\\\/]\S'
75                   
$end '+)*[\\\\\/]?(\?\S*' $end ')?)\b/is';
76         
$str preg_replace_callback($emailRegex, array($this'_email'), $str);
77         return 
preg_replace_callback($urlRegex, array($this'_autolink'), $str);
78     }
79
80     
/**
81      * Returns a color-coded code block for code custom-tags
82      *
83      * @param array $matches
84      * @return string
85      */
86     
protected function _code($matches) {
87         
//PHP-container-match regex will only catch short-open tags that are well-formatted (sloppy-code may get
 missed)
88         
$str preg_replace('/\<\?([\r\n\s])/''<?php$1'html_entity_decode($matches[3]));
89         return 
str_replace(array("\r""\n"), ''get::helper('html')->phpcode($strtrue));
90     }
91
92     
/**
93      * Returns a link for link custom-tags
94      * This is only needed for local links since autolink handles external links automatically
95      *
96      * @param array $matches
97      * @return string
98      */
99     
protected function _link($matches) {
100         
$link trim($matches[2] ? $matches[2] : $matches[3]);
101         return 
get::helper('html')->link($link$matches[3]);
102     }
103
104     
/**
105      * Returns a wiki-specific link for wiki custom-tags
106      *
107      * @param array $matches
108      * @return string
109      */
110     
protected function _wiki($matches) {
111         
$url '/' mvc::$controller '/';
112         
$wikipage trim($matches[2] ? $matches[2] : $matches[3]);
113         if (
strtolower($wikipage) != 'index') {
114             
$url .= $wikipage;
115         }
116         return 
get::helper('html')->link($url$matches[3]);
117     }
118
119     
/**
120      * Returns an image for image custom-tags
121      *
122      * @param array $matches
123      * @return string
124      */
125     
protected function _image($matches) {
126         return (
$matches[2] ? get::helper('html')->img(array('src' => $matches[2], 'alt' => trim($matches[3]))) :
 
'');
127     }
128
129     
/**
130      * Strips out content found in comment custom-tags
131      *
132      * @param array $matches
133      * @return string
134      */
135     
protected function _comment($matches) {
136         return 
'';
137     }
138
139     
/**
140      * Returns a YouTube video for youtube custom-tags
141      *
142      * @param array $matches
143      * @return string
144      */
145     
protected function _youtube($matches) {
146         return 
get::helper('tools')->youtube(array('id' => trim($matches[2] ? $matches[2] : $matches[3])));
147     }
148
149     
/**
150      * Returns a QR code for qr custom-tags
151      *
152      * @param array $matches
153      * @return string
154      */
155     
protected function _qr($matches) {
156         return 
get::helper('tools')->qrCode(trim($matches[3]));
157     }
158
159     
/**
160      * Returns a Google map for map custom-tags
161      *
162      * @param array $matches
163      * @return string
164      */
165     
protected function _map($matches) {
166         return 
get::helper('tools')->googleMap(trim($matches[3]));
167     }
168
169     
/**
170      * Switchboard action-methods on matches found by $this->customTags()
171      *
172      * @param array $matches
173      * @return string
174      */
175     
protected function _customTags($matches) {
176         return 
$this->{'_' $matches[1]}($matches);
177     }
178
179     
/**
180      * Valid custom tags
181      * @var array
182      */
183     
protected $_customTags = array('code''link''image''wiki''comment''youtube''qr''map');
184
185     
/**
186      * Parses out custom tags
187      *
188      * @param string $str
189      * @return string
190      */
191     
public function customTags($str) {
192         
$regex '/&lt;(' implode('|'$this->_customTags) . ')(?:\s+(.*))?&gt;(.*)&lt;\/\1&gt;/siU';
193         return 
preg_replace_callback($regex, array($this'_customTags'), $str);
194     }
195
196     
/**
197      * Parses shortcut-tags and does a safe version of nl2br()
198      *
199      * @param string $str
200      * @return string
201      */
202     
public function tagShortcuts($str) {
203         for (
$x 4$x >= 1$x--) {
204             
$str preg_replace('/^={' $x '}(.+)/m''<h' $x '>$1</h' $x '>'$str);
205         }
206         
$lines explode("\n"$str);
207         
$listTypes = array('-' => 'ul''#' => 'ol');
208         
$regex '/^(\s*)([' implode(array_keys($listTypes)) . '])/';
209         foreach (
$lines as $lineNum => $line) {
210             
$match = array();
211             
preg_match($regex$line$match);
212             if (
$match) {
213                 
$lists[$lineNum] = array('depth' => strlen($match[1]), 'listtype' => $listTypes[$match[2]]);
214             }
215         }
216
217         
$autobreak true;
218         
$lines[-1] = ''//removes the need for look-behind isset-checking
219         
foreach ($lines as $lineNum => $line) {
220             
$prefix '';
221             if (isset(
$lists[$lineNum])) {
222                 if (!isset(
$lists[$lineNum 1])) { //no list open
223                     
$prefix .= '<' $lists[$lineNum]['listtype'] . '><li>';
224                 } else {
225                     
$depthChange = ($lists[$lineNum]['depth'] - $lists[$lineNum 1]['depth']);
226                     if (
$depthChange 0) { //reducing depth
227                         
$prefix .= str_repeat('</li></' $lists[$lineNum]['listtype'] . '>'abs($depthChange));
228                     }
229                     if (
$lists[$lineNum 1]['listtype'] != $lists[$lineNum]['listtype']) { //switch between list
 types
230                         
$prefix .= '</' $lists[$lineNum 1]['listtype'] . '><' $lists[$lineNum]['listtype'] .
 
'>';
231                     }
232                     if (
$depthChange 0) { //increasing nesting depth
233                         
$prefix .= str_repeat('<' $lists[$lineNum]['listtype'] . '><li>'$depthChange);
234                     }
235                     
$prefix .= '</li><li>';
236                 }
237
238                 
$lines[$lineNum] = $prefix substr($lines[$lineNum], ($lists[$lineNum]['depth'] + 1));
239             } else {
240                 if (isset(
$lists[$lineNum 1])) {
241                     
$closeList '</li></' $lists[$lineNum 1]['listtype'] . '>';
242                     
$lines[$lineNum] = str_repeat($closeList, ($lists[$lineNum 1]['depth'] + 1)) .
 
$lines[$lineNum];
243                 }
244                 if (
$scriptPosition strpos($line'<script type="text/javascript">')) {
245                     
$autobreak false;
246                 }
247                 if (!
$autobreak && $scriptEndPosition strrpos($line'</script>')) { //not a 100% bulletproof
 solution
248                     
$autobreak = (!isset($scriptPosition) || $scriptPosition $scriptEndPosition);
249                 }
250                 if (
$autobreak) {
251                     if (!
preg_match('/<(\/h\d|hr\s*\/?)>/i'substr($lines[$lineNum], -5))) {
252                         
$lines[$lineNum] .= '<br />';
253                     }
254                 } else if (
$scriptPosition) {
255                     unset(
$scriptPosition);
256                 }
257             }
258         }
259         return 
implode(PHP_EOL$lines);
260     }
261
262     
/**
263      * UGC formatting guide
264      * @return string
265      */
266     
public function guide() {
267         return 
'<h4>Automatic Links</h4>Email addresses will be linked automatically (using spam-resistant
 links)<br />'
268              
'URLs that begin with http://, https://, ftp:// and www. will be automatically linked.<br /><br
 />'
269              
'<h4 stye="display: inline;">Tag shortcuts</h4>'
270              
'Tag shortcut characters must appear at the beginning of a line<br />'
271              
'<span>=</span> Header 1<br />'
272              
'<span>==</span> Header 2<br />'
273              
'<span>===</span> Header 3<br />'
274              
'<span>====</span> Header 4<br />'
275              
'<span>-</span> Unordered List Item<br />'
276              
'<span>&nbsp; -</span> Unordered list, nested one-level - precede lists with spaces to nest
 lists<br />'
277              
'<span>&nbsp; &nbsp; -</span> Nested two-levels deep<br />'
278              
'<span>#</span> Ordered List Item (nesting works the same as an unordered list)<br /><br />'
279              
'<h4>HTML tags enabled:</h4>'
280              
'<span>&lt;' implode('&gt; &lt;'$this->allowedTags) . '&gt;</span><br /><br />'
281              
'<h4>Custom tags enabled:</h4>'
282              
'<span>&lt;code&gt;</span>$anyCode = "can go here";<span>&lt;/code&gt;</span><br />'
283              
'<span>&lt;link&gt;</span>/some/page/url<span>&lt;/link&gt;</span><br />'
284              
'<span>&lt;link /another/page&gt;</span>Linked Content Here<span>&lt;/link&gt;</span><br />'
285              
'<span>&lt;wiki&gt;</span>yourWikiPage<span>&lt;/wiki&gt;</span><br />'
286              
'<span>&lt;wiki someWikiPage&gt;</span>Wiki-Linked Content Here<span>&lt;/wiki&gt;</span><br />'
287              
'<span>&lt;image /images/myphoto.jpg&gt;</span>Alt-Text Here
 (optional)<span>&lt;/image&gt;</span><br />'
288              
'<span>&lt;map&gt;</span>101 Park Ave., New York, NY<span>&lt;/map&gt;</span> (Address or
 Geolocation'
289              
' to be mapped)<br />'
290              
'<span>&lt;youtube&gt;</span>QpRUC42ab2M<span>&lt;/youtube&gt;</span> (URL or ID of a YouTube
 video)<br />'
291              
'<span>&lt;qr&gt;</span>Text to be encoded in a QR code<span>&lt;/qr&gt;</span><br />'
292              
'<span>&lt;comment&gt;</span>Content visible only to those who edit this wiki
 page<span>&lt;/comment&gt;'
293              
'</span><br />';
294     }
295 }