[ Index ] |
PHP Cross Reference of phpBB-3.2.11-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\event; 15 16 /** 17 * Class php_exporter 18 * Crawls through a list of files and grabs all php-events 19 */ 20 class php_exporter 21 { 22 /** @var string Path where we look for files*/ 23 protected $path; 24 25 /** @var string phpBB Root Path */ 26 protected $root_path; 27 28 /** @var string The minimum version for the events to return */ 29 protected $min_version; 30 31 /** @var string The maximum version for the events to return */ 32 protected $max_version; 33 34 /** @var string */ 35 protected $current_file; 36 37 /** @var string */ 38 protected $current_event; 39 40 /** @var int */ 41 protected $current_event_line; 42 43 /** @var array */ 44 protected $events; 45 46 /** @var array */ 47 protected $file_lines; 48 49 /** 50 * @param string $phpbb_root_path 51 * @param mixed $extension String 'vendor/ext' to filter, null for phpBB core 52 * @param string $min_version 53 * @param string $max_version 54 */ 55 public function __construct($phpbb_root_path, $extension = null, $min_version = null, $max_version = null) 56 { 57 $this->root_path = $phpbb_root_path; 58 $this->path = $phpbb_root_path; 59 $this->events = $this->file_lines = array(); 60 $this->current_file = $this->current_event = ''; 61 $this->current_event_line = 0; 62 $this->min_version = $min_version; 63 $this->max_version = $max_version; 64 65 $this->path = $this->root_path; 66 if ($extension) 67 { 68 $this->path .= 'ext/' . $extension . '/'; 69 } 70 } 71 72 /** 73 * Get the list of all events 74 * 75 * @return array Array with events: name => details 76 */ 77 public function get_events() 78 { 79 return $this->events; 80 } 81 82 /** 83 * Set current event data 84 * 85 * @param string $name Name of the current event (used for error messages) 86 * @param int $line Line where the current event is placed in 87 * @return null 88 */ 89 public function set_current_event($name, $line) 90 { 91 $this->current_event = $name; 92 $this->current_event_line = $line; 93 } 94 95 /** 96 * Set the content of this file 97 * 98 * @param array $content Array with the lines of the file 99 * @return null 100 */ 101 public function set_content($content) 102 { 103 $this->file_lines = $content; 104 } 105 106 /** 107 * Crawl the phpBB/ directory for php events 108 * @return int The number of events found 109 */ 110 public function crawl_phpbb_directory_php() 111 { 112 $files = $this->get_recursive_file_list(); 113 $this->events = array(); 114 foreach ($files as $file) 115 { 116 $this->crawl_php_file($file); 117 } 118 ksort($this->events); 119 120 return count($this->events); 121 } 122 123 /** 124 * Returns a list of files in $dir 125 * 126 * @return array List of files (including the path) 127 */ 128 public function get_recursive_file_list() 129 { 130 try 131 { 132 $iterator = new \RecursiveIteratorIterator( 133 new \phpbb\event\recursive_event_filter_iterator( 134 new \RecursiveDirectoryIterator( 135 $this->path, 136 \FilesystemIterator::SKIP_DOTS 137 ), 138 $this->path 139 ), 140 \RecursiveIteratorIterator::LEAVES_ONLY 141 ); 142 } 143 catch (\Exception $e) 144 { 145 return array(); 146 } 147 148 $files = array(); 149 foreach ($iterator as $file_info) 150 { 151 /** @var \RecursiveDirectoryIterator $file_info */ 152 $relative_path = $iterator->getInnerIterator()->getSubPathname(); 153 $files[] = str_replace(DIRECTORY_SEPARATOR, '/', $relative_path); 154 } 155 156 return $files; 157 } 158 159 /** 160 * Format the php events as a wiki table 161 * 162 * @param string $action 163 * @return string 164 */ 165 public function export_events_for_wiki($action = '') 166 { 167 if ($action === 'diff') 168 { 169 $wiki_page = '=== PHP Events (Hook Locations) ===' . "\n"; 170 } 171 else 172 { 173 $wiki_page = '= PHP Events (Hook Locations) =' . "\n"; 174 } 175 $wiki_page .= '{| class="sortable zebra" cellspacing="0" cellpadding="5"' . "\n"; 176 $wiki_page .= '! Identifier !! Placement !! Arguments !! Added in Release !! Explanation' . "\n"; 177 foreach ($this->events as $event) 178 { 179 $wiki_page .= '|- id="' . $event['event'] . '"' . "\n"; 180 $wiki_page .= '| [[#' . $event['event'] . '|' . $event['event'] . ']] || ' . $event['file'] . ' || ' . implode(', ', $event['arguments']) . ' || ' . $event['since'] . ' || ' . $event['description'] . "\n"; 181 } 182 $wiki_page .= '|}' . "\n"; 183 184 return $wiki_page; 185 } 186 187 /** 188 * @param string $file 189 * @return int Number of events found in this file 190 * @throws \LogicException 191 */ 192 public function crawl_php_file($file) 193 { 194 $this->current_file = $file; 195 $this->file_lines = array(); 196 $content = file_get_contents($this->path . $this->current_file); 197 $num_events_found = 0; 198 199 if (strpos($content, 'dispatcher->trigger_event(') || strpos($content, 'dispatcher->dispatch(')) 200 { 201 $this->set_content(explode("\n", $content)); 202 for ($i = 0, $num_lines = count($this->file_lines); $i < $num_lines; $i++) 203 { 204 $event_line = false; 205 $found_trigger_event = strpos($this->file_lines[$i], 'dispatcher->trigger_event('); 206 $arguments = array(); 207 if ($found_trigger_event !== false) 208 { 209 $event_line = $i; 210 $this->set_current_event($this->get_event_name($event_line, false), $event_line); 211 212 // Find variables of the event 213 $arguments = $this->get_vars_from_array(); 214 $doc_vars = $this->get_vars_from_docblock(); 215 $this->validate_vars_docblock_array($arguments, $doc_vars); 216 } 217 else 218 { 219 $found_dispatch = strpos($this->file_lines[$i], 'dispatcher->dispatch('); 220 if ($found_dispatch !== false) 221 { 222 $event_line = $i; 223 $this->set_current_event($this->get_event_name($event_line, true), $event_line); 224 } 225 } 226 227 if ($event_line) 228 { 229 // Validate @event 230 $event_line_num = $this->find_event(); 231 $this->validate_event($this->current_event, $this->file_lines[$event_line_num]); 232 233 // Validate @since 234 $since_line_num = $this->find_since(); 235 $since = $this->validate_since($this->file_lines[$since_line_num]); 236 237 $changed_line_nums = $this->find_changed('changed'); 238 if (empty($changed_line_nums)) 239 { 240 $changed_line_nums = $this->find_changed('change'); 241 } 242 $changed_versions = array(); 243 if (!empty($changed_line_nums)) 244 { 245 foreach ($changed_line_nums as $changed_line_num) 246 { 247 $changed_versions[] = $this->validate_changed($this->file_lines[$changed_line_num]); 248 } 249 } 250 251 if (!$this->version_is_filtered($since)) 252 { 253 $valid_version = false; 254 foreach ($changed_versions as $changed) 255 { 256 $valid_version = $valid_version || $this->version_is_filtered($changed); 257 } 258 259 if (!$valid_version) 260 { 261 continue; 262 } 263 } 264 265 // Find event description line 266 $description_line_num = $this->find_description(); 267 $description_lines = array(); 268 269 while (true) 270 { 271 $description_line = substr(trim($this->file_lines[$description_line_num]), strlen('*')); 272 $description_line = trim(str_replace("\t", " ", $description_line)); 273 274 // Reached end of description if line is a tag 275 if (strlen($description_line) && $description_line[0] == '@') 276 { 277 break; 278 } 279 280 $description_lines[] = $description_line; 281 $description_line_num++; 282 } 283 284 // If there is an empty line between description and first tag, remove it 285 if (!strlen(end($description_lines))) 286 { 287 array_pop($description_lines); 288 } 289 290 $description = trim(implode('<br/>', $description_lines)); 291 292 if (isset($this->events[$this->current_event])) 293 { 294 throw new \LogicException("The event '{$this->current_event}' from file " 295 . "'{$this->current_file}:{$event_line_num}' already exists in file " 296 . "'{$this->events[$this->current_event]['file']}'", 10); 297 } 298 299 sort($arguments); 300 $this->events[$this->current_event] = array( 301 'event' => $this->current_event, 302 'file' => $this->current_file, 303 'arguments' => $arguments, 304 'since' => $since, 305 'description' => $description, 306 ); 307 $num_events_found++; 308 } 309 } 310 } 311 312 return $num_events_found; 313 } 314 315 /** 316 * The version to check 317 * 318 * @param string $version 319 * @return bool 320 */ 321 protected function version_is_filtered($version) 322 { 323 return (!$this->min_version || phpbb_version_compare($this->min_version, $version, '<=')) 324 && (!$this->max_version || phpbb_version_compare($this->max_version, $version, '>=')); 325 } 326 327 /** 328 * Find the name of the event inside the dispatch() line 329 * 330 * @param int $event_line 331 * @param bool $is_dispatch Do we look for dispatch() or trigger_event() ? 332 * @return string Name of the event 333 * @throws \LogicException 334 */ 335 public function get_event_name($event_line, $is_dispatch) 336 { 337 $event_text_line = $this->file_lines[$event_line]; 338 $event_text_line = ltrim($event_text_line, "\t "); 339 340 if ($is_dispatch) 341 { 342 $regex = '#\$[a-z](?:[a-z0-9_]|->)*'; 343 $regex .= '->dispatch\((\[)?'; 344 $regex .= '\'' . $this->preg_match_event_name() . '(?(1)\', \'(?2))+\''; 345 $regex .= '(?(1)\])\);#'; 346 } 347 else 348 { 349 $regex = '#extract\(\$[a-z](?:[a-z0-9_]|->)*'; 350 $regex .= '->trigger_event\((\[)?'; 351 $regex .= '\'' . $this->preg_match_event_name() . '(?(1)\', \'(?2))+\''; 352 $regex .= '(?(1)\]), compact\(\$vars\)\)\);#'; 353 } 354 355 $match = array(); 356 preg_match($regex, $event_text_line, $match); 357 if (!isset($match[2])) 358 { 359 throw new \LogicException("Can not find event name in line '{$event_text_line}' " 360 . "in file '{$this->current_file}:{$event_line}'", 1); 361 } 362 363 return $match[2]; 364 } 365 366 /** 367 * Returns a regex match for the event name 368 * 369 * @return string 370 */ 371 protected function preg_match_event_name() 372 { 373 return '([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+)'; 374 } 375 376 /** 377 * Find the $vars array 378 * 379 * @return array List of variables 380 * @throws \LogicException 381 */ 382 public function get_vars_from_array() 383 { 384 $line = ltrim($this->file_lines[$this->current_event_line - 1], "\t"); 385 if ($line === ');' || $line === '];') 386 { 387 $vars_array = $this->get_vars_from_multi_line_array(); 388 } 389 else 390 { 391 $vars_array = $this->get_vars_from_single_line_array($line); 392 } 393 394 foreach ($vars_array as $var) 395 { 396 if (!preg_match('#^[a-z_][a-z0-9_]*$#i', $var)) 397 { 398 throw new \LogicException("Found invalid var '{$var}' in array for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 3); 399 } 400 } 401 402 sort($vars_array); 403 return $vars_array; 404 } 405 406 /** 407 * Find the variables in single line array 408 * 409 * @param string $line 410 * @param bool $throw_multiline Throw an exception when there are too 411 * many arguments in one line. 412 * @return array List of variables 413 * @throws \LogicException 414 */ 415 public function get_vars_from_single_line_array($line, $throw_multiline = true) 416 { 417 $match = array(); 418 preg_match('#^\$vars = (?:(\[)|array\()\'([a-z0-9_\' ,]+)\'(?(1)\]|\));$#i', $line, $match); 419 420 if (isset($match[2])) 421 { 422 $vars_array = explode("', '", $match[2]); 423 if ($throw_multiline && count($vars_array) > 6) 424 { 425 throw new \LogicException('Should use multiple lines for $vars definition ' 426 . "for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); 427 } 428 return $vars_array; 429 } 430 else 431 { 432 throw new \LogicException("Can not find '\$vars = array();'-line for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); 433 } 434 } 435 436 /** 437 * Find the variables in single line array 438 * 439 * @return array List of variables 440 * @throws \LogicException 441 */ 442 public function get_vars_from_multi_line_array() 443 { 444 $current_vars_line = 2; 445 $var_lines = array(); 446 while (!in_array(ltrim($this->file_lines[$this->current_event_line - $current_vars_line], "\t"), ['$vars = array(', '$vars = ['])) 447 { 448 $var_lines[] = substr(trim($this->file_lines[$this->current_event_line - $current_vars_line]), 0, -1); 449 450 $current_vars_line++; 451 if ($current_vars_line > $this->current_event_line) 452 { 453 // Reached the start of the file 454 throw new \LogicException("Can not find end of \$vars array for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); 455 } 456 } 457 458 return $this->get_vars_from_single_line_array('$vars = array(' . implode(", ", $var_lines) . ');', false); 459 } 460 461 /** 462 * Find the $vars array 463 * 464 * @return array List of variables 465 * @throws \LogicException 466 */ 467 public function get_vars_from_docblock() 468 { 469 $doc_vars = array(); 470 $current_doc_line = 1; 471 $found_comment_end = false; 472 while (ltrim($this->file_lines[$this->current_event_line - $current_doc_line], "\t") !== '/**') 473 { 474 if (ltrim($this->file_lines[$this->current_event_line - $current_doc_line], "\t ") === '*/') 475 { 476 $found_comment_end = true; 477 } 478 479 if ($found_comment_end) 480 { 481 $var_line = trim($this->file_lines[$this->current_event_line - $current_doc_line]); 482 $var_line = preg_replace('!\s+!', ' ', $var_line); 483 if (strpos($var_line, '* @var ') === 0) 484 { 485 $doc_line = explode(' ', $var_line, 5); 486 if (count($doc_line) !== 5) 487 { 488 throw new \LogicException("Found invalid line '{$this->file_lines[$this->current_event_line - $current_doc_line]}' " 489 . "for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); 490 } 491 $doc_vars[] = $doc_line[3]; 492 } 493 } 494 495 $current_doc_line++; 496 if ($current_doc_line > $this->current_event_line) 497 { 498 // Reached the start of the file 499 throw new \LogicException("Can not find end of docblock for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); 500 } 501 } 502 503 if (empty($doc_vars)) 504 { 505 // Reached the start of the file 506 throw new \LogicException("Can not find @var lines for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 3); 507 } 508 509 foreach ($doc_vars as $var) 510 { 511 if (!preg_match('#^[a-z_][a-z0-9_]*$#i', $var)) 512 { 513 throw new \LogicException("Found invalid @var '{$var}' in docblock for event " 514 . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 4); 515 } 516 } 517 518 sort($doc_vars); 519 return $doc_vars; 520 } 521 522 /** 523 * Find the "@since" Information line 524 * 525 * @return int Absolute line number 526 * @throws \LogicException 527 */ 528 public function find_since() 529 { 530 return $this->find_tag('since', array('event', 'var')); 531 } 532 533 /** 534 * Find the "@changed" Information lines 535 * 536 * @param string $tag_name Should be 'change', not 'changed' 537 * @return array Absolute line numbers 538 * @throws \LogicException 539 */ 540 public function find_changed($tag_name) 541 { 542 $lines = array(); 543 $last_line = 0; 544 try 545 { 546 while ($line = $this->find_tag($tag_name, array('since'), $last_line)) 547 { 548 $lines[] = $line; 549 $last_line = $line; 550 } 551 } 552 catch (\LogicException $e) 553 { 554 // Not changed? No problem! 555 } 556 557 return $lines; 558 } 559 560 /** 561 * Find the "@event" Information line 562 * 563 * @return int Absolute line number 564 */ 565 public function find_event() 566 { 567 return $this->find_tag('event', array()); 568 } 569 570 /** 571 * Find a "@*" Information line 572 * 573 * @param string $find_tag Name of the tag we are trying to find 574 * @param array $disallowed_tags List of tags that must not appear between 575 * the tag and the actual event 576 * @param int $skip_to_line Skip lines until this one 577 * @return int Absolute line number 578 * @throws \LogicException 579 */ 580 public function find_tag($find_tag, $disallowed_tags, $skip_to_line = 0) 581 { 582 $find_tag_line = $skip_to_line ? $this->current_event_line - $skip_to_line + 1 : 0; 583 $found_comment_end = ($skip_to_line) ? true : false; 584 while (strpos(ltrim($this->file_lines[$this->current_event_line - $find_tag_line], "\t "), '* @' . $find_tag . ' ') !== 0) 585 { 586 if ($found_comment_end && ltrim($this->file_lines[$this->current_event_line - $find_tag_line], "\t") === '/**') 587 { 588 // Reached the start of this doc block 589 throw new \LogicException("Can not find '@{$find_tag}' information for event " 590 . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); 591 } 592 593 foreach ($disallowed_tags as $disallowed_tag) 594 { 595 if ($found_comment_end && strpos(ltrim($this->file_lines[$this->current_event_line - $find_tag_line], "\t "), '* @' . $disallowed_tag) === 0) 596 { 597 // Found @var after the @since 598 throw new \LogicException("Found '@{$disallowed_tag}' information after '@{$find_tag}' for event " 599 . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 3); 600 } 601 } 602 603 if (ltrim($this->file_lines[$this->current_event_line - $find_tag_line], "\t ") === '*/') 604 { 605 $found_comment_end = true; 606 } 607 608 $find_tag_line++; 609 if ($find_tag_line >= $this->current_event_line) 610 { 611 // Reached the start of the file 612 throw new \LogicException("Can not find '@{$find_tag}' information for event " 613 . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); 614 } 615 } 616 617 return $this->current_event_line - $find_tag_line; 618 } 619 620 /** 621 * Find a "@*" Information line 622 * 623 * @return int Absolute line number 624 * @throws \LogicException 625 */ 626 public function find_description() 627 { 628 $find_desc_line = 0; 629 while (ltrim($this->file_lines[$this->current_event_line - $find_desc_line], "\t") !== '/**') 630 { 631 $find_desc_line++; 632 if ($find_desc_line > $this->current_event_line) 633 { 634 // Reached the start of the file 635 throw new \LogicException("Can not find a description for event " 636 . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); 637 } 638 } 639 640 $find_desc_line = $this->current_event_line - $find_desc_line + 1; 641 642 $desc = trim($this->file_lines[$find_desc_line]); 643 if (strpos($desc, '* @') === 0 || $desc[0] !== '*' || substr($desc, 1) == '') 644 { 645 // First line of the doc block is a @-line, empty or only contains "*" 646 throw new \LogicException("Can not find a description for event " 647 . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); 648 } 649 650 return $find_desc_line; 651 } 652 653 /** 654 * Validate "@since" Information 655 * 656 * @param string $line 657 * @return string 658 * @throws \LogicException 659 */ 660 public function validate_since($line) 661 { 662 $match = array(); 663 preg_match('#^\* @since (\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?)$#', ltrim($line, "\t "), $match); 664 if (!isset($match[1])) 665 { 666 throw new \LogicException("Invalid '@since' information for event " 667 . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'"); 668 } 669 670 return $match[1]; 671 } 672 673 /** 674 * Validate "@changed" Information 675 * 676 * @param string $line 677 * @return string 678 * @throws \LogicException 679 */ 680 public function validate_changed($line) 681 { 682 $match = array(); 683 $line = str_replace("\t", ' ', ltrim($line, "\t ")); 684 preg_match('#^\* @changed (\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?)( (?:.*))?$#', $line, $match); 685 if (!isset($match[2])) 686 { 687 throw new \LogicException("Invalid '@changed' information for event " 688 . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'"); 689 } 690 691 return $match[2]; 692 } 693 694 /** 695 * Validate "@event" Information 696 * 697 * @param string $event_name 698 * @param string $line 699 * @return string 700 * @throws \LogicException 701 */ 702 public function validate_event($event_name, $line) 703 { 704 $event = substr(ltrim($line, "\t "), strlen('* @event ')); 705 706 if ($event !== trim($event)) 707 { 708 throw new \LogicException("Invalid '@event' information for event " 709 . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); 710 } 711 712 if ($event !== $event_name) 713 { 714 throw new \LogicException("Event name does not match '@event' tag for event " 715 . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); 716 } 717 718 return $event; 719 } 720 721 /** 722 * Validates that two arrays contain the same strings 723 * 724 * @param array $vars_array Variables found in the array line 725 * @param array $vars_docblock Variables found in the doc block 726 * @return null 727 * @throws \LogicException 728 */ 729 public function validate_vars_docblock_array($vars_array, $vars_docblock) 730 { 731 $vars_array = array_unique($vars_array); 732 $vars_docblock = array_unique($vars_docblock); 733 $sizeof_vars_array = count($vars_array); 734 735 if ($sizeof_vars_array !== count($vars_docblock) || $sizeof_vars_array !== count(array_intersect($vars_array, $vars_docblock))) 736 { 737 throw new \LogicException("\$vars array does not match the list of '@var' tags for event " 738 . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'"); 739 } 740 } 741 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Nov 11 20:33:01 2020 | Cross-referenced by PHPXref 0.7.1 |