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 ?>