Chris@76: path(path, true) in that instead of an xmlArray Chris@76: of elements, an array of xmlArray's is returned for use with foreach. Chris@76: Chris@76: string xmlArray::create_xml(string path = '.') Chris@76: - returns the specified path as an xml file. Chris@76: */ Chris@76: Chris@76: // An xml array. Reads in xml, allows you to access it simply. Version 1.1. Chris@76: class xmlArray Chris@76: { Chris@76: // The array and debugging output level. Chris@76: public $array, $debug_level, $trim; Chris@76: Chris@76: // Create an xml array. Chris@76: // the xml data, trim elements?, debugging output level, reserved. Chris@76: //ie. $xml = new xmlArray(file('data.xml')); Chris@76: public function __construct($data, $auto_trim = false, $level = null, $is_clone = false) Chris@76: { Chris@76: // If we're using this try to get some more memory. Chris@76: @ini_set('memory_limit', '32M'); Chris@76: Chris@76: // Set the debug level. Chris@76: $this->debug_level = $level !== null ? $level : error_reporting(); Chris@76: $this->trim = $auto_trim; Chris@76: Chris@76: // Is the data already parsed? Chris@76: if ($is_clone) Chris@76: { Chris@76: $this->array = $data; Chris@76: return; Chris@76: } Chris@76: Chris@76: // Is the input an array? (ie. passed from file()?) Chris@76: if (is_array($data)) Chris@76: $data = implode('', $data); Chris@76: Chris@76: // Remove any xml declaration or doctype, and parse out comments and CDATA. Chris@76: $data = preg_replace('//s', '', $this->_to_cdata(preg_replace(array('/^<\?xml.+?\?' . '>/is', '/]+?' . '>/s'), '', $data))); Chris@76: Chris@76: // Now parse the xml! Chris@76: $this->array = $this->_parse($data); Chris@76: } Chris@76: Chris@76: // Get the root element's name. Chris@76: //ie. echo $element->name(); Chris@76: public function name() Chris@76: { Chris@76: return isset($this->array['name']) ? $this->array['name'] : ''; Chris@76: } Chris@76: Chris@76: // Get a specified element's value or attribute by path. Chris@76: // the path to the element to fetch, whether to include elements? Chris@76: //ie. $data = $xml->fetch('html/head/title'); Chris@76: public function fetch($path, $get_elements = false) Chris@76: { Chris@76: // Get the element, in array form. Chris@76: $array = $this->path($path); Chris@76: Chris@76: if ($array === false) Chris@76: return false; Chris@76: Chris@76: // Getting elements into this is a bit complicated... Chris@76: if ($get_elements && !is_string($array)) Chris@76: { Chris@76: $temp = ''; Chris@76: Chris@76: // Use the _xml() function to get the xml data. Chris@76: foreach ($array->array as $val) Chris@76: { Chris@76: // Skip the name and any attributes. Chris@76: if (is_array($val)) Chris@76: $temp .= $this->_xml($val, null); Chris@76: } Chris@76: Chris@76: // Just get the XML data and then take out the CDATAs. Chris@76: return $this->_to_cdata($temp); Chris@76: } Chris@76: Chris@76: // Return the value - taking care to pick out all the text values. Chris@76: return is_string($array) ? $array : $this->_fetch($array->array); Chris@76: } Chris@76: Chris@76: // Get an element, returns a new xmlArray. Chris@76: // the path to the element to get, always return full result set? (ie. don't contract a single item.) Chris@76: //ie. $element = $xml->path('html/body'); Chris@76: public function path($path, $return_full = false) Chris@76: { Chris@76: // Split up the path. Chris@76: $path = explode('/', $path); Chris@76: Chris@76: // Start with a base array. Chris@76: $array = $this->array; Chris@76: Chris@76: // For each element in the path. Chris@76: foreach ($path as $el) Chris@76: { Chris@76: // Deal with sets.... Chris@76: if (strpos($el, '[') !== false) Chris@76: { Chris@76: $lvl = (int) substr($el, strpos($el, '[') + 1); Chris@76: $el = substr($el, 0, strpos($el, '[')); Chris@76: } Chris@76: // Find an attribute. Chris@76: elseif (substr($el, 0, 1) == '@') Chris@76: { Chris@76: // It simplifies things if the attribute is already there ;). Chris@76: if (isset($array[$el])) Chris@76: return $array[$el]; Chris@76: else Chris@76: { Chris@76: if (function_exists('debug_backtrace')) Chris@76: { Chris@76: $trace = debug_backtrace(); Chris@76: $i = 0; Chris@76: while ($i < count($trace) && isset($trace[$i]['class']) && $trace[$i]['class'] == get_class($this)) Chris@76: $i++; Chris@76: $debug = ' from ' . $trace[$i - 1]['file'] . ' on line ' . $trace[$i - 1]['line']; Chris@76: } Chris@76: else Chris@76: $debug = ''; Chris@76: Chris@76: // Cause an error. Chris@76: if ($this->debug_level & E_NOTICE) Chris@76: trigger_error('Undefined XML attribute: ' . substr($el, 1) . $debug, E_USER_NOTICE); Chris@76: return false; Chris@76: } Chris@76: } Chris@76: else Chris@76: $lvl = null; Chris@76: Chris@76: // Find this element. Chris@76: $array = $this->_path($array, $el, $lvl); Chris@76: } Chris@76: Chris@76: // Clean up after $lvl, for $return_full. Chris@76: if ($return_full && (!isset($array['name']) || substr($array['name'], -1) != ']')) Chris@76: $array = array('name' => $el . '[]', $array); Chris@76: Chris@76: // Create the right type of class... Chris@76: $newClass = get_class($this); Chris@76: Chris@76: // Return a new xmlArray for the result. Chris@76: return $array === false ? false : new $newClass($array, $this->trim, $this->debug_level, true); Chris@76: } Chris@76: Chris@76: // Check if an element exists. Chris@76: // the path to the element to get. Chris@76: //ie. echo $xml->exists('html/body') ? 'y' : 'n'; Chris@76: public function exists($path) Chris@76: { Chris@76: // Split up the path. Chris@76: $path = explode('/', $path); Chris@76: Chris@76: // Start with a base array. Chris@76: $array = $this->array; Chris@76: Chris@76: // For each element in the path. Chris@76: foreach ($path as $el) Chris@76: { Chris@76: // Deal with sets.... Chris@76: if (strpos($el, '[') !== false) Chris@76: { Chris@76: $lvl = (int) substr($el, strpos($el, '[') + 1); Chris@76: $el = substr($el, 0, strpos($el, '[')); Chris@76: } Chris@76: // Find an attribute. Chris@76: elseif (substr($el, 0, 1) == '@') Chris@76: return isset($array[$el]); Chris@76: else Chris@76: $lvl = null; Chris@76: Chris@76: // Find this element. Chris@76: $array = $this->_path($array, $el, $lvl, true); Chris@76: } Chris@76: Chris@76: return $array !== false; Chris@76: } Chris@76: Chris@76: // Count the number of occurances of a path. Chris@76: // the path to search for. Chris@76: //ie. echo $xml->count('html/head/meta'); Chris@76: public function count($path) Chris@76: { Chris@76: // Get the element, always returning a full set. Chris@76: $temp = $this->path($path, true); Chris@76: Chris@76: // Start at zero, then count up all the numeric keys. Chris@76: $i = 0; Chris@76: foreach ($temp->array as $item) Chris@76: { Chris@76: if (is_array($item)) Chris@76: $i++; Chris@76: } Chris@76: Chris@76: return $i; Chris@76: } Chris@76: Chris@76: // Get an array of xmlArray's for use with foreach. Chris@76: // the path to search for. Chris@76: //ie. foreach ($xml->set('html/body/p') as $p) Chris@76: public function set($path) Chris@76: { Chris@76: // None as yet, just get the path. Chris@76: $array = array(); Chris@76: $xml = $this->path($path, true); Chris@76: Chris@76: foreach ($xml->array as $val) Chris@76: { Chris@76: // Skip these, they aren't elements. Chris@76: if (!is_array($val) || $val['name'] == '!') Chris@76: continue; Chris@76: Chris@76: // Create the right type of class... Chris@76: $newClass = get_class($this); Chris@76: Chris@76: // Create a new xmlArray and stick it in the array. Chris@76: $array[] = new $newClass($val, $this->trim, $this->debug_level, true); Chris@76: } Chris@76: Chris@76: return $array; Chris@76: } Chris@76: Chris@76: // Create an xml file from an xml array. Chris@76: // the path to the element. (optional) Chris@76: //ie. echo $this->create_xml() Chris@76: public function create_xml($path = null) Chris@76: { Chris@76: // Was a path specified? If so, use that array. Chris@76: if ($path !== null) Chris@76: { Chris@76: $path = $this->path($path); Chris@76: Chris@76: // The path was not found Chris@76: if ($path === false) Chris@76: return false; Chris@76: Chris@76: $path = $path->array; Chris@76: } Chris@76: // Just use the current array. Chris@76: else Chris@76: $path = $this->array; Chris@76: Chris@76: // Add the xml declaration to the front. Chris@76: return '' . $this->_xml($path, 0); Chris@76: } Chris@76: Chris@76: // Output the xml in an array form. Chris@76: // the path to output. Chris@76: //ie. print_r($xml->to_array()); Chris@76: public function to_array($path = null) Chris@76: { Chris@76: // Are we doing a specific path? Chris@76: if ($path !== null) Chris@76: { Chris@76: $path = $this->path($path); Chris@76: Chris@76: // The path was not found Chris@76: if ($path === false) Chris@76: return false; Chris@76: Chris@76: $path = $path->array; Chris@76: } Chris@76: // No, so just use the current array. Chris@76: else Chris@76: $path = $this->array; Chris@76: Chris@76: return $this->_array($path); Chris@76: } Chris@76: Chris@76: // Parse data into an array. (privately used...) Chris@76: protected function _parse($data) Chris@76: { Chris@76: // Start with an 'empty' array with no data. Chris@76: $current = array( Chris@76: ); Chris@76: Chris@76: // Loop until we're out of data. Chris@76: while ($data != '') Chris@76: { Chris@76: // Find and remove the next tag. Chris@76: preg_match('/\A<([\w\-:]+)((?:\s+.+?)?)([\s]?\/)?' . '>/', $data, $match); Chris@76: if (isset($match[0])) Chris@76: $data = preg_replace('/' . preg_quote($match[0], '/') . '/s', '', $data, 1); Chris@76: Chris@76: // Didn't find a tag? Keep looping.... Chris@76: if (!isset($match[1]) || $match[1] == '') Chris@76: { Chris@76: // If there's no <, the rest is data. Chris@76: if (strpos($data, '<') === false) Chris@76: { Chris@76: $text_value = $this->_from_cdata($data); Chris@76: $data = ''; Chris@76: Chris@76: if ($text_value != '') Chris@76: $current[] = array( Chris@76: 'name' => '!', Chris@76: 'value' => $text_value Chris@76: ); Chris@76: } Chris@76: // If the < isn't immediately next to the current position... more data. Chris@76: elseif (strpos($data, '<') > 0) Chris@76: { Chris@76: $text_value = $this->_from_cdata(substr($data, 0, strpos($data, '<'))); Chris@76: $data = substr($data, strpos($data, '<')); Chris@76: Chris@76: if ($text_value != '') Chris@76: $current[] = array( Chris@76: 'name' => '!', Chris@76: 'value' => $text_value Chris@76: ); Chris@76: } Chris@76: // If we're looking at a with no start, kill it. Chris@76: elseif (strpos($data, '<') !== false && strpos($data, '<') == 0) Chris@76: { Chris@76: if (strpos($data, '<', 1) !== false) Chris@76: { Chris@76: $text_value = $this->_from_cdata(substr($data, 0, strpos($data, '<', 1))); Chris@76: $data = substr($data, strpos($data, '<', 1)); Chris@76: Chris@76: if ($text_value != '') Chris@76: $current[] = array( Chris@76: 'name' => '!', Chris@76: 'value' => $text_value Chris@76: ); Chris@76: } Chris@76: else Chris@76: { Chris@76: $text_value = $this->_from_cdata($data); Chris@76: $data = ''; Chris@76: Chris@76: if ($text_value != '') Chris@76: $current[] = array( Chris@76: 'name' => '!', Chris@76: 'value' => $text_value Chris@76: ); Chris@76: } Chris@76: } Chris@76: Chris@76: // Wait for an actual occurance of an element. Chris@76: continue; Chris@76: } Chris@76: Chris@76: // Create a new element in the array. Chris@76: $el = &$current[]; Chris@76: $el['name'] = $match[1]; Chris@76: Chris@76: // If this ISN'T empty, remove the close tag and parse the inner data. Chris@76: if ((!isset($match[3]) || trim($match[3]) != '/') && (!isset($match[2]) || trim($match[2]) != '/')) Chris@76: { Chris@76: // Because PHP 5.2.0+ seems to croak using regex, we'll have to do this the less fun way. Chris@76: $last_tag_end = strpos($data, ''); Chris@76: if ($last_tag_end === false) Chris@76: continue; Chris@76: Chris@76: $offset = 0; Chris@76: while (1 == 1) Chris@76: { Chris@76: // Where is the next start tag? Chris@76: $next_tag_start = strpos($data, '<' . $match[1], $offset); Chris@76: // If the next start tag is after the last end tag then we've found the right close. Chris@76: if ($next_tag_start === false || $next_tag_start > $last_tag_end) Chris@76: break; Chris@76: Chris@76: // If not then find the next ending tag. Chris@76: $next_tag_end = strpos($data, '', $offset); Chris@76: Chris@76: // Didn't find one? Then just use the last and sod it. Chris@76: if ($next_tag_end === false) Chris@76: break; Chris@76: else Chris@76: { Chris@76: $last_tag_end = $next_tag_end; Chris@76: $offset = $next_tag_start + 1; Chris@76: } Chris@76: } Chris@76: // Parse the insides. Chris@76: $inner_match = substr($data, 0, $last_tag_end); Chris@76: // Data now starts from where this section ends. Chris@76: $data = substr($data, $last_tag_end + strlen('')); Chris@76: Chris@76: if (!empty($inner_match)) Chris@76: { Chris@76: // Parse the inner data. Chris@76: if (strpos($inner_match, '<') !== false) Chris@76: $el += $this->_parse($inner_match); Chris@76: elseif (trim($inner_match) != '') Chris@76: { Chris@76: $text_value = $this->_from_cdata($inner_match); Chris@76: if ($text_value != '') Chris@76: $el[] = array( Chris@76: 'name' => '!', Chris@76: 'value' => $text_value Chris@76: ); Chris@76: } Chris@76: } Chris@76: } Chris@76: Chris@76: // If we're dealing with attributes as well, parse them out. Chris@76: if (isset($match[2]) && $match[2] != '') Chris@76: { Chris@76: // Find all the attribute pairs in the string. Chris@76: preg_match_all('/([\w:]+)="(.+?)"/', $match[2], $attr, PREG_SET_ORDER); Chris@76: Chris@76: // Set them as @attribute-name. Chris@76: foreach ($attr as $match_attr) Chris@76: $el['@' . $match_attr[1]] = $match_attr[2]; Chris@76: } Chris@76: } Chris@76: Chris@76: // Return the parsed array. Chris@76: return $current; Chris@76: } Chris@76: Chris@76: // Get a specific element's xml. (privately used...) Chris@76: protected function _xml($array, $indent) Chris@76: { Chris@76: $indentation = $indent !== null ? ' Chris@76: ' . str_repeat(' ', $indent) : ''; Chris@76: Chris@76: // This is a set of elements, with no name... Chris@76: if (is_array($array) && !isset($array['name'])) Chris@76: { Chris@76: $temp = ''; Chris@76: foreach ($array as $val) Chris@76: $temp .= $this->_xml($val, $indent); Chris@76: return $temp; Chris@76: } Chris@76: Chris@76: // This is just text! Chris@76: if ($array['name'] == '!') Chris@76: return $indentation . ''; Chris@76: elseif (substr($array['name'], -2) == '[]') Chris@76: $array['name'] = substr($array['name'], 0, -2); Chris@76: Chris@76: // Start the element. Chris@76: $output = $indentation . '<' . $array['name']; Chris@76: Chris@76: $inside_elements = false; Chris@76: $output_el = ''; Chris@76: Chris@76: // Run through and recurively output all the elements or attrbutes inside this. Chris@76: foreach ($array as $k => $v) Chris@76: { Chris@76: if (substr($k, 0, 1) == '@') Chris@76: $output .= ' ' . substr($k, 1) . '="' . $v . '"'; Chris@76: elseif (is_array($v)) Chris@76: { Chris@76: $output_el .= $this->_xml($v, $indent === null ? null : $indent + 1); Chris@76: $inside_elements = true; Chris@76: } Chris@76: } Chris@76: Chris@76: // Indent, if necessary.... then close the tag. Chris@76: if ($inside_elements) Chris@76: $output .= '>' . $output_el . $indentation . ''; Chris@76: else Chris@76: $output .= ' />'; Chris@76: Chris@76: return $output; Chris@76: } Chris@76: Chris@76: // Return an element as an array... Chris@76: protected function _array($array) Chris@76: { Chris@76: $return = array(); Chris@76: $text = ''; Chris@76: foreach ($array as $value) Chris@76: { Chris@76: if (!is_array($value) || !isset($value['name'])) Chris@76: continue; Chris@76: Chris@76: if ($value['name'] == '!') Chris@76: $text .= $value['value']; Chris@76: else Chris@76: $return[$value['name']] = $this->_array($value); Chris@76: } Chris@76: Chris@76: if (empty($return)) Chris@76: return $text; Chris@76: else Chris@76: return $return; Chris@76: } Chris@76: Chris@76: // Parse out CDATA tags. (htmlspecialchars them...) Chris@76: function _to_cdata($data) Chris@76: { Chris@76: $inCdata = $inComment = false; Chris@76: $output = ''; Chris@76: Chris@76: $parts = preg_split('~(|)~', $data, -1, PREG_SPLIT_DELIM_CAPTURE); Chris@76: foreach ($parts as $part) Chris@76: { Chris@76: // Handle XML comments. Chris@76: if (!$inCdata && $part === '') Chris@76: $inComment = false; Chris@76: elseif ($inComment) Chris@76: continue; Chris@76: Chris@76: // Handle Cdata blocks. Chris@76: elseif (!$inComment && $part === '') Chris@76: $inCdata = false; Chris@76: elseif ($inCdata) Chris@76: $output .= htmlentities($part, ENT_QUOTES); Chris@76: Chris@76: // Everything else is kept as is. Chris@76: else Chris@76: $output .= $part; Chris@76: } Chris@76: Chris@76: return $output; Chris@76: } Chris@76: Chris@76: // Turn the CDATAs back to normal text. Chris@76: protected function _from_cdata($data) Chris@76: { Chris@76: // Get the HTML translation table and reverse it. Chris@76: $trans_tbl = array_flip(get_html_translation_table(HTML_ENTITIES, ENT_QUOTES)); Chris@76: Chris@76: // Translate all the entities out. Chris@76: $data = strtr(preg_replace('~&#(\d{1,4});~e', "chr('\$1')", $data), $trans_tbl); Chris@76: Chris@76: return $this->trim ? trim($data) : $data; Chris@76: } Chris@76: Chris@76: // Given an array, return the text from that array. (recursive and privately used.) Chris@76: protected function _fetch($array) Chris@76: { Chris@76: // Don't return anything if this is just a string. Chris@76: if (is_string($array)) Chris@76: return ''; Chris@76: Chris@76: $temp = ''; Chris@76: foreach ($array as $text) Chris@76: { Chris@76: // This means it's most likely an attribute or the name itself. Chris@76: if (!isset($text['name'])) Chris@76: continue; Chris@76: Chris@76: // This is text! Chris@76: if ($text['name'] == '!') Chris@76: $temp .= $text['value']; Chris@76: // Another element - dive in ;). Chris@76: else Chris@76: $temp .= $this->_fetch($text); Chris@76: } Chris@76: Chris@76: // Return all the bits and pieces we've put together. Chris@76: return $temp; Chris@76: } Chris@76: Chris@76: // Get a specific array by path, one level down. (privately used...) Chris@76: protected function _path($array, $path, $level, $no_error = false) Chris@76: { Chris@76: // Is $array even an array? It might be false! Chris@76: if (!is_array($array)) Chris@76: return false; Chris@76: Chris@76: // Asking for *no* path? Chris@76: if ($path == '' || $path == '.') Chris@76: return $array; Chris@76: $paths = explode('|', $path); Chris@76: Chris@76: // A * means all elements of any name. Chris@76: $show_all = in_array('*', $paths); Chris@76: Chris@76: $results = array(); Chris@76: Chris@76: // Check each element. Chris@76: foreach ($array as $value) Chris@76: { Chris@76: if (!is_array($value) || $value['name'] === '!') Chris@76: continue; Chris@76: Chris@76: if ($show_all || in_array($value['name'], $paths)) Chris@76: { Chris@76: // Skip elements before "the one". Chris@76: if ($level !== null && $level > 0) Chris@76: $level--; Chris@76: else Chris@76: $results[] = $value; Chris@76: } Chris@76: } Chris@76: Chris@76: // No results found... Chris@76: if (empty($results)) Chris@76: { Chris@76: if (function_exists('debug_backtrace')) Chris@76: { Chris@76: $trace = debug_backtrace(); Chris@76: $i = 0; Chris@76: while ($i < count($trace) && isset($trace[$i]['class']) && $trace[$i]['class'] == get_class($this)) Chris@76: $i++; Chris@76: $debug = ' from ' . $trace[$i - 1]['file'] . ' on line ' . $trace[$i - 1]['line']; Chris@76: } Chris@76: else Chris@76: $debug = ''; Chris@76: Chris@76: // Cause an error. Chris@76: if ($this->debug_level & E_NOTICE && !$no_error) Chris@76: trigger_error('Undefined XML element: ' . $path . $debug, E_USER_NOTICE); Chris@76: return false; Chris@76: } Chris@76: // Only one result. Chris@76: elseif (count($results) == 1 || $level !== null) Chris@76: return $results[0]; Chris@76: // Return the result set. Chris@76: else Chris@76: return $results + array('name' => $path . '[]'); Chris@76: } Chris@76: } Chris@76: Chris@76: // http://www.faqs.org/rfcs/rfc959.html Chris@76: if (!class_exists('ftp_connection')) Chris@76: { Chris@76: class ftp_connection Chris@76: { Chris@76: public $connection, $error, $last_message, $pasv; Chris@76: Chris@76: // Create a new FTP connection... Chris@76: public function __construct($ftp_server, $ftp_port = 21, $ftp_user = 'anonymous', $ftp_pass = 'ftpclient@simplemachines.org') Chris@76: { Chris@76: // Initialize variables. Chris@76: $this->connection = 'no_connection'; Chris@76: $this->error = false; Chris@76: $this->pasv = array(); Chris@76: Chris@76: if ($ftp_server !== null) Chris@76: $this->connect($ftp_server, $ftp_port, $ftp_user, $ftp_pass); Chris@76: } Chris@76: Chris@76: public function connect($ftp_server, $ftp_port = 21, $ftp_user = 'anonymous', $ftp_pass = 'ftpclient@simplemachines.org') Chris@76: { Chris@76: if (substr($ftp_server, 0, 6) == 'ftp://') Chris@76: $ftp_server = substr($ftp_server, 6); Chris@76: elseif (substr($ftp_server, 0, 7) == 'ftps://') Chris@76: $ftp_server = 'ssl://' . substr($ftp_server, 7); Chris@76: if (substr($ftp_server, 0, 7) == 'http://') Chris@76: $ftp_server = substr($ftp_server, 7); Chris@76: $ftp_server = strtr($ftp_server, array('/' => '', ':' => '', '@' => '')); Chris@76: Chris@76: // Connect to the FTP server. Chris@76: $this->connection = @fsockopen($ftp_server, $ftp_port, $err, $err, 5); Chris@76: if (!$this->connection) Chris@76: { Chris@76: $this->error = 'bad_server'; Chris@76: return; Chris@76: } Chris@76: Chris@76: // Get the welcome message... Chris@76: if (!$this->check_response(220)) Chris@76: { Chris@76: $this->error = 'bad_response'; Chris@76: return; Chris@76: } Chris@76: Chris@76: // Send the username, it should ask for a password. Chris@76: fwrite($this->connection, 'USER ' . $ftp_user . "\r\n"); Chris@76: if (!$this->check_response(331)) Chris@76: { Chris@76: $this->error = 'bad_username'; Chris@76: return; Chris@76: } Chris@76: Chris@76: // Now send the password... and hope it goes okay. Chris@76: fwrite($this->connection, 'PASS ' . $ftp_pass . "\r\n"); Chris@76: if (!$this->check_response(230)) Chris@76: { Chris@76: $this->error = 'bad_password'; Chris@76: return; Chris@76: } Chris@76: } Chris@76: Chris@76: public function chdir($ftp_path) Chris@76: { Chris@76: if (!is_resource($this->connection)) Chris@76: return false; Chris@76: Chris@76: // No slash on the end, please... Chris@76: if ($ftp_path !== '/' && substr($ftp_path, -1) === '/') Chris@76: $ftp_path = substr($ftp_path, 0, -1); Chris@76: Chris@76: fwrite($this->connection, 'CWD ' . $ftp_path . "\r\n"); Chris@76: if (!$this->check_response(250)) Chris@76: { Chris@76: $this->error = 'bad_path'; Chris@76: return false; Chris@76: } Chris@76: Chris@76: return true; Chris@76: } Chris@76: Chris@76: public function chmod($ftp_file, $chmod) Chris@76: { Chris@76: if (!is_resource($this->connection)) Chris@76: return false; Chris@76: Chris@76: if ($ftp_file == '') Chris@76: $ftp_file = '.'; Chris@76: Chris@76: // Convert the chmod value from octal (0777) to text ("777"). Chris@76: fwrite($this->connection, 'SITE CHMOD ' . decoct($chmod) . ' ' . $ftp_file . "\r\n"); Chris@76: if (!$this->check_response(200)) Chris@76: { Chris@76: $this->error = 'bad_file'; Chris@76: return false; Chris@76: } Chris@76: Chris@76: return true; Chris@76: } Chris@76: Chris@76: public function unlink($ftp_file) Chris@76: { Chris@76: // We are actually connected, right? Chris@76: if (!is_resource($this->connection)) Chris@76: return false; Chris@76: Chris@76: // Delete file X. Chris@76: fwrite($this->connection, 'DELE ' . $ftp_file . "\r\n"); Chris@76: if (!$this->check_response(250)) Chris@76: { Chris@76: fwrite($this->connection, 'RMD ' . $ftp_file . "\r\n"); Chris@76: Chris@76: // Still no love? Chris@76: if (!$this->check_response(250)) Chris@76: { Chris@76: $this->error = 'bad_file'; Chris@76: return false; Chris@76: } Chris@76: } Chris@76: Chris@76: return true; Chris@76: } Chris@76: Chris@76: public function check_response($desired) Chris@76: { Chris@76: // Wait for a response that isn't continued with -, but don't wait too long. Chris@76: $time = time(); Chris@76: do Chris@76: $this->last_message = fgets($this->connection, 1024); Chris@76: while ((strlen($this->last_message) < 4 || substr($this->last_message, 0, 1) == ' ' || substr($this->last_message, 3, 1) != ' ') && time() - $time < 5); Chris@76: Chris@76: // Was the desired response returned? Chris@76: return is_array($desired) ? in_array(substr($this->last_message, 0, 3), $desired) : substr($this->last_message, 0, 3) == $desired; Chris@76: } Chris@76: Chris@76: public function passive() Chris@76: { Chris@76: // We can't create a passive data connection without a primary one first being there. Chris@76: if (!is_resource($this->connection)) Chris@76: return false; Chris@76: Chris@76: // Request a passive connection - this means, we'll talk to you, you don't talk to us. Chris@76: @fwrite($this->connection, 'PASV' . "\r\n"); Chris@76: $time = time(); Chris@76: do Chris@76: $response = fgets($this->connection, 1024); Chris@76: while (substr($response, 3, 1) != ' ' && time() - $time < 5); Chris@76: Chris@76: // If it's not 227, we weren't given an IP and port, which means it failed. Chris@76: if (substr($response, 0, 4) != '227 ') Chris@76: { Chris@76: $this->error = 'bad_response'; Chris@76: return false; Chris@76: } Chris@76: Chris@76: // Snatch the IP and port information, or die horribly trying... Chris@76: if (preg_match('~\((\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))\)~', $response, $match) == 0) Chris@76: { Chris@76: $this->error = 'bad_response'; Chris@76: return false; Chris@76: } Chris@76: Chris@76: // This is pretty simple - store it for later use ;). Chris@76: $this->pasv = array('ip' => $match[1] . '.' . $match[2] . '.' . $match[3] . '.' . $match[4], 'port' => $match[5] * 256 + $match[6]); Chris@76: Chris@76: return true; Chris@76: } Chris@76: Chris@76: public function create_file($ftp_file) Chris@76: { Chris@76: // First, we have to be connected... very important. Chris@76: if (!is_resource($this->connection)) Chris@76: return false; Chris@76: Chris@76: // I'd like one passive mode, please! Chris@76: if (!$this->passive()) Chris@76: return false; Chris@76: Chris@76: // Seems logical enough, so far... Chris@76: fwrite($this->connection, 'STOR ' . $ftp_file . "\r\n"); Chris@76: Chris@76: // Okay, now we connect to the data port. If it doesn't work out, it's probably "file already exists", etc. Chris@76: $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err, 5); Chris@76: if (!$fp || !$this->check_response(150)) Chris@76: { Chris@76: $this->error = 'bad_file'; Chris@76: @fclose($fp); Chris@76: return false; Chris@76: } Chris@76: Chris@76: // This may look strange, but we're just closing it to indicate a zero-byte upload. Chris@76: fclose($fp); Chris@76: if (!$this->check_response(226)) Chris@76: { Chris@76: $this->error = 'bad_response'; Chris@76: return false; Chris@76: } Chris@76: Chris@76: return true; Chris@76: } Chris@76: Chris@76: public function list_dir($ftp_path = '', $search = false) Chris@76: { Chris@76: // Are we even connected...? Chris@76: if (!is_resource($this->connection)) Chris@76: return false; Chris@76: Chris@76: // Passive... non-agressive... Chris@76: if (!$this->passive()) Chris@76: return false; Chris@76: Chris@76: // Get the listing! Chris@76: fwrite($this->connection, 'LIST -1' . ($search ? 'R' : '') . ($ftp_path == '' ? '' : ' ' . $ftp_path) . "\r\n"); Chris@76: Chris@76: // Connect, assuming we've got a connection. Chris@76: $fp = @fsockopen($this->pasv['ip'], $this->pasv['port'], $err, $err, 5); Chris@76: if (!$fp || !$this->check_response(array(150, 125))) Chris@76: { Chris@76: $this->error = 'bad_response'; Chris@76: @fclose($fp); Chris@76: return false; Chris@76: } Chris@76: Chris@76: // Read in the file listing. Chris@76: $data = ''; Chris@76: while (!feof($fp)) Chris@76: $data .= fread($fp, 4096); Chris@76: fclose($fp); Chris@76: Chris@76: // Everything go okay? Chris@76: if (!$this->check_response(226)) Chris@76: { Chris@76: $this->error = 'bad_response'; Chris@76: return false; Chris@76: } Chris@76: Chris@76: return $data; Chris@76: } Chris@76: Chris@76: public function locate($file, $listing = null) Chris@76: { Chris@76: if ($listing === null) Chris@76: $listing = $this->list_dir('', true); Chris@76: $listing = explode("\n", $listing); Chris@76: Chris@76: @fwrite($this->connection, 'PWD' . "\r\n"); Chris@76: $time = time(); Chris@76: do Chris@76: $response = fgets($this->connection, 1024); Chris@76: while ($response[3] != ' ' && time() - $time < 5); Chris@76: Chris@76: // Check for 257! Chris@76: if (preg_match('~^257 "(.+?)" ~', $response, $match) != 0) Chris@76: $current_dir = strtr($match[1], array('""' => '"')); Chris@76: else Chris@76: $current_dir = ''; Chris@76: Chris@76: for ($i = 0, $n = count($listing); $i < $n; $i++) Chris@76: { Chris@76: if (trim($listing[$i]) == '' && isset($listing[$i + 1])) Chris@76: { Chris@76: $current_dir = substr(trim($listing[++$i]), 0, -1); Chris@76: $i++; Chris@76: } Chris@76: Chris@76: // Okay, this file's name is: Chris@76: $listing[$i] = $current_dir . '/' . trim(strlen($listing[$i]) > 30 ? strrchr($listing[$i], ' ') : $listing[$i]); Chris@76: Chris@76: if ($file[0] == '*' && substr($listing[$i], -(strlen($file) - 1)) == substr($file, 1)) Chris@76: return $listing[$i]; Chris@76: if (substr($file, -1) == '*' && substr($listing[$i], 0, strlen($file) - 1) == substr($file, 0, -1)) Chris@76: return $listing[$i]; Chris@76: if (basename($listing[$i]) == $file || $listing[$i] == $file) Chris@76: return $listing[$i]; Chris@76: } Chris@76: Chris@76: return false; Chris@76: } Chris@76: Chris@76: public function create_dir($ftp_dir) Chris@76: { Chris@76: // We must be connected to the server to do something. Chris@76: if (!is_resource($this->connection)) Chris@76: return false; Chris@76: Chris@76: // Make this new beautiful directory! Chris@76: fwrite($this->connection, 'MKD ' . $ftp_dir . "\r\n"); Chris@76: if (!$this->check_response(257)) Chris@76: { Chris@76: $this->error = 'bad_file'; Chris@76: return false; Chris@76: } Chris@76: Chris@76: return true; Chris@76: } Chris@76: Chris@76: public function detect_path($filesystem_path, $lookup_file = null) Chris@76: { Chris@76: $username = ''; Chris@76: Chris@76: if (isset($_SERVER['DOCUMENT_ROOT'])) Chris@76: { Chris@76: if (preg_match('~^/home[2]?/([^/]+?)/public_html~', $_SERVER['DOCUMENT_ROOT'], $match)) Chris@76: { Chris@76: $username = $match[1]; Chris@76: Chris@76: $path = strtr($_SERVER['DOCUMENT_ROOT'], array('/home/' . $match[1] . '/' => '', '/home2/' . $match[1] . '/' => '')); Chris@76: Chris@76: if (substr($path, -1) == '/') Chris@76: $path = substr($path, 0, -1); Chris@76: Chris@76: if (strlen(dirname($_SERVER['PHP_SELF'])) > 1) Chris@76: $path .= dirname($_SERVER['PHP_SELF']); Chris@76: } Chris@76: elseif (substr($filesystem_path, 0, 9) == '/var/www/') Chris@76: $path = substr($filesystem_path, 8); Chris@76: else Chris@76: $path = strtr(strtr($filesystem_path, array('\\' => '/')), array($_SERVER['DOCUMENT_ROOT'] => '')); Chris@76: } Chris@76: else Chris@76: $path = ''; Chris@76: Chris@76: if (is_resource($this->connection) && $this->list_dir($path) == '') Chris@76: { Chris@76: $data = $this->list_dir('', true); Chris@76: Chris@76: if ($lookup_file === null) Chris@76: $lookup_file = $_SERVER['PHP_SELF']; Chris@76: Chris@76: $found_path = dirname($this->locate('*' . basename(dirname($lookup_file)) . '/' . basename($lookup_file), $data)); Chris@76: if ($found_path == false) Chris@76: $found_path = dirname($this->locate(basename($lookup_file))); Chris@76: if ($found_path != false) Chris@76: $path = $found_path; Chris@76: } Chris@76: elseif (is_resource($this->connection)) Chris@76: $found_path = true; Chris@76: Chris@76: return array($username, $path, isset($found_path)); Chris@76: } Chris@76: Chris@76: public function close() Chris@76: { Chris@76: // Goodbye! Chris@76: fwrite($this->connection, 'QUIT' . "\r\n"); Chris@76: fclose($this->connection); Chris@76: Chris@76: return true; Chris@76: } Chris@76: } Chris@76: } Chris@76: Chris@76: ?>