[ Index ] |
PHP Cross Reference of phpBB-3.3.2-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * 4 * This file is part of the phpBB Forum Software package. 5 * 6 * @copyright (c) phpBB Limited <https://www.phpbb.com> 7 * @license GNU General Public License, version 2 (GPL-2.0) 8 * 9 * For full copyright and license information, please see 10 * the docs/CREDITS.txt file. 11 * 12 */ 13 14 namespace phpbb\db\driver; 15 16 /** 17 * Oracle Database Abstraction Layer 18 */ 19 class oracle extends \phpbb\db\driver\driver 20 { 21 var $last_query_text = ''; 22 var $connect_error = ''; 23 24 /** 25 * {@inheritDoc} 26 */ 27 function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) 28 { 29 $this->persistency = $persistency; 30 $this->user = $sqluser; 31 $this->server = $sqlserver . (($port) ? ':' . $port : ''); 32 $this->dbname = $database; 33 34 $connect = $database; 35 36 // support for "easy connect naming" 37 if ($sqlserver !== '' && $sqlserver !== '/') 38 { 39 if (substr($sqlserver, -1, 1) == '/') 40 { 41 $sqlserver == substr($sqlserver, 0, -1); 42 } 43 $connect = $sqlserver . (($port) ? ':' . $port : '') . '/' . $database; 44 } 45 46 if ($new_link) 47 { 48 if (!function_exists('ocinlogon')) 49 { 50 $this->connect_error = 'ocinlogon function does not exist, is oci extension installed?'; 51 return $this->sql_error(''); 52 } 53 $this->db_connect_id = @ocinlogon($this->user, $sqlpassword, $connect, 'UTF8'); 54 } 55 else if ($this->persistency) 56 { 57 if (!function_exists('ociplogon')) 58 { 59 $this->connect_error = 'ociplogon function does not exist, is oci extension installed?'; 60 return $this->sql_error(''); 61 } 62 $this->db_connect_id = @ociplogon($this->user, $sqlpassword, $connect, 'UTF8'); 63 } 64 else 65 { 66 if (!function_exists('ocilogon')) 67 { 68 $this->connect_error = 'ocilogon function does not exist, is oci extension installed?'; 69 return $this->sql_error(''); 70 } 71 $this->db_connect_id = @ocilogon($this->user, $sqlpassword, $connect, 'UTF8'); 72 } 73 74 return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); 75 } 76 77 /** 78 * {@inheritDoc} 79 */ 80 function sql_server_info($raw = false, $use_cache = true) 81 { 82 /** 83 * force $use_cache false. I didn't research why the caching code below is commented out 84 * but I assume its because the Oracle extension provides a direct method to access it 85 * without a query. 86 */ 87 /* 88 global $cache; 89 90 if (empty($cache) || ($this->sql_server_version = $cache->get('oracle_version')) === false) 91 { 92 $result = @ociparse($this->db_connect_id, 'SELECT * FROM v$version WHERE banner LIKE \'Oracle%\''); 93 @ociexecute($result, OCI_DEFAULT); 94 @ocicommit($this->db_connect_id); 95 96 $row = array(); 97 @ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS); 98 @ocifreestatement($result); 99 $this->sql_server_version = trim($row['BANNER']); 100 101 $cache->put('oracle_version', $this->sql_server_version); 102 } 103 */ 104 $this->sql_server_version = @ociserverversion($this->db_connect_id); 105 106 return $this->sql_server_version; 107 } 108 109 /** 110 * SQL Transaction 111 * @access private 112 */ 113 function _sql_transaction($status = 'begin') 114 { 115 switch ($status) 116 { 117 case 'begin': 118 return true; 119 break; 120 121 case 'commit': 122 return @ocicommit($this->db_connect_id); 123 break; 124 125 case 'rollback': 126 return @ocirollback($this->db_connect_id); 127 break; 128 } 129 130 return true; 131 } 132 133 /** 134 * Oracle specific code to handle the fact that it does not compare columns properly 135 * @access private 136 */ 137 function _rewrite_col_compare($args) 138 { 139 if (count($args) == 4) 140 { 141 if ($args[2] == '=') 142 { 143 return '(' . $args[0] . ' OR (' . $args[1] . ' is NULL AND ' . $args[3] . ' is NULL))'; 144 } 145 else if ($args[2] == '<>') 146 { 147 // really just a fancy way of saying foo <> bar or (foo is NULL XOR bar is NULL) but SQL has no XOR :P 148 return '(' . $args[0] . ' OR ((' . $args[1] . ' is NULL AND ' . $args[3] . ' is NOT NULL) OR (' . $args[1] . ' is NOT NULL AND ' . $args[3] . ' is NULL)))'; 149 } 150 } 151 else 152 { 153 return $this->_rewrite_where($args[0]); 154 } 155 } 156 157 /** 158 * Oracle specific code to handle it's lack of sanity 159 * @access private 160 */ 161 function _rewrite_where($where_clause) 162 { 163 preg_match_all('/\s*(AND|OR)?\s*([\w_.()]++)\s*(?:(=|<[=>]?|>=?|LIKE)\s*((?>\'(?>[^\']++|\'\')*+\'|[\d-.()]+))|((NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))/', $where_clause, $result, PREG_SET_ORDER); 164 $out = ''; 165 foreach ($result as $val) 166 { 167 if (!isset($val[5])) 168 { 169 if ($val[4] !== "''") 170 { 171 $out .= $val[0]; 172 } 173 else 174 { 175 $out .= ' ' . $val[1] . ' ' . $val[2]; 176 if ($val[3] == '=') 177 { 178 $out .= ' is NULL'; 179 } 180 else if ($val[3] == '<>') 181 { 182 $out .= ' is NOT NULL'; 183 } 184 } 185 } 186 else 187 { 188 $in_clause = array(); 189 $sub_exp = substr($val[5], strpos($val[5], '(') + 1, -1); 190 $extra = false; 191 preg_match_all('/\'(?>[^\']++|\'\')*+\'|[\d-.]++/', $sub_exp, $sub_vals, PREG_PATTERN_ORDER); 192 $i = 0; 193 foreach ($sub_vals[0] as $sub_val) 194 { 195 // two things: 196 // 1) This determines if an empty string was in the IN clausing, making us turn it into a NULL comparison 197 // 2) This fixes the 1000 list limit that Oracle has (ORA-01795) 198 if ($sub_val !== "''") 199 { 200 $in_clause[(int) $i++/1000][] = $sub_val; 201 } 202 else 203 { 204 $extra = true; 205 } 206 } 207 if (!$extra && $i < 1000) 208 { 209 $out .= $val[0]; 210 } 211 else 212 { 213 $out .= ' ' . $val[1] . '('; 214 $in_array = array(); 215 216 // constuct each IN() clause 217 foreach ($in_clause as $in_values) 218 { 219 $in_array[] = $val[2] . ' ' . (isset($val[6]) ? $val[6] : '') . 'IN(' . implode(', ', $in_values) . ')'; 220 } 221 222 // Join the IN() clauses against a few ORs (IN is just a nicer OR anyway) 223 $out .= implode(' OR ', $in_array); 224 225 // handle the empty string case 226 if ($extra) 227 { 228 $out .= ' OR ' . $val[2] . ' is ' . (isset($val[6]) ? $val[6] : '') . 'NULL'; 229 } 230 $out .= ')'; 231 232 unset($in_array, $in_clause); 233 } 234 } 235 } 236 237 return $out; 238 } 239 240 /** 241 * {@inheritDoc} 242 */ 243 function sql_query($query = '', $cache_ttl = 0) 244 { 245 if ($query != '') 246 { 247 global $cache; 248 249 if ($this->debug_sql_explain) 250 { 251 $this->sql_report('start', $query); 252 } 253 else if ($this->debug_load_time) 254 { 255 $this->curtime = microtime(true); 256 } 257 258 $this->last_query_text = $query; 259 $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; 260 $this->sql_add_num_queries($this->query_result); 261 262 if ($this->query_result === false) 263 { 264 $in_transaction = false; 265 if (!$this->transaction) 266 { 267 $this->sql_transaction('begin'); 268 } 269 else 270 { 271 $in_transaction = true; 272 } 273 274 $array = array(); 275 276 // We overcome Oracle's 4000 char limit by binding vars 277 if (strlen($query) > 4000) 278 { 279 if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/sU', $query, $regs)) 280 { 281 if (strlen($regs[3]) > 4000) 282 { 283 $cols = explode(', ', $regs[2]); 284 285 preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER); 286 287 /* The code inside this comment block breaks clob handling, but does allow the 288 database restore script to work. If you want to allow no posts longer than 4KB 289 and/or need the db restore script, uncomment this. 290 291 292 if (count($cols) !== count($vals)) 293 { 294 // Try to replace some common data we know is from our restore script or from other sources 295 $regs[3] = str_replace("'||chr(47)||'", '/', $regs[3]); 296 $_vals = explode(', ', $regs[3]); 297 298 $vals = array(); 299 $is_in_val = false; 300 $i = 0; 301 $string = ''; 302 303 foreach ($_vals as $value) 304 { 305 if (strpos($value, "'") === false && !$is_in_val) 306 { 307 $vals[$i++] = $value; 308 continue; 309 } 310 311 if (substr($value, -1) === "'") 312 { 313 $vals[$i] = $string . (($is_in_val) ? ', ' : '') . $value; 314 $string = ''; 315 $is_in_val = false; 316 317 if ($vals[$i][0] !== "'") 318 { 319 $vals[$i] = "''" . $vals[$i]; 320 } 321 $i++; 322 continue; 323 } 324 else 325 { 326 $string .= (($is_in_val) ? ', ' : '') . $value; 327 $is_in_val = true; 328 } 329 } 330 331 if ($string) 332 { 333 // New value if cols != value 334 $vals[(count($cols) !== count($vals)) ? $i : $i - 1] .= $string; 335 } 336 337 $vals = array(0 => $vals); 338 } 339 */ 340 341 $inserts = $vals[0]; 342 unset($vals); 343 344 foreach ($inserts as $key => $value) 345 { 346 if (!empty($value) && $value[0] === "'" && strlen($value) > 4002) // check to see if this thing is greater than the max + 'x2 347 { 348 $inserts[$key] = ':' . strtoupper($cols[$key]); 349 $array[$inserts[$key]] = str_replace("''", "'", substr($value, 1, -1)); 350 } 351 } 352 353 $query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')'; 354 } 355 } 356 else if (preg_match_all('/^(UPDATE [\\w_]++\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+)\\s+(WHERE.*)$/s', $query, $data, PREG_SET_ORDER)) 357 { 358 if (strlen($data[0][2]) > 4000) 359 { 360 $update = $data[0][1]; 361 $where = $data[0][3]; 362 preg_match_all('/([\\w_]++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d-.]++)/', $data[0][2], $temp, PREG_SET_ORDER); 363 unset($data); 364 365 $cols = array(); 366 foreach ($temp as $value) 367 { 368 if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 4002) // check to see if this thing is greater than the max + 'x2 369 { 370 $cols[] = $value[1] . '=:' . strtoupper($value[1]); 371 $array[$value[1]] = str_replace("''", "'", substr($value[2], 1, -1)); 372 } 373 else 374 { 375 $cols[] = $value[1] . '=' . $value[2]; 376 } 377 } 378 379 $query = $update . implode(', ', $cols) . ' ' . $where; 380 unset($cols); 381 } 382 } 383 } 384 385 switch (substr($query, 0, 6)) 386 { 387 case 'DELETE': 388 if (preg_match('/^(DELETE FROM [\w_]++ WHERE)((?:\s*(?:AND|OR)?\s*[\w_]+\s*(?:(?:=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]+)|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))*+)$/', $query, $regs)) 389 { 390 $query = $regs[1] . $this->_rewrite_where($regs[2]); 391 unset($regs); 392 } 393 break; 394 395 case 'UPDATE': 396 if (preg_match('/^(UPDATE [\\w_]++\\s+SET [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++)(?:, [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++))*+\\s+WHERE)(.*)$/s', $query, $regs)) 397 { 398 $query = $regs[1] . $this->_rewrite_where($regs[2]); 399 unset($regs); 400 } 401 break; 402 403 case 'SELECT': 404 $query = preg_replace_callback('/([\w_.]++)\s*(?:(=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]++|([\w_.]++))|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]++,? ?)*+\))/', array($this, '_rewrite_col_compare'), $query); 405 break; 406 } 407 408 $this->query_result = @ociparse($this->db_connect_id, $query); 409 410 foreach ($array as $key => $value) 411 { 412 @ocibindbyname($this->query_result, $key, $array[$key], -1); 413 } 414 415 $success = @ociexecute($this->query_result, OCI_DEFAULT); 416 417 if (!$success) 418 { 419 $this->sql_error($query); 420 $this->query_result = false; 421 } 422 else 423 { 424 if (!$in_transaction) 425 { 426 $this->sql_transaction('commit'); 427 } 428 } 429 430 if ($this->debug_sql_explain) 431 { 432 $this->sql_report('stop', $query); 433 } 434 else if ($this->debug_load_time) 435 { 436 $this->sql_time += microtime(true) - $this->curtime; 437 } 438 439 if (!$this->query_result) 440 { 441 return false; 442 } 443 444 if ($cache && $cache_ttl) 445 { 446 $this->open_queries[(int) $this->query_result] = $this->query_result; 447 $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); 448 } 449 else if (strpos($query, 'SELECT') === 0) 450 { 451 $this->open_queries[(int) $this->query_result] = $this->query_result; 452 } 453 } 454 else if ($this->debug_sql_explain) 455 { 456 $this->sql_report('fromcache', $query); 457 } 458 } 459 else 460 { 461 return false; 462 } 463 464 return $this->query_result; 465 } 466 467 /** 468 * Build LIMIT query 469 */ 470 function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) 471 { 472 $this->query_result = false; 473 474 $query = 'SELECT * FROM (SELECT /*+ FIRST_ROWS */ rownum AS xrownum, a.* FROM (' . $query . ') a WHERE rownum <= ' . ($offset + $total) . ') WHERE xrownum >= ' . $offset; 475 476 return $this->sql_query($query, $cache_ttl); 477 } 478 479 /** 480 * {@inheritDoc} 481 */ 482 function sql_affectedrows() 483 { 484 return ($this->query_result) ? @ocirowcount($this->query_result) : false; 485 } 486 487 /** 488 * {@inheritDoc} 489 */ 490 function sql_fetchrow($query_id = false) 491 { 492 global $cache; 493 494 if ($query_id === false) 495 { 496 $query_id = $this->query_result; 497 } 498 499 if ($cache && $cache->sql_exists($query_id)) 500 { 501 return $cache->sql_fetchrow($query_id); 502 } 503 504 if ($query_id) 505 { 506 $row = array(); 507 $result = ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS); 508 509 if (!$result || !$row) 510 { 511 return false; 512 } 513 514 $result_row = array(); 515 foreach ($row as $key => $value) 516 { 517 // Oracle treats empty strings as null 518 if (is_null($value)) 519 { 520 $value = ''; 521 } 522 523 // OCI->CLOB? 524 if (is_object($value)) 525 { 526 $value = $value->load(); 527 } 528 529 $result_row[strtolower($key)] = $value; 530 } 531 532 return $result_row; 533 } 534 535 return false; 536 } 537 538 /** 539 * {@inheritDoc} 540 */ 541 function sql_rowseek($rownum, &$query_id) 542 { 543 global $cache; 544 545 if ($query_id === false) 546 { 547 $query_id = $this->query_result; 548 } 549 550 if ($cache && $cache->sql_exists($query_id)) 551 { 552 return $cache->sql_rowseek($rownum, $query_id); 553 } 554 555 if (!$query_id) 556 { 557 return false; 558 } 559 560 // Reset internal pointer 561 @ociexecute($query_id, OCI_DEFAULT); 562 563 // We do not fetch the row for rownum == 0 because then the next resultset would be the second row 564 for ($i = 0; $i < $rownum; $i++) 565 { 566 if (!$this->sql_fetchrow($query_id)) 567 { 568 return false; 569 } 570 } 571 572 return true; 573 } 574 575 /** 576 * {@inheritDoc} 577 */ 578 function sql_nextid() 579 { 580 $query_id = $this->query_result; 581 582 if ($query_id !== false && $this->last_query_text != '') 583 { 584 if (preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#is', $this->last_query_text, $tablename)) 585 { 586 $query = 'SELECT ' . $tablename[1] . '_seq.currval FROM DUAL'; 587 $stmt = @ociparse($this->db_connect_id, $query); 588 if ($stmt) 589 { 590 $success = @ociexecute($stmt, OCI_DEFAULT); 591 592 if ($success) 593 { 594 $temp_result = ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS); 595 ocifreestatement($stmt); 596 597 if ($temp_result) 598 { 599 return $temp_array['CURRVAL']; 600 } 601 else 602 { 603 return false; 604 } 605 } 606 } 607 } 608 } 609 610 return false; 611 } 612 613 /** 614 * {@inheritDoc} 615 */ 616 function sql_freeresult($query_id = false) 617 { 618 global $cache; 619 620 if ($query_id === false) 621 { 622 $query_id = $this->query_result; 623 } 624 625 if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) 626 { 627 return $cache->sql_freeresult($query_id); 628 } 629 630 if (isset($this->open_queries[(int) $query_id])) 631 { 632 unset($this->open_queries[(int) $query_id]); 633 return ocifreestatement($query_id); 634 } 635 636 return false; 637 } 638 639 /** 640 * {@inheritDoc} 641 */ 642 function sql_escape($msg) 643 { 644 return str_replace(array("'", "\0"), array("''", ''), $msg); 645 } 646 647 /** 648 * Build LIKE expression 649 * @access private 650 */ 651 function _sql_like_expression($expression) 652 { 653 return $expression . " ESCAPE '\\'"; 654 } 655 656 /** 657 * Build NOT LIKE expression 658 * @access private 659 */ 660 function _sql_not_like_expression($expression) 661 { 662 return $expression . " ESCAPE '\\'"; 663 } 664 665 function _sql_custom_build($stage, $data) 666 { 667 return $data; 668 } 669 670 function _sql_bit_and($column_name, $bit, $compare = '') 671 { 672 return 'BITAND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); 673 } 674 675 function _sql_bit_or($column_name, $bit, $compare = '') 676 { 677 return 'BITOR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); 678 } 679 680 /** 681 * return sql error array 682 * @access private 683 */ 684 function _sql_error() 685 { 686 if (function_exists('ocierror')) 687 { 688 $error = @ocierror(); 689 $error = (!$error) ? @ocierror($this->query_result) : $error; 690 $error = (!$error) ? @ocierror($this->db_connect_id) : $error; 691 692 if ($error) 693 { 694 $this->last_error_result = $error; 695 } 696 else 697 { 698 $error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array(); 699 } 700 } 701 else 702 { 703 $error = array( 704 'message' => $this->connect_error, 705 'code' => '', 706 ); 707 } 708 709 return $error; 710 } 711 712 /** 713 * Close sql connection 714 * @access private 715 */ 716 function _sql_close() 717 { 718 return @ocilogoff($this->db_connect_id); 719 } 720 721 /** 722 * Build db-specific report 723 * @access private 724 */ 725 function _sql_report($mode, $query = '') 726 { 727 switch ($mode) 728 { 729 case 'start': 730 731 $html_table = false; 732 733 // Grab a plan table, any will do 734 $sql = "SELECT table_name 735 FROM USER_TABLES 736 WHERE table_name LIKE '%PLAN_TABLE%'"; 737 $stmt = ociparse($this->db_connect_id, $sql); 738 ociexecute($stmt); 739 $result = array(); 740 741 if (ocifetchinto($stmt, $result, OCI_ASSOC + OCI_RETURN_NULLS)) 742 { 743 $table = $result['TABLE_NAME']; 744 745 // This is the statement_id that will allow us to track the plan 746 $statement_id = substr(md5($query), 0, 30); 747 748 // Remove any stale plans 749 $stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'"); 750 ociexecute($stmt2); 751 ocifreestatement($stmt2); 752 753 // Explain the plan 754 $sql = "EXPLAIN PLAN 755 SET STATEMENT_ID = '$statement_id' 756 FOR $query"; 757 $stmt2 = ociparse($this->db_connect_id, $sql); 758 ociexecute($stmt2); 759 ocifreestatement($stmt2); 760 761 // Get the data from the plan 762 $sql = "SELECT operation, options, object_name, object_type, cardinality, cost 763 FROM plan_table 764 START WITH id = 0 AND statement_id = '$statement_id' 765 CONNECT BY PRIOR id = parent_id 766 AND statement_id = '$statement_id'"; 767 $stmt2 = ociparse($this->db_connect_id, $sql); 768 ociexecute($stmt2); 769 770 $row = array(); 771 while (ocifetchinto($stmt2, $row, OCI_ASSOC + OCI_RETURN_NULLS)) 772 { 773 $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); 774 } 775 776 ocifreestatement($stmt2); 777 778 // Remove the plan we just made, we delete them on request anyway 779 $stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'"); 780 ociexecute($stmt2); 781 ocifreestatement($stmt2); 782 } 783 784 ocifreestatement($stmt); 785 786 if ($html_table) 787 { 788 $this->html_hold .= '</table>'; 789 } 790 791 break; 792 793 case 'fromcache': 794 $endtime = explode(' ', microtime()); 795 $endtime = $endtime[0] + $endtime[1]; 796 797 $result = @ociparse($this->db_connect_id, $query); 798 if ($result) 799 { 800 $success = @ociexecute($result, OCI_DEFAULT); 801 if ($success) 802 { 803 $row = array(); 804 805 while (ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS)) 806 { 807 // Take the time spent on parsing rows into account 808 } 809 @ocifreestatement($result); 810 } 811 } 812 813 $splittime = explode(' ', microtime()); 814 $splittime = $splittime[0] + $splittime[1]; 815 816 $this->sql_report('record_fromcache', $query, $endtime, $splittime); 817 818 break; 819 } 820 } 821 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Nov 11 20:28:18 2020 | Cross-referenced by PHPXref 0.7.1 |