Mercurial > hg > vamp-website
comparison forum/Sources/Class-Package.php @ 76:e3e11437ecea website
Add forum code
author | Chris Cannam |
---|---|
date | Sun, 07 Jul 2013 11:25:48 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
75:72f59aa7e503 | 76:e3e11437ecea |
---|---|
1 <?php | |
2 | |
3 /** | |
4 * Simple Machines Forum (SMF) | |
5 * | |
6 * @package SMF | |
7 * @author Simple Machines http://www.simplemachines.org | |
8 * @copyright 2011 Simple Machines | |
9 * @license http://www.simplemachines.org/about/smf/license.php BSD | |
10 * | |
11 * @version 2.0 | |
12 */ | |
13 | |
14 if (!defined('SMF')) | |
15 die('Hacking attempt...'); | |
16 | |
17 /* The following functions are all within the xmlArray class, which is the xml | |
18 parser. There are more functions, but these are the ones that should be | |
19 used from outside the class: | |
20 | |
21 class xmlArray(string data, bool auto_trim = false, | |
22 int error_level = error_reporting(), bool is_clone = false) | |
23 - creates a new xmlArray, which is an simple xml dom parser. | |
24 - data should be the xml data or an array of, unless is_clone is true. | |
25 - auto_trim can be used to automatically trim textual data. | |
26 - error_level specifies whether notices should be generated for | |
27 missing elements and attributes. | |
28 - if is_clone is true, the xmlArray is cloned from another - used | |
29 internally only. | |
30 | |
31 string xmlArray::name() | |
32 - retrieves the name of the current element, usually ''. | |
33 | |
34 string xmlArray::fetch(string path, bool get_elements = false) | |
35 - retrieves the textual value of the specified path. | |
36 - children are parsed for text, but only textual data is returned | |
37 unless get_elements is true. | |
38 | |
39 xmlArray xmlArray::path(string path, bool return_set = false) | |
40 - finds any elements that match the path specified. | |
41 - will always return a set if there is more than one of the element | |
42 or return_set is true. | |
43 - returns in the form of a new xmlArray. | |
44 | |
45 bool xmlArray::exists(string path) | |
46 - returns whether the specified path matches at least one element. | |
47 | |
48 int xmlArray::count(string path) | |
49 - returns the number of elements the path matches. | |
50 | |
51 array xmlArray::set(string path) | |
52 - returns an array of xmlArray's matching the specified path. | |
53 - this differs from ->path(path, true) in that instead of an xmlArray | |
54 of elements, an array of xmlArray's is returned for use with foreach. | |
55 | |
56 string xmlArray::create_xml(string path = '.') | |
57 - returns the specified path as an xml file. | |
58 */ | |
59 | |
60 // An xml array. Reads in xml, allows you to access it simply. Version 1.1. | |
61 class xmlArray | |
62 { | |
63 // The array and debugging output level. | |
64 public $array, $debug_level, $trim; | |
65 | |
66 // Create an xml array. | |
67 // the xml data, trim elements?, debugging output level, reserved. | |
68 //ie. $xml = new xmlArray(file('data.xml')); | |
69 public function __construct($data, $auto_trim = false, $level = null, $is_clone = false) | |
70 { | |
71 // If we're using this try to get some more memory. | |
72 @ini_set('memory_limit', '32M'); | |
73 | |
74 // Set the debug level. | |
75 $this->debug_level = $level !== null ? $level : error_reporting(); | |
76 $this->trim = $auto_trim; | |
77 | |
78 // Is the data already parsed? | |
79 if ($is_clone) | |
80 { | |
81 $this->array = $data; | |
82 return; | |
83 } | |
84 | |
85 // Is the input an array? (ie. passed from file()?) | |
86 if (is_array($data)) | |
87 $data = implode('', $data); | |
88 | |
89 // Remove any xml declaration or doctype, and parse out comments and CDATA. | |
90 $data = preg_replace('/<!--.*?-->/s', '', $this->_to_cdata(preg_replace(array('/^<\?xml.+?\?' . '>/is', '/<!DOCTYPE[^>]+?' . '>/s'), '', $data))); | |
91 | |
92 // Now parse the xml! | |
93 $this->array = $this->_parse($data); | |
94 } | |
95 | |
96 // Get the root element's name. | |
97 //ie. echo $element->name(); | |
98 public function name() | |
99 { | |
100 return isset($this->array['name']) ? $this->array['name'] : ''; | |
101 } | |
102 | |
103 // Get a specified element's value or attribute by path. | |
104 // the path to the element to fetch, whether to include elements? | |
105 //ie. $data = $xml->fetch('html/head/title'); | |
106 public function fetch($path, $get_elements = false) | |
107 { | |
108 // Get the element, in array form. | |
109 $array = $this->path($path); | |
110 | |
111 if ($array === false) | |
112 return false; | |
113 | |
114 // Getting elements into this is a bit complicated... | |
115 if ($get_elements && !is_string($array)) | |
116 { | |
117 $temp = ''; | |
118 | |
119 // Use the _xml() function to get the xml data. | |
120 foreach ($array->array as $val) | |
121 { | |
122 // Skip the name and any attributes. | |
123 if (is_array($val)) | |
124 $temp .= $this->_xml($val, null); | |
125 } | |
126 | |
127 // Just get the XML data and then take out the CDATAs. | |
128 return $this->_to_cdata($temp); | |
129 } | |
130 | |
131 // Return the value - taking care to pick out all the text values. | |
132 return is_string($array) ? $array : $this->_fetch($array->array); | |
133 } | |
134 | |
135 // Get an element, returns a new xmlArray. | |
136 // the path to the element to get, always return full result set? (ie. don't contract a single item.) | |
137 //ie. $element = $xml->path('html/body'); | |
138 public function path($path, $return_full = false) | |
139 { | |
140 // Split up the path. | |
141 $path = explode('/', $path); | |
142 | |
143 // Start with a base array. | |
144 $array = $this->array; | |
145 | |
146 // For each element in the path. | |
147 foreach ($path as $el) | |
148 { | |
149 // Deal with sets.... | |
150 if (strpos($el, '[') !== false) | |
151 { | |
152 $lvl = (int) substr($el, strpos($el, '[') + 1); | |
153 $el = substr($el, 0, strpos($el, '[')); | |
154 } | |
155 // Find an attribute. | |
156 elseif (substr($el, 0, 1) == '@') | |
157 { | |
158 // It simplifies things if the attribute is already there ;). | |
159 if (isset($array[$el])) | |
160 return $array[$el]; | |
161 else | |
162 { | |
163 if (function_exists('debug_backtrace')) | |
164 { | |
165 $trace = debug_backtrace(); | |
166 $i = 0; | |
167 while ($i < count($trace) && isset($trace[$i]['class']) && $trace[$i]['class'] == get_class($this)) | |
168 $i++; | |
169 $debug = ' from ' . $trace[$i - 1]['file'] . ' on line ' . $trace[$i - 1]['line']; | |
170 } | |
171 else | |
172 $debug = ''; | |
173 | |
174 // Cause an error. | |
175 if ($this->debug_level & E_NOTICE) | |
176 trigger_error('Undefined XML attribute: ' . substr($el, 1) . $debug, E_USER_NOTICE); | |
177 return false; | |
178 } | |
179 } | |
180 else | |
181 $lvl = null; | |
182 | |
183 // Find this element. | |
184 $array = $this->_path($array, $el, $lvl); | |
185 } | |
186 | |
187 // Clean up after $lvl, for $return_full. | |
188 if ($return_full && (!isset($array['name']) || substr($array['name'], -1) != ']')) | |
189 $array = array('name' => $el . '[]', $array); | |
190 | |
191 // Create the right type of class... | |
192 $newClass = get_class($this); | |
193 | |
194 // Return a new xmlArray for the result. | |
195 return $array === false ? false : new $newClass($array, $this->trim, $this->debug_level, true); | |
196 } | |
197 | |
198 // Check if an element exists. | |
199 // the path to the element to get. | |
200 //ie. echo $xml->exists('html/body') ? 'y' : 'n'; | |
201 public function exists($path) | |
202 { | |
203 // Split up the path. | |
204 $path = explode('/', $path); | |
205 | |
206 // Start with a base array. | |
207 $array = $this->array; | |
208 | |
209 // For each element in the path. | |
210 foreach ($path as $el) | |
211 { | |
212 // Deal with sets.... | |
213 if (strpos($el, '[') !== false) | |
214 { | |
215 $lvl = (int) substr($el, strpos($el, '[') + 1); | |
216 $el = substr($el, 0, strpos($el, '[')); | |
217 } | |
218 // Find an attribute. | |
219 elseif (substr($el, 0, 1) == '@') | |
220 return isset($array[$el]); | |
221 else | |
222 $lvl = null; | |
223 | |
224 // Find this element. | |
225 $array = $this->_path($array, $el, $lvl, true); | |
226 } | |
227 | |
228 return $array !== false; | |
229 } | |
230 | |
231 // Count the number of occurances of a path. | |
232 // the path to search for. | |
233 //ie. echo $xml->count('html/head/meta'); | |
234 public function count($path) | |
235 { | |
236 // Get the element, always returning a full set. | |
237 $temp = $this->path($path, true); | |
238 | |
239 // Start at zero, then count up all the numeric keys. | |
240 $i = 0; | |
241 foreach ($temp->array as $item) | |
242 { | |
243 if (is_array($item)) | |
244 $i++; | |
245 } | |
246 | |
247 return $i; | |
248 } | |
249 | |
250 // Get an array of xmlArray's for use with foreach. | |
251 // the path to search for. | |
252 //ie. foreach ($xml->set('html/body/p') as $p) | |
253 public function set($path) | |
254 { | |
255 // None as yet, just get the path. | |
256 $array = array(); | |
257 $xml = $this->path($path, true); | |
258 | |
259 foreach ($xml->array as $val) | |
260 { | |
261 // Skip these, they aren't elements. | |
262 if (!is_array($val) || $val['name'] == '!') | |
263 continue; | |
264 | |
265 // Create the right type of class... | |
266 $newClass = get_class($this); | |
267 | |
268 // Create a new xmlArray and stick it in the array. | |
269 $array[] = new $newClass($val, $this->trim, $this->debug_level, true); | |
270 } | |
271 | |
272 return $array; | |
273 } | |
274 | |
275 // Create an xml file from an xml array. | |
276 // the path to the element. (optional) | |
277 //ie. echo $this->create_xml() | |
278 public function create_xml($path = null) | |
279 { | |
280 // Was a path specified? If so, use that array. | |
281 if ($path !== null) | |
282 { | |
283 $path = $this->path($path); | |
284 | |
285 // The path was not found | |
286 if ($path === false) | |
287 return false; | |
288 | |
289 $path = $path->array; | |
290 } | |
291 // Just use the current array. | |
292 else | |
293 $path = $this->array; | |
294 | |
295 // Add the xml declaration to the front. | |
296 return '<?xml version="1.0"?' . '>' . $this->_xml($path, 0); | |
297 } | |
298 | |
299 // Output the xml in an array form. | |
300 // the path to output. | |
301 //ie. print_r($xml->to_array()); | |
302 public function to_array($path = null) | |
303 { | |
304 // Are we doing a specific path? | |
305 if ($path !== null) | |
306 { | |
307 $path = $this->path($path); | |
308 | |
309 // The path was not found | |
310 if ($path === false) | |
311 return false; | |
312 | |
313 $path = $path->array; | |
314 } | |
315 // No, so just use the current array. | |
316 else | |
317 $path = $this->array; | |
318 | |
319 return $this->_array($path); | |
320 } | |
321 | |
322 // Parse data into an array. (privately used...) | |
323 protected function _parse($data) | |
324 { | |
325 // Start with an 'empty' array with no data. | |
326 $current = array( | |
327 ); | |
328 | |
329 // Loop until we're out of data. | |
330 while ($data != '') | |
331 { | |
332 // Find and remove the next tag. | |
333 preg_match('/\A<([\w\-:]+)((?:\s+.+?)?)([\s]?\/)?' . '>/', $data, $match); | |
334 if (isset($match[0])) | |
335 $data = preg_replace('/' . preg_quote($match[0], '/') . '/s', '', $data, 1); | |
336 | |
337 // Didn't find a tag? Keep looping.... | |
338 if (!isset($match[1]) || $match[1] == '') | |
339 { | |
340 // If there's no <, the rest is data. | |
341 if (strpos($data, '<') === false) | |
342 { | |
343 $text_value = $this->_from_cdata($data); | |
344 $data = ''; | |
345 | |
346 if ($text_value != '') | |
347 $current[] = array( | |
348 'name' => '!', | |
349 'value' => $text_value | |
350 ); | |
351 } | |
352 // If the < isn't immediately next to the current position... more data. | |
353 elseif (strpos($data, '<') > 0) | |
354 { | |
355 $text_value = $this->_from_cdata(substr($data, 0, strpos($data, '<'))); | |
356 $data = substr($data, strpos($data, '<')); | |
357 | |
358 if ($text_value != '') | |
359 $current[] = array( | |
360 'name' => '!', | |
361 'value' => $text_value | |
362 ); | |
363 } | |
364 // If we're looking at a </something> with no start, kill it. | |
365 elseif (strpos($data, '<') !== false && strpos($data, '<') == 0) | |
366 { | |
367 if (strpos($data, '<', 1) !== false) | |
368 { | |
369 $text_value = $this->_from_cdata(substr($data, 0, strpos($data, '<', 1))); | |
370 $data = substr($data, strpos($data, '<', 1)); | |
371 | |
372 if ($text_value != '') | |
373 $current[] = array( | |
374 'name' => '!', | |
375 'value' => $text_value | |
376 ); | |
377 } | |
378 else | |
379 { | |
380 $text_value = $this->_from_cdata($data); | |
381 $data = ''; | |
382 | |
383 if ($text_value != '') | |
384 $current[] = array( | |
385 'name' => '!', | |
386 'value' => $text_value | |
387 ); | |
388 } | |
389 } | |
390 | |
391 // Wait for an actual occurance of an element. | |
392 continue; | |
393 } | |
394 | |
395 // Create a new element in the array. | |
396 $el = &$current[]; | |
397 $el['name'] = $match[1]; | |
398 | |
399 // If this ISN'T empty, remove the close tag and parse the inner data. | |
400 if ((!isset($match[3]) || trim($match[3]) != '/') && (!isset($match[2]) || trim($match[2]) != '/')) | |
401 { | |
402 // Because PHP 5.2.0+ seems to croak using regex, we'll have to do this the less fun way. | |
403 $last_tag_end = strpos($data, '</' . $match[1]. '>'); | |
404 if ($last_tag_end === false) | |
405 continue; | |
406 | |
407 $offset = 0; | |
408 while (1 == 1) | |
409 { | |
410 // Where is the next start tag? | |
411 $next_tag_start = strpos($data, '<' . $match[1], $offset); | |
412 // If the next start tag is after the last end tag then we've found the right close. | |
413 if ($next_tag_start === false || $next_tag_start > $last_tag_end) | |
414 break; | |
415 | |
416 // If not then find the next ending tag. | |
417 $next_tag_end = strpos($data, '</' . $match[1]. '>', $offset); | |
418 | |
419 // Didn't find one? Then just use the last and sod it. | |
420 if ($next_tag_end === false) | |
421 break; | |
422 else | |
423 { | |
424 $last_tag_end = $next_tag_end; | |
425 $offset = $next_tag_start + 1; | |
426 } | |
427 } | |
428 // Parse the insides. | |
429 $inner_match = substr($data, 0, $last_tag_end); | |
430 // Data now starts from where this section ends. | |
431 $data = substr($data, $last_tag_end + strlen('</' . $match[1]. '>')); | |
432 | |
433 if (!empty($inner_match)) | |
434 { | |
435 // Parse the inner data. | |
436 if (strpos($inner_match, '<') !== false) | |
437 $el += $this->_parse($inner_match); | |
438 elseif (trim($inner_match) != '') | |
439 { | |
440 $text_value = $this->_from_cdata($inner_match); | |
441 if ($text_value != '') | |
442 $el[] = array( | |
443 'name' => '!', | |
444 'value' => $text_value | |
445 ); | |
446 } | |
447 } | |
448 } | |
449 | |
450 // If we're dealing with attributes as well, parse them out. | |
451 if (isset($match[2]) && $match[2] != '') | |
452 { | |
453 // Find all the attribute pairs in the string. | |
454 preg_match_all('/([\w:]+)="(.+?)"/', $match[2], $attr, PREG_SET_ORDER); | |
455 | |
456 // Set them as @attribute-name. | |
457 foreach ($attr as $match_attr) | |
458 $el['@' . $match_attr[1]] = $match_attr[2]; | |
459 } | |
460 } | |
461 | |
462 // Return the parsed array. | |
463 return $current; | |
464 } | |
465 | |
466 // Get a specific element's xml. (privately used...) | |
467 protected function _xml($array, $indent) | |
468 { | |
469 $indentation = $indent !== null ? ' | |
470 ' . str_repeat(' ', $indent) : ''; | |
471 | |
472 // This is a set of elements, with no name... | |
473 if (is_array($array) && !isset($array['name'])) | |
474 { | |
475 $temp = ''; | |
476 foreach ($array as $val) | |
477 $temp .= $this->_xml($val, $indent); | |
478 return $temp; | |
479 } | |
480 | |
481 // This is just text! | |
482 if ($array['name'] == '!') | |
483 return $indentation . '<![CDATA[' . $array['value'] . ']]>'; | |
484 elseif (substr($array['name'], -2) == '[]') | |
485 $array['name'] = substr($array['name'], 0, -2); | |
486 | |
487 // Start the element. | |
488 $output = $indentation . '<' . $array['name']; | |
489 | |
490 $inside_elements = false; | |
491 $output_el = ''; | |
492 | |
493 // Run through and recurively output all the elements or attrbutes inside this. | |
494 foreach ($array as $k => $v) | |
495 { | |
496 if (substr($k, 0, 1) == '@') | |
497 $output .= ' ' . substr($k, 1) . '="' . $v . '"'; | |
498 elseif (is_array($v)) | |
499 { | |
500 $output_el .= $this->_xml($v, $indent === null ? null : $indent + 1); | |
501 $inside_elements = true; | |
502 } | |
503 } | |
504 | |
505 // Indent, if necessary.... then close the tag. | |
506 if ($inside_elements) | |
507 $output .= '>' . $output_el . $indentation . '</' . $array['name'] . '>'; | |
508 else | |
509 $output .= ' />'; | |
510 | |
511 return $output; | |
512 } | |
513 | |
514 // Return an element as an array... | |
515 protected function _array($array) | |
516 { | |
517 $return = array(); | |
518 $text = ''; | |
519 foreach ($array as $value) | |
520 { | |
521 if (!is_array($value) || !isset($value['name'])) | |
522 continue; | |
523 | |
524 if ($value['name'] == '!') | |
525 $text .= $value['value']; | |
526 else | |
527 $return[$value['name']] = $this->_array($value); | |
528 } | |
529 | |
530 if (empty($return)) | |
531 return $text; | |
532 else | |
533 return $return; | |
534 } | |
535 | |
536 // Parse out CDATA tags. (htmlspecialchars them...) | |
537 function _to_cdata($data) | |
538 { | |
539 $inCdata = $inComment = false; | |
540 $output = ''; | |
541 | |
542 $parts = preg_split('~(<!\[CDATA\[|\]\]>|<!--|-->)~', $data, -1, PREG_SPLIT_DELIM_CAPTURE); | |
543 foreach ($parts as $part) | |
544 { | |
545 // Handle XML comments. | |
546 if (!$inCdata && $part === '<!--') | |
547 $inComment = true; | |
548 if ($inComment && $part === '-->') | |
549 $inComment = false; | |
550 elseif ($inComment) | |
551 continue; | |
552 | |
553 // Handle Cdata blocks. | |
554 elseif (!$inComment && $part === '<![CDATA[') | |
555 $inCdata = true; | |
556 elseif ($inCdata && $part === ']]>') | |
557 $inCdata = false; | |
558 elseif ($inCdata) | |
559 $output .= htmlentities($part, ENT_QUOTES); | |
560 | |
561 // Everything else is kept as is. | |
562 else | |
563 $output .= $part; | |
564 } | |
565 | |
566 return $output; | |
567 } | |
568 | |
569 // Turn the CDATAs back to normal text. | |
570 protected function _from_cdata($data) | |
571 { | |
572 // Get the HTML translation table and reverse it. | |
573 $trans_tbl = array_flip(get_html_translation_table(HTML_ENTITIES, ENT_QUOTES)); | |
574 | |
575 // Translate all the entities out. | |
576 $data = strtr(preg_replace('~&#(\d{1,4});~e', "chr('\$1')", $data), $trans_tbl); | |
577 | |
578 return $this->trim ? trim($data) : $data; | |
579 } | |
580 | |
581 // Given an array, return the text from that array. (recursive and privately used.) | |
582 protected function _fetch($array) | |
583 { | |
584 // Don't return anything if this is just a string. | |
585 if (is_string($array)) | |
586 return ''; | |
587 | |
588 $temp = ''; | |
589 foreach ($array as $text) | |
590 { | |
591 // This means it's most likely an attribute or the name itself. | |
592 if (!isset($text['name'])) | |
593 continue; | |
594 | |
595 // This is text! | |
596 if ($text['name'] == '!') | |
597 $temp .= $text['value']; | |
598 // Another element - dive in ;). | |
599 else | |
600 $temp .= $this->_fetch($text); | |
601 } | |
602 | |
603 // Return all the bits and pieces we've put together. | |
604 return $temp; | |
605 } | |
606 | |
607 // Get a specific array by path, one level down. (privately used...) | |
608 protected function _path($array, $path, $level, $no_error = false) | |
609 { | |
610 // Is $array even an array? It might be false! | |
611 if (!is_array($array)) | |
612 return false; | |
613 | |
614 // Asking for *no* path? | |
615 if ($path == '' || $path == '.') | |
616 return $array; | |
617 $paths = explode('|', $path); | |
618 | |
619 // A * means all elements of any name. | |
620 $show_all = in_array('*', $paths); | |
621 | |
622 $results = array(); | |
623 | |
624 // Check each element. | |
625 foreach ($array as $value) | |
626 { | |
627 if (!is_array($value) || $value['name'] === '!') | |
628 continue; | |
629 | |
630 if ($show_all || in_array($value['name'], $paths)) | |
631 { | |
632 // Skip elements before "the one". | |
633 if ($level !== null && $level > 0) | |
634 $level--; | |
635 else | |
636 $results[] = $value; | |
637 } | |
638 } | |
639 | |
640 // No results found... | |
641 if (empty($results)) | |
642 { | |
643 if (function_exists('debug_backtrace')) | |
644 { | |
645 $trace = debug_backtrace(); | |
646 $i = 0; | |
647 while ($i < count($trace) && isset($trace[$i]['class']) && $trace[$i]['class'] == get_class($this)) | |
648 $i++; | |
649 $debug = ' from ' . $trace[$i - 1]['file'] . ' on line ' . $trace[$i - 1]['line']; | |
650 } | |
651 else | |
652 $debug = ''; | |
653 | |
654 // Cause an error. | |
655 if ($this->debug_level & E_NOTICE && !$no_error) | |
656 trigger_error('Undefined XML element: ' . $path . $debug, E_USER_NOTICE); | |
657 return false; | |
658 } | |
659 // Only one result. | |
660 elseif (count($results) == 1 || $level !== null) | |
661 return $results[0]; | |
662 // Return the result set. | |
663 else | |
664 return $results + array('name' => $path . '[]'); | |
665 } | |
666 } | |
667 | |
668 // http://www.faqs.org/rfcs/rfc959.html | |
669 if (!class_exists('ftp_connection')) | |
670 { | |
671 class ftp_connection | |
672 { | |
673 public $connection, $error, $last_message, $pasv; | |
674 | |
675 // Create a new FTP connection... | |
676 public function __construct($ftp_server, $ftp_port = 21, $ftp_user = 'anonymous', $ftp_pass = 'ftpclient@simplemachines.org') | |
677 { | |
678 // Initialize variables. | |
679 $this->connection = 'no_connection'; | |
680 $this->error = false; | |
681 $this->pasv = array(); | |
682 | |
683 if ($ftp_server !== null) | |
684 $this->connect($ftp_server, $ftp_port, $ftp_user, $ftp_pass); | |
685 } | |
686 | |
687 public function connect($ftp_server, $ftp_port = 21, $ftp_user = 'anonymous', $ftp_pass = 'ftpclient@simplemachines.org') | |
688 { | |
689 if (substr($ftp_server, 0, 6) == 'ftp://') | |
690 $ftp_server = substr($ftp_server, 6); | |
691 elseif (substr($ftp_server, 0, 7) == 'ftps://') | |
692 $ftp_server = 'ssl://' . substr($ftp_server, 7); | |
693 if (substr($ftp_server, 0, 7) == 'http://') | |
694 $ftp_server = substr($ftp_server, 7); | |
695 $ftp_server = strtr($ftp_server, array('/' => '', ':' => '', '@' => '')); | |
696 | |
697 // Connect to the FTP server. | |
698 $this->connection = @fsockopen($ftp_server, $ftp_port, $err, $err, 5); | |
699 if (!$this->connection) | |
700 { | |
701 $this->error = 'bad_server'; | |
702 return; | |
703 } | |
704 | |
705 // Get the welcome message... | |
706 if (!$this->check_response(220)) | |
707 { | |
708 $this->error = 'bad_response'; | |
709 return; | |
710 } | |
711 | |
712 // Send the username, it should ask for a password. | |
713 fwrite($this->connection, 'USER ' . $ftp_user . "\r\n"); | |
714 if (!$this->check_response(331)) | |
715 { | |
716 $this->error = 'bad_username'; | |
717 return; | |
718 } | |
719 | |
720 // Now send the password... and hope it goes okay. | |
721 fwrite($this->connection, 'PASS ' . $ftp_pass . "\r\n"); | |
722 if (!$this->check_response(230)) | |
723 { | |
724 $this->error = 'bad_password'; | |
725 return; | |
726 } | |
727 } | |
728 | |
729 public function chdir($ftp_path) | |
730 { | |
731 if (!is_resource($this->connection)) | |
732 return false; | |
733 | |
734 // No slash on the end, please... | |
735 if ($ftp_path !== '/' && substr($ftp_path, -1) === '/') | |
736 $ftp_path = substr($ftp_path, 0, -1); | |
737 | |
738 fwrite($this->connection, 'CWD ' . $ftp_path . "\r\n"); | |
739 if (!$this->check_response(250)) | |
740 { | |
741 $this->error = 'bad_path'; | |
742 return false; | |
743 } | |
744 | |
745 return true; | |
746 } | |
747 | |
748 public function chmod($ftp_file, $chmod) | |
749 { | |
750 if (!is_resource($this->connection)) | |
751 return false; | |
752 | |
753 if ($ftp_file == '') | |
754 $ftp_file = '.'; | |
755 | |
756 // Convert the chmod value from octal (0777) to text ("777"). | |
757 fwrite($this->connection, 'SITE CHMOD ' . decoct($chmod) . ' ' . $ftp_file . "\r\n"); | |
758 if (!$this->check_response(200)) | |
759 { | |
760 $this->error = 'bad_file'; | |
761 return false; | |
762 } | |
763 | |
764 return true; | |
765 } | |
766 | |
767 public function unlink($ftp_file) | |
768 { | |
769 // We are actually connected, right? | |
770 if (!is_resource($this->connection)) | |
771 return false; | |
772 | |
773 // Delete file X. | |
774 fwrite($this->connection, 'DELE ' . $ftp_file . "\r\n"); | |
775 if (!$this->check_response(250)) | |
776 { | |
777 fwrite($this->connection, 'RMD ' . $ftp_file . "\r\n"); | |
778 | |
779 // Still no love? | |
780 if (!$this->check_response(250)) | |
781 { | |
782 $this->error = 'bad_file'; | |
783 return false; | |
784 } | |
785 } | |
786 | |
787 return true; | |
788 } | |
789 | |
790 public function check_response($desired) | |
791 { | |
792 // Wait for a response that isn't continued with -, but don't wait too long. | |
793 $time = time(); | |
794 do | |
795 $this->last_message = fgets($this->connection, 1024); | |
796 while ((strlen($this->last_message) < 4 || substr($this->last_message, 0, 1) == ' ' || substr($this->last_message, 3, 1) != ' ') && time() - $time < 5); | |
797 | |
798 // Was the desired response returned? | |
799 return is_array($desired) ? in_array(substr($this->last_message, 0, 3), $desired) : substr($this->last_message, 0, 3) == $desired; | |
800 } | |
801 | |
802 public function passive() | |
803 { | |
804 // We can't create a passive data connection without a primary one first being there. | |
805 if (!is_resource($this->connection)) | |
806 return false; | |
807 | |
808 // Request a passive connection - this means, we'll talk to you, you don't talk to us. | |
809 @fwrite($this->connection, 'PASV' . "\r\n"); | |
810 $time = time(); | |
811 do | |
812 $response = fgets($this->connection, 1024); | |
813 while (substr($response, 3, 1) != ' ' && time() - $time < 5); | |
814 | |
815 // If it's not 227, we weren't given an IP and port, which means it failed. | |
816 if (substr($response, 0, 4) != '227 ') | |
817 { | |
818 $this->error = 'bad_response'; | |
819 return false; | |
820 } | |
821 | |
822 // Snatch the IP and port information, or die horribly trying... | |
823 if (preg_match('~\((\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))\)~', $response, $match) == 0) | |
824 { | |
825 $this->error = 'bad_response'; | |
826 return false; | |
827 } | |
828 | |
829 // This is pretty simple - store it for later use ;). | |
830 $this->pasv = array('ip' => $match[1] . '.' . $match[2] . '.' . $match[3] . '.' . $match[4], 'port' => $match[5] * 256 + $match[6]); | |
831 | |
832 return true; | |
833 } | |
834 | |
835 public function create_file($ftp_file) | |
836 { | |
837 // First, we have to be connected... very important. | |
838 if (!is_resource($this->connection)) | |
839 return false; | |
840 | |
841 // I'd like one passive mode, please! | |
842 if (!$this->passive()) | |
843 return false; | |
844 | |
845 // Seems logical enough, so far... | |
846 fwrite($this->connection, 'STOR ' . $ftp_file . "\r\n"); | |
847 | |
848 // Okay, now we connect to the data port. If it doesn't work out, it's probably "file already exists", etc. | |
849 $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err, 5); | |
850 if (!$fp || !$this->check_response(150)) | |
851 { | |
852 $this->error = 'bad_file'; | |
853 @fclose($fp); | |
854 return false; | |
855 } | |
856 | |
857 // This may look strange, but we're just closing it to indicate a zero-byte upload. | |
858 fclose($fp); | |
859 if (!$this->check_response(226)) | |
860 { | |
861 $this->error = 'bad_response'; | |
862 return false; | |
863 } | |
864 | |
865 return true; | |
866 } | |
867 | |
868 public function list_dir($ftp_path = '', $search = false) | |
869 { | |
870 // Are we even connected...? | |
871 if (!is_resource($this->connection)) | |
872 return false; | |
873 | |
874 // Passive... non-agressive... | |
875 if (!$this->passive()) | |
876 return false; | |
877 | |
878 // Get the listing! | |
879 fwrite($this->connection, 'LIST -1' . ($search ? 'R' : '') . ($ftp_path == '' ? '' : ' ' . $ftp_path) . "\r\n"); | |
880 | |
881 // Connect, assuming we've got a connection. | |
882 $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err, 5); | |
883 if (!$fp || !$this->check_response(array(150, 125))) | |
884 { | |
885 $this->error = 'bad_response'; | |
886 @fclose($fp); | |
887 return false; | |
888 } | |
889 | |
890 // Read in the file listing. | |
891 $data = ''; | |
892 while (!feof($fp)) | |
893 $data .= fread($fp, 4096); | |
894 fclose($fp); | |
895 | |
896 // Everything go okay? | |
897 if (!$this->check_response(226)) | |
898 { | |
899 $this->error = 'bad_response'; | |
900 return false; | |
901 } | |
902 | |
903 return $data; | |
904 } | |
905 | |
906 public function locate($file, $listing = null) | |
907 { | |
908 if ($listing === null) | |
909 $listing = $this->list_dir('', true); | |
910 $listing = explode("\n", $listing); | |
911 | |
912 @fwrite($this->connection, 'PWD' . "\r\n"); | |
913 $time = time(); | |
914 do | |
915 $response = fgets($this->connection, 1024); | |
916 while ($response[3] != ' ' && time() - $time < 5); | |
917 | |
918 // Check for 257! | |
919 if (preg_match('~^257 "(.+?)" ~', $response, $match) != 0) | |
920 $current_dir = strtr($match[1], array('""' => '"')); | |
921 else | |
922 $current_dir = ''; | |
923 | |
924 for ($i = 0, $n = count($listing); $i < $n; $i++) | |
925 { | |
926 if (trim($listing[$i]) == '' && isset($listing[$i + 1])) | |
927 { | |
928 $current_dir = substr(trim($listing[++$i]), 0, -1); | |
929 $i++; | |
930 } | |
931 | |
932 // Okay, this file's name is: | |
933 $listing[$i] = $current_dir . '/' . trim(strlen($listing[$i]) > 30 ? strrchr($listing[$i], ' ') : $listing[$i]); | |
934 | |
935 if ($file[0] == '*' && substr($listing[$i], -(strlen($file) - 1)) == substr($file, 1)) | |
936 return $listing[$i]; | |
937 if (substr($file, -1) == '*' && substr($listing[$i], 0, strlen($file) - 1) == substr($file, 0, -1)) | |
938 return $listing[$i]; | |
939 if (basename($listing[$i]) == $file || $listing[$i] == $file) | |
940 return $listing[$i]; | |
941 } | |
942 | |
943 return false; | |
944 } | |
945 | |
946 public function create_dir($ftp_dir) | |
947 { | |
948 // We must be connected to the server to do something. | |
949 if (!is_resource($this->connection)) | |
950 return false; | |
951 | |
952 // Make this new beautiful directory! | |
953 fwrite($this->connection, 'MKD ' . $ftp_dir . "\r\n"); | |
954 if (!$this->check_response(257)) | |
955 { | |
956 $this->error = 'bad_file'; | |
957 return false; | |
958 } | |
959 | |
960 return true; | |
961 } | |
962 | |
963 public function detect_path($filesystem_path, $lookup_file = null) | |
964 { | |
965 $username = ''; | |
966 | |
967 if (isset($_SERVER['DOCUMENT_ROOT'])) | |
968 { | |
969 if (preg_match('~^/home[2]?/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match)) | |
970 { | |
971 $username = $match[1]; | |
972 | |
973 $path = strtr($_SERVER['DOCUMENT_ROOT'], array('/home/' . $match[1] . '/' => '', '/home2/' . $match[1] . '/' => '')); | |
974 | |
975 if (substr($path, -1) == '/') | |
976 $path = substr($path, 0, -1); | |
977 | |
978 if (strlen(dirname($_SERVER['PHP_SELF'])) > 1) | |
979 $path .= dirname($_SERVER['PHP_SELF']); | |
980 } | |
981 elseif (substr($filesystem_path, 0, 9) == '/var/www/') | |
982 $path = substr($filesystem_path, 8); | |
983 else | |
984 $path = strtr(strtr($filesystem_path, array('\\' => '/')), array($_SERVER['DOCUMENT_ROOT'] => '')); | |
985 } | |
986 else | |
987 $path = ''; | |
988 | |
989 if (is_resource($this->connection) && $this->list_dir($path) == '') | |
990 { | |
991 $data = $this->list_dir('', true); | |
992 | |
993 if ($lookup_file === null) | |
994 $lookup_file = $_SERVER['PHP_SELF']; | |
995 | |
996 $found_path = dirname($this->locate('*' . basename(dirname($lookup_file)) . '/' . basename($lookup_file), $data)); | |
997 if ($found_path == false) | |
998 $found_path = dirname($this->locate(basename($lookup_file))); | |
999 if ($found_path != false) | |
1000 $path = $found_path; | |
1001 } | |
1002 elseif (is_resource($this->connection)) | |
1003 $found_path = true; | |
1004 | |
1005 return array($username, $path, isset($found_path)); | |
1006 } | |
1007 | |
1008 public function close() | |
1009 { | |
1010 // Goodbye! | |
1011 fwrite($this->connection, 'QUIT' . "\r\n"); | |
1012 fclose($this->connection); | |
1013 | |
1014 return true; | |
1015 } | |
1016 } | |
1017 } | |
1018 | |
1019 ?> |