PluginProbe
Modern Footnotes / 1.5.1
Modern Footnotes v1.5.1
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.1, at modern-footnotes.php

828 lines 37.0 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.1
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.1';
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($GLOBALS['current_modern_footnotes_post_number'])) {
183 $GLOBALS['current_modern_footnotes_post_number'] = 0;
184 }
185
186 if (!isset($modern_footnotes_all_posts_data[$scope_id])) {
187 $modern_footnotes_all_posts_data[$scope_id] = array(
188 'modern_footnotes_post_number' => $GLOBALS['current_modern_footnotes_post_number'],
189 'used_reference_numbers' => array($reference_label),
190 'footnotes' => array($footnote)
191 );
192 $GLOBALS['current_modern_footnotes_post_number']++;
193 } else {
194 if (is_numeric($reference_label)) {
195 $modern_footnotes_all_posts_data[$scope_id]['used_reference_numbers'][] = $reference_label;
196 }
197 $modern_footnotes_all_posts_data[$scope_id]['footnotes'][] = $footnote;
198 }
199
200 $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'];
201 $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'];
202
203 //create a unique ID to use in HTML
204 $content_id = "mfn-content-" . $scope_id . '-' . preg_replace('/[^a-zA-Z0-9-_]/i', '', esc_attr($id));
205
206 if (isset($atts['for_rss_feed']) && $atts['for_rss_feed']) {
207 $content = '<sup class="modern-footnotes-footnote ' . $additional_classes . '">' . esc_html($reference_label) . '</sup>';
208 } else {
209 $content = '<sup id="' . $content_id . '" ' .
210 'class="modern-footnotes-footnote ' . $additional_classes . '" ' .
211 'data-mfn="' . str_replace('"',"\\\"", esc_attr($reference_label)) . '" ' .
212 'data-mfn-post-scope="' . $scope_id . '">' .
213 '<a href="javascript:void(0)" ' . $additional_attributes . ' role="button" aria-pressed="false" aria-describedby="' . $content_id . '">' . esc_html($reference_label) . '</a>' .
214 '</sup>' .
215 '<span role="tooltip" class="modern-footnotes-footnote__note" tabindex="0" data-mfn="' . str_replace('"',"\\\"", esc_attr($reference_label)) . '">' .
216 $content .
217 ($display_footnotes_at_bottom_of_posts && $show_jump_to_list_link ?
218 '<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>' :
219 '')
220 .
221 '</span>'; //use a block element, not an inline element: otherwise, footnotes with line breaks won't display correctly
222 }
223
224 return $content;
225 }
226
227 //if the options are set to do so, list the footnotes at the bottom of the page
228 function modern_footnotes_display_after_content($content) {
229 global $modern_footnotes_options;
230
231 $show_only_when_printing = FALSE;
232 $hide_when_printing = FALSE;
233 if (
234 (!isset($GLOBALS['modern_footnotes_displaying_rss_feed']) || !$GLOBALS['modern_footnotes_displaying_rss_feed']) &&
235 (
236 (isset($modern_footnotes_options['display_footnotes_at_bottom_of_posts']) && $modern_footnotes_options['display_footnotes_at_bottom_of_posts']) ||
237 (isset($modern_footnotes_options['display_footnotes_at_bottom_of_posts_when_printing']) && $modern_footnotes_options['display_footnotes_at_bottom_of_posts_when_printing'])
238 )
239 ) {
240 $options = array();
241 if (isset($modern_footnotes_options['display_footnotes_at_bottom_of_posts_when_printing']) && $modern_footnotes_options['display_footnotes_at_bottom_of_posts_when_printing']) {
242 if (!isset($modern_footnotes_options['display_footnotes_at_bottom_of_posts']) || !$modern_footnotes_options['display_footnotes_at_bottom_of_posts']) {
243 $show_only_when_printing = TRUE;
244 }
245 } else {
246 $hide_when_printing = TRUE;
247 }
248 $content .= modern_footnotes_list_footnotes($show_only_when_printing, $hide_when_printing);
249 }
250
251 return $content;
252 }
253
254 $modern_footnotes_shortcodes = array('modern_footnote','mfn');
255 if (isset($modern_footnotes_options['modern_footnotes_custom_shortcode']) && !empty($modern_footnotes_options['modern_footnotes_custom_shortcode'])) {
256 $modern_footnotes_shortcodes[] = $modern_footnotes_options['modern_footnotes_custom_shortcode'];
257 }
258 foreach ($modern_footnotes_shortcodes as $modern_footnote_shortcode) {
259 add_shortcode($modern_footnote_shortcode, 'modern_footnotes_func');
260 }
261
262 add_shortcode('mfn_list', 'modern_footnotes_list_func');
263
264 add_filter('the_content', 'modern_footnotes_reset_count', 10); // run before shortcodes are processed
265 add_filter('the_content', 'modern_footnotes_display_after_content', 11);
266 add_filter('the_content', 'modern_footnotes_execute_mfn_list_shortcode', 12);
267
268 //reset the footnote counter for every new post
269 function modern_footnotes_reset_count($content) {
270 // 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.
271 $scope = modern_footnotes_get_post_scope_id();
272 if (isset($GLOBALS['modern_footnotes_all_posts_data'][$scope])) {
273 unset($GLOBALS['modern_footnotes_all_posts_data'][$scope]);
274 }
275 return $content;
276 }
277
278 add_action('the_post', 'modern_footnotes_check_post_query',10,2);
279
280 // save the unique queries on the page, so that if a post is contained within another post, we can properly scope the
281 // footnote numbering to that particular post
282 function modern_footnotes_check_post_query($scoped_post, $scoped_query = null) {
283 global $modern_footnotes_active_query;
284 if (isset($scoped_query)) {
285 $modern_footnotes_active_query = $scoped_query;
286 }
287 }
288
289 // return an ID that can be used to identify the unique post that we are in -- used for listing multiple posts on the
290 // same page, including when posts are nested with plugins like DisplayPosts
291 function modern_footnotes_get_post_scope_id() {
292 if (isset($GLOBALS['post'])) {
293 $global_post = $GLOBALS['post'];
294 if (is_object($global_post)) {
295 if (property_exists($global_post, 'ID')) {
296 $global_post_id = $global_post->ID;
297 } else {
298 // 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/)
299 $global_post_id = spl_object_hash($global_post);
300 }
301 } else {
302 $global_post_id = $global_post;
303 }
304 if (isset($GLOBALS['modern_footnotes_active_query'])) {
305 return spl_object_hash($GLOBALS['modern_footnotes_active_query']) . '_' . $global_post_id;
306 } else {
307 return 'post_' . $global_post_id;
308 }
309 } else {
310 return 'na';
311 }
312 }
313
314 // replace <mfn> HTML tags added by Gutenberg/block editor to [mfn] shortcodes
315 // 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)
316 function modern_footnotes_replace_mfn_tag_with_shortcode( $content ) {
317 $content = str_replace('</mfn>','<mfn>',$content); //using [mfn] instead of [/mfn] is intentional here
318 $content_parts = explode('<mfn>', $content);
319 $content_data = array();
320 //$tagsFromPreviousSegment = array();
321 $inFootnote = FALSE;
322 foreach ($content_parts as $c) {
323 $content_data[] = array(
324 "content" => $c,
325 "inFootnote" => $inFootnote
326 );
327 $inFootnote = !$inFootnote;
328 }
329 $wasInFootnote = FALSE;
330 for ($i = 0; $i < count($content_data); $i++) {
331 //if this is only opening tags or only closing tags, place it in the footnote
332 $replacedString = preg_replace("/<\/?\\w+\\s?\\w?.*?>/ms", "", $content_data[$i]['content']);
333 if (strlen($replacedString) === 0 && !$content_data[$i]['inFootnote'] && $wasInFootnote) { // check $wasInFootnote to fix https://github.com/seankwilliams/modern-footnotes/issues/18
334 $content_data[$i]['inFootnote'] = TRUE;
335 } else {
336 $wasInFootnote = $content_data[$i]['inFootnote'];
337 }
338 }
339 $final_content = '';
340 $inFootnote = FALSE;
341 foreach ($content_data as $cd) {
342 if ($cd['inFootnote'] && !$inFootnote) {
343 $inFootnote = TRUE;
344 $final_content .= '[mfn]';
345 } else if ($inFootnote && !$cd['inFootnote']) {
346 $inFootnote = FALSE;
347 $final_content .= '[/mfn]';
348 }
349
350 $final_content .= $cd['content'];
351 }
352 if ($inFootnote) {
353 $final_content .= '[/mfn]';
354 }
355 return $final_content;
356 }
357 add_filter( 'the_content', 'modern_footnotes_replace_mfn_tag_with_shortcode' );
358
359 // remove <mfn> HTML tags added by Gutenberg/block editor
360 function modern_footnotes_strip_rendered_mfn_tag( $content ) {
361
362 // we will remove all rendered text from the <mfn> tags
363 global $modern_footnotes_all_posts_data;
364 $scope_id = modern_footnotes_get_post_scope_id();
365 if (empty($modern_footnotes_all_posts_data[$scope_id])) {
366 return $content;
367 }
368 $footnotes = $modern_footnotes_all_posts_data[$scope_id]['footnotes'];
369
370 foreach ($footnotes as $footnote) {
371 if (!empty($footnote['content'])) { //ensure footnote_content is not empty: otherwise, we may be replacing just a number, which is far too common
372 $content = str_replace($footnote['reference_label'] . wp_strip_all_tags($footnote['content']), '', $content);
373 }
374 }
375
376 return $content;
377
378 }
379 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
380
381
382
383 function modern_footnotes_register_scripts_styles() {
384 global $modern_footnotes_options, $modern_footnotes_shortcodes, $modern_footnotes_version, $post;
385 wp_register_style('modern_footnotes', plugin_dir_url(__FILE__) . 'styles.min.css', array(), $modern_footnotes_version);
386 wp_register_script('modern_footnotes', plugin_dir_url(__FILE__) . 'modern-footnotes.min.js', array('jquery'), $modern_footnotes_version, TRUE);
387
388 if (!is_admin() && isset($modern_footnotes_options['modern_footnotes_custom_css']) && !empty($modern_footnotes_options['modern_footnotes_custom_css'])) {
389 wp_add_inline_style( 'modern_footnotes', $modern_footnotes_options['modern_footnotes_custom_css'] );
390 }
391
392 // if we are in a post, and the post uses the shortcode, enqueue the style + script
393 // this is not foolproof since the shortcode could be used in other ways (like post metadata), so we
394 // will have to check when rendering the shortcodes to ensure that the scripts/styles are enqueued
395 if (is_a( $post, 'WP_Post' )) {
396 $has_shortcode = FALSE;
397 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.
398 foreach ($modern_footnotes_shortcodes as $modern_footnote_shortcode) {
399 if (has_shortcode($post->post_content, $modern_footnote_shortcode)) {
400 $has_shortcode = TRUE;
401 }
402 }
403 }
404 if (has_shortcode($post->post_content, 'mfn_list')) {
405 $has_shortcode = TRUE;
406 }
407 if ($has_shortcode) {
408 wp_enqueue_style('modern_footnotes');
409 wp_enqueue_script('modern_footnotes');
410 }
411 }
412 }
413
414 add_action('wp_enqueue_scripts', 'modern_footnotes_register_scripts_styles');
415
416 function modern_footnotes_enqueue_scripts_styles_if_not_already_enqueued() {
417 if (!wp_style_is('modern_footnotes')) {
418 wp_enqueue_style('modern_footnotes');
419 }
420 if (!wp_script_is('modern_footnotes')) {
421 wp_enqueue_script('modern_footnotes');
422 }
423 }
424
425 //
426 //modify the admin
427 //
428
429 //create a settings page
430 function modern_footnotes_menu() {
431 add_options_page( __('Modern Footnotes Settings', 'modern-footnotes'),
432 __('Modern Footnotes','modern-footnotes'),
433 'manage_options', __FILE__, 'modern_footnotes_options' );
434 }
435
436 function modern_footnotes_options() {
437 if ( !current_user_can( 'manage_options' ) ) {
438 wp_die( __( 'You do not have sufficient permissions to access this page.' ) );
439 }
440 echo '<div class="wrap">';
441 echo '<h1>' . esc_html__('Modern Footnotes Settings','modern-footnotes') . '</h1>';
442 // Indent sub-settings (e.g. options that only apply when a parent option is on)
443 // so they read as nested beneath the setting above them.
444 echo '<style>
445 .form-table tr.mfn-sub-setting th { font-weight: 400; padding-left: 30px; }
446 .form-table tr.mfn-sub-setting td { padding-left: 30px; }
447 </style>';
448 echo '<form method="post" action="options.php">';
449 settings_fields('modern_footnotes_settings');
450 do_settings_sections(__FILE__);
451 submit_button();
452 echo '</form>';
453 echo '</div>';
454 }
455
456 function modern_footnotes_register_settings() { // whitelist options
457 register_setting('modern_footnotes_settings', 'modern_footnotes_settings',
458 array(
459 'type' => 'boolean',
460 'default' => FALSE,
461 'sanitize_callback' => 'modern_footnotes_sanitize_callback'
462 ));
463 add_settings_section(
464 'modern_footnotes_option_group_section',
465 __('Modern Footnotes Settings', 'modern-footnotes'),
466 function() { /* do nothing, no HTML needed for section heading */ },
467 __FILE__
468 );
469
470 add_settings_field(
471 'modern_footnotes_desktop_footnote_behavior',
472 __('Desktop footnote behavior', 'modern-footnotes'),
473 'modern_footnotes_desktop_footnote_behavior_dropdown_callback',
474 __FILE__,
475 'modern_footnotes_option_group_section'
476 );
477
478 add_settings_field(
479 'use_title_tags_for_footnote_links',
480 __('Show browser tooltip on hover', 'modern-footnotes'),
481 'modern_footnotes_checkbox_element_callback',
482 __FILE__,
483 'modern_footnotes_option_group_section',
484 array(
485 'property_name' => 'use_title_tags_for_footnote_links',
486 'property_label' => 'Make footnote content appear in web browser\'s native tooltip when hovering over footnote number'
487 )
488 );
489
490
491
492 add_settings_field(
493 'display_footnotes_at_bottom_of_posts',
494 __('Display footnote list at bottom of posts', 'modern-footnotes'),
495 'modern_footnotes_checkbox_element_callback',
496 __FILE__,
497 'modern_footnotes_option_group_section',
498 array(
499 'property_name' => 'display_footnotes_at_bottom_of_posts',
500 'property_label' => 'Display footnote list at bottom of posts'
501 )
502 );
503 add_settings_field(
504 'modern_footnotes_show_jump_to_list_link_in_footnotes',
505 __('Add jump-to-list link in footnotes', 'modern-footnotes'),
506 'modern_footnotes_checkbox_element_callback',
507 __FILE__,
508 'modern_footnotes_option_group_section',
509 array(
510 'property_name' => 'modern_footnotes_show_jump_to_list_link_in_footnotes',
511 'property_label' => 'Add a down-arrow link in each footnote that jumps to its entry in the list at the bottom of the post',
512 'class' => 'mfn-sub-setting'
513 )
514 );
515 add_settings_field(
516 'display_footnotes_at_bottom_of_posts_when_printing',
517 __('When printing, list footnotes at the bottom of posts', 'modern-footnotes'),
518 'modern_footnotes_checkbox_element_callback',
519 __FILE__,
520 'modern_footnotes_option_group_section',
521 array(
522 'property_name' => 'display_footnotes_at_bottom_of_posts_when_printing',
523 'property_label' => 'When printing, list footnotes at the bottom of posts'
524 )
525 );
526
527 add_settings_field(
528 'modern_footnotes_include_footnote_list_at_end_of_rss_content',
529 __('For post content in RSS feeds, list footnotes at the bottom of posts', 'modern-footnotes'),
530 'modern_footnotes_checkbox_element_callback',
531 __FILE__,
532 'modern_footnotes_option_group_section',
533 array(
534 'property_name' => 'modern_footnotes_include_footnote_list_at_end_of_rss_content',
535 'property_label' => 'For post content in RSS feeds, list footnotes at the bottom of posts'
536 )
537 );
538
539 add_settings_field(
540 'modern_footnotes_heading_for_footnote_list',
541 __('Heading for footnote list', 'modern-footnotes'),
542 'modern_footnotes_textbox_element_callback',
543 __FILE__,
544 'modern_footnotes_option_group_section',
545 array(
546 'property_name' => 'modern_footnotes_heading_for_footnote_list',
547 'property_label' => 'If provided, this text will be displayed above footnote lists'
548 )
549 );
550
551 add_settings_field(
552 'modern_footnotes_heading_tag_name_for_footnote_list',
553 __('Heading tag name for footnote list', 'modern-footnotes'),
554 'modern_footnotes_tag_name_for_footnote_list_dropdown_callback',
555 __FILE__,
556 'modern_footnotes_option_group_section'
557 );
558
559 add_settings_field(
560 'modern_footnotes_custom_css',
561 __('Modern Footnotes Custom CSS', 'modern-footnotes'),
562 'modern_footnotes_custom_css_element_callback',
563 __FILE__,
564 'modern_footnotes_option_group_section'
565 );
566 add_settings_field(
567 'modern_footnotes_custom_shortcode',
568 __('Modern Footnotes Custom Shortcode', 'modern-footnotes'),
569 'modern_footnotes_custom_shortcode_element_callback',
570 __FILE__,
571 'modern_footnotes_option_group_section'
572 );
573 }
574
575 function modern_footnotes_sanitize_callback($plugin_options) {
576 global $modern_footnotes_options;
577
578 if (isset($plugin_options['modern_footnotes_custom_css']) && !empty($plugin_options['modern_footnotes_custom_css'])) {
579 //strip style HTML tags from the custom CSS property
580 $plugin_options['modern_footnotes_custom_css'] = preg_replace('/<\/?style.*?>/i', '', $plugin_options['modern_footnotes_custom_css']);
581 }
582
583 if (isset($plugin_options['modern_footnotes_custom_shortcode']) && !empty($plugin_options['modern_footnotes_custom_shortcode'])) {
584 //remove invalid characters from shortcode
585 $plugin_options['modern_footnotes_custom_shortcode'] = preg_replace('/[^a-zA-Z0-9-_]/i', '', $plugin_options['modern_footnotes_custom_shortcode']);
586 if ((!isset($modern_footnotes_options['modern_footnotes_custom_shortcode']) || $modern_footnotes_options['modern_footnotes_custom_shortcode'] != $plugin_options['modern_footnotes_custom_shortcode']) &&
587 shortcode_exists($plugin_options['modern_footnotes_custom_shortcode'])) {
588 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' );
589 $plugin_options['modern_footnotes_custom_shortcode'] = '';
590 }
591 }
592 return $plugin_options;
593 }
594
595 function modern_footnotes_checkbox_element_callback($args) {
596 global $modern_footnotes_options;
597
598 $property_name = $args['property_name'];
599 $property_label = $args['property_label'];
600
601 $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 ) . '/>';
602 $html .= '<label for="%1$s">' .
603 esc_html__($property_label, 'modern-footnotes') .
604 '</label>';
605 $html = sprintf($html, $property_name);
606
607 echo $html;
608 }
609
610 function modern_footnotes_textbox_element_callback($args) {
611 global $modern_footnotes_options;
612
613 $property_name = $args['property_name'];
614 $property_label = $args['property_label'];
615
616 $html = '<input type="text" id="%1$s" name="modern_footnotes_settings[%1$s]" value="%2$s" />';
617 $html .= ' <label for="%1$s">' .
618 esc_html__($property_label, 'modern-footnotes') .
619 '</label>';
620 $html = sprintf($html, $property_name, isset($modern_footnotes_options[$property_name]) ? esc_attr($modern_footnotes_options[$property_name]) : '');
621
622 echo $html;
623 }
624
625
626
627 function modern_footnotes_desktop_footnote_behavior_dropdown_callback() {
628 global $modern_footnotes_options;
629
630 $selected_value = isset($modern_footnotes_options['desktop_footnote_behavior']) ? $modern_footnotes_options['desktop_footnote_behavior'] : '';
631
632 $options = array(
633 'tooltip_click' => __('Tooltip footnotes that open on click', 'modern-footnotes'),
634 'tooltip_hover' => __('Tooltip footnotes that open on hover', 'modern-footnotes'),
635 'expandable' => __('Expandable footnotes', 'modern-footnotes'),
636 );
637
638 $html = '<select id="modern_footnotes_desktop_footnote_behavior" name="modern_footnotes_settings[desktop_footnote_behavior]"> aria-label="%1$s"';
639 foreach ($options as $key => $value) {
640 $option_html = '<option value="%s" %s>%s</option>';
641 $html .= sprintf($option_html, esc_attr($key), $selected_value == $key ? 'selected' : '', esc_html($value));
642 }
643 $html .= '</select>';
644
645 $html = sprintf($html, __('Desktop footnote behavior', 'modern-footnotes'));
646
647 echo $html;
648 }
649
650 function modern_footnotes_tag_name_for_footnote_list_dropdown_callback() {
651 global $modern_footnotes_options;
652
653 $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';
654
655 $options = array(
656 'h2' => __('Heading 2', 'access2'),
657 'h3' => __('Heading 3', 'access3'),
658 'h4' => __('Heading 4', 'access4'),
659 'h5' => __('Heading 5', 'access5'),
660 'h6' => __('Heading 6', 'access6')
661 );
662
663 $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"';
664 foreach ($options as $key => $value) {
665 $option_html = '<option value="%s" %s>%s</option>';
666 $html .= sprintf($option_html, esc_attr($key), $selected_value == $key ? 'selected' : '', esc_html($value));
667 }
668 $html .= '</select>';
669
670 $html = sprintf($html, __('Heading tag name for footnote list', 'modern-footnotes'));
671
672 echo $html;
673 }
674
675
676
677
678 function modern_footnotes_custom_css_element_callback() {
679 global $modern_footnotes_options;
680
681 $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>';
682 $html .= '<label for="modern_footnotes_custom_css">' .
683 esc_html__('Enter any custom CSS for the plugin, without any <style> tags.', 'modern-footnotes') .
684 '</label>';
685
686 echo $html;
687 }
688
689 function modern_footnotes_custom_shortcode_element_callback() {
690 global $modern_footnotes_options;
691
692 $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'] : '') . '" />';
693 $html .= '<label for="modern_footnotes_custom_shortcode">' .
694 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') .
695 '</label>';
696
697 echo $html;
698 }
699
700 function modern_footnotes_return_blank_for_rss_func($atts, $content = "") {
701 return "";
702 }
703
704 // remove shortcode from RSS feed
705 function modern_footnotes_remove_from_rss_content_feed($content) {
706 return modern_footnotes_remove_from_rss_feed($content, TRUE);
707 }
708 function modern_footnotes_remove_from_rss_feed($content, $is_content_section = FALSE){
709 foreach ($GLOBALS['modern_footnotes_shortcodes'] as $modern_footnote_shortcode) {
710 remove_shortcode($modern_footnote_shortcode);
711 if ($GLOBALS['modern_footnotes_options']['modern_footnotes_include_footnote_list_at_end_of_rss_content']) {
712 add_shortcode($modern_footnote_shortcode, 'modern_footnotes_rss_func');
713 } else {
714 add_shortcode($modern_footnote_shortcode, 'modern_footnotes_return_blank_for_rss_func');
715 }
716 }
717 // do not let the [mfn_list] shortcode execute in the RSS feed
718 remove_shortcode('mfn_list');
719 add_shortcode('mfn_list', 'modern_footnotes_return_blank_for_rss_func');
720 // add a [mfn_rss_list] shortcode to the end of the post content
721 if ($GLOBALS['modern_footnotes_options']['modern_footnotes_include_footnote_list_at_end_of_rss_content'] && $is_content_section) {
722 $content .= modern_footnotes_list_footnotes(FALSE,FALSE,TRUE);
723 }
724
725 return $content;
726 }
727 add_filter('the_excerpt_rss', 'modern_footnotes_remove_from_rss_feed');
728 add_filter('the_content_feed', 'modern_footnotes_remove_from_rss_content_feed');
729
730 // set variable to let us know we are in an rss feed
731 function modern_footnotes_in_rss() {
732 $GLOBALS['modern_footnotes_displaying_rss_feed'] = TRUE;
733 }
734 add_action('rss_head', 'modern_footnotes_in_rss'); // for RSS 1
735 add_action('rss_tag_pre', 'modern_footnotes_in_rss'); //for all other feed types
736
737
738 if (is_admin()) { // admin actions
739 add_action( 'admin_menu', 'modern_footnotes_menu' );
740 add_action( 'admin_init', 'modern_footnotes_register_settings' );
741 }
742
743 //setup button on the WordPress editor
744
745 //
746 // Pre-Gutenberg editor
747 //
748 function modern_footnotes_add_container_button() {
749 if ( ! current_user_can('edit_posts') && ! current_user_can('edit_pages') )
750 return;
751 if ( get_user_option('rich_editing') == 'true') {
752 add_filter('mce_external_plugins', 'modern_footnotes_add_container_plugin');
753 add_filter('mce_buttons', 'modern_footnotes_register_container_button');
754 }
755 }
756 if (is_admin()) {
757 add_filter('init', 'modern_footnotes_add_container_button');
758
759 function modern_footnotes_enqueue_admin_scripts() {
760 global $modern_footnotes_version;
761 wp_enqueue_style('modern_footnotes', plugin_dir_url(__FILE__) . 'styles.mce-button.min.css', array(), $modern_footnotes_version);
762 }
763
764 add_action('admin_enqueue_scripts', 'modern_footnotes_enqueue_admin_scripts');
765 }
766
767
768 function modern_footnotes_register_container_button($buttons) {
769 array_push($buttons, "modern_footnotes");
770 return $buttons;
771 }
772
773 function modern_footnotes_add_container_plugin($plugin_array) {
774 $plugin_array['modern_footnotes'] = plugin_dir_url(__FILE__) . 'modern-footnotes.mce-button.min.js';
775 return $plugin_array;
776 }
777 //
778 // End Pre-Gutenberg editor
779 //
780
781 //
782 // Gutenberg / Block Editor
783 //
784 function modern_footnotes_block_editor_button() {
785
786 $currentScreen = get_current_screen();
787 if ($currentScreen->id === "widgets") {
788 return;
789 }
790
791 global $modern_footnotes_version;
792 wp_enqueue_script( 'modern_footnotes_block_editor_js',
793 plugin_dir_url(__FILE__) . 'modern-footnotes.block-editor.min.js',
794 array( 'wp-rich-text', 'wp-element', 'wp-block-editor', 'wp-i18n' ),
795 $modern_footnotes_version
796 );
797 wp_set_script_translations('modern_footnotes_block_editor_js','modern-footnotes');
798 wp_enqueue_style('modern_footnotes_block_editor_css', plugin_dir_url(__FILE__) . 'styles.block-editor-button.min.css', array(), $modern_footnotes_version);
799
800 }
801 add_action( 'enqueue_block_editor_assets', 'modern_footnotes_block_editor_button' );
802
803 // Load footnote *content* styles into the iframed editor canvas (WP 6.3+/7.x).
804 // enqueue_block_editor_assets only reaches the admin page (outside the iframe),
805 // so content rules (mfn highlight, "footnote" label) need enqueue_block_assets.
806 function modern_footnotes_block_editor_content_styles() {
807 // enqueue_block_assets fires on BOTH the front end and the editor. These are
808 // editor-only authoring hints (the grey `mfn` highlight and the "footnote" label)
809 // that help writers see footnotes while composing. On the published page footnotes
810 // get their real appearance from the plugin's own styles.css / styles.min.css
811 // (enqueued via wp_enqueue_scripts), where the grey editor highlight would look
812 // wrong, so we bail out on the front end and only inject into the editor iframe.
813 if ( ! is_admin() ) {
814 return;
815 }
816 global $modern_footnotes_version;
817 wp_enqueue_style(
818 'modern_footnotes_block_editor_css',
819 plugin_dir_url( __FILE__ ) . 'styles.block-editor-button.min.css',
820 array(),
821 $modern_footnotes_version
822 );
823 }
824 add_action( 'enqueue_block_assets', 'modern_footnotes_block_editor_content_styles' );
825 //
826 // End Gutenberg / Block Editor
827 //
828