PluginProbe
Modern Footnotes / 1.5.0
Modern Footnotes v1.5.0
1.5.1 1.5.0 1.4.21 trunk 1.0 1.0.1 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.3.0 1.3.1 1.3.10 1.3.11 1.3.2 1.3.3 All 52 releases
modern-footnotes / modern-footnotes.php

modern-footnotes.php in Modern Footnotes 1.5.0, at modern-footnotes.php

823 lines 36.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: Modern Footnotes
4 Plugin URI: http://prismtechstudios.com/modern-footnotes
5 Text Domain: modern-footnotes
6 Description: Add inline footnotes to your post via the footnote icon on the toolbar for editing posts and pages. Or, use the [mfn] or [modern_footnote] shortcodes [mfn]like this[/mfn].
7 Version: 1.5.0
8 Author: Prism Tech Studios
9 Author URI: http://prismtechstudios.com/
10 License: GPL2
11 License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html
12 */
13
14 //don't let users call this file directly
15 defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
16
17 $modern_footnotes_version = '1.5.0';
18
19 $modern_footnotes_options = get_option('modern_footnotes_settings');
20
21 // set default options, if they have not been set
22 if ($modern_footnotes_options === FALSE) {
23 $modern_footnotes_options = [];
24 }
25 if (get_option('modern_footnotes_include_footnote_list_at_end_of_rss_content_default_value_has_been_set') === FALSE &&
26 !isset($modern_footnotes_options['modern_footnotes_include_footnote_list_at_end_of_rss_content'])) {
27 $modern_footnotes_options['modern_footnotes_include_footnote_list_at_end_of_rss_content'] = 1;
28 update_option('modern_footnotes_include_footnote_list_at_end_of_rss_content_default_value_has_been_set', 1); // this variable is so the default value doesn't get reset if modern_footnotes_include_footnote_list_at_end_of_rss_content is removed from optiosn (which happens when the settings checkbox is unchecked)
29 update_option('modern_footnotes_settings', $modern_footnotes_options);
30 }
31 if (get_option('modern_footnotes_use_expandable_footnotes_on_desktop_instead_of_tooltips_has_been_migrated') === FALSE &&
32 !isset($modern_footnotes_options['desktop_footnote_behavior']) &&
33 isset($modern_footnotes_options['use_expandable_footnotes_on_desktop_instead_of_tooltips']) &&
34 $modern_footnotes_options['use_expandable_footnotes_on_desktop_instead_of_tooltips']) {
35 $modern_footnotes_options['desktop_footnote_behavior'] = 'expandable';
36 update_option('modern_footnotes_use_expandable_footnotes_on_desktop_instead_of_tooltips_has_been_migrated', 1); // this variable is so the default value doesn't get re-migrated
37 update_option('modern_footnotes_settings', $modern_footnotes_options);
38 }
39
40 //will contain an entry for each unique post displayed on the page. Each post will have three values:
41 // modern_footnotes_post_number -- a number identifying the post that can be written out to the HTML
42 // used_reference_numbers -- an array of the reference numbers for the footnotes that have been used
43 // footnotes -- an array containing each individual footnote, with the following values:
44 // reference_label - the text or number displayed as the superscript for the footnote
45 // content - the text content of the footnote
46 // id - a numberic ID unique to the footnote within the scope of a single post
47 $modern_footnotes_all_posts_data = array();
48
49 $current_modern_footnotes_post_number = 0;
50
51 function modern_footnotes_execute_mfn_list_shortcode($content) {
52 $content = str_replace('[mfn_list_execute_after_content_processed]', modern_footnotes_list_footnotes(), $content);
53 return $content;
54 }
55
56 function modern_footnotes_list_footnotes($show_only_when_printing = FALSE, $hide_when_printing = FALSE, $for_rss_feed = FALSE) {
57 global $modern_footnotes_all_posts_data, $modern_footnotes_options;
58 $scope_id = modern_footnotes_get_post_scope_id();
59 if (empty($modern_footnotes_all_posts_data[$scope_id])) {
60 return '';
61 }
62 $footnotes = $modern_footnotes_all_posts_data[$scope_id]['footnotes'];
63
64 $content = '';
65 if (isset($modern_footnotes_options['modern_footnotes_heading_for_footnote_list']) && strlen($modern_footnotes_options['modern_footnotes_heading_for_footnote_list']) > 0) {
66 $tag_name = isset($modern_footnotes_options['modern_footnotes_heading_tag_name_for_footnote_list']) ? $modern_footnotes_options['modern_footnotes_heading_tag_name_for_footnote_list'] : 'h3';
67 $content .= '<' . $tag_name . ' class="modern-footnotes-list-heading ' .
68 ($show_only_when_printing ? 'modern-footnotes-list-heading--show-only-for-print' : '') .
69 ($hide_when_printing ? 'modern-footnotes-list-heading--hide-for-print' : '')
70 . '">' . $modern_footnotes_options['modern_footnotes_heading_for_footnote_list'] . '</' . $tag_name . '>';
71 }
72 if ($for_rss_feed) {
73 foreach ($footnotes as $footnote) {
74 $content .= '<div>';
75 $content .= esc_html($footnote['reference_label']);
76 $content .= '&nbsp;&nbsp;&nbsp;&nbsp;';
77 $content .= $footnote['content'];
78 $content .= '</div>';
79 }
80 } else {
81
82 $content .= '<ul class="modern-footnotes-list ' .
83 ($show_only_when_printing ? 'modern-footnotes-list--show-only-for-print' : '') .
84 ($hide_when_printing ? 'modern-footnotes-list--hide-for-print' : '')
85 . '">';
86 foreach ($footnotes as $footnote) {
87 $content .= '<li id="footnote-' . esc_attr($scope_id) . '-' . esc_attr($footnote['id']) . '">';
88 $content .= '<span>' . esc_html($footnote['reference_label']) . '</span>';
89 $content .= '<div>';
90 $content .= $footnote['content'];
91 $content .= ' <a href="#mfn-content-' . esc_attr($scope_id) . '-' . esc_attr($footnote['id']) . '" class="modern-footnotes-scroll-to-footnote" aria-label="Back to reference ' . esc_attr($footnote['reference_label']) . ' in text">↩︎</a>';
92 $content .= '</div>';
93 $content .= '</li>';
94 }
95 $content .= '</ul>';
96 }
97 return $content;
98 }
99
100 function modern_footnotes_list_func($atts=[], $content = "") {
101 modern_footnotes_enqueue_scripts_styles_if_not_already_enqueued();
102 return '[mfn_list_execute_after_content_processed]';
103 }
104
105 function modern_footnotes_rss_func($atts, $content = "") {
106 if (!is_array($atts)) {
107 $atts = [];
108 }
109 $atts['for_rss_feed'] = TRUE;
110 return modern_footnotes_func($atts,$content);
111 }
112
113 function modern_footnotes_func($atts, $content = "") {
114
115 global $modern_footnotes_all_posts_data, $modern_footnotes_options;
116
117 modern_footnotes_enqueue_scripts_styles_if_not_already_enqueued();
118
119 $additional_classes = '';
120 if (
121 (isset($modern_footnotes_options['desktop_footnote_behavior']) && $modern_footnotes_options['desktop_footnote_behavior'] == 'expandable')
122 /* legacy option use_expandable_footnotes_on_desktop_instead_of_tooltips - should not be set in modern footnotes 1.4.5+ */
123 || (
124 !isset($modern_footnotes_options['desktop_footnote_behavior']) && isset($modern_footnotes_options['use_expandable_footnotes_on_desktop_instead_of_tooltips']) && $modern_footnotes_options['use_expandable_footnotes_on_desktop_instead_of_tooltips']
125 )
126 ) {
127 $additional_classes .= 'modern-footnotes-footnote--expands-on-desktop ';
128 } else if (isset($modern_footnotes_options['desktop_footnote_behavior']) && $modern_footnotes_options['desktop_footnote_behavior'] == 'tooltip_hover') {
129 $additional_classes .= 'modern-footnotes-footnote--hover-on-desktop ';
130 }
131
132 // If additional space-seperated classes are provided to an individual footnote using [mfn class="some-class"], they are added to the footnote
133 if (isset($atts['class'])) {
134 $additional_classes .= esc_attr($atts['class']).' ';
135 }
136
137 // $scope_id will have a unique value for each post on the page -- this helps handle when a post is
138 // nested inside another post, as can happen with the Display Posts plugin
139 $scope_id = modern_footnotes_get_post_scope_id();
140
141 $additional_attributes = '';
142
143 if (isset($atts['referencereset']) && $atts['referencereset'] == 'true') {
144 if (isset($modern_footnotes_all_posts_data[$scope_id])) {
145 $modern_footnotes_all_posts_data[$scope_id]['used_reference_numbers'] = array();
146 $additional_attributes .= ' data-mfn-reset';
147 }
148 }
149
150 if (isset($atts['referencenumber'])) {
151 $reference_label = $atts['referencenumber'];
152 $additional_attributes = 'refnum="' . esc_attr($reference_label) . '"';
153 } else if (!isset($modern_footnotes_all_posts_data[$scope_id]) || count($modern_footnotes_all_posts_data[$scope_id]['used_reference_numbers']) == 0) {
154 $reference_label = 1;
155 } else {
156 $reference_label = max($modern_footnotes_all_posts_data[$scope_id]['used_reference_numbers']) + 1;
157 }
158
159 $content = do_shortcode($content); // render out any shortcodes within the contents
160
161 $content = str_replace('<p>','', $content);
162 $content = str_replace('</p>','<br /><br />', $content);
163
164 if (isset($modern_footnotes_options['use_title_tags_for_footnote_links']) && $modern_footnotes_options['use_title_tags_for_footnote_links']) {
165 $additional_attributes .= ' title="' . str_replace('"','&quot;', strip_tags($content)) . '" ';
166 }
167
168 //calculate an id that will be unique to each footnote within the scope of the post
169 $id = 1;
170 if (isset($modern_footnotes_all_posts_data[$scope_id])) {
171 $current_scope_footnotes = $modern_footnotes_all_posts_data[$scope_id]['footnotes'];
172 $last_id = $current_scope_footnotes[count($current_scope_footnotes) - 1]['id'];
173 $id = $last_id + 1;
174 }
175
176 $footnote = array(
177 'reference_label' => $reference_label,
178 'content' => $content,
179 'id' => $id
180 );
181
182 if (!isset($modern_footnotes_all_posts_data[$scope_id])) {
183 $modern_footnotes_all_posts_data[$scope_id] = array(
184 'modern_footnotes_post_number' => $GLOBALS['current_modern_footnotes_post_number'],
185 'used_reference_numbers' => array($reference_label),
186 'footnotes' => array($footnote)
187 );
188 $GLOBALS['current_modern_footnotes_post_number']++;
189 } else {
190 if (is_numeric($reference_label)) {
191 $modern_footnotes_all_posts_data[$scope_id]['used_reference_numbers'][] = $reference_label;
192 }
193 $modern_footnotes_all_posts_data[$scope_id]['footnotes'][] = $footnote;
194 }
195
196 $display_footnotes_at_bottom_of_posts = isset($modern_footnotes_options['display_footnotes_at_bottom_of_posts']) && $modern_footnotes_options['display_footnotes_at_bottom_of_posts'];
197 $show_jump_to_list_link = isset($modern_footnotes_options['modern_footnotes_show_jump_to_list_link_in_footnotes']) && $modern_footnotes_options['modern_footnotes_show_jump_to_list_link_in_footnotes'];
198
199 //create a unique ID to use in HTML
200 $content_id = "mfn-content-" . $scope_id . '-' . preg_replace('/[^a-zA-Z0-9-_]/i', '', esc_attr($id));
201
202 if (isset($atts['for_rss_feed']) && $atts['for_rss_feed']) {
203 $content = '<sup class="modern-footnotes-footnote ' . $additional_classes . '">' . esc_html($reference_label) . '</sup>';
204 } else {
205 $content = '<sup id="' . $content_id . '" ' .
206 'class="modern-footnotes-footnote ' . $additional_classes . '" ' .
207 'data-mfn="' . str_replace('"',"\\\"", esc_attr($reference_label)) . '" ' .
208 'data-mfn-post-scope="' . $scope_id . '">' .
209 '<a href="javascript:void(0)" ' . $additional_attributes . ' role="button" aria-pressed="false" aria-describedby="' . $content_id . '">' . esc_html($reference_label) . '</a>' .
210 '</sup>' .
211 '<span role="tooltip" class="modern-footnotes-footnote__note" tabindex="0" data-mfn="' . str_replace('"',"\\\"", esc_attr($reference_label)) . '">' .
212 $content .
213 ($display_footnotes_at_bottom_of_posts && $show_jump_to_list_link ?
214 '<a href="#footnote-' . esc_attr($scope_id) . '-' . esc_attr($id) . '" class="modern-footnotes-scroll-to-reference" aria-label="Jump to footnote ' . esc_attr($reference_label) . '">↓</a>' :
215 '')
216 .
217 '</span>'; //use a block element, not an inline element: otherwise, footnotes with line breaks won't display correctly
218 }
219
220 return $content;
221 }
222
223 //if the options are set to do so, list the footnotes at the bottom of the page
224 function modern_footnotes_display_after_content($content) {
225 global $modern_footnotes_options;
226
227 $show_only_when_printing = FALSE;
228 $hide_when_printing = FALSE;
229 if (
230 (!isset($GLOBALS['modern_footnotes_displaying_rss_feed']) || !$GLOBALS['modern_footnotes_displaying_rss_feed']) &&
231 (
232 (isset($modern_footnotes_options['display_footnotes_at_bottom_of_posts']) && $modern_footnotes_options['display_footnotes_at_bottom_of_posts']) ||
233 (isset($modern_footnotes_options['display_footnotes_at_bottom_of_posts_when_printing']) && $modern_footnotes_options['display_footnotes_at_bottom_of_posts_when_printing'])
234 )
235 ) {
236 $options = array();
237 if (isset($modern_footnotes_options['display_footnotes_at_bottom_of_posts_when_printing']) && $modern_footnotes_options['display_footnotes_at_bottom_of_posts_when_printing']) {
238 if (!isset($modern_footnotes_options['display_footnotes_at_bottom_of_posts']) || !$modern_footnotes_options['display_footnotes_at_bottom_of_posts']) {
239 $show_only_when_printing = TRUE;
240 }
241 } else {
242 $hide_when_printing = TRUE;
243 }
244 $content .= modern_footnotes_list_footnotes($show_only_when_printing, $hide_when_printing);
245 }
246
247 return $content;
248 }
249
250 $modern_footnotes_shortcodes = array('modern_footnote','mfn');
251 if (isset($modern_footnotes_options['modern_footnotes_custom_shortcode']) && !empty($modern_footnotes_options['modern_footnotes_custom_shortcode'])) {
252 $modern_footnotes_shortcodes[] = $modern_footnotes_options['modern_footnotes_custom_shortcode'];
253 }
254 foreach ($modern_footnotes_shortcodes as $modern_footnote_shortcode) {
255 add_shortcode($modern_footnote_shortcode, 'modern_footnotes_func');
256 }
257
258 add_shortcode('mfn_list', 'modern_footnotes_list_func');
259
260 add_filter('the_content', 'modern_footnotes_reset_count', 10); // run before shortcodes are processed
261 add_filter('the_content', 'modern_footnotes_display_after_content', 11);
262 add_filter('the_content', 'modern_footnotes_execute_mfn_list_shortcode', 12);
263
264 //reset the footnote counter for every new post
265 function modern_footnotes_reset_count($content) {
266 // if we are loading a scope that has previously been loaded, this is our second time loading the post. Reset the footnotes for the post.
267 $scope = modern_footnotes_get_post_scope_id();
268 if (isset($GLOBALS['modern_footnotes_all_posts_data'][$scope])) {
269 unset($GLOBALS['modern_footnotes_all_posts_data'][$scope]);
270 }
271 return $content;
272 }
273
274 add_action('the_post', 'modern_footnotes_check_post_query',10,2);
275
276 // save the unique queries on the page, so that if a post is contained within another post, we can properly scope the
277 // footnote numbering to that particular post
278 function modern_footnotes_check_post_query($scoped_post, $scoped_query = null) {
279 global $modern_footnotes_active_query;
280 if (isset($scoped_query)) {
281 $modern_footnotes_active_query = $scoped_query;
282 }
283 }
284
285 // return an ID that can be used to identify the unique post that we are in -- used for listing multiple posts on the
286 // same page, including when posts are nested with plugins like DisplayPosts
287 function modern_footnotes_get_post_scope_id() {
288 if (isset($GLOBALS['post'])) {
289 $global_post = $GLOBALS['post'];
290 if (is_object($global_post)) {
291 if (property_exists($global_post, 'ID')) {
292 $global_post_id = $global_post->ID;
293 } else {
294 // some plugins, like relevanssi, modify the 'post' object to something other than a WP_Post instance (https://wordpress.org/support/topic/v1-4-18-breaks-search/)
295 $global_post_id = spl_object_hash($global_post);
296 }
297 } else {
298 $global_post_id = $global_post;
299 }
300 if (isset($GLOBALS['modern_footnotes_active_query'])) {
301 return spl_object_hash($GLOBALS['modern_footnotes_active_query']) . '_' . $global_post_id;
302 } else {
303 return 'post_' . $global_post_id;
304 }
305 } else {
306 return 'na';
307 }
308 }
309
310 // replace <mfn> HTML tags added by Gutenberg/block editor to [mfn] shortcodes
311 // When multiple formats are applied, Gutenberg can have multiple <mfn> tags for one footnote, so we'll have to iterate through the text and group sibling tags together (see https://github.com/seankwilliams/modern-footnotes/issues/14)
312 function modern_footnotes_replace_mfn_tag_with_shortcode( $content ) {
313 $content = str_replace('</mfn>','<mfn>',$content); //using [mfn] instead of [/mfn] is intentional here
314 $content_parts = explode('<mfn>', $content);
315 $content_data = array();
316 //$tagsFromPreviousSegment = array();
317 $inFootnote = FALSE;
318 foreach ($content_parts as $c) {
319 $content_data[] = array(
320 "content" => $c,
321 "inFootnote" => $inFootnote
322 );
323 $inFootnote = !$inFootnote;
324 }
325 $wasInFootnote = FALSE;
326 for ($i = 0; $i < count($content_data); $i++) {
327 //if this is only opening tags or only closing tags, place it in the footnote
328 $replacedString = preg_replace("/<\/?\\w+\\s?\\w?.*?>/ms", "", $content_data[$i]['content']);
329 if (strlen($replacedString) === 0 && !$content_data[$i]['inFootnote'] && $wasInFootnote) { // check $wasInFootnote to fix https://github.com/seankwilliams/modern-footnotes/issues/18
330 $content_data[$i]['inFootnote'] = TRUE;
331 } else {
332 $wasInFootnote = $content_data[$i]['inFootnote'];
333 }
334 }
335 $final_content = '';
336 $inFootnote = FALSE;
337 foreach ($content_data as $cd) {
338 if ($cd['inFootnote'] && !$inFootnote) {
339 $inFootnote = TRUE;
340 $final_content .= '[mfn]';
341 } else if ($inFootnote && !$cd['inFootnote']) {
342 $inFootnote = FALSE;
343 $final_content .= '[/mfn]';
344 }
345
346 $final_content .= $cd['content'];
347 }
348 if ($inFootnote) {
349 $final_content .= '[/mfn]';
350 }
351 return $final_content;
352 }
353 add_filter( 'the_content', 'modern_footnotes_replace_mfn_tag_with_shortcode' );
354
355 // remove <mfn> HTML tags added by Gutenberg/block editor
356 function modern_footnotes_strip_rendered_mfn_tag( $content ) {
357
358 // we will remove all rendered text from the <mfn> tags
359 global $modern_footnotes_all_posts_data;
360 $scope_id = modern_footnotes_get_post_scope_id();
361 if (empty($modern_footnotes_all_posts_data[$scope_id])) {
362 return $content;
363 }
364 $footnotes = $modern_footnotes_all_posts_data[$scope_id]['footnotes'];
365
366 foreach ($footnotes as $footnote) {
367 if (!empty($footnote['content'])) { //ensure footnote_content is not empty: otherwise, we may be replacing just a number, which is far too common
368 $content = str_replace($footnote['reference_label'] . wp_strip_all_tags($footnote['content']), '', $content);
369 }
370 }
371
372 return $content;
373
374 }
375 add_filter( 'wp_trim_words', 'modern_footnotes_strip_rendered_mfn_tag'); // use this filter so that <mfn> HTML tags added through gutenberg are removed in wp_trim_excerpt -- in wp_trim_excerpt, strip_shortcodes runs AFTER the_content filters do
376
377
378
379 function modern_footnotes_register_scripts_styles() {
380 global $modern_footnotes_options, $modern_footnotes_shortcodes, $modern_footnotes_version, $post;
381 wp_register_style('modern_footnotes', plugin_dir_url(__FILE__) . 'styles.min.css', array(), $modern_footnotes_version);
382 wp_register_script('modern_footnotes', plugin_dir_url(__FILE__) . 'modern-footnotes.min.js', array('jquery'), $modern_footnotes_version, TRUE);
383
384 if (!is_admin() && isset($modern_footnotes_options['modern_footnotes_custom_css']) && !empty($modern_footnotes_options['modern_footnotes_custom_css'])) {
385 wp_add_inline_style( 'modern_footnotes', $modern_footnotes_options['modern_footnotes_custom_css'] );
386 }
387
388 // if we are in a post, and the post uses the shortcode, enqueue the style + script
389 // this is not foolproof since the shortcode could be used in other ways (like post metadata), so we
390 // will have to check when rendering the shortcodes to ensure that the scripts/styles are enqueued
391 if (is_a( $post, 'WP_Post' )) {
392 $has_shortcode = FALSE;
393 if (isset($modern_footnotes_shortcodes)) { // attempt to resolve https://wordpress.org/support/topic/error-causes-php-process-hang/ - I am not really clear how this variable could be null here, though.
394 foreach ($modern_footnotes_shortcodes as $modern_footnote_shortcode) {
395 if (has_shortcode($post->post_content, $modern_footnote_shortcode)) {
396 $has_shortcode = TRUE;
397 }
398 }
399 }
400 if (has_shortcode($post->post_content, 'mfn_list')) {
401 $has_shortcode = TRUE;
402 }
403 if ($has_shortcode) {
404 wp_enqueue_style('modern_footnotes');
405 wp_enqueue_script('modern_footnotes');
406 }
407 }
408 }
409
410 add_action('wp_enqueue_scripts', 'modern_footnotes_register_scripts_styles');
411
412 function modern_footnotes_enqueue_scripts_styles_if_not_already_enqueued() {
413 if (!wp_style_is('modern_footnotes')) {
414 wp_enqueue_style('modern_footnotes');
415 }
416 if (!wp_script_is('modern_footnotes')) {
417 wp_enqueue_script('modern_footnotes');
418 }
419 }
420
421 //
422 //modify the admin
423 //
424
425 //create a settings page
426 function modern_footnotes_menu() {
427 add_options_page( __('Modern Footnotes Settings', 'modern-footnotes'),
428 __('Modern Footnotes','modern-footnotes'),
429 'manage_options', __FILE__, 'modern_footnotes_options' );
430 }
431
432 function modern_footnotes_options() {
433 if ( !current_user_can( 'manage_options' ) ) {
434 wp_die( __( 'You do not have sufficient permissions to access this page.' ) );
435 }
436 echo '<div class="wrap">';
437 echo '<h1>' . esc_html__('Modern Footnotes Settings','modern-footnotes') . '</h1>';
438 // Indent sub-settings (e.g. options that only apply when a parent option is on)
439 // so they read as nested beneath the setting above them.
440 echo '<style>
441 .form-table tr.mfn-sub-setting th { font-weight: 400; padding-left: 30px; }
442 .form-table tr.mfn-sub-setting td { padding-left: 30px; }
443 </style>';
444 echo '<form method="post" action="options.php">';
445 settings_fields('modern_footnotes_settings');
446 do_settings_sections(__FILE__);
447 submit_button();
448 echo '</form>';
449 echo '</div>';
450 }
451
452 function modern_footnotes_register_settings() { // whitelist options
453 register_setting('modern_footnotes_settings', 'modern_footnotes_settings',
454 array(
455 'type' => 'boolean',
456 'default' => FALSE,
457 'sanitize_callback' => 'modern_footnotes_sanitize_callback'
458 ));
459 add_settings_section(
460 'modern_footnotes_option_group_section',
461 __('Modern Footnotes Settings', 'modern-footnotes'),
462 function() { /* do nothing, no HTML needed for section heading */ },
463 __FILE__
464 );
465
466 add_settings_field(
467 'modern_footnotes_desktop_footnote_behavior',
468 __('Desktop footnote behavior', 'modern-footnotes'),
469 'modern_footnotes_desktop_footnote_behavior_dropdown_callback',
470 __FILE__,
471 'modern_footnotes_option_group_section'
472 );
473
474 add_settings_field(
475 'use_title_tags_for_footnote_links',
476 __('Show browser tooltip on hover', 'modern-footnotes'),
477 'modern_footnotes_checkbox_element_callback',
478 __FILE__,
479 'modern_footnotes_option_group_section',
480 array(
481 'property_name' => 'use_title_tags_for_footnote_links',
482 'property_label' => 'Make footnote content appear in web browser\'s native tooltip when hovering over footnote number'
483 )
484 );
485
486
487
488 add_settings_field(
489 'display_footnotes_at_bottom_of_posts',
490 __('Display footnote list at bottom of posts', 'modern-footnotes'),
491 'modern_footnotes_checkbox_element_callback',
492 __FILE__,
493 'modern_footnotes_option_group_section',
494 array(
495 'property_name' => 'display_footnotes_at_bottom_of_posts',
496 'property_label' => 'Display footnote list at bottom of posts'
497 )
498 );
499 add_settings_field(
500 'modern_footnotes_show_jump_to_list_link_in_footnotes',
501 __('Add jump-to-list link in footnotes', 'modern-footnotes'),
502 'modern_footnotes_checkbox_element_callback',
503 __FILE__,
504 'modern_footnotes_option_group_section',
505 array(
506 'property_name' => 'modern_footnotes_show_jump_to_list_link_in_footnotes',
507 'property_label' => 'Add a down-arrow link in each footnote that jumps to its entry in the list at the bottom of the post',
508 'class' => 'mfn-sub-setting'
509 )
510 );
511 add_settings_field(
512 'display_footnotes_at_bottom_of_posts_when_printing',
513 __('When printing, list footnotes at the bottom of posts', 'modern-footnotes'),
514 'modern_footnotes_checkbox_element_callback',
515 __FILE__,
516 'modern_footnotes_option_group_section',
517 array(
518 'property_name' => 'display_footnotes_at_bottom_of_posts_when_printing',
519 'property_label' => 'When printing, list footnotes at the bottom of posts'
520 )
521 );
522
523 add_settings_field(
524 'modern_footnotes_include_footnote_list_at_end_of_rss_content',
525 __('For post content in RSS feeds, list footnotes at the bottom of posts', 'modern-footnotes'),
526 'modern_footnotes_checkbox_element_callback',
527 __FILE__,
528 'modern_footnotes_option_group_section',
529 array(
530 'property_name' => 'modern_footnotes_include_footnote_list_at_end_of_rss_content',
531 'property_label' => 'For post content in RSS feeds, list footnotes at the bottom of posts'
532 )
533 );
534
535 add_settings_field(
536 'modern_footnotes_heading_for_footnote_list',
537 __('Heading for footnote list', 'modern-footnotes'),
538 'modern_footnotes_textbox_element_callback',
539 __FILE__,
540 'modern_footnotes_option_group_section',
541 array(
542 'property_name' => 'modern_footnotes_heading_for_footnote_list',
543 'property_label' => 'If provided, this text will be displayed above footnote lists'
544 )
545 );
546
547 add_settings_field(
548 'modern_footnotes_heading_tag_name_for_footnote_list',
549 __('Heading tag name for footnote list', 'modern-footnotes'),
550 'modern_footnotes_tag_name_for_footnote_list_dropdown_callback',
551 __FILE__,
552 'modern_footnotes_option_group_section'
553 );
554
555 add_settings_field(
556 'modern_footnotes_custom_css',
557 __('Modern Footnotes Custom CSS', 'modern-footnotes'),
558 'modern_footnotes_custom_css_element_callback',
559 __FILE__,
560 'modern_footnotes_option_group_section'
561 );
562 add_settings_field(
563 'modern_footnotes_custom_shortcode',
564 __('Modern Footnotes Custom Shortcode', 'modern-footnotes'),
565 'modern_footnotes_custom_shortcode_element_callback',
566 __FILE__,
567 'modern_footnotes_option_group_section'
568 );
569 }
570
571 function modern_footnotes_sanitize_callback($plugin_options) {
572 global $modern_footnotes_options;
573
574 if (isset($plugin_options['modern_footnotes_custom_css']) && !empty($plugin_options['modern_footnotes_custom_css'])) {
575 //strip style HTML tags from the custom CSS property
576 $plugin_options['modern_footnotes_custom_css'] = preg_replace('/<\/?style.*?>/i', '', $plugin_options['modern_footnotes_custom_css']);
577 }
578
579 if (isset($plugin_options['modern_footnotes_custom_shortcode']) && !empty($plugin_options['modern_footnotes_custom_shortcode'])) {
580 //remove invalid characters from shortcode
581 $plugin_options['modern_footnotes_custom_shortcode'] = preg_replace('/[^a-zA-Z0-9-_]/i', '', $plugin_options['modern_footnotes_custom_shortcode']);
582 if ((!isset($modern_footnotes_options['modern_footnotes_custom_shortcode']) || $modern_footnotes_options['modern_footnotes_custom_shortcode'] != $plugin_options['modern_footnotes_custom_shortcode']) &&
583 shortcode_exists($plugin_options['modern_footnotes_custom_shortcode'])) {
584 add_settings_error( 'modern_footnotes_custom_shortcode', 'shortcode-in-use', 'The shortcode "' . $plugin_options['modern_footnotes_custom_shortcode'] . '" is already in use, please enter a different one' );
585 $plugin_options['modern_footnotes_custom_shortcode'] = '';
586 }
587 }
588 return $plugin_options;
589 }
590
591 function modern_footnotes_checkbox_element_callback($args) {
592 global $modern_footnotes_options;
593
594 $property_name = $args['property_name'];
595 $property_label = $args['property_label'];
596
597 $html = '<input type="checkbox" id="%1$s" name="modern_footnotes_settings[%1$s]" value="1"' . checked( 1, isset($modern_footnotes_options[$property_name]) && $modern_footnotes_options[$property_name], FALSE ) . '/>';
598 $html .= '<label for="%1$s">' .
599 esc_html__($property_label, 'modern-footnotes') .
600 '</label>';
601 $html = sprintf($html, $property_name);
602
603 echo $html;
604 }
605
606 function modern_footnotes_textbox_element_callback($args) {
607 global $modern_footnotes_options;
608
609 $property_name = $args['property_name'];
610 $property_label = $args['property_label'];
611
612 $html = '<input type="text" id="%1$s" name="modern_footnotes_settings[%1$s]" value="%2$s" />';
613 $html .= ' <label for="%1$s">' .
614 esc_html__($property_label, 'modern-footnotes') .
615 '</label>';
616 $html = sprintf($html, $property_name, isset($modern_footnotes_options[$property_name]) ? esc_attr($modern_footnotes_options[$property_name]) : '');
617
618 echo $html;
619 }
620
621
622
623 function modern_footnotes_desktop_footnote_behavior_dropdown_callback() {
624 global $modern_footnotes_options;
625
626 $selected_value = isset($modern_footnotes_options['desktop_footnote_behavior']) ? $modern_footnotes_options['desktop_footnote_behavior'] : '';
627
628 $options = array(
629 'tooltip_click' => __('Tooltip footnotes that open on click', 'modern-footnotes'),
630 'tooltip_hover' => __('Tooltip footnotes that open on hover', 'modern-footnotes'),
631 'expandable' => __('Expandable footnotes', 'modern-footnotes'),
632 );
633
634 $html = '<select id="modern_footnotes_desktop_footnote_behavior" name="modern_footnotes_settings[desktop_footnote_behavior]"> aria-label="%1$s"';
635 foreach ($options as $key => $value) {
636 $option_html = '<option value="%s" %s>%s</option>';
637 $html .= sprintf($option_html, esc_attr($key), $selected_value == $key ? 'selected' : '', esc_html($value));
638 }
639 $html .= '</select>';
640
641 $html = sprintf($html, __('Desktop footnote behavior', 'modern-footnotes'));
642
643 echo $html;
644 }
645
646 function modern_footnotes_tag_name_for_footnote_list_dropdown_callback() {
647 global $modern_footnotes_options;
648
649 $selected_value = isset($modern_footnotes_options['modern_footnotes_heading_tag_name_for_footnote_list']) ? $modern_footnotes_options['modern_footnotes_heading_tag_name_for_footnote_list'] : 'h3';
650
651 $options = array(
652 'h2' => __('Heading 2', 'access2'),
653 'h3' => __('Heading 3', 'access3'),
654 'h4' => __('Heading 4', 'access4'),
655 'h5' => __('Heading 5', 'access5'),
656 'h6' => __('Heading 6', 'access6')
657 );
658
659 $html = '<select id="modern_footnotes_heading_tag_name_for_footnote_list" name="modern_footnotes_settings[modern_footnotes_heading_tag_name_for_footnote_list]"> aria-label="%1$s"';
660 foreach ($options as $key => $value) {
661 $option_html = '<option value="%s" %s>%s</option>';
662 $html .= sprintf($option_html, esc_attr($key), $selected_value == $key ? 'selected' : '', esc_html($value));
663 }
664 $html .= '</select>';
665
666 $html = sprintf($html, __('Heading tag name for footnote list', 'modern-footnotes'));
667
668 echo $html;
669 }
670
671
672
673
674 function modern_footnotes_custom_css_element_callback() {
675 global $modern_footnotes_options;
676
677 $html = '<textarea id="modern_footnotes_custom_css" name="modern_footnotes_settings[modern_footnotes_custom_css]" style="max-width:100%;width:400px;height:200px">' . (isset($modern_footnotes_options['modern_footnotes_custom_css']) ? esc_textarea($modern_footnotes_options['modern_footnotes_custom_css']) : '') . '</textarea>';
678 $html .= '<label for="modern_footnotes_custom_css">' .
679 esc_html__('Enter any custom CSS for the plugin, without any <style> tags.', 'modern-footnotes') .
680 '</label>';
681
682 echo $html;
683 }
684
685 function modern_footnotes_custom_shortcode_element_callback() {
686 global $modern_footnotes_options;
687
688 $html = '<input type="text" id="modern_footnotes_custom_shortcode" name="modern_footnotes_settings[modern_footnotes_custom_shortcode]" value="' . (isset($modern_footnotes_options['modern_footnotes_custom_shortcode']) ? $modern_footnotes_options['modern_footnotes_custom_shortcode'] : '') . '" />';
689 $html .= '<label for="modern_footnotes_custom_shortcode">' .
690 esc_html__('Custom shortcode if you\'d like to use something other than [mfn] or [modern_footnote]. Enter the shortcode without the brackets.', 'modern-footnotes') .
691 '</label>';
692
693 echo $html;
694 }
695
696 function modern_footnotes_return_blank_for_rss_func($atts, $content = "") {
697 return "";
698 }
699
700 // remove shortcode from RSS feed
701 function modern_footnotes_remove_from_rss_content_feed($content) {
702 return modern_footnotes_remove_from_rss_feed($content, TRUE);
703 }
704 function modern_footnotes_remove_from_rss_feed($content, $is_content_section = FALSE){
705 foreach ($GLOBALS['modern_footnotes_shortcodes'] as $modern_footnote_shortcode) {
706 remove_shortcode($modern_footnote_shortcode);
707 if ($GLOBALS['modern_footnotes_options']['modern_footnotes_include_footnote_list_at_end_of_rss_content']) {
708 add_shortcode($modern_footnote_shortcode, 'modern_footnotes_rss_func');
709 } else {
710 add_shortcode($modern_footnote_shortcode, 'modern_footnotes_return_blank_for_rss_func');
711 }
712 }
713 // do not let the [mfn_list] shortcode execute in the RSS feed
714 remove_shortcode('mfn_list');
715 add_shortcode('mfn_list', 'modern_footnotes_return_blank_for_rss_func');
716 // add a [mfn_rss_list] shortcode to the end of the post content
717 if ($GLOBALS['modern_footnotes_options']['modern_footnotes_include_footnote_list_at_end_of_rss_content'] && $is_content_section) {
718 $content .= modern_footnotes_list_footnotes(FALSE,FALSE,TRUE);
719 }
720
721 return $content;
722 }
723 add_filter('the_excerpt_rss', 'modern_footnotes_remove_from_rss_feed');
724 add_filter('the_content_feed', 'modern_footnotes_remove_from_rss_content_feed');
725
726 // set variable to let us know we are in an rss feed
727 function modern_footnotes_in_rss() {
728 $GLOBALS['modern_footnotes_displaying_rss_feed'] = TRUE;
729 }
730 add_action('rss_head', 'modern_footnotes_in_rss'); // for RSS 1
731 add_action('rss_tag_pre', 'modern_footnotes_in_rss'); //for all other feed types
732
733
734 if (is_admin()) { // admin actions
735 add_action( 'admin_menu', 'modern_footnotes_menu' );
736 add_action( 'admin_init', 'modern_footnotes_register_settings' );
737 }
738
739 //setup button on the WordPress editor
740
741 //
742 // Pre-Gutenberg editor
743 //
744 function modern_footnotes_add_container_button() {
745 if ( ! current_user_can('edit_posts') && ! current_user_can('edit_pages') )
746 return;
747 if ( get_user_option('rich_editing') == 'true') {
748 add_filter('mce_external_plugins', 'modern_footnotes_add_container_plugin');
749 add_filter('mce_buttons', 'modern_footnotes_register_container_button');
750 }
751 }
752 if (is_admin()) {
753 add_filter('init', 'modern_footnotes_add_container_button');
754
755 function modern_footnotes_enqueue_admin_scripts() {
756 global $modern_footnotes_version;
757 wp_enqueue_style('modern_footnotes', plugin_dir_url(__FILE__) . 'styles.mce-button.min.css', array(), $modern_footnotes_version);
758 }
759
760 add_action('admin_enqueue_scripts', 'modern_footnotes_enqueue_admin_scripts');
761 }
762
763
764 function modern_footnotes_register_container_button($buttons) {
765 array_push($buttons, "modern_footnotes");
766 return $buttons;
767 }
768
769 function modern_footnotes_add_container_plugin($plugin_array) {
770 $plugin_array['modern_footnotes'] = plugin_dir_url(__FILE__) . 'modern-footnotes.mce-button.min.js';
771 return $plugin_array;
772 }
773 //
774 // End Pre-Gutenberg editor
775 //
776
777 //
778 // Gutenberg / Block Editor
779 //
780 function modern_footnotes_block_editor_button() {
781
782 $currentScreen = get_current_screen();
783 if ($currentScreen->id === "widgets") {
784 return;
785 }
786
787 global $modern_footnotes_version;
788 wp_enqueue_script( 'modern_footnotes_block_editor_js',
789 plugin_dir_url(__FILE__) . 'modern-footnotes.block-editor.min.js',
790 array( 'wp-rich-text', 'wp-element', 'wp-block-editor', 'wp-i18n' ),
791 $modern_footnotes_version
792 );
793 wp_set_script_translations('modern_footnotes_block_editor_js','modern-footnotes');
794 wp_enqueue_style('modern_footnotes_block_editor_css', plugin_dir_url(__FILE__) . 'styles.block-editor-button.min.css', array(), $modern_footnotes_version);
795
796 }
797 add_action( 'enqueue_block_editor_assets', 'modern_footnotes_block_editor_button' );
798
799 // Load footnote *content* styles into the iframed editor canvas (WP 6.3+/7.x).
800 // enqueue_block_editor_assets only reaches the admin page (outside the iframe),
801 // so content rules (mfn highlight, "footnote" label) need enqueue_block_assets.
802 function modern_footnotes_block_editor_content_styles() {
803 // enqueue_block_assets fires on BOTH the front end and the editor. These are
804 // editor-only authoring hints (the grey `mfn` highlight and the "footnote" label)
805 // that help writers see footnotes while composing. On the published page footnotes
806 // get their real appearance from the plugin's own styles.css / styles.min.css
807 // (enqueued via wp_enqueue_scripts), where the grey editor highlight would look
808 // wrong, so we bail out on the front end and only inject into the editor iframe.
809 if ( ! is_admin() ) {
810 return;
811 }
812 global $modern_footnotes_version;
813 wp_enqueue_style(
814 'modern_footnotes_block_editor_css',
815 plugin_dir_url( __FILE__ ) . 'styles.block-editor-button.min.css',
816 array(),
817 $modern_footnotes_version
818 );
819 }
820 add_action( 'enqueue_block_assets', 'modern_footnotes_block_editor_content_styles' );
821 //
822 // End Gutenberg / Block Editor
823 //