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. |
|
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
md_exporter.php
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