The MP-WP Weightloss Program: Removing JavaScript

June 15th, 2020

This is the second major slimming patch for mp-wp1 and the result is a streamlined mp-wp roughly half the size of the original genesis, all without removing anything that actual people actually use. The following are the stats for this patch:

Before After Reduction
Lines of Code 138,088 89,186 35.4%
Bytes on Disk 4,923,363 3,060,6652 37.8%

Since this was more than a simple find ./mp-wp/ -iname "*.js" | xargs rm, it's worth outlining what exactly was removed/changed in this patch:

  • ALL JS and JS related code has been removed, including inline JS, PHP used to load JS, as well as code related to popups and other elements generated by JS.
  • The JS widget to edit categories inline while editing a post has been removed. You can select existing categories for your post but to manage them (add/delete) you must navigate to the category page. Tags can be specified as a comma-delimited list and new tags will be created on save.
  • JS warning alerts have been removed (e.g. "are you sure you want to delete this?), to mitigate against accidental deletion, in-row delete links for Posts have been removed.3
  • Inline editing ("quick edit") and bulk editing has been removed from tables, as this relied on JS. Bulk edit can be achieved directly via MySQL.
  • The notion of media "attached" to an article is gone, as this relied on the JS popup modal for selecting and attaching media. Based on the survey it also seemed like no one really used this feature, instead relying on their own scripts for generating the HTML code to insert images into articles. Removing the media upload interface entirely was discussed, but in order to support the multi-author use case it was decided to keep this around for now.
  • Interfaces for modifying themes and plugins have been removed. Similarly interfaces for adding/deleting plugins has been removed. All of these features still work the same way you're probably used to using them already, by modifying files on the filesystem directly.
  • Any function in wp-includes/deprecated.php that wasn't called anywhere has been removed. In a future patch the rest of these could probably be removed.
  • The no-op wptexturize() function (defined in wp-includes/formatting.php and all calls to it have been removed.
  • The Adobe Flash file uploader has been removed.
  • Theme "functions" have been removed, except for registering sidebar widgets. I would've liked to have removed that as well but simply couldn't muster the will to do it in this patch.

In addition to the above I also decided to clean up the admin UI a bit and give it, what I thought to be, a much needed facelift:

  • The wp-admin/images/ directory has been removed. This contained base64 encoded PNGs, saved as SVGs, for use as icons in the admin interface. Along with this, the entire admin interface has been given a minor but comprehensive facelift4.
  • All "widgets" have been removed from the Dashboard except for the overview, recent drafts, and recent comments widgets.
  • Bulk action rows below tables have all been removed. They just weren't necessary since long pages are paginated anyway.
  • The wp-admin.css file was moved from wp-admin/ to wp-admin/css/, where all the other admin CSS lives.
  • All "rtl" CSS has been removed.

This site has been running on this patch for the last 24 hours, and I've been banging on it locally for the last month or so. However, as always, you should backup your db and site directory before deploying this or any other major changes to mp-wp. If you have a custom theme, it should work fine as is with these changes. Likewise, if you're using any plugins not included in the trunk of mp-wp, they should work as well.

Feedback/additional testing is welcome and appreciated. This is a big change so I want to make sure everything works as expected for all current mp-wp users.

Patch and Signature

mp-wp_remove-all-javascript.vpatch
mp-wp_remove-all-javascript.vpatch.billymg.sig

  1. After the first, which removed TinyMCE. []
  2. of these remaining bytes, about 220k are dedicated to empty directories not removed by V. []
  3. Should they be removed in all tables? In some cases this means adding "Delete" buttons to single-item views where they previously were only present in the list view rows, e.g. in the Media Library. []
  4. The ability to select a color scheme for the admin UI, and all associated code, has also been removed. []

Updated Vpatch: Add Embeddable Codeblocks and Server-Side Select Mechanism

May 14th, 2020

UPDATE 2020/5/18: When I first published this patch I forgot to include the change to the root .htaccess file. This has been fixed and the links below now point to the updated patch.

UPDATE 2020/5/24: CSS change to force the hash in the meta line to wrap.

See below for the updated embeddable codeblocks patch. This is the third iteration after the first two discussions. This patch also now contains the server-side select mechanism, including the updates to xmlrpc.php. The functionality is included in what is now the MP-WP Content Processing plugin1 so it will work across all themes without modification.

You can grab the patch from the links below or from my code shelf.

mp-wp_add-embeddable-codeblocks-and-server-side-selection.vpatch
mp-wp_add-embeddable-codeblocks-and-server-side-selection.vpatch.billymg.sig

1 diff -uNr a/mp-wp/.htaccess b/mp-wp/.htaccess
2 --- a/mp-wp/.htaccess 23164212887a9fa35d330f33bce2c3b96604e7331b79f2a0de7f8b845556cc06bc5fbee0b01b16e026461aa92302f92d971fbd39ec267528a65c421684226688
3 +++ b/mp-wp/.htaccess 1794794b2197805ec99a93d1e3269d6095ef597bb19229a41d5859f615e84ecbd3f2afb414bcb3bfc217e835c6b744a2cbef04b2ea1f36f45330d093227417c3
4 @@ -20,7 +20,7 @@
5 RewriteBase /
6 RewriteCond %{REQUEST_FILENAME} !-f
7 RewriteCond %{REQUEST_FILENAME} !-d
8 -RewriteRule . /index.php [L]
9 +RewriteRule . /index.php [QSA,L]
10 </IfModule>
11
12 # END WordPress
13 diff -uNr a/mp-wp/manifest b/mp-wp/manifest
14 --- a/mp-wp/manifest 14d47d8eb66fd8266c00a6911255bbf873aa47dd418bf20975c6432f3190ec9914708760fe5d327c693a579b193b1c808698cbba3be3a2138d3b62257ad77f13
15 +++ b/mp-wp/manifest 00b581121b09f282560a29d90fbb53a6ad22a7799428554bb1d17584200fb45310a8022757daf326c1709b2032434fb9eb4c507743af87e517adda50e2974365
16 @@ -7,3 +7,4 @@
17 605926 mp-wp_comments_filtering diana_coman Recent comments widget should show only people's comments (no track/pingbacks); theme default changed to show trackbacks/pingbacks as last/at the bottom in an article's comments list.
18 629903 mp-wp_remove-textselectionjs-pop3-etc-r2 jfw Remove the unreliable JS-based selection (reground: including wrapper spans), posting by POP3 login, and a stray .php.orig file. Neutralize and comment the example pingback updater.
19 629903 mp-wp_svg-screenshots-and-errorreporting-r2 jfw Allow .svg extensions in theme screenshot search. Don't clobber the user's errorreporting level without WP_DEBUG. (Reground following antecedent.)
20 +631542 mp-wp_add-embeddable-codeblocks-and-server-side-selection billymg Add embeddable codeblocks and the server-side select mechanism to the "footnotes" plugin (now "mp-wp content processing")
21 diff -uNr a/mp-wp/wp-content/plugins/footnotes.php b/mp-wp/wp-content/plugins/footnotes.php
22 --- a/mp-wp/wp-content/plugins/footnotes.php 8e2449d4ac26ea05f080cec9d025ef8a8585221ee30da439b37ff1578d084e1c63cbe3f89e3d6868c19d0fa9f73a9af99b444251e7a854f6c87e316628d94859
23 +++ b/mp-wp/wp-content/plugins/footnotes.php 5f8bedbdaf66b24223d250d56acc0050ad7df6b7abade9d7834e835142ec46a5baff035c48fece400e10db78116d2af885d2ebfb8522f4ffcee876d58a0969a3
24 @@ -1,49 +1,29 @@
25 <?php
26 /*
27 -Plugin Name: WP-Footnotes
28 -Plugin URI: http://www.elvery.net/drzax/more-things/wordpress-footnotes-plugin/
29 -Version: 4.2
30 -Description: Allows a user to easily add footnotes to a post.
31 -Author: Simon Elvery
32 -Author URI: http://www.elvery.net/drzax/
33 +Plugin Name: MP-WP Content Processing
34 +Plugin URI: http://billymg.com/category/mp-wp/
35 +Description: Allows for the custom processing of article content. Currently supports footnotes and embedded vpatch snippets.
36 +Author: billymg
37 +Author URI: http://billymg.com
38 */
39
40 -/*
41 - * This file is part of WP-Footnotes a plugin for Word Press
42 - * Copyright (C) 2007 Simon Elvery
43 - *
44 - * This program is free software; you can redistribute it and/or
45 - * modify it under the terms of the GNU General Public License
46 - * as published by the Free Software Foundation; either version 2
47 - * of the License, or (at your option) any later version.
48 - *
49 - * This program is distributed in the hope that it will be useful,
50 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
51 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
52 - * GNU General Public License for more details.
53 - *
54 - * You should have received a copy of the GNU General Public License
55 - * along with this program; if not, write to the Free Software
56 - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
57 - */
58 -
59 -// Some important constants
60 -define('WP_FOOTNOTES_OPEN', " (("); //You can change this if you really have to, but I wouldn't recommend it.
61 -define('WP_FOOTNOTES_CLOSE', "))"); //Same with this one.
62 -define('WP_FOOTNOTES_VERSION', '4.2');
63 -
64 // Instantiate the class
65 -$swas_wp_footnotes = new swas_wp_footnotes();
66 +$mp_wp_content_processing = new mp_wp_content_processing();
67
68 // Encapsulate in a class
69 -class swas_wp_footnotes {
70 - var $current_options;
71 +class mp_wp_content_processing {
72 + const MP_WP_FOOTNOTES_OPEN = " ((";
73 + const MP_WP_FOOTNOTES_CLOSE = "))";
74 + const MP_WP_CODEBLOCKS_PRIORITY = 9; // highest value that comes before wpautop filter
75 + const MP_WP_FOOTNOTES_PRIORITY = 11;
76 +
77 + var $options;
78 var $default_options;
79
80 /**
81 * Constructor.
82 */
83 - function swas_wp_footnotes() {
84 + function mp_wp_content_processing() {
85 // Define the implemented option styles
86 $this->styles = array(
87 'decimal' => '1,2...10',
88 @@ -56,111 +36,168 @@
89 );
90
91 // Define default options
92 - $this->default_options = array('superscript'=>true,
93 - 'pre_backlink'=>' [',
94 - 'backlink'=>'&#8617;',
95 - 'post_backlink'=>']',
96 - 'pre_identifier'=>'',
97 - 'list_style_type'=>'decimal',
98 - 'list_style_symbol'=>'&dagger;',
99 - 'post_identifier'=>'',
100 - 'pre_footnotes'=>'',
101 - 'post_footnotes'=>'',
102 - 'style_rules'=>'ol.footnotes{font-size:0.8em; color:#666666;}',
103 - 'no_display_home'=>false,
104 - 'no_display_archive'=>false,
105 - 'no_display_date'=>false,
106 - 'no_display_category'=>false,
107 - 'no_display_search'=>false,
108 - 'no_display_feed'=>false,
109 - 'combine_identical_notes'=>false,
110 - 'priority'=>11,
111 - 'version'=>WP_FOOTNOTES_VERSION);
112 + $this->default_options = array(
113 + 'pre_backlink'=>' [',
114 + 'backlink'=>'&#8617;',
115 + 'post_backlink'=>']',
116 + 'pre_identifier'=>'',
117 + 'list_style_type'=>'decimal',
118 + 'list_style_symbol'=>'&dagger;',
119 + 'post_identifier'=>'',
120 + 'pre_footnotes'=>'',
121 + 'post_footnotes'=>''
122 + );
123
124 // Get the current settings or setup some defaults if needed
125 - if (!$this->current_options = get_option('swas_footnote_options')){
126 - $this->current_options = $this->default_options;
127 - update_option('swas_footnote_options', $this->current_options);
128 - } else {
129 + $this->options = get_option('mp_wp_cpp_options');
130 + if (!$this->options) {
131 + $this->options = $this->default_options;
132 + update_option('mp_wp_cpp_options', $this->options);
133 + } else {
134 // Set any unset options
135 - if ($this->current_options['version'] != WP_FOOTNOTES_VERSION) {
136 - foreach ($this->default_options as $key => $value) {
137 - if (!isset($this->current_options[$key])) {
138 - $this->current_options[$key] = $value;
139 - }
140 + $updated = false;
141 + foreach ($this->default_options as $key => $value) {
142 + if (!isset($this->options[$key])) {
143 + $this->options[$key] = $value;
144 + $updated = true;
145 }
146 - $this->current_options['version'] = WP_FOOTNOTES_VERSION;
147 - update_option('swas_footnote_options', $this->current_options);
148 + }
149 + if ($updated) {
150 + update_option('mp_wp_cpp_options', $this->options);
151 }
152 }
153
154 -/*
155 - if (!empty($_POST['save_options'])){
156 - $footnotes_options['superscript'] = (array_key_exists('superscript', $_POST)) ? true : false;
157 - $footnotes_options['pre_backlink'] = $_POST['pre_backlink'];
158 - $footnotes_options['backlink'] = $_POST['backlink'];
159 - $footnotes_options['post_backlink'] = $_POST['post_backlink'];
160 - $footnotes_options['pre_identifier'] = $_POST['pre_identifier'];
161 - $footnotes_options['list_style_type'] = $_POST['list_style_type'];
162 - $footnotes_options['post_identifier'] = $_POST['post_identifier'];
163 - $footnotes_options['list_style_symbol'] = $_POST['list_style_symbol'];
164 - $footnotes_options['pre_footnotes'] = stripslashes($_POST['pre_footnotes']);
165 - $footnotes_options['post_footnotes'] = stripslashes($_POST['post_footnotes']);
166 - $footnotes_options['style_rules'] = stripslashes($_POST['style_rules']);
167 - $footnotes_options['no_display_home'] = (array_key_exists('no_display_home', $_POST)) ? true : false;
168 - $footnotes_options['no_display_archive'] = (array_key_exists('no_display_archive', $_POST)) ? true : false;
169 - $footnotes_options['no_display_date'] = (array_key_exists('no_display_date', $_POST)) ? true : false;
170 - $footnotes_options['no_display_category'] = (array_key_exists('no_display_category', $_POST)) ? true : false;
171 - $footnotes_options['no_display_search'] = (array_key_exists('no_display_search', $_POST)) ? true : false;
172 - $footnotes_options['no_display_feed'] = (array_key_exists('no_display_feed', $_POST)) ? true : false;
173 - $footnotes_options['combine_identical_notes'] = (array_key_exists('combine_identical_notes', $_POST)) ? true : false;
174 - $footnotes_options['priority'] = $_POST['priority'];
175 - update_option('swas_footnote_options', $footnotes_options);
176 - }elseif(!empty($_POST['reset_options'])){
177 - update_option('swas_footnote_options', '');
178 - update_option('swas_footnote_options', $this->default_options);
179 - }
180 -*/
181 -
182 // Hook me up
183 - add_action('the_content', array($this, 'process'), $this->current_options['priority']);
184 - add_action('admin_menu', array($this, 'add_options_page')); // Insert the Admin panel.
185 + add_action('the_content', array($this, 'process_codeblocks'), self::MP_WP_CODEBLOCKS_PRIORITY);
186 + add_action('the_content', array($this, 'process_footnotes'), self::MP_WP_FOOTNOTES_PRIORITY);
187 + add_filter('the_content', array($this, 'server_side_selection'));
188 add_action('wp_head', array($this, 'insert_styles'));
189 }
190
191 +
192 + /**
193 + * Finds the selection from the query params.
194 + * @param $data string The content of the post.
195 + * @return string The new content with the highlighted selection.
196 + */
197 + function server_side_selection($data) {
198 + //bookend code goes here
199 + $b_code = '<span class="mp-wp-selection" id="select">';
200 + $b_code .= $_GET["b"];
201 + $e_code = $_GET["e"].'</span>';
202 +
203 + //change page ; last to first to preserve indexes.
204 + $b_pos = strpos($data,$_GET["b"]);
205 + $e_pos = strpos($data,$_GET["e"], $b_pos);
206 + if ($e_pos>0)
207 + $data = substr_replace($data, $e_code, $e_pos, strlen($_GET["e"]));
208 + if ($b_pos>0)
209 + $data = substr_replace($data, $b_code, $b_pos, strlen($_GET["b"]));
210 + return $data;
211 + }
212 +
213 + /**
214 + * Searches the text and apply markup to codeblocks.
215 + * Adds line number links and diff syntax highlighting.
216 + * @param $data string The content of the post.
217 + * @return string The new content with formatted codeblocks.
218 + */
219 + function process_codeblocks($data) {
220 + global $post;
221 +
222 + // Regex extraction of all codeblocks (or return if there are none)
223 + if ( !preg_match_all("/\[([a-z]+)\[(.*)(\]\])/Us", $data, $codeblocks, PREG_SET_ORDER) ) {
224 + return $data;
225 + }
226 +
227 + for ($i = 0; $i < count($codeblocks); $i++) {
228 + $codeblocks[$i]['snippet'] = $this->format_snippet($codeblocks[$i][2], $codeblocks[$i][1], $i+1);
229 + }
230 +
231 + foreach ($codeblocks as $key => $value) {
232 + $data = substr_replace($data, $value['snippet'], strpos($data,$value[0]),strlen($value[0]));
233 + }
234 +
235 + return $data;
236 + }
237 +
238 + function format_snippet($snippet, $syntax, $snippet_number) {
239 + $highlighting_functions = array(
240 + 'plaintext' => 'highlight_as_plain_text',
241 + 'diff' => 'highlight_as_diff'
242 + );
243 +
244 + if (is_null($highlighting_functions[$syntax])) {
245 + $syntax = 'plaintext';
246 + }
247 +
248 + $code_lines = explode("\r\n", $snippet);
249 +
250 + foreach ($code_lines as $idx => $line) {
251 + $line_number = sprintf('S%d-L%d', $snippet_number, $idx+1);
252 + $line_link = sprintf('<a href="#%s" name="%s">%d</a>', $line_number, $line_number, $idx+1);
253 + $line_open = sprintf('<tr><td class="line-number-column">%s</td><td class="content-column">', $line_link);
254 + $line_close = '</td></tr>';
255 +
256 + $code_lines[$idx] = $this->$highlighting_functions[$syntax]($line_open, $line, $line_close);
257 + }
258 +
259 + $formatted_snippet = implode("\n", $code_lines);
260 +
261 + $formatted_snippet = sprintf(
262 + '%s%s%s',
263 + '<div class="mp-wp-codeblock"><table cellpadding="0" cellspacing="0"><tbody>',
264 + $formatted_snippet,
265 + '</tbody></table></div>'
266 + );
267 +
268 + return $formatted_snippet;
269 + }
270 +
271 + function highlight_as_plain_text($line_open, $line, $line_close) {
272 + return sprintf('%s<span class="line-default">%s</span>%s', $line_open, $line, $line_close);
273 + }
274 +
275 + function highlight_as_diff($line_open, $line, $line_close) {
276 + if (substr($line, 0, 5) == 'diff ') {
277 + $highlighted_line = sprintf('%s<span class="line-filename">%s</span>%s', $line_open, $line, $line_close);
278 + } elseif (substr($line, 0, 4) == '--- ' || substr($line, 0, 4) == '+++ ' || substr($line, 0, 3) == '@@ ') {
279 + $highlighted_line = sprintf('%s<span class="line-meta">%s</span>%s', $line_open, $line, $line_close);
280 + } elseif (substr($line, 0, 1) == '-') {
281 + $highlighted_line = sprintf('%s<span class="line-removed">%s</span>%s', $line_open, $line, $line_close);
282 + } elseif (substr($line, 0, 1) == '+') {
283 + $highlighted_line = sprintf('%s<span class="line-added">%s</span>%s', $line_open, $line, $line_close);
284 + } else {
285 + $highlighted_line = sprintf('%s<span class="line-default">%s</span>%s', $line_open, $line, $line_close);
286 + }
287 +
288 + return $highlighted_line;
289 + }
290 +
291 /**
292 * Searches the text and extracts footnotes.
293 * Adds the identifier links and creats footnotes list.
294 * @param $data string The content of the post.
295 * @return string The new content with footnotes generated.
296 */
297 - function process($data) {
298 + function process_footnotes($data) {
299 global $post;
300
301 // Check for and setup the starting number
302 $start_number = (preg_match("|<!\-\-startnum=(\d+)\-\->|",$data,$start_number_array)==1) ? $start_number_array[1] : 1;
303
304 // Regex extraction of all footnotes (or return if there are none)
305 - if (!preg_match_all("/(".preg_quote(WP_FOOTNOTES_OPEN)."|<footnote>)(.*)(".preg_quote(WP_FOOTNOTES_CLOSE)."|<\/footnote>)/Us", $data, $identifiers, PREG_SET_ORDER)) {
306 + if (!preg_match_all("/(".preg_quote(self::MP_WP_FOOTNOTES_OPEN)."|<footnote>)(.*)(".preg_quote(self::MP_WP_FOOTNOTES_CLOSE)."|<\/footnote>)/Us", $data, $identifiers, PREG_SET_ORDER)) {
307 return $data;
308 }
309
310 - // Check whether we are displaying them or not
311 - $display = true;
312 - if ($this->current_options['no_display_home'] && is_home()) $display = false;
313 - if ($this->current_options['no_display_archive'] && is_archive()) $display = false;
314 - if ($this->current_options['no_display_date'] && is_date()) $display = false;
315 - if ($this->current_options['no_display_category'] && is_category()) $display = false;
316 - if ($this->current_options['no_display_search'] && is_search()) $display = false;
317 - if ($this->current_options['no_display_feed'] && is_feed()) $display = false;
318 -
319 $footnotes = array();
320
321 // Check if this post is using a different list style to the settings
322 if ( array_key_exists(get_post_meta($post->ID, 'footnote_style', true), $this->styles) ) {
323 $style = get_post_meta($post->ID, 'footnote_style', true);
324 } else {
325 - $style = $this->current_options['list_style_type'];
326 + $style = $this->options['list_style_type'];
327 }
328
329 // Create 'em
330 @@ -173,25 +210,11 @@
331 $identifiers[$i]['text'] = $identifiers[$i][2];
332 }
333
334 -
335 - // if we're combining identical notes check if we've already got one like this & record keys
336 - if ($this->current_options['combine_identical_notes']){
337 - for ($j=0; $j<count($footnotes); $j++){
338 - if ($footnotes[$j]['text'] == $identifiers[$i]['text']){
339 - $identifiers[$i]['use_footnote'] = $j;
340 - $footnotes[$j]['identifiers'][] = $i;
341 - break;
342 - }
343 - }
344 - }
345 -
346 - if (!isset($identifiers[$i]['use_footnote'])){
347 - // Add footnote and record the key
348 - $identifiers[$i]['use_footnote'] = count($footnotes);
349 - $footnotes[$identifiers[$i]['use_footnote']]['text'] = $identifiers[$i]['text'];
350 - $footnotes[$identifiers[$i]['use_footnote']]['symbol'] = $identifiers[$i]['symbol'];
351 - $footnotes[$identifiers[$i]['use_footnote']]['identifiers'][] = $i;
352 - }
353 + // Add footnote and record the key
354 + $identifiers[$i]['use_footnote'] = count($footnotes);
355 + $footnotes[$identifiers[$i]['use_footnote']]['text'] = $identifiers[$i]['text'];
356 + $footnotes[$identifiers[$i]['use_footnote']]['symbol'] = $identifiers[$i]['symbol'];
357 + $footnotes[$identifiers[$i]['use_footnote']]['identifiers'][] = $i;
358 }
359
360 // Footnotes and identifiers are stored in the array
361 @@ -206,86 +229,89 @@
362 $id_id = "identifier_".$key."_".$post->ID;
363 $id_num = ($style == 'decimal') ? $value['use_footnote']+$start_number : $this->convert_num($value['use_footnote']+$start_number, $style, count($footnotes));
364 $id_href = ( ($use_full_link) ? get_permalink($post->ID) : '' ) . "#footnote_".$value['use_footnote']."_".$post->ID;
365 -
366 -// $id_title = str_replace('"', "&quot;", htmlentities(strip_tags($value['text']), ENT_QUOTES, 'UTF-8'));
367 -
368 $id_title = str_replace('"', '`', strip_tags($value['text']));
369 - $id_replace = $this->current_options['pre_identifier'].'<a href="'.$id_href.'" id="'.$id_id.'" class="footnote-link footnote-identifier-link" title="'.$id_title.'">'.$id_num.'</a>'.$this->current_options['post_identifier'];
370 - if ($this->current_options['superscript']) $id_replace = '<sup>'.$id_replace.'</sup>';
371 - if ($display) $data = substr_replace($data, $id_replace, strpos($data,$value[0]),strlen($value[0]));
372 - else $data = substr_replace($data, '', strpos($data,$value[0]),strlen($value[0]));
373 + $id_replace = '<sup>'.$this->options['pre_identifier'].'<a href="'.$id_href.'" id="'.$id_id.'" class="footnote-link footnote-identifier-link" title="'.$id_title.'">'.$id_num.'</a>'.$this->options['post_identifier'].'</sup>';
374 + $data = substr_replace($data, $id_replace, strpos($data,$value[0]),strlen($value[0]));
375 }
376
377 // Display footnotes
378 - if ($display) {
379 - $start = ($start_number != 1) ? 'start="'.$start_number.'" ' : '';
380 - $data = $data.$this->current_options['pre_footnotes'];
381 -
382 - $data = $data . '<ol '.$start.'class="footnotes">';
383 - foreach ($footnotes as $key => $value) {
384 - $data = $data.'<li id="footnote_'.$key.'_'.$post->ID.'" class="footnote"';
385 - if ($style == 'symbol') {
386 - $data = $data . ' style="list-style-type:none;"';
387 - } elseif($style != $this->current_options['list_style_type']) {
388 - $data = $data . ' style="list-style-type:' . $style . ';"';
389 - }
390 - $data = $data . '>';
391 - if ($style == 'symbol') {
392 - $data = $data . '<span class="symbol">' . $this->convert_num($key+$start_number, $style, count($footnotes)) . '</span> ';
393 - }
394 - $data = $data.$value['text'];
395 - if (!is_feed()){
396 - foreach($value['identifiers'] as $identifier){
397 - $data = $data.$this->current_options['pre_backlink'].'<a href="'.( ($use_full_link) ? get_permalink($post->ID) : '' ).'#identifier_'.$identifier.'_'.$post->ID.'" class="footnote-link footnote-back-link">'.$this->current_options['backlink'].'</a>'.$this->current_options['post_backlink'];
398 - }
399 + $start = ($start_number != 1) ? 'start="'.$start_number.'" ' : '';
400 + $data = $data.$this->options['pre_footnotes'];
401 +
402 + $data = $data . '<ol '.$start.'class="footnotes">';
403 + foreach ($footnotes as $key => $value) {
404 + $data = $data.'<li id="footnote_'.$key.'_'.$post->ID.'" class="footnote"';
405 + if ($style == 'symbol') {
406 + $data = $data . ' style="list-style-type:none;"';
407 + } elseif($style != $this->options['list_style_type']) {
408 + $data = $data . ' style="list-style-type:' . $style . ';"';
409 + }
410 + $data = $data . '>';
411 + if ($style == 'symbol') {
412 + $data = $data . '<span class="symbol">' . $this->convert_num($key+$start_number, $style, count($footnotes)) . '</span> ';
413 + }
414 + $data = $data.$value['text'];
415 + if (!is_feed()){
416 + foreach($value['identifiers'] as $identifier){
417 + $data = $data.$this->options['pre_backlink'].'<a href="'.( ($use_full_link) ? get_permalink($post->ID) : '' ).'#identifier_'.$identifier.'_'.$post->ID.'" class="footnote-link footnote-back-link">'.$this->options['backlink'].'</a>'.$this->options['post_backlink'];
418 }
419 - $data = $data . '</li>';
420 }
421 - $data = $data . '</ol>' . $this->current_options['post_footnotes'];
422 + $data = $data . '</li>';
423 }
424 - return $data;
425 - }
426 -
427 - /**
428 - * Really insert the options page.
429 - */
430 - function footnotes_options_page() {
431 - $this->current_options = get_option('swas_footnote_options');
432 - foreach ($this->current_options as $key=>$setting) {
433 - $new_setting[$key] = htmlentities($setting);
434 - }
435 - $this->current_options = $new_setting;
436 - unset($new_setting);
437 - include (dirname(__FILE__) . '/options.php');
438 - }
439 + $data = $data . '</ol>' . $this->options['post_footnotes'];
440
441 - /**
442 - * Insert the options page into the admin area.
443 - */
444 - function add_options_page() {
445 - // Add a new menu under Options:
446 - add_options_page('Footnotes', 'Footnotes', 8, __FILE__, array($this, 'footnotes_options_page'));
447 - }
448 -
449 - function upgrade_post($data){
450 - $data = str_replace('<footnote>',WP_FOOTNOTES_OPEN,$data);
451 - $data = str_replace('</footnote>',WP_FOOTNOTES_CLOSE,$data);
452 return $data;
453 }
454
455 - function insert_styles(){
456 + function insert_styles() {
457 ?>
458 <style type="text/css">
459 - <?php if ($this->current_options['list_style_type'] != 'symbol'): ?>
460 - ol.footnotes li {list-style-type:<?php echo $this->current_options['list_style_type']; ?>;}
461 + ol.footnotes { font-size: 0.8em; color: #666; }
462 + a.footnote-link,
463 + td.line-number-column {
464 + -moz-user-select: none;
465 + -webkit-user-select: none;
466 + user-select: none;
467 + }
468 + div.mp-wp-codeblock {
469 + background: none;
470 + font-family: monospace;
471 + color: #333;
472 + border: 1px solid #ddd;
473 + padding: 0;
474 + overflow: auto;
475 + }
476 + td.line-number-column { background: #f5f6f7; text-align: right; vertical-align: top; }
477 + td.line-number-column a { color: #555; padding: 0 5px; }
478 + td.content-column {
479 + padding-left: 10px;
480 + white-space: pre-wrap;
481 + word-break: break-word;
482 + tab-size: 4;
483 + -moz-tab-size: 4;
484 + max-width: 100%; /* adjust, if necessary, to fit your blog's viewport */
485 + }
486 + span.line-filename { font-weight: bold; }
487 + span.line-meta {
488 + display: block;
489 + color: #999;
490 + word-wrap: break-word;
491 + word-break: break-all;
492 + }
493 + span.line-added { color: green; }
494 + span.line-removed { color:red; }
495 +
496 + <?php if ($this->options['list_style_type'] != 'symbol'): ?>
497 + ol.footnotes li { list-style-type: <?php echo $this->options['list_style_type']; ?>; }
498 <?php endif; ?>
499 - <?php echo $this->current_options['style_rules'];?>
500 +
501 + span.mp-wp-selection { background-color: #d3d3d3; }
502 </style>
503 <?php
504 }
505
506
507 - function convert_num ($num, $style, $total){
508 + function convert_num ($num, $style, $total) {
509 switch ($style) {
510 case 'decimal-leading-zero' :
511 $width = max(2, strlen($total));
512 @@ -301,7 +327,7 @@
513 case 'symbol' :
514 $sym = '';
515 for ($i = 0; $i<$num; $i++) {
516 - $sym .= $this->current_options['list_style_symbol'];
517 + $sym .= $this->options['list_style_symbol'];
518 }
519 return $sym;
520 }
521 @@ -318,7 +344,7 @@
522 * @param string $case Upper or lower case.
523 * @return string The roman numeral
524 */
525 - function roman($num, $case= 'upper'){
526 + function roman($num, $case= 'upper') {
527 $num = (int) $num;
528 $conversion = array('M'=>1000, 'CM'=>900, 'D'=>500, 'CD'=>400, 'C'=>100, 'XC'=>90, 'L'=>50, 'XL'=>40, 'X'=>10, 'IX'=>9, 'V'=>5, 'IV'=>4, 'I'=>1);
529 $roman = '';
530 @@ -331,7 +357,7 @@
531 return ($case == 'lower') ? strtolower($roman) : $roman;
532 }
533
534 - function alpha($num, $case='upper'){
535 + function alpha($num, $case='upper') {
536 $j = 1;
537 for ($i = 'A'; $i <= 'ZZ'; $i++){
538 if ($j == $num){
539 diff -uNr a/mp-wp/xmlrpc.php b/mp-wp/xmlrpc.php
540 --- a/mp-wp/xmlrpc.php 4f496f538195a62e0f48c2d27dd787923795583c1ae1419c1135d132ca848efe79b148f31de66d9414829b034981427920b317952be6fe23a097447815b14000
541 +++ b/mp-wp/xmlrpc.php d26b211e8c163bae44b267a173ba2724e8475a6ee44ea0fd0caed998d85de6de1589efae5a82efe598ed884b0e0db2c9b8b5b6d644fef1142546d9fb1df16849
542 @@ -3326,7 +3326,8 @@
543 $comment_post_ID = (int) $post_ID;
544 $comment_author = $title;
545 $this->escape($comment_author);
546 - $comment_author_url = $pagelinkedfrom;
547 + $select_tail = "?b=".substr(rawurlencode(strip_tags($excerpt)),0,12)."&e=".substr(rawurlencode(strip_tags($excerpt)),0,-4)."#select";
548 + $comment_author_url = $pagelinkedfrom.$select_tail;
549 $comment_content = $context;
550 $this->escape($comment_content);
551 $comment_type = 'pingback';
552
  1. Previously just the footnotes plugin before embeddable codeblocks and server-side selection were added in this patch. []

Guanacaste, San Jose, and Arenal: Two Months in Costa Rica

May 4th, 2020

It's been almost two months since we escaped the godforsaken hellhole known to normies as the United States. Turns out we got out just in time too, since a "global pandemic"1 came along a week later and finally gave the USG the pretense it needed to put all of the serfs on indefinite house arrest2.

Costa Rica is much dryer now than when I visited in October. During Semana Santa3 we mostly had to entertain ourselves around the property. At times this included walks around the neighborhood close to sunset when the air was starting to get a bit cooler, usually followed by a quick dip in the pool before dinner.




A gaggle of geese. We gave a wide berth as we passed, not wanting to provoke these avian Goodfellas.

Junkyard cat, protecting the junk from any would-be thieves.

Just before Semana Santa we also made a quick trip down to San Jose4 to visit MP and his harem. Hannah gave us good intel on where to go for shopping so the following day we spent the morning walking around downtown San Jose. This was just at the start of the virus hysteria so things still seemed normal, save for the staff at shop entrances standing by to goop your hands as you enter and exit.



The public park was closed off with caution tape, a few cops idly standing around.

Catedral Metropolitana.


We stayed at the AC Hotel in Escazu. It was honestly one of the nicer hotels I've stayed in, anywhere, and it felt like we were the only guests there. The food provided by room service was excellent: Ceviche, steak and chicken skewers, pasta carbonara for a late lunch. Spanish baked egg casserole and a traditional American plate of eggs, bacon, and toast for breakfast. And of course a couple plates of Nutella crepes, fruit, and ice cream as a late night snack to restore some of the calories burned just prior to bed.


Some of the food mentioned above. I really need to get better about remembering to take pictures of the more appetizing meals set in front of me.

The cat came along as well, seen here in her camo vest.

Most recently, last week we took a trip up to Arenal to beat the heat. As dry as it is in most of Costa Rica during the high season5, Arenal is lush green year round. We stayed at a charming boutique hotel in Nueve Arenal called The Gingerbread Restaurant and Hotel. The owner and chef, Eyal, has been running the place for 14 years. There are three standard rooms upstairs from the bar/restaurant and three miniature cabins nearby on the property. The cabin we stayed in was great and the food was some of the best I've had in the country so far. During our two night stay we had chicken schnitzel, steak, seafood pasta, lamb "kebabitas", tuna poke, mushrooms in cream sauce, shrimp fried rice, scrambled eggs with goat cheese and truffle oil, along with freshly baked banana bread, apple strudel, chocolate cake, and bread pudding6.


The forest themed room we stayed in had this unique shower that made you feel like you were showering in the rain—in a small, sunny clearing in the middle of the forest.





We took a walk up the hill of a little gated neighborhood next door to the hotel in order to get a few more shots of the surrounding scenery.

Hoes love flowers, it's a fact.

And now we're all back in Guanacaste, watching the sun come through the trees in the early morning and drinking pipa fria7 in the hot afternoons. It's a simple life, but I'll take it over being locked in an apartment any day.


  1. Who knew something with a mortality rate of less than a tenth of a percent could be considered a pandemic? []
  2. The Costa Rican government derped about as well to some degree, including closing its borders to foreigners, closing its beaches and national parks, and imposing China-like driving restrictions based your license plate number. []
  3. Holy Week, or, Easter. Here Easter is taken more seriously and nearly all of Costa Rica slows to a crawl for an entire week starting April 5th. []
  4. Escazu and Santa Ana more specifically. []
  5. When all the tourists from colder climates come to escape their winters. []
  6. The last two served with ice cream of course. []
  7. Speaking of, the other day my groundskeeper's wife dropped by with a whole pitcher of the stuff, that they made fresh from coconuts dropped from trees on this very property. Probably $50 worth, in overinflated usian prices, but tasting far better than anything you could get there—at any price. []