1 <?php
if (!defined('PmWiki')) exit();
2 /** \pastebin-embed.php
3 * \Copyright 2017-2021 Said Achmiz
4 * \Licensed under the MIT License
5 * \brief Embed Pastebin pastes in a wikipage.
7 $RecipeInfo['PastebinEmbed']['Version'] = '2021-12-11';
10 Markup('pastebin-embed', '<fulltext', '/\(:pastebin-embed\s+(.+?)\s*:\)/', 'PastebinEmbed');
12 SDV($PastebinEmbedHighlightStyle, "background-color: yellow;");
14 function PastebinEmbed ($m) {
17 ## Parse arguments to the markup.
18 $parsed = ParseArgs($m[1]);
20 ## These are the ‘bare’ arguments (ones which don't require a key, just value(s)).
23 $noJS = in_array('no-js', $args);
24 $noFooter = in_array('nofooter', $args);
25 $noLineNumbers = in_array('nolinenums', $args);
26 $raw = in_array('raw', $args);
27 $noPre = in_array('no-pre', $args);
29 ## Dark theme, if specified.
30 $theme = $parsed['theme'] ?
: "";
32 ## Convert the comma-delimited line ranges to an array containing each line to be
33 ## included as values.
34 ## Note that the line numbers will be zero-indexed (for use with raw text, etc.).
35 $line_ranges = $parsed['lines']
36 ?
explode(',', $parsed['lines'])
40 foreach ($line_ranges as $key => $line_range) {
41 if (preg_match("/([0-9]+)[-–]([0-9]+)/", $line_range, $m)) {
42 $line_numbers = array_merge($line_numbers, range(--$m[1],--$m[2]));
43 } else if (preg_match("/([0-9]+)[-–]$/", $line_range, $m)) {
44 $line_numbers[] = $to_end_from = --$m[1];
46 $line_numbers[] = --$line_range;
50 ## Same thing, but for highlighted line ranges.
51 $hl_line_ranges = $parsed['hl']
52 ?
explode(',', $parsed['hl'])
54 $hl_line_numbers = [ ];
56 foreach ($hl_line_ranges as $key => $hl_line_range) {
57 if (preg_match("/([0-9]+)[-–]([0-9]+)/", $hl_line_range, $m)) {
58 $hl_line_numbers = array_merge($hl_line_numbers, range(--$m[1],--$m[2]));
59 } else if (preg_match("/([0-9]+)[-–]$/", $hl_line_range, $m)) {
60 $hl_line_numbers[] = $hl_to_end_from = --$m[1];
62 $hl_line_numbers[] = --$hl_line_range;
66 $embed_js_url = "https://pastebin.com/embed_js/$paste_id" . ($theme ?
"?theme={$theme}" : "");
67 $embed_iframe_url = "https://pastebin.com/embed_iframe/$paste_id" . ($theme ?
"?theme={$theme}" : "");
68 $embed_raw_url = "https://pastebin.com/raw/$paste_id";
70 $out = "<span class='pastebin-embed-error'>Unknown error.</span>";
72 ## There are three ‘modes’: raw (retrieve just the text, server-side, and insert it
73 ## into the page), no-js (retrieve the HTML, server-side, and insert it into the
74 ## page), and default (i.e., JS-based: insert the scriptlet, and let it retrieve and
75 ## insert the HTML into the page, client-side & async).
76 ## The mode is set by arguments to the (:pastebin-embed:) markup:
77 ## - if neither the ‘raw’ nor the ‘no-js’ option is given, default (JS) mode is used
78 ## - if the ‘raw’ option is given, raw mode is used
79 ## - if the ‘no-js’ option is given, no-js mode is used
81 $raw_text = file_get_contents($embed_raw_url);
83 return Keep("<span class='pastebin-embed-error'>Could not retrieve paste!</span>");
85 $raw_lines = explode("\n", $raw_text);
86 ## Convert HTML entities.
88 foreach ($raw_lines as $line)
91 ## Highlighting only works if no-pre is NOT enabled.
92 if ( !empty($hl_line_numbers)
94 if ($hl_to_end_from >= 0)
95 $hl_line_numbers = array_merge($hl_line_numbers, range($hl_to_end_from, count($raw_lines) - 1));
96 foreach ($hl_line_numbers as $l) {
97 $raw_lines[$l] = "<span class='pastebin-embed-highlighted-line'>" . rtrim($raw_lines[$l]) . "</span>";
100 ## Filter by line number ranges, if specified.
101 if (!empty($line_numbers)) {
102 if ($to_end_from >= 0)
103 $line_numbers = array_merge($line_numbers, range($to_end_from, count($raw_lines) - 1));
104 $raw_lines = array_intersect_key($raw_lines, array_flip($line_numbers));
106 $raw_text = implode("\n", $raw_lines);
108 ## The ‘no-pre’ option means we shouldn’t wrap the text in a <pre> tag.
111 : Keep("<pre class='escaped embedPastebinRaw' id='pastebinEmbed_$id'>\n" . $raw_text . "\n</pre>\n");
113 include_once('simplehtmldom/simple_html_dom.php');
115 $content_html = file_get_html($embed_iframe_url);
117 return Keep("<span class='pastebin-embed-error'>Could not retrieve paste!</span>");
118 $content = $content_html->find(".embedPastebin", 0);
119 $content->id
= "pastebinEmbed_$id";
121 $styles_html = file_get_html($embed_js_url);
123 return Keep("<span class='pastebin-embed-error'>Could not retrieve styles!</span>");
124 $styles = $styles_html->find("style", 0);
126 ## Filter specified line ranges (if any have been specified via the lines=
128 if (!empty($line_numbers)) {
129 $lines = $content_html->find(".embedPastebin > ol > li");
130 if ($to_end_from >= 0)
131 $line_numbers = array_merge($line_numbers, range($to_end_from, count($lines) - 1));
132 foreach ($lines as $i => $line) {
133 if (!in_array($i, $line_numbers))
134 $line->outertext
= '';
140 ## Highlight specified line ranges (if any have been specified via the hl=
142 if (!empty($hl_line_numbers)) {
143 $lines = $content_html->find(".embedPastebin > ol > li");
144 if ($hl_to_end_from >= 0)
145 $hl_line_numbers = array_merge($hl_line_numbers, range($hl_to_end_from, count($lines) - 1));
146 foreach ($lines as $i => $line) {
147 if (in_array($i, $hl_line_numbers)) {
148 $line->children(0)->class .= " pastebin-embed-highlighted-line";
153 $out = Keep($styles.$content);
155 $out = Keep("<script id='pastebinEmbedScript_$id' src='$embed_js_url'></script>");
158 document.querySelector('#pastebinEmbedScript_$id').parentElement.nextSibling.querySelector('.embedPastebin').id = 'pastebinEmbed_$id';
162 if ( !empty($hl_line_numbers)
163 ||
!empty($line_numbers)) {
164 $line_numbers_js = "[ " . implode(", ", $line_numbers) . " ]";
165 $hl_line_numbers_js = "[ " . implode(", ", $hl_line_numbers) . " ]";
168 let num_lines = document.querySelector('#pastebinEmbed_$id').querySelectorAll('.embedPastebin > ol > li').length;
170 let line_numbers = $line_numbers_js;
171 let to_end_from = $to_end_from;
172 if (to_end_from >= 0)
173 line_numbers = [...line_numbers, ...[...Array(num_lines - to_end_from)].map((_, i) => to_end_from + i)];
175 let hl_line_numbers = $hl_line_numbers_js;
176 let hl_to_end_from = $hl_to_end_from;
177 if (hl_to_end_from >= 0)
178 hl_line_numbers = [...hl_line_numbers, ...[...Array(num_lines - hl_to_end_from)].map((_, i) => hl_to_end_from + i)];
180 document.querySelector('#pastebinEmbed_$id').querySelectorAll('.embedPastebin .source > ol > li').forEach(function (line, i) {
181 // Highlight specified line ranges (if any have been specified via the hl= parameter).
182 if (hl_line_numbers.indexOf(i) != -1)
183 line.firstChild.className += ' pastebin-embed-highlighted-line';
185 // Filter specified line ranges (if any have been specified via the lines= parameter).
186 if (line_numbers.length > 0) {
187 if (line_numbers.indexOf(i) == -1)
188 line.parentElement.removeChild(line);
198 global $HTMLStylesFmt;
199 if (!$raw && $noFooter) {
200 $HTMLStylesFmt['pastebin-embed'][] = "#pastebinEmbed_$id .embedFooter { display: none; }\n";
202 if (!$raw && $noLineNumbers) {
203 $HTMLStylesFmt['pastebin-embed'][] = "#pastebinEmbed_$id .source > ol { padding-left: 5px; }\n";
206 PastebinEmbedInjectStyles();
212 function PastebinEmbedInjectStyles() {
213 static $ran_once = false;
215 global $HTMLStylesFmt, $PastebinEmbedHighlightStyle;
217 .embedPastebinRaw .pastebin-embed-highlighted-line { $PastebinEmbedHighlightStyle display: inline-block; width: calc(100% + 4px); padding-left: 4px; margin-left: -4px; }
218 .embedPastebin li .pastebin-embed-highlighted-line { $PastebinEmbedHighlightStyle }
219 #wikitext .embedPastebin ol { margin: 0; padding: 0 0 0 60px; }
221 $HTMLStylesFmt['pastebin-embed'][] = $styles;