annotate forum/Sources/Class-Package.php @ 87:df86d318892b website

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