Verzeichnisstruktur phpBB-3.2.0
- Veröffentlicht
- 06.01.2017
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 sizeof($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 sizeof($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 $this->current_event = $event_name;
147
148 if (isset($this->events[$this->current_event]))
149 {
150 throw new \LogicException("The event '{$this->current_event}' is defined multiple times");
151 }
152
153 if (($this->filter == 'adm' && strpos($this->current_event, 'acp_') !== 0)
154 || ($this->filter == 'styles' && strpos($this->current_event, 'acp_') === 0))
155 {
156 continue;
157 }
158
159 list($file_details, $details) = explode("\n* Since: ", $details, 2);
160
161 $changed_versions = array();
162 if (strpos($details, "\n* Changed: ") !== false)
163 {
164 list($since, $details) = explode("\n* Changed: ", $details, 2);
165 while (strpos($details, "\n* Changed: ") !== false)
166 {
167 list($changed, $details) = explode("\n* Changed: ", $details, 2);
168 $changed_versions[] = $changed;
169 }
170 list($changed, $description) = explode("\n* Purpose: ", $details, 2);
171 $changed_versions[] = $changed;
172 }
173 else
174 {
175 list($since, $description) = explode("\n* Purpose: ", $details, 2);
176 $changed_versions = array();
177 }
178
179 $files = $this->validate_file_list($file_details);
180 $since = $this->validate_since($since);
181 $changes = array();
182 foreach ($changed_versions as $changed)
183 {
184 list($changed_version, $changed_description) = $this->validate_changed($changed);
185
186 if (isset($changes[$changed_version]))
187 {
188 throw new \LogicException("Duplicate change information found for event '{$this->current_event}'");
189 }
190
191 $changes[$changed_version] = $changed_description;
192 }
193 $description = trim($description, "\n") . "\n";
194
195 if (!$this->version_is_filtered($since))
196 {
197 $is_filtered = false;
198 foreach ($changes as $version => $null)
199 {
200 if ($this->version_is_filtered($version))
201 {
202 $is_filtered = true;
203 break;
204 }
205 }
206
207 if (!$is_filtered)
208 {
209 continue;
210 }
211 }
212
213 $this->events[$event_name] = array(
214 'event' => $this->current_event,
215 'files' => $files,
216 'since' => $since,
217 'changed' => $changes,
218 'description' => $description,
219 );
220 }
221
222 return sizeof($this->events);
223 }
224
225 /**
226 * The version to check
227 *
228 * @param string $version
229 * @return bool
230 */
231 protected function version_is_filtered($version)
232 {
233 return (!$this->min_version || phpbb_version_compare($this->min_version, $version, '<='))
234 && (!$this->max_version || phpbb_version_compare($this->max_version, $version, '>='));
235 }
236
237 /**
238 * Format the php events as a wiki table
239 *
240 * @param string $action
241 * @return string Number of events found
242 */
243 public function export_events_for_wiki($action = '')
244 {
245 if ($this->filter === 'adm')
246 {
247 if ($action === 'diff')
248 {
249 $wiki_page = '=== ACP Template Events ===' . "\n";
250 }
251 else
252 {
253 $wiki_page = '= ACP Template Events =' . "\n";
254 }
255 $wiki_page .= '{| class="zebra sortable" cellspacing="0" cellpadding="5"' . "\n";
256 $wiki_page .= '! Identifier !! Placement !! Added in Release !! Explanation' . "\n";
257 }
258 else
259 {
260 if ($action === 'diff')
261 {
262 $wiki_page = '=== Template Events ===' . "\n";
263 }
264 else
265 {
266 $wiki_page = '= Template Events =' . "\n";
267 }
268 $wiki_page .= '{| class="zebra sortable" cellspacing="0" cellpadding="5"' . "\n";
269 $wiki_page .= '! Identifier !! Prosilver Placement (If applicable) !! Added in Release !! Explanation' . "\n";
270 }
271
272 foreach ($this->events as $event_name => $event)
273 {
274 $wiki_page .= "|- id=\"{$event_name}\"\n";
275 $wiki_page .= "| [[#{$event_name}|{$event_name}]] || ";
276
277 if ($this->filter === 'adm')
278 {
279 $wiki_page .= implode(', ', $event['files']['adm']);
280 }
281 else
282 {
283 $wiki_page .= implode(', ', $event['files']['prosilver']);
284 }
285
286 $wiki_page .= " || {$event['since']} || " . str_replace("\n", ' ', $event['description']) . "\n";
287 }
288 $wiki_page .= '|}' . "\n";
289
290 return $wiki_page;
291 }
292
293 /**
294 * Validates a template event name
295 *
296 * @param $event_name
297 * @return null
298 * @throws \LogicException
299 */
300 public function validate_event_name($event_name)
301 {
302 if (!preg_match('#^([a-z][a-z0-9]*(?:_[a-z][a-z0-9]*)+)$#', $event_name))
303 {
304 throw new \LogicException("Invalid event name '{$event_name}'");
305 }
306 }
307
308 /**
309 * Validate "Since" Information
310 *
311 * @param string $since
312 * @return string
313 * @throws \LogicException
314 */
315 public function validate_since($since)
316 {
317 if (!$this->validate_version($since))
318 {
319 throw new \LogicException("Invalid since information found for event '{$this->current_event}'");
320 }
321
322 return $since;
323 }
324
325 /**
326 * Validate "Changed" Information
327 *
328 * @param string $changed
329 * @return string
330 * @throws \LogicException
331 */
332 public function validate_changed($changed)
333 {
334 if (strpos($changed, ' ') !== false)
335 {
336 list($version, $description) = explode(' ', $changed, 2);
337 }
338 else
339 {
340 $version = $changed;
341 $description = '';
342 }
343
344 if (!$this->validate_version($version))
345 {
346 throw new \LogicException("Invalid changed information found for event '{$this->current_event}'");
347 }
348
349 return array($version, $description);
350 }
351
352 /**
353 * Validate "version" Information
354 *
355 * @param string $version
356 * @return bool True if valid, false otherwise
357 */
358 public function validate_version($version)
359 {
360 return preg_match('#^\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?$#', $version);
361 }
362
363 /**
364 * Validate the files list
365 *
366 * @param string $file_details
367 * @return array
368 * @throws \LogicException
369 */
370 public function validate_file_list($file_details)
371 {
372 $files_list = array(
373 'prosilver' => array(),
374 'adm' => array(),
375 );
376
377 // Multi file list
378 if (strpos($file_details, "* Locations:\n + ") === 0)
379 {
380 $file_details = substr($file_details, strlen("* Locations:\n + "));
381 $files = explode("\n + ", $file_details);
382 foreach ($files as $file)
383 {
384 if (!file_exists($this->path . $file) || substr($file, -5) !== '.html')
385 {
386 throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 1);
387 }
388
389 if (($this->filter !== 'adm') && strpos($file, 'styles/prosilver/template/') === 0)
390 {
391 $files_list['prosilver'][] = substr($file, strlen('styles/prosilver/template/'));
392 }
393 else if (($this->filter === 'adm') && strpos($file, 'adm/style/') === 0)
394 {
395 $files_list['adm'][] = substr($file, strlen('adm/style/'));
396 }
397 else
398 {
399 throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 2);
400 }
401
402 $this->events_by_file[$file][] = $this->current_event;
403 }
404 }
405 else if ($this->filter == 'adm')
406 {
407 $file = substr($file_details, strlen('* Location: '));
408 if (!file_exists($this->path . $file) || substr($file, -5) !== '.html')
409 {
410 throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 1);
411 }
412
413 $files_list['adm'][] = substr($file, strlen('adm/style/'));
414
415 $this->events_by_file[$file][] = $this->current_event;
416 }
417 else
418 {
419 throw new \LogicException("Invalid file list found for event '{$this->current_event}'", 2);
420 }
421
422 return $files_list;
423 }
424
425 /**
426 * Get all template events in a template file
427 *
428 * @param string $file
429 * @return array
430 * @throws \LogicException
431 */
432 public function crawl_file_for_events($file)
433 {
434 if (!file_exists($this->path . $file))
435 {
436 throw new \LogicException("File '{$file}' does not exist", 1);
437 }
438
439 $event_list = array();
440 $file_content = file_get_contents($this->path . $file);
441
442 $events = explode('<!-- EVENT ', $file_content);
443 // Remove the code before the first event
444 array_shift($events);
445 foreach ($events as $event)
446 {
447 $event = explode(' -->', $event, 2);
448 $event_list[] = array_shift($event);
449 }
450
451 return $event_list;
452 }
453
454 /**
455 * Validates whether all events from $file are in the md file and vice-versa
456 *
457 * @param string $file
458 * @param array $events
459 * @return true
460 * @throws \LogicException
461 */
462 public function validate_events_from_file($file, array $events)
463 {
464 if (empty($this->events_by_file[$file]) && empty($events))
465 {
466 return true;
467 }
468 else if (empty($this->events_by_file[$file]))
469 {
470 $event_list = implode("', '", $events);
471 throw new \LogicException("File '{$file}' should not contain events, but contains: "
472 . "'{$event_list}'", 1);
473 }
474 else if (empty($events))
475 {
476 $event_list = implode("', '", $this->events_by_file[$file]);
477 throw new \LogicException("File '{$file}' contains no events, but should contain: "
478 . "'{$event_list}'", 1);
479 }
480
481 $missing_events_from_file = array();
482 foreach ($this->events_by_file[$file] as $event)
483 {
484 if (!in_array($event, $events))
485 {
486 $missing_events_from_file[] = $event;
487 }
488 }
489
490 if (!empty($missing_events_from_file))
491 {
492 $event_list = implode("', '", $missing_events_from_file);
493 throw new \LogicException("File '{$file}' does not contain events: '{$event_list}'", 2);
494 }
495
496 $missing_events_from_md = array();
497 foreach ($events as $event)
498 {
499 if (!in_array($event, $this->events_by_file[$file]))
500 {
501 $missing_events_from_md[] = $event;
502 }
503 }
504
505 if (!empty($missing_events_from_md))
506 {
507 $event_list = implode("', '", $missing_events_from_md);
508 throw new \LogicException("File '{$file}' contains additional events: '{$event_list}'", 3);
509 }
510
511 return true;
512 }
513
514 /**
515 * Returns a list of files in $dir
516 *
517 * Works recursive with any depth
518 *
519 * @param string $dir Directory to go through
520 * @return array List of files (including directories)
521 */
522 public function get_recursive_file_list($dir)
523 {
524 try
525 {
526 $iterator = new \RecursiveIteratorIterator(
527 new \phpbb\recursive_dot_prefix_filter_iterator(
528 new \RecursiveDirectoryIterator(
529 $dir,
530 \FilesystemIterator::SKIP_DOTS
531 )
532 ),
533 \RecursiveIteratorIterator::SELF_FIRST
534 );
535 }
536 catch (\Exception $e)
537 {
538 return array();
539 }
540
541 $files = array();
542 foreach ($iterator as $file_info)
543 {
544 /** @var \RecursiveDirectoryIterator $file_info */
545 if ($file_info->isDir())
546 {
547 continue;
548 }
549
550 $relative_path = $iterator->getInnerIterator()->getSubPathname();
551
552 if (substr($relative_path, -5) == '.html')
553 {
554 $files[] = str_replace(DIRECTORY_SEPARATOR, '/', $relative_path);
555 }
556 }
557
558 return $files;
559 }
560 }
561