Total Complexity | 238 |
Total Lines | 1764 |
Duplicated Lines | 0 % |
Changes | 16 | ||
Bugs | 1 | Features | 0 |
Complex classes like General often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use General, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
10 | class General |
||
11 | { |
||
12 | /** |
||
13 | * Convert any special characters into their entity equivalents. Since |
||
14 | * Symphony 2.3, this function assumes UTF-8 and will not double |
||
15 | * encode strings. |
||
16 | * |
||
17 | * @uses htmlspecialchars() |
||
18 | * @param string $source |
||
19 | * a string to operate on. |
||
20 | * @return string |
||
21 | * the encoded version of the string. |
||
22 | */ |
||
23 | public static function sanitize($source) |
||
24 | { |
||
25 | $source = htmlspecialchars($source, ENT_COMPAT, 'UTF-8', false); |
||
26 | |||
27 | return $source; |
||
28 | } |
||
29 | |||
30 | /** |
||
31 | * Convert any special characters into their entity equivalents. |
||
32 | * Contrary to `sanitize()`, this version does double encode existing entities. |
||
33 | * |
||
34 | * @since Symphony 2.7.5 |
||
35 | * @uses htmlspecialchars() |
||
36 | * @param string $source |
||
37 | * a string to operate on. |
||
38 | * @return string |
||
39 | * the fully encoded version of the string. |
||
40 | */ |
||
41 | public static function sanitizeDouble($source) |
||
42 | { |
||
43 | $source = htmlspecialchars($source, ENT_COMPAT, 'UTF-8', true); |
||
44 | |||
45 | return $source; |
||
46 | } |
||
47 | |||
48 | /** |
||
49 | * Revert any html entities to their character equivalents. |
||
50 | * |
||
51 | * @param string $str |
||
52 | * a string to operate on |
||
53 | * @return string |
||
54 | * the decoded version of the string |
||
55 | */ |
||
56 | public static function reverse_sanitize($str) |
||
59 | } |
||
60 | |||
61 | /** |
||
62 | * Validate a string against a set of regular expressions. |
||
63 | * |
||
64 | * @param array|string $string |
||
65 | * string to operate on |
||
66 | * @param array|string $rule |
||
67 | * a single rule or array of rules |
||
68 | * @return boolean |
||
69 | * false if any of the rules in $rule do not match any of the strings in |
||
70 | * `$string`, return true otherwise. |
||
71 | */ |
||
72 | public static function validateString($string, $rule) |
||
73 | { |
||
74 | if (!is_array($rule) && ($rule == '' || $rule == null)) { |
||
75 | return true; |
||
76 | } |
||
77 | |||
78 | if (!is_array($string) && ($string == '' || $rule == null)) { |
||
79 | return true; |
||
80 | } |
||
81 | |||
82 | if (!is_array($rule)) { |
||
83 | $rule = array($rule); |
||
84 | } |
||
85 | |||
86 | if (!is_array($string)) { |
||
87 | $string = array($string); |
||
88 | } |
||
89 | |||
90 | foreach ($rule as $r) { |
||
91 | foreach ($string as $s) { |
||
92 | if (!preg_match($r, $s)) { |
||
93 | return false; |
||
94 | } |
||
95 | } |
||
96 | } |
||
|
|||
97 | return true; |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * Replace the tabs with spaces in the input string. |
||
102 | * |
||
103 | * @param string $string |
||
104 | * the string in which to replace the tabs with spaces. |
||
105 | * @param integer $spaces (optional) |
||
106 | * the number of spaces to replace each tab with. This argument is optional |
||
107 | * with a default of 4. |
||
108 | * @return string |
||
109 | * the resulting string. |
||
110 | */ |
||
111 | public static function tabsToSpaces($string, $spaces = 4) |
||
112 | { |
||
113 | return str_replace("\t", str_pad(null, $spaces), $string); |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * Checks an xml document for well-formedness. |
||
118 | * |
||
119 | * @param string $data |
||
120 | * filename, xml document as a string, or arbitrary string |
||
121 | * @param pointer &$errors |
||
122 | * pointer to an array which will contain any validation errors |
||
123 | * @param boolean $isFile (optional) |
||
124 | * if this is true, the method will attempt to read from a file, `$data` |
||
125 | * instead. |
||
126 | * @param XsltProcess $xsltProcessor (optional) |
||
127 | * if set, the validation will be done using this XSLT processor rather |
||
128 | * than the built in XML parser. the default is null. |
||
129 | * @param string $encoding (optional) |
||
130 | * if no XML header is expected, than this should be set to match the |
||
131 | * encoding of the XML |
||
132 | * @return boolean |
||
133 | * true if there are no errors in validating the XML, false otherwise. |
||
134 | */ |
||
135 | public static function validateXML($data, &$errors, $isFile = true, $xsltProcessor = null, $encoding = 'UTF-8') |
||
136 | { |
||
137 | $_data = ($isFile) ? file_get_contents($data) : $data; |
||
138 | $_data = preg_replace('/<!DOCTYPE[-.:"\'\/\\w\\s]+>/', null, $_data); |
||
139 | |||
140 | if (strpos($_data, '<?xml') === false) { |
||
141 | $_data = '<?xml version="1.0" encoding="'.$encoding.'"?><rootelement>'.$_data.'</rootelement>'; |
||
142 | } |
||
143 | |||
144 | if (is_object($xsltProcessor)) { |
||
145 | $xsl = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> |
||
146 | |||
147 | <xsl:template match="/"></xsl:template> |
||
148 | |||
149 | </xsl:stylesheet>'; |
||
150 | |||
151 | $xsltProcessor->process($_data, $xsl, array()); |
||
152 | |||
153 | if ($xsltProcessor->isErrors()) { |
||
154 | $errors = $xsltProcessor->getError(true); |
||
155 | return false; |
||
156 | } |
||
157 | } else { |
||
158 | $_parser = xml_parser_create(); |
||
159 | xml_parser_set_option($_parser, XML_OPTION_SKIP_WHITE, 0); |
||
160 | xml_parser_set_option($_parser, XML_OPTION_CASE_FOLDING, 0); |
||
161 | |||
162 | if (!xml_parse($_parser, $_data)) { |
||
163 | $errors = array('error' => xml_get_error_code($_parser) . ': ' . xml_error_string(xml_get_error_code($_parser)), |
||
164 | 'col' => xml_get_current_column_number($_parser), |
||
165 | 'line' => (xml_get_current_line_number($_parser) - 2)); |
||
166 | return false; |
||
167 | } |
||
168 | |||
169 | xml_parser_free($_parser); |
||
170 | } |
||
171 | |||
172 | return true; |
||
173 | } |
||
174 | |||
175 | /** |
||
176 | * Check that a string is a valid URL. |
||
177 | * |
||
178 | * @param string $url |
||
179 | * string to operate on |
||
180 | * @return string |
||
181 | * a blank string or a valid URL |
||
182 | */ |
||
183 | public static function validateURL($url = null) |
||
184 | { |
||
185 | $url = trim($url); |
||
186 | |||
187 | if (is_null($url) || $url == '') { |
||
188 | return $url; |
||
189 | } |
||
190 | |||
191 | if (!preg_match('#^http[s]?:\/\/#i', $url)) { |
||
192 | $url = 'http://' . $url; |
||
193 | } |
||
194 | |||
195 | include TOOLKIT . '/util.validators.php'; |
||
196 | |||
197 | if (!preg_match($validators['URI'], $url)) { |
||
198 | $url = ''; |
||
199 | } |
||
200 | |||
201 | return $url; |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Strip any slashes from all array values. |
||
206 | * |
||
207 | * @param array &$arr |
||
208 | * Pointer to an array to operate on. Can be multi-dimensional. |
||
209 | */ |
||
210 | public static function cleanArray(array &$arr) |
||
211 | { |
||
212 | foreach ($arr as $k => $v) { |
||
213 | if (is_array($v)) { |
||
214 | self::cleanArray($arr[$k]); |
||
215 | } else { |
||
216 | $arr[$k] = stripslashes($v); |
||
217 | } |
||
218 | } |
||
219 | } |
||
220 | |||
221 | /** |
||
222 | * Flatten the input array. Any elements of the input array that are |
||
223 | * themselves arrays will be removed and the contents of the removed array |
||
224 | * inserted in its place. The keys for the inserted values will be the |
||
225 | * concatenation of the keys in the original arrays in which it was embedded. |
||
226 | * The elements of the path are separated by periods (.). For example, |
||
227 | * given the following nested array structure: |
||
228 | * ` |
||
229 | * array(1 => |
||
230 | * array('key' => 'value'), |
||
231 | * 2 => |
||
232 | * array('key2' => 'value2', 'key3' => 'value3') |
||
233 | * ) |
||
234 | * ` |
||
235 | * will flatten to: |
||
236 | * `array('1.key' => 'value', '2.key2' => 'value2', '2.key3' => 'value3')` |
||
237 | * |
||
238 | * @param array &$source |
||
239 | * The array to flatten, passed by reference |
||
240 | * @param array &$output (optional) |
||
241 | * The array in which to store the flattened input, passed by reference. |
||
242 | * if this is not provided then a new array will be created. |
||
243 | * @param string $path (optional) |
||
244 | * the current prefix of the keys to insert into the output array. this |
||
245 | * defaults to null. |
||
246 | */ |
||
247 | public static function flattenArray(array &$source, &$output = null, $path = null) |
||
248 | { |
||
249 | if (is_null($output)) { |
||
250 | $output = array(); |
||
251 | } |
||
252 | |||
253 | foreach ($source as $key => $value) { |
||
254 | if (is_int($key)) { |
||
255 | $key = (string)($key + 1); |
||
256 | } |
||
257 | |||
258 | if (!is_null($path)) { |
||
259 | $key = $path . '.' . (string)$key; |
||
260 | } |
||
261 | |||
262 | if (is_array($value)) { |
||
263 | self::flattenArray($value, $output, $key); |
||
264 | } else { |
||
265 | $output[$key] = $value; |
||
266 | } |
||
267 | } |
||
268 | |||
269 | $source = $output; |
||
270 | } |
||
271 | |||
272 | /** |
||
273 | * Flatten the input array. Any elements of the input array that are |
||
274 | * themselves arrays will be removed and the contents of the removed array |
||
275 | * inserted in its place. The keys for the inserted values will be the |
||
276 | * concatenation of the keys in the original arrays in which it was embedded. |
||
277 | * The elements of the path are separated by colons (:). For example, given |
||
278 | * the following nested array structure: |
||
279 | * ` |
||
280 | * array(1 => |
||
281 | * array('key' => 'value'), |
||
282 | * 2 => |
||
283 | * array('key2' => 'value2', 'key3' => 'value3') |
||
284 | * ) |
||
285 | * ` |
||
286 | * will flatten to: |
||
287 | * `array('1:key' => 'value', '2:key2' => 'value2', '2:key3' => 'value3')` |
||
288 | * |
||
289 | * |
||
290 | * @param array &$output |
||
291 | * The array in which to store the flattened input, passed by reference. |
||
292 | * @param array &$source |
||
293 | * The array to flatten, passed by reference |
||
294 | * @param string $path |
||
295 | * the current prefix of the keys to insert into the output array. |
||
296 | */ |
||
297 | protected static function flattenArraySub(array &$output, array &$source, $path) |
||
298 | { |
||
299 | foreach ($source as $key => $value) { |
||
300 | $key = $path . ':' . $key; |
||
301 | |||
302 | if (is_array($value)) { |
||
303 | self::flattenArraySub($output, $value, $key); |
||
304 | } else { |
||
305 | $output[$key] = $value; |
||
306 | } |
||
307 | } |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * Given a string, this will clean it for use as a Symphony handle. Preserves multi-byte characters. |
||
312 | * |
||
313 | * @since Symphony 2.2.1 |
||
314 | * @param string $string |
||
315 | * String to be cleaned up |
||
316 | * @param integer $max_length |
||
317 | * The maximum number of characters in the handle |
||
318 | * @param string $delim |
||
319 | * All non-valid characters will be replaced with this |
||
320 | * @param boolean $uriencode |
||
321 | * Force the resultant string to be uri encoded making it safe for URLs |
||
322 | * @param array $additional_rule_set |
||
323 | * An array of REGEX patterns that should be applied to the `$string`. This |
||
324 | * occurs after the string has been trimmed and joined with the `$delim` |
||
325 | * @return string |
||
326 | * Returns resultant handle |
||
327 | */ |
||
328 | public static function createHandle($string, $max_length = 255, $delim = '-', $uriencode = false, $additional_rule_set = null) |
||
329 | { |
||
330 | $max_length = intval($max_length); |
||
331 | |||
332 | // Strip out any tag |
||
333 | $string = strip_tags($string); |
||
334 | |||
335 | // Remove punctuation |
||
336 | $string = preg_replace('/[\\.\'"]+/', null, $string); |
||
337 | |||
338 | // Trim it |
||
339 | if ($max_length > 0) { |
||
340 | $string = General::limitWords($string, $max_length); |
||
341 | } |
||
342 | |||
343 | // Replace spaces (tab, newline etc) with the delimiter |
||
344 | $string = preg_replace('/[\s]+/', $delim, $string); |
||
345 | |||
346 | // Find all legal characters |
||
347 | preg_match_all('/[^<>?@:!\-\/\[-`;‘’…]+/u', $string, $matches); |
||
348 | |||
349 | // Join only legal character with the $delim |
||
350 | $string = implode($delim, $matches[0]); |
||
351 | |||
352 | // Allow for custom rules |
||
353 | if (is_array($additional_rule_set) && !empty($additional_rule_set)) { |
||
354 | foreach ($additional_rule_set as $rule => $replacement) { |
||
355 | $string = preg_replace($rule, $replacement, $string); |
||
356 | } |
||
357 | } |
||
358 | |||
359 | // Remove leading or trailing delim characters |
||
360 | $string = trim($string, $delim); |
||
361 | |||
362 | // Encode it for URI use |
||
363 | if ($uriencode) { |
||
364 | $string = urlencode($string); |
||
365 | } |
||
366 | |||
367 | // Make it lowercase |
||
368 | $string = strtolower($string); |
||
369 | |||
370 | return $string; |
||
371 | } |
||
372 | |||
373 | /** |
||
374 | * Given a string, this will clean it for use as a filename. Preserves multi-byte characters. |
||
375 | * |
||
376 | * @since Symphony 2.2.1 |
||
377 | * @param string $string |
||
378 | * String to be cleaned up |
||
379 | * @param string $delim |
||
380 | * All non-valid characters will be replaced with this |
||
381 | * @return string |
||
382 | * Returns created filename |
||
383 | */ |
||
384 | public static function createFilename($string, $delim = '-') |
||
385 | { |
||
386 | // Strip out any tag |
||
387 | $string = strip_tags($string); |
||
388 | |||
389 | // Find all legal characters |
||
390 | $count = preg_match_all('/[\p{L}\w:;.,+=~]+/u', $string, $matches); |
||
391 | if ($count <= 0 || $count == false) { |
||
392 | preg_match_all('/[\w:;.,+=~]+/', $string, $matches); |
||
393 | } |
||
394 | |||
395 | // Join only legal character with the $delim |
||
396 | $string = implode($delim, $matches[0]); |
||
397 | |||
398 | // Remove leading or trailing delim characters |
||
399 | $string = trim($string, $delim); |
||
400 | |||
401 | // Make it lowercase |
||
402 | $string = strtolower($string); |
||
403 | |||
404 | return $string; |
||
405 | } |
||
406 | |||
407 | /** |
||
408 | * Computes the length of the string. |
||
409 | * This function will attempt to use PHP's `mbstring` functions if they are available. |
||
410 | * This function also forces utf-8 encoding. |
||
411 | * |
||
412 | * @since Symphony 2.5.0 |
||
413 | * @param string $str |
||
414 | * the string to operate on |
||
415 | * @return int |
||
416 | * the string's length |
||
417 | */ |
||
418 | public static function strlen($str) |
||
419 | { |
||
420 | if (function_exists('mb_strlen')) { |
||
421 | return mb_strlen($str, 'utf-8'); |
||
422 | } |
||
423 | return strlen($str); |
||
424 | } |
||
425 | |||
426 | /** |
||
427 | * Finds position of the first occurrence of a string in a string. |
||
428 | * This function will attempt to use PHP's `mbstring` functions if they are available. |
||
429 | * This function also forces utf-8 encoding for mbstring. |
||
430 | * |
||
431 | * @since Symphony 2.7.0 |
||
432 | * @param string $haystack |
||
433 | * the string to look into |
||
434 | * @param string $needle |
||
435 | * the string to look for |
||
436 | * @param int $offset |
||
437 | * the search offset. If it is not specified, 0 is used. |
||
438 | * A negative offset counts from the end of the string. |
||
439 | * @return int |
||
440 | * the numeric position of the first occurrence of needle in the haystack |
||
441 | */ |
||
442 | public static function strpos($haystack, $needle, $offset = 0) |
||
443 | { |
||
444 | if (function_exists('mb_strpos')) { |
||
445 | return mb_strpos($haystack, $needle, $offset, 'utf-8'); |
||
446 | } |
||
447 | return strpos($haystack, $needle, $offset); |
||
448 | } |
||
449 | |||
450 | /** |
||
451 | * Creates a sub string. |
||
452 | * This function will attempt to use PHP's `mbstring` functions if they are available. |
||
453 | * This function also forces utf-8 encoding. |
||
454 | * |
||
455 | * @since Symphony 2.5.0 |
||
456 | * @param string $str |
||
457 | * the string to operate on |
||
458 | * @param int $start |
||
459 | * the starting offset |
||
460 | * @param int $length |
||
461 | * the length of the substring |
||
462 | * @return string |
||
463 | * the resulting substring |
||
464 | */ |
||
465 | public static function substr($str, $start, $length = null) |
||
466 | { |
||
467 | if (function_exists('mb_substr')) { |
||
468 | return mb_substr($str, $start, $length, 'utf-8'); |
||
469 | } |
||
470 | if ($length === null) { |
||
471 | return substr($str, $start); |
||
472 | } |
||
473 | return substr($str, $start, $length); |
||
474 | } |
||
475 | |||
476 | /** |
||
477 | * Extract the first `$val` characters of the input string. If `$val` |
||
478 | * is larger than the length of the input string then the original |
||
479 | * input string is returned. |
||
480 | * |
||
481 | * @param string $str |
||
482 | * the string to operate on |
||
483 | * @param integer $val |
||
484 | * the number to compare lengths with |
||
485 | * @return string|boolean |
||
486 | * the resulting string or false on failure. |
||
487 | */ |
||
488 | public static function substrmin($str, $val) |
||
489 | { |
||
490 | return self::substr($str, 0, min(self::strlen($str), $val)); |
||
491 | } |
||
492 | |||
493 | /** |
||
494 | * Extract the first `$val` characters of the input string. If |
||
495 | * `$val` is larger than the length of the input string then |
||
496 | * the original input string is returned |
||
497 | * |
||
498 | * @param string $str |
||
499 | * the string to operate on |
||
500 | * @param integer $val |
||
501 | * the number to compare lengths with |
||
502 | * @return string|boolean |
||
503 | * the resulting string or false on failure. |
||
504 | */ |
||
505 | public static function substrmax($str, $val) |
||
508 | } |
||
509 | |||
510 | /** |
||
511 | * Extract the last `$num` characters from a string. |
||
512 | * |
||
513 | * @param string $str |
||
514 | * the string to extract the characters from. |
||
515 | * @param integer $num |
||
516 | * the number of characters to extract. |
||
517 | * @return string|boolean |
||
518 | * a string containing the last `$num` characters of the |
||
519 | * input string, or false on failure. |
||
520 | */ |
||
521 | public static function right($str, $num) |
||
522 | { |
||
523 | $str = self::substr($str, self::strlen($str)-$num, $num); |
||
524 | return $str; |
||
525 | } |
||
526 | |||
527 | /** |
||
528 | * Extract the first `$num` characters from a string. |
||
529 | * |
||
530 | * @param string $str |
||
531 | * the string to extract the characters from. |
||
532 | * @param integer $num |
||
533 | * the number of characters to extract. |
||
534 | * @return string|boolean |
||
535 | * a string containing the last `$num` characters of the |
||
536 | * input string, or false on failure. |
||
537 | */ |
||
538 | public static function left($str, $num) |
||
539 | { |
||
540 | $str = self::substr($str, 0, $num); |
||
541 | return $str; |
||
542 | } |
||
543 | |||
544 | /** |
||
545 | * Create all the directories as specified by the input path. If the current |
||
546 | * directory already exists, this function will return true. |
||
547 | * |
||
548 | * @param string $path |
||
549 | * the path containing the directories to create. |
||
550 | * @param string|integer $mode (optional) |
||
551 | * the permissions (in octal) of the directories to create. Defaults to 0755 |
||
552 | * @param boolean $silent (optional) |
||
553 | * true if an exception should be raised if an error occurs, false |
||
554 | * otherwise. this defaults to true. |
||
555 | * @throws Exception |
||
556 | * @return boolean |
||
557 | */ |
||
558 | public static function realiseDirectory($path, $mode = 0755, $silent = true) |
||
559 | { |
||
560 | if (is_dir($path)) { |
||
561 | return true; |
||
562 | } |
||
563 | |||
564 | try { |
||
565 | $current_umask = umask(0); |
||
566 | $success = @mkdir($path, intval($mode, 8), true); |
||
567 | umask($current_umask); |
||
568 | |||
569 | return $success; |
||
570 | } catch (Exception $ex) { |
||
571 | if ($silent === false) { |
||
572 | throw new Exception(__('Unable to create path - %s', array($path))); |
||
573 | } |
||
574 | |||
575 | return false; |
||
576 | } |
||
577 | } |
||
578 | |||
579 | /** |
||
580 | * Recursively deletes all files and directories given a directory. This |
||
581 | * function has two path. This function optionally takes a `$silent` parameter, |
||
582 | * which when `false` will throw an `Exception` if there is an error deleting a file |
||
583 | * or folder. |
||
584 | * |
||
585 | * @since Symphony 2.3 |
||
586 | * @param string $dir |
||
587 | * the path of the directory to delete |
||
588 | * @param boolean $silent (optional) |
||
589 | * true if an exception should be raised if an error occurs, false |
||
590 | * otherwise. this defaults to true. |
||
591 | * @throws Exception |
||
592 | * @return boolean |
||
593 | */ |
||
594 | public static function deleteDirectory($dir, $silent = true) |
||
595 | { |
||
596 | try { |
||
597 | if (!@file_exists($dir)) { |
||
598 | return true; |
||
599 | } |
||
600 | |||
601 | if (!@is_dir($dir)) { |
||
602 | return @unlink($dir); |
||
603 | } |
||
604 | |||
605 | foreach (scandir($dir) as $item) { |
||
606 | if ($item == '.' || $item == '..') { |
||
607 | continue; |
||
608 | } |
||
609 | |||
610 | if (!self::deleteDirectory($dir.DIRECTORY_SEPARATOR.$item)) { |
||
611 | return false; |
||
612 | } |
||
613 | } |
||
614 | |||
615 | return rmdir($dir); |
||
616 | } catch (Exception $ex) { |
||
617 | if ($silent === false) { |
||
618 | throw new Exception(__('Unable to remove - %s', array($dir))); |
||
619 | } |
||
620 | |||
621 | return false; |
||
622 | } |
||
623 | } |
||
624 | |||
625 | /** |
||
626 | * Search a multi-dimensional array for a value. |
||
627 | * |
||
628 | * @param mixed $needle |
||
629 | * the value to search for. |
||
630 | * @param array $haystack |
||
631 | * the multi-dimensional array to search. |
||
632 | * @return boolean |
||
633 | * true if `$needle` is found in `$haystack`. |
||
634 | * true if `$needle` == `$haystack`. |
||
635 | * true if `$needle` is found in any of the arrays contained within `$haystack`. |
||
636 | * false otherwise. |
||
637 | */ |
||
638 | public static function in_array_multi($needle, $haystack) |
||
639 | { |
||
640 | if ($needle == $haystack) { |
||
641 | return true; |
||
642 | } |
||
643 | |||
644 | if (is_array($haystack)) { |
||
645 | foreach ($haystack as $key => $val) { |
||
646 | if (is_array($val)) { |
||
647 | if (self::in_array_multi($needle, $val)) { |
||
648 | return true; |
||
649 | } |
||
650 | } elseif (!strcmp($needle, $key) || !strcmp($needle, $val)) { |
||
651 | return true; |
||
652 | } |
||
653 | } |
||
654 | } |
||
655 | |||
656 | return false; |
||
657 | } |
||
658 | |||
659 | /** |
||
660 | * Search an array for multiple values. |
||
661 | * |
||
662 | * @param array $needles |
||
663 | * the values to search the `$haystack` for. |
||
664 | * @param array $haystack |
||
665 | * the in which to search for the `$needles` |
||
666 | * @return boolean |
||
667 | * true if any of the `$needles` are in `$haystack`, |
||
668 | * false otherwise. |
||
669 | */ |
||
670 | public static function in_array_all($needles, $haystack) |
||
671 | { |
||
672 | foreach ($needles as $n) { |
||
673 | if (!in_array($n, $haystack)) { |
||
674 | return false; |
||
675 | } |
||
676 | } |
||
677 | |||
678 | return true; |
||
679 | } |
||
680 | |||
681 | /** |
||
682 | * Transform a multi-dimensional array to a flat array. The input array |
||
683 | * is expected to conform to the structure of the `$_FILES` variable. |
||
684 | * |
||
685 | * @param array $filedata |
||
686 | * the raw `$_FILES` data structured array |
||
687 | * @return array |
||
688 | * the flattened array. |
||
689 | */ |
||
690 | public static function processFilePostData($filedata) |
||
691 | { |
||
692 | $result = array(); |
||
693 | |||
694 | foreach ($filedata as $key => $data) { |
||
695 | foreach ($data as $handle => $value) { |
||
696 | if (is_array($value)) { |
||
697 | foreach ($value as $index => $pair) { |
||
698 | if (!is_array($result[$handle][$index])) { |
||
699 | $result[$handle][$index] = array(); |
||
700 | } |
||
701 | |||
702 | if (!is_array($pair)) { |
||
703 | $result[$handle][$index][$key] = $pair; |
||
704 | } else { |
||
705 | $result[$handle][$index][array_pop(array_keys($pair))][$key] = array_pop(array_values($pair)); |
||
706 | } |
||
707 | } |
||
708 | } else { |
||
709 | $result[$handle][$key] = $value; |
||
710 | } |
||
711 | } |
||
712 | } |
||
713 | |||
714 | return $result; |
||
715 | } |
||
716 | |||
717 | /** |
||
718 | * Merge `$_POST` with `$_FILES` to produce a flat array of the contents |
||
719 | * of both. If there is no merge_file_post_data function defined then |
||
720 | * such a function is created. This is necessary to overcome PHP's ability |
||
721 | * to handle forms. This overcomes PHP's convoluted `$_FILES` structure |
||
722 | * to make it simpler to access `multi-part/formdata`. |
||
723 | * |
||
724 | * @return array |
||
725 | * a flat array containing the flattened contents of both `$_POST` and |
||
726 | * `$_FILES`. |
||
727 | */ |
||
728 | public static function getPostData() |
||
729 | { |
||
730 | if (!function_exists('merge_file_post_data')) { |
||
731 | function merge_file_post_data($type, array $file, &$post) |
||
732 | { |
||
733 | foreach ($file as $key => $value) { |
||
734 | if (!isset($post[$key])) { |
||
735 | $post[$key] = array(); |
||
736 | } |
||
737 | |||
738 | if (is_array($value)) { |
||
739 | merge_file_post_data($type, $value, $post[$key]); |
||
740 | } else { |
||
741 | $post[$key][$type] = $value; |
||
742 | } |
||
743 | } |
||
744 | } |
||
745 | } |
||
746 | |||
747 | $files = array( |
||
748 | 'name' => array(), |
||
749 | 'type' => array(), |
||
750 | 'tmp_name' => array(), |
||
751 | 'error' => array(), |
||
752 | 'size' => array() |
||
753 | ); |
||
754 | $post = $_POST; |
||
755 | |||
756 | if (is_array($_FILES) && !empty($_FILES)) { |
||
757 | foreach ($_FILES as $key_a => $data_a) { |
||
758 | if (!is_array($data_a)) { |
||
759 | continue; |
||
760 | } |
||
761 | |||
762 | foreach ($data_a as $key_b => $data_b) { |
||
763 | $files[$key_b][$key_a] = $data_b; |
||
764 | } |
||
765 | } |
||
766 | } |
||
767 | |||
768 | foreach ($files as $type => $data) { |
||
769 | merge_file_post_data($type, $data, $post); |
||
770 | } |
||
771 | |||
772 | return $post; |
||
773 | } |
||
774 | |||
775 | /** |
||
776 | * Find the next available index in an array. Works best with numeric keys. |
||
777 | * The next available index is the minimum integer such that the array does |
||
778 | * not have a mapping for that index. Uses the increment operator on the |
||
779 | * index type of the input array, whatever that may do. |
||
780 | * |
||
781 | * @param array $array |
||
782 | * the array to find the next index for. |
||
783 | * @param mixed $seed (optional) |
||
784 | * the object with which the search for an empty index is initialized. this |
||
785 | * defaults to null. |
||
786 | * @return integer |
||
787 | * the minimum empty index into the input array. |
||
788 | */ |
||
789 | public static function array_find_available_index($array, $seed = null) |
||
790 | { |
||
791 | if (!is_null($seed)) { |
||
792 | $index = $seed; |
||
793 | } else { |
||
794 | $keys = array_keys($array); |
||
795 | sort($keys); |
||
796 | $index = array_pop($keys); |
||
797 | } |
||
798 | |||
799 | if (isset($array[$index])) { |
||
800 | do { |
||
801 | $index++; |
||
802 | } while (isset($array[$index])); |
||
803 | } |
||
804 | |||
805 | return $index; |
||
806 | } |
||
807 | |||
808 | /** |
||
809 | * Filter the duplicate values from an array into a new array, optionally |
||
810 | * ignoring the case of the values (assuming they are strings?). A new array |
||
811 | * is returned, the input array is left unchanged. |
||
812 | * |
||
813 | * @param array $array |
||
814 | * the array to filter. |
||
815 | * @param boolean $ignore_case |
||
816 | * true if the case of the values in the array should be ignored, false otherwise. |
||
817 | * @return array |
||
818 | * a new array containing only the unique elements of the input array. |
||
819 | */ |
||
820 | public static function array_remove_duplicates(array $array, $ignore_case = false) |
||
823 | } |
||
824 | |||
825 | /** |
||
826 | * Test whether a value is in an array based on string comparison, ignoring |
||
827 | * the case of the values. |
||
828 | * |
||
829 | * @param mixed $needle |
||
830 | * the object to search the array for. |
||
831 | * @param array $haystack |
||
832 | * the array to search for the `$needle`. |
||
833 | * @return boolean |
||
834 | * true if the `$needle` is in the `$haystack`, false otherwise. |
||
835 | */ |
||
836 | public static function in_iarray($needle, array $haystack) |
||
837 | { |
||
838 | foreach ($haystack as $key => $value) { |
||
839 | if (strcasecmp($value, $needle) == 0) { |
||
840 | return true; |
||
841 | } |
||
842 | } |
||
843 | return false; |
||
844 | } |
||
845 | |||
846 | /** |
||
847 | * Filter the input array for duplicates, treating each element in the array |
||
848 | * as a string and comparing them using a case insensitive comparison function. |
||
849 | * |
||
850 | * @param array $array |
||
851 | * the array to filter. |
||
852 | * @return array |
||
853 | * a new array containing only the unique elements of the input array. |
||
854 | */ |
||
855 | public static function array_iunique(array $array) |
||
856 | { |
||
857 | $tmp = array(); |
||
858 | |||
859 | foreach ($array as $key => $value) { |
||
860 | if (!self::in_iarray($value, $tmp)) { |
||
861 | $tmp[$key] = $value; |
||
862 | } |
||
863 | } |
||
864 | |||
865 | return $tmp; |
||
866 | } |
||
867 | |||
868 | /** |
||
869 | * Function recursively apply a function to an array's values. |
||
870 | * This will not touch the keys, just the values. |
||
871 | * |
||
872 | * @since Symphony 2.2 |
||
873 | * @param string $function |
||
874 | * @param array $array |
||
875 | * @return array |
||
876 | * a new array with all the values passed through the given `$function` |
||
877 | */ |
||
878 | public static function array_map_recursive($function, array $array) |
||
879 | { |
||
880 | $tmp = array(); |
||
881 | |||
882 | foreach ($array as $key => $value) { |
||
883 | if (is_array($value)) { |
||
884 | $tmp[$key] = self::array_map_recursive($function, $value); |
||
885 | } else { |
||
886 | $tmp[$key] = call_user_func($function, $value); |
||
887 | } |
||
888 | } |
||
889 | |||
890 | return $tmp; |
||
891 | } |
||
892 | |||
893 | /** |
||
894 | * Convert an array into an XML fragment and append it to an existing |
||
895 | * XML element. Any arrays contained as elements in the input array will |
||
896 | * also be recursively formatted and appended to the input XML fragment. |
||
897 | * The input XML element will be modified as a result of calling this. |
||
898 | * |
||
899 | * @param XMLElement $parent |
||
900 | * the XML element to append the formatted array data to. |
||
901 | * @param array $data |
||
902 | * the array to format and append to the XML fragment. |
||
903 | * @param boolean $validate |
||
904 | * true if the formatted array data should be validated as it is |
||
905 | * constructed, false otherwise. |
||
906 | */ |
||
907 | public static function array_to_xml(XMLElement $parent, array $data, $validate = false) |
||
908 | { |
||
909 | foreach ($data as $element_name => $value) { |
||
910 | if (!is_numeric($value) && empty($value)) { |
||
911 | continue; |
||
912 | } |
||
913 | |||
914 | if (is_int($element_name)) { |
||
915 | $child = new XMLElement('item'); |
||
916 | $child->setAttribute('index', $element_name + 1); |
||
917 | } else { |
||
918 | $child = new XMLElement($element_name, null, array(), true); |
||
919 | } |
||
920 | |||
921 | if (is_array($value) || is_object($value)) { |
||
922 | self::array_to_xml($child, (array)$value); |
||
923 | |||
924 | if ($child->getNumberOfChildren() == 0) { |
||
925 | continue; |
||
926 | } |
||
927 | } elseif ($validate === true && !self::validateXML(self::sanitize($value), $errors, false, new XSLTProcess)) { |
||
928 | continue; |
||
929 | } else { |
||
930 | $child->setValue(self::sanitize($value)); |
||
931 | } |
||
932 | |||
933 | $parent->appendChild($child); |
||
934 | } |
||
935 | } |
||
936 | |||
937 | /** |
||
938 | * Create a file at the input path with the (optional) input permissions |
||
939 | * with the input content. This function will ignore errors in opening, |
||
940 | * writing, closing and changing the permissions of the resulting file. |
||
941 | * If opening or writing the file fail then this will return false. |
||
942 | * This method calls `General::checkFileWritable()` which properly checks |
||
943 | * for permissions. |
||
944 | * |
||
945 | * @uses General::checkFileWritable() |
||
946 | * @param string $file |
||
947 | * the path of the file to write. |
||
948 | * @param mixed $data |
||
949 | * the data to write to the file. |
||
950 | * @param integer|string $perm (optional) |
||
951 | * the permissions as an octal number to set set on the resulting file. |
||
952 | * this defaults to 0644 (if omitted or set to null) |
||
953 | * @param string $mode (optional) |
||
954 | * the mode that the file should be opened with, defaults to 'w'. See modes |
||
955 | * at http://php.net/manual/en/function.fopen.php |
||
956 | * @param boolean $trim (optional) |
||
957 | * removes tripple linebreaks |
||
958 | * @return boolean |
||
959 | * true if the file is successfully opened, written to, closed and has the |
||
960 | * required permissions set. false, otherwise. |
||
961 | */ |
||
962 | public static function writeFile($file, $data, $perm = 0644, $mode = 'w', $trim = false) |
||
963 | { |
||
964 | if (static::checkFileWritable($file) === false) { |
||
965 | return false; |
||
966 | } |
||
967 | |||
968 | if (!$handle = fopen($file, $mode)) { |
||
969 | return false; |
||
970 | } |
||
971 | |||
972 | if ($trim === true) { |
||
973 | $data = preg_replace("/(" . PHP_EOL . "([ |\t]+)?){2,}" . PHP_EOL . "/", PHP_EOL . PHP_EOL, trim($data)); |
||
974 | } |
||
975 | |||
976 | if (fwrite($handle, $data, strlen($data)) === false) { |
||
977 | return false; |
||
978 | } |
||
979 | |||
980 | fclose($handle); |
||
981 | |||
982 | try { |
||
983 | if (is_null($perm)) { |
||
984 | $perm = 0644; |
||
985 | } |
||
986 | |||
987 | @chmod($file, intval($perm, 8)); |
||
988 | } catch (Exception $ex) { |
||
989 | // If we can't chmod the file, this is probably because our host is |
||
990 | // running PHP with a different user to that of the file. Although we |
||
991 | // can delete the file, create a new one and then chmod it, we run the |
||
992 | // risk of losing the file as we aren't saving it anywhere. For the immediate |
||
993 | // future, atomic saving isn't needed by Symphony and it's recommended that |
||
994 | // if your extension require this logic, it uses it's own function rather |
||
995 | // than this 'General' one. |
||
996 | return true; |
||
997 | } |
||
998 | |||
999 | return true; |
||
1000 | } |
||
1001 | |||
1002 | /** |
||
1003 | * Checks that the file and its folder are readable and writable. |
||
1004 | * |
||
1005 | * @deprecated @since Symphony 2.7.0 |
||
1006 | * @since Symphony 2.6.3 |
||
1007 | * @return boolean |
||
1008 | */ |
||
1009 | public static function checkFile($file) |
||
1010 | { |
||
1011 | if (Symphony::Log()) { |
||
1012 | Symphony::Log()->pushDeprecateWarningToLog('General::checkFile()', '`General::checkFileWritable()'); |
||
1013 | } |
||
1014 | clearstatcache(); |
||
1015 | $dir = dirname($file); |
||
1016 | |||
1017 | if ( |
||
1018 | (!is_writable($dir) || !is_readable($dir)) // Folder |
||
1019 | || (file_exists($file) && (!is_readable($file) || !is_writable($file))) // File |
||
1020 | ) { |
||
1021 | return false; |
||
1022 | } |
||
1023 | |||
1024 | return true; |
||
1025 | } |
||
1026 | |||
1027 | /** |
||
1028 | * Checks that the file is readable. |
||
1029 | * It first checks to see if the $file path exists |
||
1030 | * and if it does, checks that it is readable. |
||
1031 | * |
||
1032 | * @uses clearstatcache() |
||
1033 | * @since Symphony 2.7.0 |
||
1034 | * @param string $file |
||
1035 | * The path of the file |
||
1036 | * @return boolean |
||
1037 | */ |
||
1038 | public static function checkFileReadable($file) |
||
1039 | { |
||
1040 | clearstatcache(); |
||
1041 | // Reading a file requires that is exists and can be read |
||
1042 | return @file_exists($file) && @is_readable($file); |
||
1043 | } |
||
1044 | |||
1045 | /** |
||
1046 | * Checks that the file is writable. |
||
1047 | * It first checks to see if the $file path exists |
||
1048 | * and if it does, checks that is it writable. If the file |
||
1049 | * does not exits, it checks that the directory exists and if it does, |
||
1050 | * checks that it is writable. |
||
1051 | * |
||
1052 | * @uses clearstatcache() |
||
1053 | * @since Symphony 2.7.0 |
||
1054 | * @param string $file |
||
1055 | * The path of the file |
||
1056 | * @return boolean |
||
1057 | */ |
||
1058 | public static function checkFileWritable($file) |
||
1059 | { |
||
1060 | clearstatcache(); |
||
1061 | if (@file_exists($file)) { |
||
1062 | // Writing to an existing file does not require write |
||
1063 | // permissions on the directory. |
||
1064 | return @is_writable($file); |
||
1065 | } |
||
1066 | $dir = dirname($file); |
||
1067 | // Creating a file requires write permissions on the directory. |
||
1068 | return @file_exists($dir) && @is_writable($dir); |
||
1069 | } |
||
1070 | |||
1071 | /** |
||
1072 | * Checks that the file is deletable. |
||
1073 | * It first checks to see if the $file path exists |
||
1074 | * and if it does, checks that is it writable. |
||
1075 | * |
||
1076 | * @uses clearstatcache() |
||
1077 | * @since Symphony 2.7.0 |
||
1078 | * @param string $file |
||
1079 | * The path of the file |
||
1080 | * @return boolean |
||
1081 | */ |
||
1082 | public static function checkFileDeletable($file) |
||
1083 | { |
||
1084 | clearstatcache(); |
||
1085 | $dir = dirname($file); |
||
1086 | // Deleting a file requires write permissions on the directory. |
||
1087 | // It does not require write permissions on the file |
||
1088 | return @file_exists($dir) && @is_writable($dir); |
||
1089 | } |
||
1090 | |||
1091 | /** |
||
1092 | * Delete a file at a given path, silently ignoring errors depending |
||
1093 | * on the value of the input variable $silent. |
||
1094 | * |
||
1095 | * @uses General::checkFileDeletable() |
||
1096 | * @param string $file |
||
1097 | * the path of the file to delete |
||
1098 | * @param boolean $silent (optional) |
||
1099 | * true if an exception should be raised if an error occurs, false |
||
1100 | * otherwise. this defaults to true. |
||
1101 | * @throws Exception |
||
1102 | * @return boolean |
||
1103 | * true if the file is successfully unlinked, if the unlink fails and |
||
1104 | * silent is set to true then an exception is thrown. if the unlink |
||
1105 | * fails and silent is set to false then this returns false. |
||
1106 | */ |
||
1107 | public static function deleteFile($file, $silent = true) |
||
1108 | { |
||
1109 | try { |
||
1110 | if (static::checkFileDeletable($file) === false) { |
||
1111 | throw new Exception(__('Denied by permission')); |
||
1112 | } |
||
1113 | if (!@file_exists($file)) { |
||
1114 | return true; |
||
1115 | } |
||
1116 | return @unlink($file); |
||
1117 | } catch (Exception $ex) { |
||
1118 | if ($silent === false) { |
||
1119 | throw new Exception(__('Unable to remove file - %s', array($file)), 0, $ex); |
||
1120 | } |
||
1121 | |||
1122 | return false; |
||
1123 | } |
||
1124 | } |
||
1125 | |||
1126 | /** |
||
1127 | * Extract the file extension from the input file path. |
||
1128 | * |
||
1129 | * @param string $file |
||
1130 | * the path of the file to extract the extension of. |
||
1131 | * @return array |
||
1132 | * an array with a single key 'extension' and a value of the extension |
||
1133 | * of the input path. |
||
1134 | */ |
||
1135 | public static function getExtension($file) |
||
1136 | { |
||
1137 | return pathinfo($file, PATHINFO_EXTENSION); |
||
1138 | } |
||
1139 | |||
1140 | /** |
||
1141 | * Gets mime type of a file. |
||
1142 | * |
||
1143 | * For email attachments, the mime type is very important. |
||
1144 | * Uses the PHP 5.3 function `finfo_open` when available, otherwise falls |
||
1145 | * back to using a mapping of known of common mimetypes. If no matches |
||
1146 | * are found `application/octet-stream` will be returned. |
||
1147 | * |
||
1148 | * @author Michael Eichelsdoerfer |
||
1149 | * @author Huib Keemink |
||
1150 | * @param string $file |
||
1151 | * @return string|boolean |
||
1152 | * the mime type of the file, or false is none found |
||
1153 | */ |
||
1154 | public function getMimeType($file) |
||
1155 | { |
||
1156 | if (!empty($file)) { |
||
1157 | // in PHP 5.3 we can use 'finfo' |
||
1158 | if (PHP_VERSION_ID >= 50300 && function_exists('finfo_open')) { |
||
1159 | $finfo = finfo_open(FILEINFO_MIME_TYPE); |
||
1160 | $mime_type = finfo_file($finfo, $file); |
||
1161 | finfo_close($finfo); |
||
1162 | } else { |
||
1163 | // A few mimetypes to "guess" using the file extension. |
||
1164 | $mimetypes = array( |
||
1165 | 'txt' => 'text/plain', |
||
1166 | 'csv' => 'text/csv', |
||
1167 | 'pdf' => 'application/pdf', |
||
1168 | 'doc' => 'application/msword', |
||
1169 | 'docx' => 'application/msword', |
||
1170 | 'xls' => 'application/vnd.ms-excel', |
||
1171 | 'ppt' => 'application/vnd.ms-powerpoint', |
||
1172 | 'eps' => 'application/postscript', |
||
1173 | 'zip' => 'application/zip', |
||
1174 | 'gif' => 'image/gif', |
||
1175 | 'jpg' => 'image/jpeg', |
||
1176 | 'jpeg' => 'image/jpeg', |
||
1177 | 'png' => 'image/png', |
||
1178 | 'mp3' => 'audio/mpeg', |
||
1179 | 'mp4a' => 'audio/mp4', |
||
1180 | 'aac' => 'audio/x-aac', |
||
1181 | 'aif' => 'audio/x-aiff', |
||
1182 | 'aiff' => 'audio/x-aiff', |
||
1183 | 'wav' => 'audio/x-wav', |
||
1184 | 'wma' => 'audio/x-ms-wma', |
||
1185 | 'mpeg' => 'video/mpeg', |
||
1186 | 'mpg' => 'video/mpeg', |
||
1187 | 'mp4' => 'video/mp4', |
||
1188 | 'mov' => 'video/quicktime', |
||
1189 | 'avi' => 'video/x-msvideo', |
||
1190 | 'wmv' => 'video/x-ms-wmv', |
||
1191 | ); |
||
1192 | |||
1193 | $extension = substr(strrchr($file, '.'), 1); |
||
1194 | |||
1195 | if ($mimetypes[strtolower($extension)] !== null) { |
||
1196 | $mime_type = $mimetypes[$extension]; |
||
1197 | } else { |
||
1198 | $mime_type = 'application/octet-stream'; |
||
1199 | } |
||
1200 | } |
||
1201 | |||
1202 | return $mime_type; |
||
1203 | } |
||
1204 | return false; |
||
1205 | } |
||
1206 | |||
1207 | /** |
||
1208 | * Construct a multi-dimensional array that reflects the directory |
||
1209 | * structure of a given path. |
||
1210 | * |
||
1211 | * @param string $dir (optional) |
||
1212 | * the path of the directory to construct the multi-dimensional array |
||
1213 | * for. this defaults to '.'. |
||
1214 | * @param string $filter (optional) |
||
1215 | * A regular expression to filter the directories. This is positive filter, ie. |
||
1216 | * if the filter matches, the directory is included. Defaults to null. |
||
1217 | * @param boolean $recurse (optional) |
||
1218 | * true if sub-directories should be traversed and reflected in the |
||
1219 | * resulting array, false otherwise. |
||
1220 | * @param mixed $strip_root (optional) |
||
1221 | * If null, the full path to the file will be returned, otherwise the value |
||
1222 | * of `strip_root` will be removed from the file path. |
||
1223 | * @param array $exclude (optional) |
||
1224 | * ignore directories listed in this array. this defaults to an empty array. |
||
1225 | * @param boolean $ignore_hidden (optional) |
||
1226 | * ignore hidden directory (i.e.directories that begin with a period). this defaults |
||
1227 | * to true. |
||
1228 | * @return null|array |
||
1229 | * return the array structure reflecting the input directory or null if |
||
1230 | * the input directory is not actually a directory. |
||
1231 | */ |
||
1232 | public static function listDirStructure($dir = '.', $filter = null, $recurse = true, $strip_root = null, $exclude = array(), $ignore_hidden = true) |
||
1233 | { |
||
1234 | if (!is_dir($dir)) { |
||
1235 | return null; |
||
1236 | } |
||
1237 | |||
1238 | $files = array(); |
||
1239 | |||
1240 | foreach (scandir($dir) as $file) { |
||
1241 | if ( |
||
1242 | ($file == '.' || $file == '..') |
||
1243 | || ($ignore_hidden && $file{0} == '.') |
||
1244 | || !is_dir("$dir/$file") |
||
1245 | || in_array($file, $exclude) |
||
1246 | || in_array("$dir/$file", $exclude) |
||
1247 | ) { |
||
1248 | continue; |
||
1249 | } |
||
1250 | |||
1251 | if (!is_null($filter)) { |
||
1252 | if (!preg_match($filter, $file)) { |
||
1253 | continue; |
||
1254 | } |
||
1255 | } |
||
1256 | |||
1257 | $files[] = rtrim(str_replace($strip_root, '', $dir), '/') ."/$file/"; |
||
1258 | |||
1259 | if ($recurse) { |
||
1260 | $files = @array_merge($files, self::listDirStructure("$dir/$file", $filter, $recurse, $strip_root, $exclude, $ignore_hidden)); |
||
1261 | } |
||
1262 | } |
||
1263 | |||
1264 | return $files; |
||
1265 | } |
||
1266 | |||
1267 | /** |
||
1268 | * Construct a multi-dimensional array that reflects the directory |
||
1269 | * structure of a given path grouped into directory and file keys |
||
1270 | * matching any input constraints. |
||
1271 | * |
||
1272 | * @param string $dir (optional) |
||
1273 | * the path of the directory to construct the multi-dimensional array |
||
1274 | * for. this defaults to '.'. |
||
1275 | * @param array|string $filters (optional) |
||
1276 | * either a regular expression to filter the files by or an array of |
||
1277 | * files to include. |
||
1278 | * @param boolean $recurse (optional) |
||
1279 | * true if sub-directories should be traversed and reflected in the |
||
1280 | * resulting array, false otherwise. |
||
1281 | * @param string $sort (optional) |
||
1282 | * 'asc' if the resulting filelist array should be sorted, anything else otherwise. |
||
1283 | * this defaults to 'asc'. |
||
1284 | * @param mixed $strip_root (optional) |
||
1285 | * If null, the full path to the file will be returned, otherwise the value |
||
1286 | * of `strip_root` will be removed from the file path. |
||
1287 | * @param array $exclude (optional) |
||
1288 | * ignore files listed in this array. this defaults to an empty array. |
||
1289 | * @param boolean $ignore_hidden (optional) |
||
1290 | * ignore hidden files (i.e. files that begin with a period). this defaults |
||
1291 | * to true. |
||
1292 | * @return null|array |
||
1293 | * return the array structure reflecting the input directory or null if |
||
1294 | * the input directory is not actually a directory. |
||
1295 | */ |
||
1296 | public static function listStructure($dir = ".", $filters = array(), $recurse = true, $sort = "asc", $strip_root = null, $exclude = array(), $ignore_hidden = true) |
||
1297 | { |
||
1298 | if (!is_dir($dir)) { |
||
1299 | return null; |
||
1300 | } |
||
1301 | |||
1302 | // Check to see if $filters is a string containing a regex, or an array of file types |
||
1303 | if (is_array($filters) && !empty($filters)) { |
||
1304 | $filter_type = 'file'; |
||
1305 | } elseif (is_string($filters)) { |
||
1306 | $filter_type = 'regex'; |
||
1307 | } else { |
||
1308 | $filter_type = null; |
||
1309 | } |
||
1310 | $files = array(); |
||
1311 | |||
1312 | $prefix = str_replace($strip_root, '', $dir); |
||
1313 | |||
1314 | if ($prefix !== "" && substr($prefix, -1) !== "/") { |
||
1315 | $prefix .= "/"; |
||
1316 | } |
||
1317 | |||
1318 | $files['dirlist'] = array(); |
||
1319 | $files['filelist'] = array(); |
||
1320 | |||
1321 | foreach (scandir($dir) as $file) { |
||
1322 | if ( |
||
1323 | ($file == '.' || $file === '..') |
||
1324 | || ($ignore_hidden && $file{0} === '.') |
||
1325 | || in_array($file, $exclude) |
||
1326 | || in_array("$dir/$file", $exclude) |
||
1327 | ) { |
||
1328 | continue; |
||
1329 | } |
||
1330 | |||
1331 | $dir = rtrim($dir, '/'); |
||
1332 | |||
1333 | if (is_dir("$dir/$file")) { |
||
1334 | if ($recurse) { |
||
1335 | $files["$prefix$file/"] = self::listStructure("$dir/$file", $filters, $recurse, $sort, $strip_root, $exclude, $ignore_hidden); |
||
1336 | } |
||
1337 | |||
1338 | $files['dirlist'][] = "$prefix$file/"; |
||
1339 | } elseif ($filter_type === 'regex') { |
||
1340 | if (preg_match($filters, $file)) { |
||
1341 | $files['filelist'][] = "$prefix$file"; |
||
1342 | } |
||
1343 | } elseif ($filter_type === 'file') { |
||
1344 | if (in_array(self::getExtension($file), $filters)) { |
||
1345 | $files['filelist'][] = "$prefix$file"; |
||
1346 | } |
||
1347 | } elseif (is_null($filter_type)) { |
||
1348 | $files['filelist'][] = "$prefix$file"; |
||
1349 | } |
||
1350 | } |
||
1351 | |||
1352 | if (is_array($files['filelist'])) { |
||
1353 | ($sort == 'desc') ? rsort($files['filelist']) : sort($files['filelist']); |
||
1354 | } |
||
1355 | |||
1356 | return $files; |
||
1357 | } |
||
1358 | |||
1359 | /** |
||
1360 | * Count the number of words in a string. Words are delimited by "spaces". |
||
1361 | * The characters included in the set of "spaces" are: |
||
1362 | * ' ', ' ', ' ', ' ', |
||
1363 | * ' ', ' ', ' ', ' ', |
||
1364 | * '​', '𠀯', ' ' |
||
1365 | * Any html/xml tags are first removed by strip_tags() and any included html |
||
1366 | * entities are decoded. The resulting string is then split by the above set |
||
1367 | * of spaces and the resulting size of the resulting array returned. |
||
1368 | * |
||
1369 | * @param string $string |
||
1370 | * the string from which to count the contained words. |
||
1371 | * @return integer |
||
1372 | * the number of words contained in the input string. |
||
1373 | */ |
||
1374 | public static function countWords($string) |
||
1375 | { |
||
1376 | $string = strip_tags($string); |
||
1377 | |||
1378 | // Strip spaces: |
||
1379 | $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8'); |
||
1380 | $spaces = array( |
||
1381 | ' ', ' ', ' ', ' ', |
||
1382 | ' ', ' ', ' ', ' ', |
||
1383 | '​', '𠀯', ' ' |
||
1384 | ); |
||
1385 | |||
1386 | foreach ($spaces as &$space) { |
||
1387 | $space = html_entity_decode($space, ENT_NOQUOTES, 'UTF-8'); |
||
1388 | } |
||
1389 | |||
1390 | $string = str_replace($spaces, ' ', $string); |
||
1391 | $string = preg_replace('/[^\w\s]/i', '', $string); |
||
1392 | |||
1393 | return str_word_count($string); |
||
1394 | } |
||
1395 | |||
1396 | /** |
||
1397 | * Truncate a string to a given length, respecting word boundaries. The returned |
||
1398 | * string will always be less than `$maxChars`. Newlines, HTML elements and |
||
1399 | * leading or trailing spaces are removed from the string. |
||
1400 | * |
||
1401 | * @param string $string |
||
1402 | * the string to truncate. |
||
1403 | * @param integer $maxChars (optional) |
||
1404 | * the maximum length of the string to truncate the input string to. this |
||
1405 | * defaults to 200 characters. |
||
1406 | * @param boolean $appendHellip (optional) |
||
1407 | * true if the ellipses should be appended to the result in circumstances |
||
1408 | * where the result is shorter than the input string. false otherwise. this |
||
1409 | * defaults to false. |
||
1410 | * @return null|string |
||
1411 | * if the resulting string contains only spaces then null is returned. otherwise |
||
1412 | * a string that satisfies the input constraints. |
||
1413 | */ |
||
1414 | public static function limitWords($string, $maxChars = 200, $appendHellip = false) |
||
1415 | { |
||
1416 | if ($appendHellip) { |
||
1417 | $maxChars -= 1; |
||
1418 | } |
||
1419 | |||
1420 | $string = trim(strip_tags(nl2br($string))); |
||
1421 | $original_length = strlen($string); |
||
1422 | |||
1423 | if ($original_length == 0) { |
||
1424 | return null; |
||
1425 | } elseif ($original_length <= $maxChars) { |
||
1426 | return $string; |
||
1427 | } |
||
1428 | |||
1429 | // Compute the negative offset |
||
1430 | $offset = $maxChars - $original_length; |
||
1431 | // Find the first word break char before the maxChars limit is hit. |
||
1432 | $last_word_break = max(array_filter(array_map(function ($wb) use ($string, $offset) { |
||
1433 | return strrpos($string, $wb, $offset); |
||
1434 | }, array(' ', '-', ',', '.', '!', '?', PHP_EOL)))); |
||
1435 | $result = substr($string, 0, $last_word_break); |
||
1436 | |||
1437 | if ($appendHellip) { |
||
1438 | $result .= "…"; |
||
1439 | } |
||
1440 | |||
1441 | return $result; |
||
1442 | } |
||
1443 | |||
1444 | /** |
||
1445 | * Move a file from the source path to the destination path and name and |
||
1446 | * set its permissions to the input permissions. This will ignore errors |
||
1447 | * in the `is_uploaded_file()`, `move_uploaded_file()` and `chmod()` functions. |
||
1448 | * |
||
1449 | * @uses General::checkFileWritable() |
||
1450 | * @param string $dest_path |
||
1451 | * the file path to which the source file is to be moved. |
||
1452 | * @param string $dest_name |
||
1453 | * the file name within the file path to which the source file is to be moved. |
||
1454 | * @param string $tmp_name |
||
1455 | * the full path name of the source file to move. |
||
1456 | * @param integer|string $perm (optional) |
||
1457 | * the permissions to apply to the moved file. this defaults to 0644 @since |
||
1458 | * Symphony 2.7.0. It was 0777 in 2.6.x and less. |
||
1459 | * @return boolean |
||
1460 | * true if the file was moved and its permissions set as required. false otherwise. |
||
1461 | */ |
||
1462 | public static function uploadFile($dest_path, $dest_name, $tmp_name, $perm = 0644) |
||
1463 | { |
||
1464 | // Upload the file |
||
1465 | if (@is_uploaded_file($tmp_name)) { |
||
1466 | $dest_path = rtrim($dest_path, '/') . '/'; |
||
1467 | $dest = $dest_path . $dest_name; |
||
1468 | |||
1469 | // Check that destination is writable |
||
1470 | if (!static::checkFileWritable($dest)) { |
||
1471 | return false; |
||
1472 | } |
||
1473 | // Try place the file in the correction location |
||
1474 | if (@move_uploaded_file($tmp_name, $dest)) { |
||
1475 | if (is_null($perm)) { |
||
1476 | $perm = 0644; |
||
1477 | } |
||
1478 | @chmod($dest, intval($perm, 8)); |
||
1479 | return true; |
||
1480 | } |
||
1481 | } |
||
1482 | |||
1483 | // Could not move the file |
||
1484 | return false; |
||
1485 | } |
||
1486 | |||
1487 | /** |
||
1488 | * Format a number of bytes in human readable format. This will append MB as |
||
1489 | * appropriate for values greater than 1,024*1,024, KB for values between |
||
1490 | * 1,024 and 1,024*1,024-1 and bytes for values between 0 and 1,024. |
||
1491 | * |
||
1492 | * @param integer $file_size |
||
1493 | * the number to format. |
||
1494 | * @return string |
||
1495 | * the formatted number. |
||
1496 | */ |
||
1497 | public static function formatFilesize($file_size) |
||
1498 | { |
||
1499 | $file_size = intval($file_size); |
||
1500 | |||
1501 | if ($file_size >= (1024 * 1024)) { |
||
1502 | $file_size = number_format($file_size * (1 / (1024 * 1024)), 2) . ' MB'; |
||
1503 | } elseif ($file_size >= 1024) { |
||
1504 | $file_size = intval($file_size * (1/1024)) . ' KB'; |
||
1505 | } else { |
||
1506 | $file_size = intval($file_size) . ' bytes'; |
||
1507 | } |
||
1508 | |||
1509 | return $file_size; |
||
1510 | } |
||
1511 | |||
1512 | /** |
||
1513 | * Gets the number of bytes from 'human readable' size value. Supports |
||
1514 | * the output of `General::formatFilesize` as well as reading values |
||
1515 | * from the PHP configuration. eg. 1 MB or 1M |
||
1516 | * |
||
1517 | * @since Symphony 2.5.2 |
||
1518 | * @param string $file_size |
||
1519 | * @return integer |
||
1520 | */ |
||
1521 | public static function convertHumanFileSizeToBytes($file_size) |
||
1522 | { |
||
1523 | $file_size = str_replace( |
||
1524 | array(' MB', ' KB', ' bytes'), |
||
1525 | array('M', 'K', 'B'), |
||
1526 | trim($file_size) |
||
1527 | ); |
||
1528 | |||
1529 | $last = strtolower($file_size[strlen($file_size)-1]); |
||
1530 | |||
1531 | $file_size = (int) $file_size; |
||
1532 | |||
1533 | switch ($last) { |
||
1534 | case 'g': |
||
1535 | $file_size *= 1024; |
||
1536 | case 'm': |
||
1537 | $file_size *= 1024; |
||
1538 | case 'k': |
||
1539 | $file_size *= 1024; |
||
1540 | } |
||
1541 | |||
1542 | return $file_size; |
||
1543 | } |
||
1544 | |||
1545 | /** |
||
1546 | * Construct an XML fragment that reflects the structure of the input timestamp. |
||
1547 | * |
||
1548 | * @param integer $timestamp |
||
1549 | * the timestamp to construct the XML element from. |
||
1550 | * @param string $element (optional) |
||
1551 | * the name of the element to append to the namespace of the constructed XML. |
||
1552 | * this defaults to "date". |
||
1553 | * @param string $date_format (optional) |
||
1554 | * the format to apply to the date, defaults to `Y-m-d`. |
||
1555 | * if empty, uses DateTimeObj settings. |
||
1556 | * @param string $time_format (optional) |
||
1557 | * the format to apply to the date, defaults to `H:i`. |
||
1558 | * if empty, uses DateTimeObj settings. |
||
1559 | * @param string $namespace (optional) |
||
1560 | * the namespace in which the resulting XML entity will reside. this defaults |
||
1561 | * to null. |
||
1562 | * @return boolean|XMLElement |
||
1563 | * false if there is no XMLElement class on the system, the constructed XML element |
||
1564 | * otherwise. |
||
1565 | */ |
||
1566 | public static function createXMLDateObject($timestamp, $element = 'date', $date_format = 'Y-m-d', $time_format = 'H:i', $namespace = null) |
||
1567 | { |
||
1568 | if (!class_exists('XMLElement')) { |
||
1569 | return false; |
||
1570 | } |
||
1571 | |||
1572 | if (empty($date_format)) { |
||
1573 | $date_format = DateTimeObj::getSetting('date_format'); |
||
1574 | } |
||
1575 | if (empty($time_format)) { |
||
1576 | $time_format = DateTimeObj::getSetting('time_format'); |
||
1577 | } |
||
1578 | |||
1579 | $xDate = new XMLElement( |
||
1580 | (!is_null($namespace) ? $namespace . ':' : '') . $element, |
||
1581 | DateTimeObj::get($date_format, $timestamp), |
||
1582 | array( |
||
1583 | 'iso' => DateTimeObj::get('c', $timestamp), |
||
1584 | 'timestamp' => DateTimeObj::get('U', $timestamp), |
||
1585 | 'time' => DateTimeObj::get($time_format, $timestamp), |
||
1586 | 'weekday' => DateTimeObj::get('N', $timestamp), |
||
1587 | 'offset' => DateTimeObj::get('O', $timestamp) |
||
1588 | ) |
||
1589 | ); |
||
1590 | |||
1591 | return $xDate; |
||
1592 | } |
||
1593 | |||
1594 | /** |
||
1595 | * Construct an XML fragment that describes a pagination structure. |
||
1596 | * |
||
1597 | * @param integer $total_entries (optional) |
||
1598 | * the total number of entries that this structure is paginating. this |
||
1599 | * defaults to 0. |
||
1600 | * @param integer $total_pages (optional) |
||
1601 | * the total number of pages within the pagination structure. this defaults |
||
1602 | * to 0. |
||
1603 | * @param integer $entries_per_page (optional) |
||
1604 | * the number of entries per page. this defaults to 1. |
||
1605 | * @param integer $current_page (optional) |
||
1606 | * the current page within the total number of pages within this pagination |
||
1607 | * structure. this defaults to 1. |
||
1608 | * @return XMLElement |
||
1609 | * the constructed XML fragment. |
||
1610 | */ |
||
1611 | public static function buildPaginationElement($total_entries = 0, $total_pages = 0, $entries_per_page = 1, $current_page = 1) |
||
1612 | { |
||
1613 | $pageinfo = new XMLElement('pagination'); |
||
1614 | |||
1615 | $pageinfo->setAttribute('total-entries', $total_entries); |
||
1616 | $pageinfo->setAttribute('total-pages', $total_pages); |
||
1617 | $pageinfo->setAttribute('entries-per-page', $entries_per_page); |
||
1618 | $pageinfo->setAttribute('current-page', $current_page); |
||
1619 | |||
1620 | return $pageinfo; |
||
1621 | } |
||
1622 | |||
1623 | /** |
||
1624 | * Uses `SHA1` or `MD5` to create a hash based on some input |
||
1625 | * This function is currently very basic, but would allow |
||
1626 | * future expansion. Salting the hash comes to mind. |
||
1627 | * |
||
1628 | * @param string $input |
||
1629 | * the string to be hashed |
||
1630 | * @param string $algorithm |
||
1631 | * This function supports 'md5', 'sha1' and 'pbkdf2'. Any |
||
1632 | * other algorithm will default to 'pbkdf2'. |
||
1633 | * @return string |
||
1634 | * the hashed string |
||
1635 | */ |
||
1636 | public static function hash($input, $algorithm = 'sha1') |
||
1637 | { |
||
1638 | switch ($algorithm) { |
||
1639 | case 'sha1': |
||
1640 | return SHA1::hash($input); |
||
1641 | |||
1642 | case 'md5': |
||
1643 | return MD5::hash($input); |
||
1644 | |||
1645 | case 'pbkdf2': |
||
1646 | default: |
||
1647 | return Crytography::hash($input, $algorithm); |
||
1648 | } |
||
1649 | } |
||
1650 | |||
1651 | /** |
||
1652 | * Helper to cut down on variables' type check. |
||
1653 | * Currently known types are the PHP defaults. |
||
1654 | * Uses `is_XXX()` functions internally. |
||
1655 | * |
||
1656 | * @since Symphony 2.3 |
||
1657 | * |
||
1658 | * @param array $params - an array of arrays containing variables info |
||
1659 | * |
||
1660 | * Array[ |
||
1661 | * $key1 => $value1 |
||
1662 | * $key2 => $value2 |
||
1663 | * ... |
||
1664 | * ] |
||
1665 | * |
||
1666 | * $key = the name of the variable |
||
1667 | * $value = Array[ |
||
1668 | * 'var' => the variable to check |
||
1669 | * 'type' => enforced type. Must match the XXX part from an `is_XXX()` function |
||
1670 | * 'optional' => boolean. If this is set, the default value of the variable must be null |
||
1671 | * ] |
||
1672 | * |
||
1673 | * @throws InvalidArgumentException if validator doesn't exist. |
||
1674 | * @throws InvalidArgumentException if variable type validation fails. |
||
1675 | * |
||
1676 | * @example |
||
1677 | * $color = 'red'; |
||
1678 | * $foo = null; |
||
1679 | * $bar = 21; |
||
1680 | * |
||
1681 | * General::ensureType(array( |
||
1682 | * 'color' => array('var' => $color, 'type'=> 'string'), // success |
||
1683 | * 'foo' => array('var' => $foo, 'type'=> 'int', 'optional' => true), // success |
||
1684 | * 'bar' => array('var' => $bar, 'type'=> 'string') // fail |
||
1685 | * )); |
||
1686 | */ |
||
1687 | public static function ensureType(array $params) |
||
1688 | { |
||
1689 | foreach ($params as $name => $param) { |
||
1690 | if (isset($param['optional']) && ($param['optional'] === true)) { |
||
1691 | if (is_null($param['var'])) { |
||
1692 | continue; |
||
1693 | } |
||
1694 | // if not null, check it's type |
||
1695 | } |
||
1696 | |||
1697 | // validate the validator |
||
1698 | $validator = 'is_'.$param['type']; |
||
1699 | |||
1700 | if (!function_exists($validator)) { |
||
1701 | throw new InvalidArgumentException(__('Enforced type `%1$s` for argument `$%2$s` does not match any known variable types.', array($param['type'], $name))); |
||
1702 | } |
||
1703 | |||
1704 | // validate variable type |
||
1705 | if (!call_user_func($validator, $param['var'])) { |
||
1706 | throw new InvalidArgumentException(__('Argument `$%1$s` is not of type `%2$s`, given `%3$s`.', array($name, $param['type'], gettype($param['var'])))); |
||
1707 | } |
||
1708 | } |
||
1709 | } |
||
1710 | |||
1711 | |||
1712 | /** |
||
1713 | * Wrap a value in CDATA tags for XSL output of non encoded data, only |
||
1714 | * if not already wrapped. |
||
1715 | * |
||
1716 | * @since Symphony 2.3.2 |
||
1717 | * |
||
1718 | * @param string $value |
||
1719 | * The string to wrap in CDATA |
||
1720 | * @return string |
||
1721 | * The wrapped string |
||
1722 | */ |
||
1723 | public static function wrapInCDATA($value) |
||
1724 | { |
||
1725 | if (empty($value)) { |
||
1726 | return $value; |
||
1727 | } |
||
1728 | |||
1729 | $startRegExp = '/^' . preg_quote(CDATA_BEGIN) . '/'; |
||
1730 | $endRegExp = '/' . preg_quote(CDATA_END) . '$/'; |
||
1731 | |||
1732 | if (!preg_match($startRegExp, $value)) { |
||
1733 | $value = CDATA_BEGIN . $value; |
||
1734 | } |
||
1735 | |||
1736 | if (!preg_match($endRegExp, $value)) { |
||
1737 | $value .= CDATA_END; |
||
1738 | } |
||
1739 | |||
1740 | return $value; |
||
1741 | } |
||
1742 | |||
1743 | /** |
||
1744 | * Unwrap a value from CDATA tags to return the raw string |
||
1745 | * |
||
1746 | * @since Symphony 2.3.4 |
||
1747 | * @param string $value |
||
1748 | * The string to unwrap from CDATA |
||
1749 | * @return string |
||
1750 | * The unwrapped string |
||
1751 | */ |
||
1752 | public static function unwrapCDATA($value) |
||
1755 | } |
||
1756 | |||
1757 | /** |
||
1758 | * Converts a value to a positive integer. This method makes sure that the |
||
1759 | * value is a valid positive integer representation before doing the cast. |
||
1760 | * |
||
1761 | * @since Symphony 2.5 |
||
1762 | * @param mixed $value |
||
1763 | * The value to cast to an integer |
||
1764 | * @return int |
||
1765 | * The casted integer value if the input is valid, -1 otherwise. |
||
1766 | */ |
||
1767 | public static function intval($value) |
||
1768 | { |
||
1769 | if (is_numeric($value) && preg_match('/^[0-9]+$/i', $value) === 1) { |
||
1770 | return intval($value); |
||
1771 | } |
||
1772 | |||
1773 | return -1; |
||
1774 | } |
||
1775 | } |
||
1776 |