[ Index ]

PHP Cross Reference of phpBB-3.3.7-deutsch

title

Body

[close]

/phpbb/event/ -> md_exporter.php (source)

   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  * Crawls through a markdown file and grabs all events
  18  */
  19  class md_exporter
  20  {
  21      /** @var string Path where we look for files*/
  22      protected $path;
  23  
  24      /** @var string phpBB Root Path */
  25      protected $root_path;
  26  
  27      /** @var string The minimum version for the events to return */
  28      protected $min_version;
  29  
  30      /** @var string The maximum version for the events to return */
  31      protected $max_version;
  32  
  33      /** @var string */
  34      protected $filter;
  35  
  36      /** @var string */
  37      protected $current_event;
  38  
  39      /** @var array */
  40      protected $events;
  41  
  42      /**
  43      * @param string $phpbb_root_path
  44      * @param mixed $extension    String 'vendor/ext' to filter, null for phpBB core
  45      * @param string $min_version
  46      * @param string $max_version
  47      */
  48  	public function __construct($phpbb_root_path, $extension = null, $min_version = null, $max_version = null)
  49      {
  50          $this->root_path = $phpbb_root_path;
  51          $this->path = $this->root_path;
  52          if ($extension)
  53          {
  54              $this->path .= 'ext/' . $extension . '/';
  55          }
  56  
  57          $this->events = array();
  58          $this->events_by_file = array();
  59          $this->filter = $this->current_event = '';
  60          $this->min_version = $min_version;
  61          $this->max_version = $max_version;
  62      }
  63  
  64      /**
  65      * Get the list of all events
  66      *
  67      * @return array        Array with events: name => details
  68      */
  69  	public function get_events()
  70      {
  71          return $this->events;
  72      }
  73  
  74      /**
  75      * @param string $md_file    Relative from phpBB root
  76      * @return int        Number of events found
  77      * @throws \LogicException
  78      */
  79  	public function crawl_phpbb_directory_adm($md_file)
  80      {
  81          $this->crawl_eventsmd($md_file, 'adm');
  82  
  83          $file_list = $this->get_recursive_file_list($this->path  . 'adm/style/');
  84          foreach ($file_list as $file)
  85          {
  86              $file_name = 'adm/style/' . $file;
  87              $this->validate_events_from_file($file_name, $this->crawl_file_for_events($file_name));
  88          }
  89  
  90          return count($this->events);
  91      }
  92  
  93      /**
  94      * @param string $md_file    Relative from phpBB root
  95      * @return int        Number of events found
  96      * @throws \LogicException
  97      */
  98  	public function crawl_phpbb_directory_styles($md_file)
  99      {
 100          $this->crawl_eventsmd($md_file, 'styles');
 101  
 102          $styles = array('prosilver');
 103          foreach ($styles as $style)
 104          {
 105              $file_list = $this->get_recursive_file_list(
 106                  $this->path . 'styles/' . $style . '/template/'
 107              );
 108  
 109              foreach ($file_list as $file)
 110              {
 111                  $file_name = 'styles/' . $style . '/template/' . $file;
 112                  $this->validate_events_from_file($file_name, $this->crawl_file_for_events($file_name));
 113              }
 114          }
 115  
 116          return count($this->events);
 117      }
 118  
 119      /**
 120      * @param string $md_file    Relative from phpBB root
 121      * @param string $filter        Should be 'styles' or 'adm'
 122      * @return int        Number of events found
 123      * @throws \LogicException
 124      */
 125  	public function crawl_eventsmd($md_file, $filter)
 126      {
 127          if (!file_exists($this->path . $md_file))
 128          {
 129              throw new \LogicException("The event docs file '{$md_file}' could not be found");
 130          }
 131  
 132          $file_content = file_get_contents($this->path . $md_file);
 133          $this->filter = $filter;
 134  
 135          $events = explode("\n\n", $file_content);
 136          foreach ($events as $event)
 137          {
 138              // Last row of the file
 139              if (strpos($event, "\n===\n") === false)
 140              {
 141                  continue;
 142              }
 143  
 144              list($event_name, $details) = explode("\n===\n", $event, 2);
 145              $this->validate_event_name($event_name);
 146              $sorted_events = [$this->current_event, $event_name];
 147              natsort($sorted_events);
 148              $this->current_event = $event_name;
 149  
 150              if (isset($this->events[$this->current_event]))
 151              {
 152                  throw new \LogicException("The event '{$this->current_event}' is defined multiple times");
 153              }
 154  
 155              // Use array_values() to get actual first element and check against natural order
 156              if (array_values($sorted_events)[0] === $event_name)
 157              {
 158                  throw new \LogicException("The event '{$sorted_events[1]}' should be defined before '{$sorted_events[0]}'");
 159              }
 160  
 161              if (($this->filter == 'adm' && strpos($this->current_event, 'acp_') !== 0)
 162                  || ($this->filter == 'styles' && strpos($this->current_event, 'acp_') === 0))
 163              {
 164                  continue;
 165              }
 166  
 167              list($file_details, $details) = explode("\n* Since: ", $details, 2);
 168  
 169              $changed_versions = array();
 170              if (strpos($details, "\n* Changed: ") !== false)
 171              {
 172                  list($since, $details) = explode("\n* Changed: ", $details, 2);
 173                  while (strpos($details, "\n* Changed: ") !== false)
 174                  {
 175                      list($changed, $details) = explode("\n* Changed: ", $details, 2);
 176                      $changed_versions[] = $changed;
 177                  }
 178                  list($changed, $description) = explode("\n* Purpose: ", $details, 2);
 179                  $changed_versions[] = $changed;
 180              }
 181              else
 182              {
 183                  list($since, $description) = explode("\n* Purpose: ", $details, 2);
 184                  $changed_versions = array();
 185              }
 186  
 187              $files = $this->validate_file_list($file_details);
 188              $since = $this->validate_since($since);
 189              $changes = array();
 190              foreach ($changed_versions as $changed)
 191              {
 192                  list($changed_version, $changed_description) = $this->validate_changed($changed);
 193  
 194                  if (isset($changes[$changed_version]))
 195                  {
 196                      throw new \LogicException("Duplicate change information found for event '{$this->current_event}'");
 197                  }
 198  
 199                  $changes[$changed_version] = $changed_description;
 200              }
 201              $description = trim($description, "\n") . "\n";
 202  
 203              if (!$this->version_is_filtered($since))
 204              {
 205                  $is_filtered = false;
 206                  foreach ($changes as $version => $null)
 207                  {
 208                      if ($this->version_is_filtered($version))
 209                      {
 210                          $is_filtered = true;
 211                          break;
 212                      }
 213                  }
 214  
 215                  if (!$is_filtered)
 216                  {
 217                      continue;
 218                  }
 219              }
 220  
 221              $this->events[$event_name] = array(
 222                  'event'            => $this->current_event,
 223                  'files'            => $files,
 224                  'since'            => $since,
 225                  'changed'        => $changes,
 226                  'description'    => $description,
 227              );
 228          }
 229  
 230          return count($this->events);
 231      }
 232  
 233      /**
 234       * The version to check
 235       *
 236       * @param string $version
 237       * @return bool
 238       */
 239  	protected function version_is_filtered($version)
 240      {
 241          return (!$this->min_version || phpbb_version_compare($this->min_version, $version, '<='))
 242          && (!$this->max_version || phpbb_version_compare($this->max_version, $version, '>='));
 243      }
 244  
 245      /**
 246      * Format the md events as a wiki table
 247      *
 248      * @param string $action
 249      * @return string        Number of events found * @deprecated since 3.2
 250      * @deprecated 3.3.5-RC1 (To be removed: 4.0.0-a1)
 251      */
 252  	public function export_events_for_wiki($action = '')
 253      {
 254          if ($this->filter === 'adm')
 255          {
 256              if ($action === 'diff')
 257              {
 258                  $wiki_page = '=== ACP Template Events ===' . "\n";
 259              }
 260              else
 261              {
 262                  $wiki_page = '= ACP Template Events =' . "\n";
 263              }
 264              $wiki_page .= '{| class="zebra sortable" cellspacing="0" cellpadding="5"' . "\n";
 265              $wiki_page .= '! Identifier !! Placement !! Added in Release !! Explanation' . "\n";
 266          }
 267          else
 268          {
 269              if ($action === 'diff')
 270              {
 271                  $wiki_page = '=== Template Events ===' . "\n";
 272              }
 273              else
 274              {
 275                  $wiki_page = '= Template Events =' . "\n";
 276              }
 277              $wiki_page .= '{| class="zebra sortable" cellspacing="0" cellpadding="5"' . "\n";
 278              $wiki_page .= '! Identifier !! Prosilver Placement (If applicable) !! Added in Release !! Explanation' . "\n";
 279          }
 280  
 281          foreach ($this->events as $event_name => $event)
 282          {
 283              $wiki_page .= "|- id=\"{$event_name}\"\n";
 284              $wiki_page .= "| [[#{$event_name}|{$event_name}]] || ";
 285  
 286              if ($this->filter === 'adm')
 287              {
 288                  $wiki_page .= implode(', ', $event['files']['adm']);
 289              }
 290              else
 291              {
 292                  $wiki_page .= implode(', ', $event['files']['prosilver']);
 293              }
 294  
 295              $wiki_page .= " || {$event['since']} || " . str_replace("\n", ' ', $event['description']) . "\n";
 296          }
 297          $wiki_page .= '|}' . "\n";
 298  
 299          return $wiki_page;
 300      }
 301  
 302      /**
 303       * Format the md events as a rst table
 304       *
 305       * @param string $action
 306       * @return string        Number of events found
 307       */
 308  	public function export_events_for_rst(string $action = ''): string
 309      {
 310          $rst_exporter = new rst_exporter();
 311  
 312          if ($this->filter === 'adm')
 313          {
 314              if ($action === 'diff')
 315              {
 316                  $rst_exporter->add_section_header('h3', 'ACP Template Events');
 317              }
 318              else
 319              {
 320                  $rst_exporter->add_section_header('h2', 'ACP Template Events');
 321              }
 322  
 323              $rst_exporter->set_columns([
 324                  'event'            => 'Identifier',
 325                  'files'            => 'Placement',
 326                  'since'            => 'Added in Release',
 327                  'description'    => 'Explanation',
 328              ]);
 329          }
 330          else
 331          {
 332              if ($action === 'diff')
 333              {
 334                  $rst_exporter->add_section_header('h3', 'Template Events');
 335              }
 336              else
 337              {
 338                  $rst_exporter->add_section_header('h2', 'Template Events');
 339              }
 340  
 341              $rst_exporter->set_columns([
 342                  'event'            => 'Identifier',
 343                  'files'            => 'Prosilver Placement (If applicable)',
 344                  'since'            => 'Added in Release',
 345                  'description'    => 'Explanation',
 346              ]);
 347          }
 348  
 349          $events = [];
 350          foreach ($this->events as $event_name => $event)
 351          {
 352              $files = $this->filter === 'adm' ? implode(', ', $event['files']['adm']) : implode(', ', $event['files']['prosilver']);
 353  
 354              $events[] = [
 355                  'event'            => $event_name,
 356                  'files'            => $files,
 357                  'since'            => $event['since'],
 358                  'description'    => str_replace("\n", '<br>', rtrim($event['description'])),
 359              ];
 360          }
 361  
 362          $rst_exporter->generate_events_table($events);
 363  
 364          return $rst_exporter->get_rst_output();
 365      }
 366  
 367      /**
 368       * Format the md events as BBCode list
 369       *
 370       * @param string $action
 371       * @return string        Events BBCode
 372       */
 373  	public function export_events_for_bbcode(string $action = ''): string
 374      {
 375          if ($this->filter === 'adm')
 376          {
 377              if ($action === 'diff')
 378              {
 379                  $bbcode_text = "[size=150]ACP Template Events[/size]\n";
 380              }
 381              else
 382              {
 383                  $bbcode_text = "[size=200]ACP Template Events[/size]\n";
 384              }
 385          }
 386          else
 387          {
 388              if ($action === 'diff')
 389              {
 390                  $bbcode_text = "[size=150]Template Events[/size]\n";
 391              }
 392              else
 393              {
 394                  $bbcode_text = "[size=200]Template Events[/size]\n";
 395              }
 396          }
 397  
 398          if (!count($this->events))
 399          {
 400              return $bbcode_text . "[list][*][i]None[/i][/list]\n";
 401          }
 402  
 403          foreach ($this->events as $event_name => $event)
 404          {
 405              $bbcode_text .= "[list]\n";
 406              $bbcode_text .= "[*][b]{$event_name}[/b]\n";
 407  
 408              if ($this->filter === 'adm')
 409              {
 410                  $bbcode_text .= "Placement: " . implode(', ', $event['files']['adm']) . "\n";
 411              }
 412              else
 413              {
 414                  $bbcode_text .= "Prosilver Placement: " . implode(', ', $event['files']['prosilver']) . "\n";
 415              }
 416  
 417              $bbcode_text .= "Added in Release: {$event['since']}\n";
 418              $bbcode_text .= "Explanation: {$event['description']}\n";
 419              $bbcode_text .= "[/list]\n";
 420          }
 421  
 422          return $bbcode_text;
 423      }
 424  
 425      /**
 426      * Validates a template event name
 427      *
 428      * @param $event_name
 429      * @return null
 430      * @throws \LogicException
 431      */
 432  	public function validate_event_name($event_name)
 433      {
 434          if (!preg_match('#^([a-z][a-z0-9]*(?:_[a-z][a-z0-9]*)+)$#', $event_name))
 435          {
 436              throw new \LogicException("Invalid event name '{$event_name}'");
 437          }
 438      }
 439  
 440      /**
 441      * Validate "Since" Information
 442      *
 443      * @param string $since
 444      * @return string
 445      * @throws \LogicException
 446      */
 447  	public function validate_since($since)
 448      {
 449          if (!$this->validate_version($since))
 450          {
 451              throw new \LogicException("Invalid since information found for event '{$this->current_event}'");
 452          }
 453  
 454          return $since;
 455      }
 456  
 457      /**
 458      * Validate "Changed" Information
 459      *
 460      * @param string $changed
 461      * @return string
 462      * @throws \LogicException
 463      */
 464  	public function validate_changed($changed)
 465      {
 466          if (strpos($changed, ' ') !== false)
 467          {
 468              list($version, $description) = explode(' ', $changed, 2);
 469          }
 470          else
 471          {
 472              $version = $changed;
 473              $description = '';
 474          }
 475  
 476          if (!$this->validate_version($version))
 477          {
 478              throw new \LogicException("Invalid changed information found for event '{$this->current_event}'");
 479          }
 480  
 481          return array($version, $description);
 482      }
 483  
 484      /**
 485      * Validate "version" Information
 486      *
 487      * @param string $version
 488      * @return bool True if valid, false otherwise
 489      */
 490  	public function validate_version($version)
 491      {
 492          return preg_match('#^\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?$#', $version);
 493      }
 494  
 495      /**
 496      * Validate the files list
 497      *
 498      * @param string $file_details
 499      * @return array
 500      * @throws \LogicException
 501      */
 502  	public function validate_file_list($file_details)
 503      {
 504          $files_list = array(
 505              'prosilver'        => array(),
 506              'adm'            => array(),
 507          );
 508  
 509          // Multi file list
 510          if (strpos($file_details, "* Locations:\n    + ") === 0)
 511          {
 512              $file_details = substr($file_details, strlen("* Locations:\n    + "));
 513              $files = explode("\n    + ", $file_details);
 514              foreach ($files as $file)
 515              {
 516                  if (!preg_match('#^([^ ]+)( \([0-9]+\))?$#', $file))
 517                  {
 518                      throw new \LogicException("Invalid event instances for file '{$file}' found for event '{$this->current_event}'", 1);
 519                  }
 520  
 521                  list($file) = explode(" ", $file);
 522  
 523                  if (!file_exists($this->path . $file) || substr($file, -5) !== '.html')
 524                  {
 525                      throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 2);
 526                  }
 527  
 528                  if (($this->filter !== 'adm') && strpos($file, 'styles/prosilver/template/') === 0)
 529                  {
 530                      $files_list['prosilver'][] = substr($file, strlen('styles/prosilver/template/'));
 531                  }
 532                  else if (($this->filter === 'adm') && strpos($file, 'adm/style/') === 0)
 533                  {
 534                      $files_list['adm'][] = substr($file, strlen('adm/style/'));
 535                  }
 536                  else
 537                  {
 538                      throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 3);
 539                  }
 540  
 541                  $this->events_by_file[$file][] = $this->current_event;
 542              }
 543          }
 544          else if ($this->filter == 'adm')
 545          {
 546              $file = substr($file_details, strlen('* Location: '));
 547              if (!file_exists($this->path . $file) || substr($file, -5) !== '.html')
 548              {
 549                  throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 1);
 550              }
 551  
 552              $files_list['adm'][] =  substr($file, strlen('adm/style/'));
 553  
 554              $this->events_by_file[$file][] = $this->current_event;
 555          }
 556          else
 557          {
 558              throw new \LogicException("Invalid file list found for event '{$this->current_event}'", 1);
 559          }
 560  
 561          return $files_list;
 562      }
 563  
 564      /**
 565      * Get all template events in a template file
 566      *
 567      * @param string $file
 568      * @return array
 569      * @throws \LogicException
 570      */
 571  	public function crawl_file_for_events($file)
 572      {
 573          if (!file_exists($this->path . $file))
 574          {
 575              throw new \LogicException("File '{$file}' does not exist", 1);
 576          }
 577  
 578          $event_list = array();
 579          $file_content = file_get_contents($this->path . $file);
 580  
 581          preg_match_all('/(?:{%|<!--) EVENT (.*) (?:%}|-->)/U', $file_content, $event_list);
 582  
 583          return $event_list[1];
 584      }
 585  
 586      /**
 587      * Validates whether all events from $file are in the md file and vice-versa
 588      *
 589      * @param string $file
 590      * @param array $events
 591      * @return true
 592      * @throws \LogicException
 593      */
 594  	public function validate_events_from_file($file, array $events)
 595      {
 596          if (empty($this->events_by_file[$file]) && empty($events))
 597          {
 598              return true;
 599          }
 600          else if (empty($this->events_by_file[$file]))
 601          {
 602              $event_list = implode("', '", $events);
 603              throw new \LogicException("File '{$file}' should not contain events, but contains: "
 604                  . "'{$event_list}'", 1);
 605          }
 606          else if (empty($events))
 607          {
 608              $event_list = implode("', '", $this->events_by_file[$file]);
 609              throw new \LogicException("File '{$file}' contains no events, but should contain: "
 610                  . "'{$event_list}'", 1);
 611          }
 612  
 613          $missing_events_from_file = array();
 614          foreach ($this->events_by_file[$file] as $event)
 615          {
 616              if (!in_array($event, $events))
 617              {
 618                  $missing_events_from_file[] = $event;
 619              }
 620          }
 621  
 622          if (!empty($missing_events_from_file))
 623          {
 624              $event_list = implode("', '", $missing_events_from_file);
 625              throw new \LogicException("File '{$file}' does not contain events: '{$event_list}'", 2);
 626          }
 627  
 628          $missing_events_from_md = array();
 629          foreach ($events as $event)
 630          {
 631              if (!in_array($event, $this->events_by_file[$file]))
 632              {
 633                  $missing_events_from_md[] = $event;
 634              }
 635          }
 636  
 637          if (!empty($missing_events_from_md))
 638          {
 639              $event_list = implode("', '", $missing_events_from_md);
 640              throw new \LogicException("File '{$file}' contains additional events: '{$event_list}'", 3);
 641          }
 642  
 643          return true;
 644      }
 645  
 646      /**
 647      * Returns a list of files in $dir
 648      *
 649      * Works recursive with any depth
 650      *
 651      * @param    string    $dir    Directory to go through
 652      * @return    array    List of files (including directories)
 653      */
 654  	public function get_recursive_file_list($dir)
 655      {
 656          try
 657          {
 658              $iterator = new \RecursiveIteratorIterator(
 659                  new \phpbb\recursive_dot_prefix_filter_iterator(
 660                      new \RecursiveDirectoryIterator(
 661                          $dir,
 662                          \FilesystemIterator::SKIP_DOTS
 663                      )
 664                  ),
 665                  \RecursiveIteratorIterator::SELF_FIRST
 666              );
 667          }
 668          catch (\Exception $e)
 669          {
 670              return array();
 671          }
 672  
 673          $files = array();
 674          foreach ($iterator as $file_info)
 675          {
 676              /** @var \RecursiveDirectoryIterator $file_info */
 677              if ($file_info->isDir())
 678              {
 679                  continue;
 680              }
 681  
 682              $relative_path = $iterator->getInnerIterator()->getSubPathname();
 683  
 684              if (substr($relative_path, -5) == '.html')
 685              {
 686                  $files[] = str_replace(DIRECTORY_SEPARATOR, '/', $relative_path);
 687              }
 688          }
 689  
 690          return $files;
 691      }
 692  }


Generated: Thu Mar 24 21:31:15 2022 Cross-referenced by PHPXref 0.7.1