Merge branch 'package'
[speedfreak] / Server / system / helpers / text.php
1 <?php defined('SYSPATH') OR die('No direct access allowed.');
2 /**
3  * Text helper class.
4  *
5  * $Id: text.php 3769 2008-12-15 00:48:56Z zombor $
6  *
7  * @package    Core
8  * @author     Kohana Team
9  * @copyright  (c) 2007-2008 Kohana Team
10  * @license    http://kohanaphp.com/license.html
11  */
12 class text_Core {
13
14         /**
15          * Limits a phrase to a given number of words.
16          *
17          * @param   string   phrase to limit words of
18          * @param   integer  number of words to limit to
19          * @param   string   end character or entity
20          * @return  string
21          */
22         public static function limit_words($str, $limit = 100, $end_char = NULL)
23         {
24                 $limit = (int) $limit;
25                 $end_char = ($end_char === NULL) ? '&#8230;' : $end_char;
26
27                 if (trim($str) === '')
28                         return $str;
29
30                 if ($limit <= 0)
31                         return $end_char;
32
33                 preg_match('/^\s*+(?:\S++\s*+){1,'.$limit.'}/u', $str, $matches);
34
35                 // Only attach the end character if the matched string is shorter
36                 // than the starting string.
37                 return rtrim($matches[0]).(strlen($matches[0]) === strlen($str) ? '' : $end_char);
38         }
39
40         /**
41          * Limits a phrase to a given number of characters.
42          *
43          * @param   string   phrase to limit characters of
44          * @param   integer  number of characters to limit to
45          * @param   string   end character or entity
46          * @param   boolean  enable or disable the preservation of words while limiting
47          * @return  string
48          */
49         public static function limit_chars($str, $limit = 100, $end_char = NULL, $preserve_words = FALSE)
50         {
51                 $end_char = ($end_char === NULL) ? '&#8230;' : $end_char;
52
53                 $limit = (int) $limit;
54
55                 if (trim($str) === '' OR utf8::strlen($str) <= $limit)
56                         return $str;
57
58                 if ($limit <= 0)
59                         return $end_char;
60
61                 if ($preserve_words == FALSE)
62                 {
63                         return rtrim(utf8::substr($str, 0, $limit)).$end_char;
64                 }
65
66                 preg_match('/^.{'.($limit - 1).'}\S*/us', $str, $matches);
67
68                 return rtrim($matches[0]).(strlen($matches[0]) == strlen($str) ? '' : $end_char);
69         }
70
71         /**
72          * Alternates between two or more strings.
73          *
74          * @param   string  strings to alternate between
75          * @return  string
76          */
77         public static function alternate()
78         {
79                 static $i;
80
81                 if (func_num_args() === 0)
82                 {
83                         $i = 0;
84                         return '';
85                 }
86
87                 $args = func_get_args();
88                 return $args[($i++ % count($args))];
89         }
90
91         /**
92          * Generates a random string of a given type and length.
93          *
94          * @param   string   a type of pool, or a string of characters to use as the pool
95          * @param   integer  length of string to return
96          * @return  string
97          *
98          * @tutorial  alnum     alpha-numeric characters
99          * @tutorial  alpha     alphabetical characters
100          * @tutorial  hexdec    hexadecimal characters, 0-9 plus a-f
101          * @tutorial  numeric   digit characters, 0-9
102          * @tutorial  nozero    digit characters, 1-9
103          * @tutorial  distinct  clearly distinct alpha-numeric characters
104          */
105         public static function random($type = 'alnum', $length = 8)
106         {
107                 $utf8 = FALSE;
108
109                 switch ($type)
110                 {
111                         case 'alnum':
112                                 $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
113                         break;
114                         case 'alpha':
115                                 $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
116                         break;
117                         case 'hexdec':
118                                 $pool = '0123456789abcdef';
119                         break;
120                         case 'numeric':
121                                 $pool = '0123456789';
122                         break;
123                         case 'nozero':
124                                 $pool = '123456789';
125                         break;
126                         case 'distinct':
127                                 $pool = '2345679ACDEFHJKLMNPRSTUVWXYZ';
128                         break;
129                         default:
130                                 $pool = (string) $type;
131                                 $utf8 = ! utf8::is_ascii($pool);
132                         break;
133                 }
134
135                 // Split the pool into an array of characters
136                 $pool = ($utf8 === TRUE) ? utf8::str_split($pool, 1) : str_split($pool, 1);
137
138                 // Largest pool key
139                 $max = count($pool) - 1;
140
141                 $str = '';
142                 for ($i = 0; $i < $length; $i++)
143                 {
144                         // Select a random character from the pool and add it to the string
145                         $str .= $pool[mt_rand(0, $max)];
146                 }
147
148                 // Make sure alnum strings contain at least one letter and one digit
149                 if ($type === 'alnum' AND $length > 1)
150                 {
151                         if (ctype_alpha($str))
152                         {
153                                 // Add a random digit
154                                 $str[mt_rand(0, $length - 1)] = chr(mt_rand(48, 57));
155                         }
156                         elseif (ctype_digit($str))
157                         {
158                                 // Add a random letter
159                                 $str[mt_rand(0, $length - 1)] = chr(mt_rand(65, 90));
160                         }
161                 }
162
163                 return $str;
164         }
165
166         /**
167          * Reduces multiple slashes in a string to single slashes.
168          *
169          * @param   string  string to reduce slashes of
170          * @return  string
171          */
172         public static function reduce_slashes($str)
173         {
174                 return preg_replace('#(?<!:)//+#', '/', $str);
175         }
176
177         /**
178          * Replaces the given words with a string.
179          *
180          * @param   string   phrase to replace words in
181          * @param   array    words to replace
182          * @param   string   replacement string
183          * @param   boolean  replace words across word boundries (space, period, etc)
184          * @return  string
185          */
186         public static function censor($str, $badwords, $replacement = '#', $replace_partial_words = FALSE)
187         {
188                 foreach ((array) $badwords as $key => $badword)
189                 {
190                         $badwords[$key] = str_replace('\*', '\S*?', preg_quote((string) $badword));
191                 }
192
193                 $regex = '('.implode('|', $badwords).')';
194
195                 if ($replace_partial_words == TRUE)
196                 {
197                         // Just using \b isn't sufficient when we need to replace a badword that already contains word boundaries itself
198                         $regex = '(?<=\b|\s|^)'.$regex.'(?=\b|\s|$)';
199                 }
200
201                 $regex = '!'.$regex.'!ui';
202
203                 if (utf8::strlen($replacement) == 1)
204                 {
205                         $regex .= 'e';
206                         return preg_replace($regex, 'str_repeat($replacement, utf8::strlen(\'$1\'))', $str);
207                 }
208
209                 return preg_replace($regex, $replacement, $str);
210         }
211
212         /**
213          * Finds the text that is similar between a set of words.
214          *
215          * @param   array   words to find similar text of
216          * @return  string
217          */
218         public static function similar(array $words)
219         {
220                 // First word is the word to match against
221                 $word = current($words);
222
223                 for ($i = 0, $max = strlen($word); $i < $max; ++$i)
224                 {
225                         foreach ($words as $w)
226                         {
227                                 // Once a difference is found, break out of the loops
228                                 if ( ! isset($w[$i]) OR $w[$i] !== $word[$i])
229                                         break 2;
230                         }
231                 }
232
233                 // Return the similar text
234                 return substr($word, 0, $i);
235         }
236
237         /**
238          * Converts text email addresses and anchors into links.
239          *
240          * @param   string   text to auto link
241          * @return  string
242          */
243         public static function auto_link($text)
244         {
245                 // Auto link emails first to prevent problems with "www.domain.com@example.com"
246                 return text::auto_link_urls(text::auto_link_emails($text));
247         }
248
249         /**
250          * Converts text anchors into links.
251          *
252          * @param   string   text to auto link
253          * @return  string
254          */
255         public static function auto_link_urls($text)
256         {
257                 // Finds all http/https/ftp/ftps links that are not part of an existing html anchor
258                 if (preg_match_all('~\b(?<!href="|">)(?:ht|f)tps?://\S+(?:/|\b)~i', $text, $matches))
259                 {
260                         foreach ($matches[0] as $match)
261                         {
262                                 // Replace each link with an anchor
263                                 $text = str_replace($match, html::anchor($match), $text);
264                         }
265                 }
266
267                 // Find all naked www.links.com (without http://)
268                 if (preg_match_all('~\b(?<!://)www(?:\.[a-z0-9][-a-z0-9]*+)+\.[a-z]{2,6}\b~i', $text, $matches))
269                 {
270                         foreach ($matches[0] as $match)
271                         {
272                                 // Replace each link with an anchor
273                                 $text = str_replace($match, html::anchor('http://'.$match, $match), $text);
274                         }
275                 }
276
277                 return $text;
278         }
279
280         /**
281          * Converts text email addresses into links.
282          *
283          * @param   string   text to auto link
284          * @return  string
285          */
286         public static function auto_link_emails($text)
287         {
288                 // Finds all email addresses that are not part of an existing html mailto anchor
289                 // Note: The "58;" negative lookbehind prevents matching of existing encoded html mailto anchors
290                 //       The html entity for a colon (:) is &#58; or &#058; or &#0058; etc.
291                 if (preg_match_all('~\b(?<!href="mailto:|">|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b~i', $text, $matches))
292                 {
293                         foreach ($matches[0] as $match)
294                         {
295                                 // Replace each email with an encoded mailto
296                                 $text = str_replace($match, html::mailto($match), $text);
297                         }
298                 }
299
300                 return $text;
301         }
302
303         /**
304          * Automatically applies <p> and <br /> markup to text. Basically nl2br() on steroids.
305          *
306          * @param   string   subject
307          * @return  string
308          */
309         public static function auto_p($str)
310         {
311                 // Trim whitespace
312                 if (($str = trim($str)) === '')
313                         return '';
314
315                 // Standardize newlines
316                 $str = str_replace(array("\r\n", "\r"), "\n", $str);
317
318                 // Trim whitespace on each line
319                 $str = preg_replace('~^[ \t]+~m', '', $str);
320                 $str = preg_replace('~[ \t]+$~m', '', $str);
321
322                 // The following regexes only need to be executed if the string contains html
323                 if ($html_found = (strpos($str, '<') !== FALSE))
324                 {
325                         // Elements that should not be surrounded by p tags
326                         $no_p = '(?:p|div|h[1-6r]|ul|ol|li|blockquote|d[dlt]|pre|t[dhr]|t(?:able|body|foot|head)|c(?:aption|olgroup)|form|s(?:elect|tyle)|a(?:ddress|rea)|ma(?:p|th))';
327
328                         // Put at least two linebreaks before and after $no_p elements
329                         $str = preg_replace('~^<'.$no_p.'[^>]*+>~im', "\n$0", $str);
330                         $str = preg_replace('~</'.$no_p.'\s*+>$~im', "$0\n", $str);
331                 }
332
333                 // Do the <p> magic!
334                 $str = '<p>'.trim($str).'</p>';
335                 $str = preg_replace('~\n{2,}~', "</p>\n\n<p>", $str);
336
337                 // The following regexes only need to be executed if the string contains html
338                 if ($html_found !== FALSE)
339                 {
340                         // Remove p tags around $no_p elements
341                         $str = preg_replace('~<p>(?=</?'.$no_p.'[^>]*+>)~i', '', $str);
342                         $str = preg_replace('~(</?'.$no_p.'[^>]*+>)</p>~i', '$1', $str);
343                 }
344
345                 // Convert single linebreaks to <br />
346                 $str = preg_replace('~(?<!\n)\n(?!\n)~', "<br />\n", $str);
347
348                 return $str;
349         }
350
351         /**
352          * Returns human readable sizes.
353          * @see  Based on original functions written by:
354          * @see  Aidan Lister: http://aidanlister.com/repos/v/function.size_readable.php
355          * @see  Quentin Zervaas: http://www.phpriot.com/d/code/strings/filesize-format/
356          *
357          * @param   integer  size in bytes
358          * @param   string   a definitive unit
359          * @param   string   the return string format
360          * @param   boolean  whether to use SI prefixes or IEC
361          * @return  string
362          */
363         public static function bytes($bytes, $force_unit = NULL, $format = NULL, $si = TRUE)
364         {
365                 // Format string
366                 $format = ($format === NULL) ? '%01.2f %s' : (string) $format;
367
368                 // IEC prefixes (binary)
369                 if ($si == FALSE OR strpos($force_unit, 'i') !== FALSE)
370                 {
371                         $units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB');
372                         $mod   = 1024;
373                 }
374                 // SI prefixes (decimal)
375                 else
376                 {
377                         $units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB');
378                         $mod   = 1000;
379                 }
380
381                 // Determine unit to use
382                 if (($power = array_search((string) $force_unit, $units)) === FALSE)
383                 {
384                         $power = ($bytes > 0) ? floor(log($bytes, $mod)) : 0;
385                 }
386
387                 return sprintf($format, $bytes / pow($mod, $power), $units[$power]);
388         }
389
390         /**
391          * Prevents widow words by inserting a non-breaking space between the last two words.
392          * @see  http://www.shauninman.com/archive/2006/08/22/widont_wordpress_plugin
393          *
394          * @param   string  string to remove widows from
395          * @return  string
396          */
397         public static function widont($str)
398         {
399                 $str = rtrim($str);
400                 $space = strrpos($str, ' ');
401
402                 if ($space !== FALSE)
403                 {
404                         $str = substr($str, 0, $space).'&nbsp;'.substr($str, $space + 1);
405                 }
406
407                 return $str;
408         }
409
410 } // End text