Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

md_exporter.php

Zuletzt modifiziert: 02.04.2025, 15:02 - Dateigröße: 16.52 KiB


001  <?php
002  /**
003  *
004  * This file is part of the phpBB Forum Software package.
005  *
006  * @copyright (c) phpBB Limited <https://www.phpbb.com>
007  * @license GNU General Public License, version 2 (GPL-2.0)
008  *
009  * For full copyright and license information, please see
010  * the docs/CREDITS.txt file.
011  *
012  */
013   
014  namespace phpbb\event;
015   
016  /**
017  * Crawls through a markdown file and grabs all events
018  */
019  class md_exporter
020  {
021      /** @var string Path where we look for files*/
022      protected $path;
023   
024      /** @var string phpBB Root Path */
025      protected $root_path;
026   
027      /** @var string The minimum version for the events to return */
028      protected $min_version;
029   
030      /** @var string The maximum version for the events to return */
031      protected $max_version;
032   
033      /** @var string */
034      protected $filter;
035   
036      /** @var string */
037      protected $current_event;
038   
039      /** @var array */
040      protected $events;
041   
042      /**
043      * @param string $phpbb_root_path
044      * @param mixed $extension    String 'vendor/ext' to filter, null for phpBB core
045      * @param string $min_version
046      * @param string $max_version
047      */
048      public function __construct($phpbb_root_path, $extension = null, $min_version = null, $max_version = null)
049      {
050          $this->root_path = $phpbb_root_path;
051          $this->path = $this->root_path;
052          if ($extension)
053          {
054              $this->path .= 'ext/' . $extension . '/';
055          }
056   
057          $this->events = array();
058          $this->events_by_file = array();
059          $this->filter = $this->current_event = '';
060          $this->min_version = $min_version;
061          $this->max_version = $max_version;
062      }
063   
064      /**
065      * Get the list of all events
066      *
067      * @return array        Array with events: name => details
068      */
069      public function get_events()
070      {
071          return $this->events;
072      }
073   
074      /**
075      * @param string $md_file    Relative from phpBB root
076      * @return int        Number of events found
077      * @throws \LogicException
078      */
079      public function crawl_phpbb_directory_adm($md_file)
080      {
081          $this->crawl_eventsmd($md_file, 'adm');
082   
083          $file_list = $this->get_recursive_file_list($this->path  . 'adm/style/');
084          foreach ($file_list as $file)
085          {
086              $file_name = 'adm/style/' . $file;
087              $this->validate_events_from_file($file_name, $this->crawl_file_for_events($file_name));
088          }
089   
090          return count($this->events);
091      }
092   
093      /**
094      * @param string $md_file    Relative from phpBB root
095      * @return int        Number of events found
096      * @throws \LogicException
097      */
098      public function crawl_phpbb_directory_styles($md_file)
099      {
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  }
693