Slightly rearranged directory structure
[pmwiki-pastebin-embed.git] / cookbook / pastebin-embed.php
blob75f7c942be56ebc0ba2b8d9e15083c5e50246287
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.
6 */
7 $RecipeInfo['PastebinEmbed']['Version'] = '2021-12-11';
9 ## (:pastebin-embed:)
10 Markup('pastebin-embed', '<fulltext', '/\(:pastebin-embed\s+(.+?)\s*:\)/', 'PastebinEmbed');
12 SDV($PastebinEmbedHighlightStyle, "background-color: yellow;");
14 function PastebinEmbed ($m) {
15 static $id = 1;
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)).
21 $args = $parsed[''];
22 $paste_id = $args[0];
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'])
37 : [ ];
38 $line_numbers = [ ];
39 $to_end_from = -1;
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];
45 } else {
46 $line_numbers[] = --$line_range;
50 ## Same thing, but for highlighted line ranges.
51 $hl_line_ranges = $parsed['hl']
52 ? explode(',', $parsed['hl'])
53 : [ ];
54 $hl_line_numbers = [ ];
55 $hl_to_end_from = -1;
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];
61 } else {
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
80 if ($raw) {
81 $raw_text = file_get_contents($embed_raw_url);
82 if (!$raw_text)
83 return Keep("<span class='pastebin-embed-error'>Could not retrieve paste!</span>");
85 $raw_lines = explode("\n", $raw_text);
86 ## Convert HTML entities.
87 if (!$noPre) {
88 foreach ($raw_lines as $line)
89 $line = PVSE($line);
91 ## Highlighting only works if no-pre is NOT enabled.
92 if ( !empty($hl_line_numbers)
93 && !$noPre) {
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.
109 $out = $noPre
110 ? $raw_text
111 : Keep("<pre class='escaped embedPastebinRaw' id='pastebinEmbed_$id'>\n" . $raw_text . "\n</pre>\n");
112 } else if ($noJS) {
113 include_once('simplehtmldom/simple_html_dom.php');
115 $content_html = file_get_html($embed_iframe_url);
116 if (!$content_html)
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);
122 if (!$styles_html)
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=
127 ## parameter).
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 = '';
135 else
136 $line->value = ++$i;
140 ## Highlight specified line ranges (if any have been specified via the hl=
141 ## parameter).
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);
154 } else {
155 $out = Keep("<script id='pastebinEmbedScript_$id' src='$embed_js_url'></script>");
156 $out .= Keep("
157 <script>
158 document.querySelector('#pastebinEmbedScript_$id').parentElement.nextSibling.querySelector('.embedPastebin').id = 'pastebinEmbed_$id';
159 </script>
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) . " ]";
166 $out .= Keep("
167 <script>{
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);
189 else
190 line.value = ++i;
193 }</script>
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();
208 $id++;
209 return $out;
212 function PastebinEmbedInjectStyles() {
213 static $ran_once = false;
214 if (!$ran_once) {
215 global $HTMLStylesFmt, $PastebinEmbedHighlightStyle;
216 $styles = "
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;
223 $ran_once = true;