PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/forum/Sources/Class-Package.php

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