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