[ Index ]

PHP Cross Reference of phpBB-3.2.11-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 php events as a wiki table
 247      *
 248      * @param string $action
 249      * @return string        Number of events found
 250      */
 251  	public function export_events_for_wiki($action = '')
 252      {
 253          if ($this->filter === 'adm')
 254          {
 255              if ($action === 'diff')
 256              {
 257                  $wiki_page = '=== ACP Template Events ===' . "\n";
 258              }
 259              else
 260              {
 261                  $wiki_page = '= ACP Template Events =' . "\n";
 262              }
 263              $wiki_page .= '{| class="zebra sortable" cellspacing="0" cellpadding="5"' . "\n";
 264              $wiki_page .= '! Identifier !! Placement !! Added in Release !! Explanation' . "\n";
 265          }
 266          else
 267          {
 268              if ($action === 'diff')
 269              {
 270                  $wiki_page = '=== Template Events ===' . "\n";
 271              }
 272              else
 273              {
 274                  $wiki_page = '= Template Events =' . "\n";
 275              }
 276              $wiki_page .= '{| class="zebra sortable" cellspacing="0" cellpadding="5"' . "\n";
 277              $wiki_page .= '! Identifier !! Prosilver Placement (If applicable) !! Added in Release !! Explanation' . "\n";
 278          }
 279  
 280          foreach ($this->events as $event_name => $event)
 281          {
 282              $wiki_page .= "|- id=\"{$event_name}\"\n";
 283              $wiki_page .= "| [[#{$event_name}|{$event_name}]] || ";
 284  
 285              if ($this->filter === 'adm')
 286              {
 287                  $wiki_page .= implode(', ', $event['files']['adm']);
 288              }
 289              else
 290              {
 291                  $wiki_page .= implode(', ', $event['files']['prosilver']);
 292              }
 293  
 294              $wiki_page .= " || {$event['since']} || " . str_replace("\n", ' ', $event['description']) . "\n";
 295          }
 296          $wiki_page .= '|}' . "\n";
 297  
 298          return $wiki_page;
 299      }
 300  
 301      /**
 302      * Validates a template event name
 303      *
 304      * @param $event_name
 305      * @return null
 306      * @throws \LogicException
 307      */
 308  	public function validate_event_name($event_name)
 309      {
 310          if (!preg_match('#^([a-z][a-z0-9]*(?:_[a-z][a-z0-9]*)+)$#', $event_name))
 311          {
 312              throw new \LogicException("Invalid event name '{$event_name}'");
 313          }
 314      }
 315  
 316      /**
 317      * Validate "Since" Information
 318      *
 319      * @param string $since
 320      * @return string
 321      * @throws \LogicException
 322      */
 323  	public function validate_since($since)
 324      {
 325          if (!$this->validate_version($since))
 326          {
 327              throw new \LogicException("Invalid since information found for event '{$this->current_event}'");
 328          }
 329  
 330          return $since;
 331      }
 332  
 333      /**
 334      * Validate "Changed" Information
 335      *
 336      * @param string $changed
 337      * @return string
 338      * @throws \LogicException
 339      */
 340  	public function validate_changed($changed)
 341      {
 342          if (strpos($changed, ' ') !== false)
 343          {
 344              list($version, $description) = explode(' ', $changed, 2);
 345          }
 346          else
 347          {
 348              $version = $changed;
 349              $description = '';
 350          }
 351  
 352          if (!$this->validate_version($version))
 353          {
 354              throw new \LogicException("Invalid changed information found for event '{$this->current_event}'");
 355          }
 356  
 357          return array($version, $description);
 358      }
 359  
 360      /**
 361      * Validate "version" Information
 362      *
 363      * @param string $version
 364      * @return bool True if valid, false otherwise
 365      */
 366  	public function validate_version($version)
 367      {
 368          return preg_match('#^\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?$#', $version);
 369      }
 370  
 371      /**
 372      * Validate the files list
 373      *
 374      * @param string $file_details
 375      * @return array
 376      * @throws \LogicException
 377      */
 378  	public function validate_file_list($file_details)
 379      {
 380          $files_list = array(
 381              'prosilver'        => array(),
 382              'adm'            => array(),
 383          );
 384  
 385          // Multi file list
 386          if (strpos($file_details, "* Locations:\n    + ") === 0)
 387          {
 388              $file_details = substr($file_details, strlen("* Locations:\n    + "));
 389              $files = explode("\n    + ", $file_details);
 390              foreach ($files as $file)
 391              {
 392                  if (!preg_match('#^([^ ]+)( \([0-9]+\))?$#', $file))
 393                  {
 394                      throw new \LogicException("Invalid event instances for file '{$file}' found for event '{$this->current_event}'", 1);
 395                  }
 396  
 397                  list($file) = explode(" ", $file);
 398  
 399                  if (!file_exists($this->path . $file) || substr($file, -5) !== '.html')
 400                  {
 401                      throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 2);
 402                  }
 403  
 404                  if (($this->filter !== 'adm') && strpos($file, 'styles/prosilver/template/') === 0)
 405                  {
 406                      $files_list['prosilver'][] = substr($file, strlen('styles/prosilver/template/'));
 407                  }
 408                  else if (($this->filter === 'adm') && strpos($file, 'adm/style/') === 0)
 409                  {
 410                      $files_list['adm'][] = substr($file, strlen('adm/style/'));
 411                  }
 412                  else
 413                  {
 414                      throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 3);
 415                  }
 416  
 417                  $this->events_by_file[$file][] = $this->current_event;
 418              }
 419          }
 420          else if ($this->filter == 'adm')
 421          {
 422              $file = substr($file_details, strlen('* Location: '));
 423              if (!file_exists($this->path . $file) || substr($file, -5) !== '.html')
 424              {
 425                  throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 1);
 426              }
 427  
 428              $files_list['adm'][] =  substr($file, strlen('adm/style/'));
 429  
 430              $this->events_by_file[$file][] = $this->current_event;
 431          }
 432          else
 433          {
 434              throw new \LogicException("Invalid file list found for event '{$this->current_event}'", 1);
 435          }
 436  
 437          return $files_list;
 438      }
 439  
 440      /**
 441      * Get all template events in a template file
 442      *
 443      * @param string $file
 444      * @return array
 445      * @throws \LogicException
 446      */
 447  	public function crawl_file_for_events($file)
 448      {
 449          if (!file_exists($this->path . $file))
 450          {
 451              throw new \LogicException("File '{$file}' does not exist", 1);
 452          }
 453  
 454          $event_list = array();
 455          $file_content = file_get_contents($this->path . $file);
 456  
 457          preg_match_all('/(?:{%|<!--) EVENT (.*) (?:%}|-->)/U', $file_content, $event_list);
 458  
 459          return $event_list[1];
 460      }
 461  
 462      /**
 463      * Validates whether all events from $file are in the md file and vice-versa
 464      *
 465      * @param string $file
 466      * @param array $events
 467      * @return true
 468      * @throws \LogicException
 469      */
 470  	public function validate_events_from_file($file, array $events)
 471      {
 472          if (empty($this->events_by_file[$file]) && empty($events))
 473          {
 474              return true;
 475          }
 476          else if (empty($this->events_by_file[$file]))
 477          {
 478              $event_list = implode("', '", $events);
 479              throw new \LogicException("File '{$file}' should not contain events, but contains: "
 480                  . "'{$event_list}'", 1);
 481          }
 482          else if (empty($events))
 483          {
 484              $event_list = implode("', '", $this->events_by_file[$file]);
 485              throw new \LogicException("File '{$file}' contains no events, but should contain: "
 486                  . "'{$event_list}'", 1);
 487          }
 488  
 489          $missing_events_from_file = array();
 490          foreach ($this->events_by_file[$file] as $event)
 491          {
 492              if (!in_array($event, $events))
 493              {
 494                  $missing_events_from_file[] = $event;
 495              }
 496          }
 497  
 498          if (!empty($missing_events_from_file))
 499          {
 500              $event_list = implode("', '", $missing_events_from_file);
 501              throw new \LogicException("File '{$file}' does not contain events: '{$event_list}'", 2);
 502          }
 503  
 504          $missing_events_from_md = array();
 505          foreach ($events as $event)
 506          {
 507              if (!in_array($event, $this->events_by_file[$file]))
 508              {
 509                  $missing_events_from_md[] = $event;
 510              }
 511          }
 512  
 513          if (!empty($missing_events_from_md))
 514          {
 515              $event_list = implode("', '", $missing_events_from_md);
 516              throw new \LogicException("File '{$file}' contains additional events: '{$event_list}'", 3);
 517          }
 518  
 519          return true;
 520      }
 521  
 522      /**
 523      * Returns a list of files in $dir
 524      *
 525      * Works recursive with any depth
 526      *
 527      * @param    string    $dir    Directory to go through
 528      * @return    array    List of files (including directories)
 529      */
 530  	public function get_recursive_file_list($dir)
 531      {
 532          try
 533          {
 534              $iterator = new \RecursiveIteratorIterator(
 535                  new \phpbb\recursive_dot_prefix_filter_iterator(
 536                      new \RecursiveDirectoryIterator(
 537                          $dir,
 538                          \FilesystemIterator::SKIP_DOTS
 539                      )
 540                  ),
 541                  \RecursiveIteratorIterator::SELF_FIRST
 542              );
 543          }
 544          catch (\Exception $e)
 545          {
 546              return array();
 547          }
 548  
 549          $files = array();
 550          foreach ($iterator as $file_info)
 551          {
 552              /** @var \RecursiveDirectoryIterator $file_info */
 553              if ($file_info->isDir())
 554              {
 555                  continue;
 556              }
 557  
 558              $relative_path = $iterator->getInnerIterator()->getSubPathname();
 559  
 560              if (substr($relative_path, -5) == '.html')
 561              {
 562                  $files[] = str_replace(DIRECTORY_SEPARATOR, '/', $relative_path);
 563              }
 564          }
 565  
 566          return $files;
 567      }
 568  }


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1