PluginProbe
Print, PDF & Email by PrintFriendly / trunk
Print, PDF & Email by PrintFriendly vtrunk
5.5.12 5.5.11 trunk 0.2 0.9 1.0 2.0 2.1.8 3.0 3.0.9 3.1.6 3.10.0 3.11.0 3.11.1 3.11.2 3.12.0 3.12.1 3.12.2 3.12.3 3.12.4 3.12.5 3.13.0 3.14.0 3.14.1 3.14.2 All 94 releases
printfriendly / pf.php

pf.php in Print, PDF & Email by PrintFriendly trunk, at pf.php

1,879 lines 73.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore PSR1.Files.SideEffects.FoundWithSymbols
2
3 /*
4 Plugin Name: Print, PDF & Email by PrintFriendly
5 Plugin URI: https://www.printfriendly.com
6 Description: PrintFriendly & PDF button for your website. Optimizes your pages and brand for print, pdf, and email.
7 Name and URL are included to ensure repeat visitors and new visitors when printed versions are shared.
8 Version: 5.5.12
9 Author: Print, PDF, & Email by PrintFriendly
10 Author URI: https://www.printfriendly.com
11 License: GPLv2 or later
12 License URI: https://www.gnu.org/licenses/gpl-2.0.html
13 Domain Path: /languages
14 Text Domain: printfriendly
15 */
16
17 if (! defined('ABSPATH')) {
18 exit; // Exit if accessed directly.
19 }
20
21 // phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
22 // phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps
23 // phpcs:disable PSR2.Classes.PropertyDeclaration.VarUsed
24 // phpcs:disable PSR2.Classes.PropertyDeclaration.ScopeMissing
25 // phpcs:disable Squiz.Scope.MethodScope.Missing
26 // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
27
28 /**
29 * PrintFriendly WordPress plugin. Allows easy embedding of printfriendly.com buttons.
30 *
31 * @package PrintFriendly_WordPress
32 * @author PrintFriendly <support@printfriendly.com>
33 * @copyright Copyright (C) 2023, PrintFriendly
34 */
35
36 if (! class_exists('PrintFriendly_WordPress')) {
37 /**
38 * Class containing all the plugins functionality.
39 *
40 * @package PrintFriendly_WordPress
41 */
42 class PrintFriendly_WordPress
43 {
44 /**
45 * Current plugin version.
46 *
47 * @var string
48 */
49 var $plugin_version = '5.5.12';
50 /**
51 * The hook, used for text domain as well as hooks on pages and in get requests for admin.
52 *
53 * @var string
54 */
55 var $hook = 'printfriendly';
56 /**
57 * The option name, used throughout to refer to the plugins option and option group.
58 *
59 * @var string
60 */
61 var $option_name = 'printfriendly_option';
62 /**
63 * The plugins options, loaded on init containing all the plugins settings.
64 *
65 * @var array
66 */
67 var $options = array();
68 /**
69 * Database version, used to allow for easy upgrades to / additions in plugin options between plugin versions.
70 *
71 * @var int
72 */
73 var $db_version = 21;
74 /**
75 * Settings page, used within the plugin to reliably load the plugins admin JS and CSS files only on the admin page.
76 *
77 * @var string
78 */
79 var $settings_page = '';
80 /**
81 * List of all buttons with their attributes.
82 *
83 * @var array
84 */
85 private static $_buttons = array( // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
86 'buttons/printfriendly-pdf-email-button.png' => array( 'width' => 170, 'height' => 24 ),
87 'buttons/printfriendly-pdf-email-button-md.png' => array( 'width' => 194, 'height' => 30 ),
88 'buttons/printfriendly-pdf-email-button-notext.png' => array( 'width' => 110, 'height' => 30 ),
89 'buttons/printfriendly-pdf-button.png' => array( 'width' => 112, 'height' => 24 ),
90 'buttons/printfriendly-pdf-button-nobg.png' => array( 'width' => 112, 'height' => 24 ),
91 'buttons/printfriendly-pdf-button-nobg-md.png' => array( 'width' => 124, 'height' => 30 ),
92 'buttons/printfriendly-button.png' => array( 'width' => 112, 'height' => 24 ),
93 'buttons/printfriendly-button-nobg.png' => array( 'width' => 112, 'height' => 24 ),
94 'buttons/printfriendly-button-md.png' => array( 'width' => 124, 'height' => 30 ),
95 'buttons/printfriendly-button-lg.png' => array( 'width' => 154, 'height' => 28 ),
96 'buttons/print-button.png' => array( 'width' => 66, 'height' => 24 ),
97 'buttons/print-button-nobg.png' => array( 'width' => 66, 'height' => 24 ),
98 'buttons/print-button-gray.png' => array( 'width' => 66, 'height' => 24 ),
99 'icons/printfriendly-icon-sm.png' => array( 'width' => 17, 'height' => 17 ),
100 'icons/printfriendly-icon-md.png' => array( 'width' => 16, 'height' => 16 ),
101 'icons/printfriendly-icon-lg.png' => array( 'width' => 25, 'height' => 25 ),
102 );
103
104 /**
105 * Constructor
106 *
107 * @since 3.0
108 */
109 function __construct()
110 {
111 define('PRINTFRIENDLY_BASEPATH', dirname(__FILE__));
112 define('PRINTFRIENDLY_BASEURL', plugins_url('/', __FILE__));
113 // Retrieve the plugin options
114 $this->options = get_option($this->option_name);
115 // If the options array is empty, set defaults
116 if (! is_array($this->options)) {
117 $this->set_defaults();
118 }
119
120 /**
121 * Set page content selection option "WordPress Standard/Strict" to "WP Template"
122 */
123 if (isset($this->options['pf_algo']) && $this->options['pf_algo'] === 'ws') {
124 $this->options['pf_algo'] = 'wp';
125 update_option($this->option_name, $this->options);
126 }
127
128 // If the version number doesn't match, upgrade
129 if ($this->db_version > $this->options['db_version']) {
130 $this->upgrade();
131 }
132
133 $this->public_hooks();
134 if (is_admin()) {
135 $this->admin_hooks();
136 }
137 }
138
139 /**
140 * All public hooks.
141 */
142 function public_hooks()
143 {
144 add_action('wp_enqueue_scripts', array( $this, 'wp_enqueue_scripts' ));
145 add_action('wp_head', array(&$this, 'front_head'));
146
147 // automaticaly add the link
148 add_filter('the_content', array(&$this, 'show_link_on_content'));
149 add_filter('the_excerpt', array(&$this, 'show_link_on_excerpt'));
150
151 add_action('the_content', array(&$this, 'add_pf_content_class_around_content_hook'));
152 }
153
154 /**
155 * Load front end dependencies.
156 */
157 function wp_enqueue_scripts()
158 {
159 wp_enqueue_script('jquery');
160 }
161
162 /**
163 * All admin hooks.
164 */
165 function admin_hooks()
166 {
167 // Hook into init for registration of the option and the language files
168 add_action('admin_init', array(&$this, 'init'));
169 // Register the settings page
170 add_action('admin_menu', array(&$this, 'add_config_page'));
171 // Enqueue the needed scripts and styles
172 add_action('admin_enqueue_scripts', array(&$this, 'admin_enqueue_scripts'));
173 // Register a link to the settings page on the plugins overview page
174 add_filter('plugin_action_links', array(&$this, 'filter_plugin_actions'), 10, 2);
175 add_filter('plugin_row_meta', array(&$this, 'additional_links'), 10, 2);
176 add_filter('wp_dropdown_cats', array( &$this, 'wp_dropdown_cats_multiple' ), 10, 2);
177 }
178
179 /**
180 * Adds multiple select support for category dropdown.
181 *
182 * @since 4.2
183 **/
184 function wp_dropdown_cats_multiple($output, $attributes)
185 {
186 if (isset($attributes['multiple']) && $attributes['multiple']) {
187 $output = preg_replace('/^<select/i', '<select multiple', $output);
188 $output = str_replace("name='{$attributes['name']}'", "name='{$attributes['name']}[]'", $output);
189 foreach (array_map('trim', explode(',', $attributes['selected'])) as $value) {
190 $output = str_replace("value=\"{$value}\"", "value=\"{$value}\" selected", $output);
191 }
192 }
193 return $output;
194 }
195
196 /**
197 * Adds wraps content in pf-content class to help Printfriendly algo determine the content
198 *
199 * @since 3.2.8
200 **/
201 function add_pf_content_class_around_content_hook($content = false)
202 {
203 if ($this->is_enabled() && $this->is_wp_algo_on($content)) {
204 add_action('wp_footer', array(&$this, 'print_script_footer'));
205 return '<div class="pf-content">' . $content . '</div>';
206 } else {
207 return $content;
208 }
209 }
210
211 /**
212 * Override to check if print-only command is being used
213 *
214 * @since 3.3.0
215 **/
216 function print_only_override($content)
217 {
218 $pattern = '/class\s*?=\s*?(["\']|["\']([^"\']*?)\s)print-only(["\']|\s([^"\']*?)["\'])/';
219 $pf_pattern = '/class\s*?=\s*?(["\']|["\']([^"\']*?)\s)pf-content(["\']|\s([^"\']*?)["\'])/';
220 return ( preg_match($pattern, $content) || preg_match($pf_pattern, $content) );
221 }
222
223 /**
224 * Check if WP Algorithm is selected and content doesn't use print-only
225 *
226 * @since 3.5.4
227 **/
228 function is_wp_algo_on($content)
229 {
230 return ! class_exists('WooCommerce') && $content && $this->getVal('pf_algo') === 'wp' && ! $this->print_only_override($content);
231 }
232
233 /**
234 * PHP 4 Compatible Constructor
235 *
236 * @since 3.0
237 */
238 function PrintFriendly_WordPress()
239 {
240 $this->__construct();
241 }
242
243 /**
244 * Check if button should be visible on particular page
245 */
246 function is_enabled()
247 {
248 if (
249 ( is_page() && 'on' === $this->getVal('show_on_pages') )
250 || ( is_home() && 'on' === $this->getVal('show_on_homepage') )
251 || ( is_tax() && 'on' === $this->getVal('show_on_taxonomies') && $this->category_included() )
252 || ( is_category() && 'on' === $this->getVal('show_on_categories') && $this->category_included() )
253 || ( is_single() && 'on' === $this->getVal('show_on_posts') && $this->category_included() )
254 ) {
255 return true;
256 }
257 return false;
258 }
259
260 /**
261 * Determines the CSS rules to return for the `printfriendly` class.
262 *
263 * @since 5.2.5
264 */
265 function get_css_rules_for_button()
266 {
267 $css = array( 'z-index: 1000' );
268
269 if ($this->getVal('button_alignment_method') === 'default') {
270 $css[] = 'display: flex';
271 $css[] = sprintf(
272 'margin: %dpx %dpx %dpx %dpx',
273 $this->getVal('margin_top', 0),
274 $this->getVal('margin_right', 0),
275 $this->getVal('margin_bottom', 0),
276 $this->getVal('margin_left', 0)
277 );
278 } else {
279 $css[] = 'position: relative';
280 }
281
282 return implode('; ', $css);
283 }
284
285 /**
286 * Determines the CSS rules to return for the front end.
287 *
288 * @since 5.2.5
289 */
290 function get_main_css()
291 {
292 if ($this->getVal('button_alignment_method') === 'css') {
293 return '
294 @media print {
295 .printfriendly {
296 display: none;
297 }
298 }
299 ';
300 }
301
302 return sprintf(
303 '
304 @media screen {
305 .printfriendly {
306 %s
307 }
308 .printfriendly a, .printfriendly a:link, .printfriendly a:visited, .printfriendly a:hover, .printfriendly a:active {
309 font-weight: 600;
310 cursor: pointer;
311 text-decoration: none;
312 border: none;
313 -webkit-box-shadow: none;
314 -moz-box-shadow: none;
315 box-shadow: none;
316 outline:none;
317 font-size: %dpx !important;
318 color: %s !important;
319 }
320 .printfriendly.pf-alignleft {
321 %s
322 }
323 .printfriendly.pf-alignright {
324 %s
325 }
326 .printfriendly.pf-aligncenter {
327 justify-content: center;
328 %s
329 }
330 }
331
332 .pf-button-img {
333 border: none;
334 -webkit-box-shadow: none;
335 -moz-box-shadow: none;
336 box-shadow: none;
337 padding: 0;
338 margin: 0;
339 display: inline;
340 vertical-align: middle;
341 }
342
343 img.pf-button-img + .pf-button-text {
344 margin-left: 6px;
345 }
346
347 @media print {
348 .printfriendly {
349 display: none;
350 }
351 }
352 ',
353 $this->get_css_rules_for_button(),
354 $this->getVal('text_size'),
355 $this->getVal('text_color'),
356 $this->getVal('button_alignment_method') === 'default' ? 'justify-content: start;' : 'float: left;',
357 $this->getVal('button_alignment_method') === 'default' ? 'justify-content: end;' : 'float: right;',
358 $this->getVal('button_alignment_method') === 'default' ? '' : 'display: flex; align-items: center;'
359 );
360 }
361
362 /**
363 * Returns the custom CSS (including the style tag) for content positioning.
364 *
365 * @since 5.2.5
366 */
367 function get_content_position_css_tag()
368 {
369 $css = $this->getVal('content_position_css');
370 if (! empty($css)) {
371 // Defense in depth: strip any HTML tags (e.g. a stored "</style>"
372 // breakout) before echoing into the <style> element. wp_strip_all_tags()
373 // leaves bare ">" characters intact, so CSS child combinators keep working.
374 $css = wp_strip_all_tags($css);
375 return sprintf('<style type="text/css" id="pf-button-css">%s</style>', $css);
376 }
377 return null;
378 }
379
380 /**
381 * Prints the PrintFriendly button CSS, in the header.
382 *
383 * @since 3.0
384 */
385 function front_head()
386 {
387 if (! $this->is_enabled()) {
388 return;
389 }
390
391 if ($this->getVal('enable_css') !== 'on') {
392 return;
393 }
394 ?>
395 <style type="text/css" id="pf-main-css">
396 <?php
397 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Inline CSS assembled from integer- and hex-validated option values.
398 echo $this->get_main_css();
399 ?>
400 </style>
401
402 <?php
403 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Returns a <style> tag whose CSS is run through wp_strip_all_tags() inside the method.
404 echo $this->get_content_position_css_tag();
405 ?>
406
407 <style type="text/css" id="pf-excerpt-styles">
408 .pf-button.pf-button-excerpt {
409 display: none;
410 }
411 </style>
412
413 <?php
414 }
415
416 /**
417 * Prints the PrintFriendly JavaScript, in the footer, and loads it asynchronously.
418 *
419 * @since 3.0
420 */
421 function print_script_footer()
422 {
423 $tagline = $this->getVal('tagline');
424 $image_url = $this->getVal('image_url');
425 if ($this->getVal('logo') === 'favicon') {
426 $tagline = '';
427 $image_url = '';
428 }
429
430 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Returns markup whose dynamic attributes are escaped with esc_attr() inside the method.
431 echo $this->getSelectorsFromCustomCSS();
432
433 // Currently we use v3 for both: normal and password protected sites
434 ?>
435 <script type="text/javascript" id="pf_script">
436 <?php
437 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static inline analytics JavaScript; contains no user input.
438 echo $this->get_analytics_code();
439 ?>
440 var pfHeaderImgUrl = '<?php echo esc_js(esc_url($image_url)); ?>';
441 var pfHeaderTagline = '<?php echo esc_js($tagline); ?>';
442 var pfdisableClickToDel = '<?php echo esc_js($this->getVal('click_to_delete')); ?>';
443 var pfImagesSize = '<?php echo esc_js($this->getVal('images-size')); ?>';
444 var pfImageDisplayStyle = '<?php echo esc_js($this->getVal('image-style')); ?>';
445 var pfEncodeImages = '<?php echo esc_js($this->getVal('password_protected') === 'yes' ? 1 : 0); ?>';
446 var pfShowHiddenContent = '<?php echo esc_js($this->getVal('show_hidden_content') === 'yes' ? 1 : 0); ?>';
447 var pfDisableEmail = '<?php echo esc_js($this->getVal('email')); ?>';
448 var pfDisablePDF = '<?php echo esc_js($this->getVal('pdf')); ?>';
449 var pfDisablePrint = '<?php echo esc_js($this->getVal('print')); ?>';
450
451 <?php
452 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Returns a JS variable whose value is escaped via esc_js( esc_url() ) inside the method.
453 echo $this->get_custom_css_js_var();
454 ?>
455
456 var pfPlatform = 'WordPress';
457
458 (function($){
459 $(document).ready(function(){
460 if($('.pf-button-content').length === 0){
461 $('style#pf-excerpt-styles').remove();
462 }
463 });
464 })(jQuery);
465 </script>
466 <?php
467 wp_enqueue_script('printfriendly-sdk', 'https://cdn.printfriendly.com/printfriendly.js', array(), null, true);
468 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Returns a custom element whose content is escaped via esc_html() inside the method.
469 echo $this->get_custom_css_tag();
470 ?>
471
472 <?php
473 }
474
475 /**
476 * Used as a filter for the_content.
477 *
478 * @since ?
479 *
480 * @param string $content The content of the post, when the function is used as a filter.
481 *
482 * @return string The content with the button added to the content.
483 */
484 function show_link_on_content($content)
485 {
486 $content = $this->show_link($content, true);
487 return $content;
488 }
489
490 /**
491 * Used as a filter for the_excerpt.
492 *
493 * @since ?
494 *
495 * @param string $content The content of the post, when the function is used as a filter.
496 *
497 * @return string The content with the button added to the content.
498 */
499 function show_link_on_excerpt($content)
500 {
501 return $this->show_link($content, false);
502 }
503
504 /**
505 * Used to generate the the final content as per the required placement.
506 *
507 * @since 3.0
508 *
509 * @param string $content The content of the post, when the function is used as a filter.
510 * @param bool $on_content True if showing on the_content and False if showing on the_excerpt.
511 *
512 * @return string $button or $content with the button added to the content when appropriate, just the content when button shouldn't be added or just button when called manually.
513 */
514 private function show_link($content = false, $on_content = true)
515 {
516 if ($this->is_enabled() && $content) {
517 $button = $this->getButton(false, $on_content ? 'pf-button-content' : 'pf-button-excerpt');
518 // Hook the script call now, so it only get's loaded when needed, and need is determined by the user calling pf_button
519 add_action('wp_footer', array(&$this, 'print_script_footer'));
520 if ($this->getVal('content_placement') === 'before') {
521 return $button . $content;
522 } else {
523 return $content . $button;
524 }
525 }
526 return $content;
527 }
528
529 /**
530 * Returns the analytics function to be included in the front end.
531 *
532 * @since 5.1
533 *
534 * @return string
535 */
536 private function get_analytics_code()
537 {
538 $return = null;
539 if ($this->google_analytics_enabled()) {
540 $code = apply_filters(
541 'printfriendly_analytics_code',
542 "
543 if(typeof(_gaq) === 'function') {
544 _gaq.push(['_trackEvent','PRINTFRIENDLY', 'print', title]);
545 }else if(typeof(ga) === 'function') {
546 ga('send', 'event','PRINTFRIENDLY', 'print', title);
547 }else if(typeof(gtag) === 'function') {
548 gtag('event', 'printfriendly_button_click', {'event_category': 'printfriendly', 'event_label': title})
549 }else if(typeof(dataLayer) === 'object') {
550 dataLayer.push({
551 'event': 'printfriendly_button_click',
552 'pageTitle': title
553 })
554 }
555 "
556 );
557 $return = "
558 function pfTrackEvent(title) {
559 $code
560 }
561 ";
562 }
563 return $return;
564 }
565
566 /**
567 * Returns the HTML of the button.
568 *
569 * This can be called publicly from `pf_show_link()`.
570 *
571 * @since 3.3.8
572 *
573 * @param bool $add_footer_script Whether to add the script in the footer.
574 * @param string $add_class Additional class for the HTML button div.
575 * @param bool $print_whole_page Whether to print the whole page or follow default behaviour.
576 *
577 * @return Printfriendly Button HTML
578 */
579 public function getButton($add_footer_script = false, $add_class = '', $print_whole_page = false)
580 {
581 if ($add_footer_script) {
582 add_action('wp_footer', array(&$this, 'print_script_footer'));
583 }
584
585 $onclick = array();
586 $href = '#';
587
588 if ($print_whole_page) {
589 $onclick[] = 'window.print()';
590 } else {
591 if (is_singular()) {
592 $onclick[] = 'window.print()';
593 } else {
594 $href = add_query_arg('pfstyle', 'wp', get_permalink());
595 }
596 }
597
598 if ($this->google_analytics_enabled()) {
599 $onclick[] = "pfTrackEvent('" . esc_attr(get_the_title()) . "')";
600 }
601
602 $align_class = '';
603 $position = $this->getVal('content_position');
604
605 // none option has been removed and translates to left
606 if ('none' === $position) {
607 $position = 'left';
608 }
609 $align_class = 'pf-align' . $position;
610 $button = sprintf(
611 '<div class="printfriendly pf-button %s %s">
612 <a href="%s" rel="nofollow" onclick="%s" title="Printer Friendly, PDF & Email">
613 %s
614 </a>
615 </div>',
616 sanitize_html_class($add_class),
617 sanitize_html_class($align_class),
618 esc_attr(esc_url($href)),
619 ! empty($onclick) ? esc_attr(implode('; ', $onclick)) . '; return false;' : '',
620 $this->button()
621 );
622 $button = apply_filters('printfriendly_button', $button);
623 return $button;
624 }
625
626 /**
627 * Returns the custom selector CSS to be added.
628 *
629 * @since 5.0
630 * @returns string
631 */
632 private function getSelectorsFromCustomCSS()
633 {
634 $css = '';
635 if ($this->getVal('pf_algo') === 'css') {
636 $attributes = array();
637 $names = array( 'author', 'date', 'content', 'title', 'primaryImage' );
638 $data = '';
639 foreach ($names as $name) {
640 $selector = $this->getVal("css-{$name}");
641 $suffix = $name === 'content' ? 'Selectors' : 'Selector';
642 if (! empty($selector)) {
643 $data .= "{$name}{$suffix}=$selector;";
644 }
645 }
646 $fallback = $this->getVal('pf_algo_css_content');
647 if ($data) {
648 $css = sprintf('<printfriendly-options data-selectors="%s" data-fallback-strategy="%s"></printfriendly-options>', esc_attr($data), esc_attr($fallback));
649 }
650 }
651 return $css;
652 }
653
654
655 /**
656 * Checks if GA is enabled.
657 *
658 * @since 3.2.9
659 * @returns if google analytics enabled
660 */
661 function google_analytics_enabled()
662 {
663 return $this->getVal('enable_google_analytics') === 'yes';
664 }
665
666 /**
667 * Filter posts by category.
668 *
669 * @since 3.2.2
670 * @return boolean true if post belongs to category selected for button display
671 */
672 function category_included()
673 {
674 return !empty($this->getVal('show_on_cat')) ? in_category(explode(',', $this->getVal('show_on_cat'))) : true;
675 }
676
677 /**
678 * Register the textdomain and the options array along with the validation function
679 *
680 * @since 3.0
681 */
682 function init()
683 {
684 // Translations for WordPress.org-hosted plugins load automatically since WP 4.6; no manual textdomain loading is required.
685 // Register our option array
686 register_setting($this->option_name, $this->option_name, array( 'sanitize_callback' => array(&$this, 'options_validate') ));
687 }
688
689 /**
690 * Validate the saved options.
691 *
692 * @since 3.0
693 * @param array $input with unvalidated options.
694 * @return array $valid_input with validated options.
695 */
696 function options_validate($input)
697 {
698 // Prevent CSRF attack
699 check_admin_referer('pf-options', 'pf-nonce');
700 $valid_input = $input;
701
702 // Section 1 options
703 if (
704 ! isset($input['button_type']) || ! in_array(
705 $input['button_type'],
706 array(
707 'buttons/printfriendly-pdf-email-button.png',
708 'buttons/printfriendly-pdf-email-button-md.png',
709 'buttons/printfriendly-pdf-email-button-notext.png', // buttongroup1
710 'buttons/printfriendly-pdf-button.png',
711 'buttons/printfriendly-pdf-button-nobg.png',
712 'buttons/printfriendly-pdf-button-nobg-md.png', // buttongroup2
713 'buttons/printfriendly-button.png',
714 'buttons/printfriendly-button-nobg.png',
715 'buttons/printfriendly-button-md.png',
716 'buttons/printfriendly-button-lg.png', // buttongroup3
717 'buttons/print-button.png',
718 'buttons/print-button-nobg.png',
719 'buttons/print-button-gray.png', // buttongroup3
720 'custom-button', // custom
721 ),
722 true
723 )
724 ) {
725 $valid_input['button_type'] = 'printfriendly-pdf-button.png';
726 }
727
728 if (
729 ! isset($input['custom_button_icon']) || ! in_array(
730 $input['custom_button_icon'],
731 array(
732 'https://cdn.printfriendly.com/icons/printfriendly-icon-sm.png',
733 'https://cdn.printfriendly.com/icons/printfriendly-icon-md.png',
734 'https://cdn.printfriendly.com/icons/printfriendly-icon-lg.png',
735 'custom-image',
736 'no-image',
737 ),
738 true
739 )
740 ) {
741 $valid_input['custom_button_icon'] = 'https://cdn.printfriendly.com/icons/printfriendly-icon-md.png';
742 }
743
744 // if a custom image is not being chosen, reset it in case it existed in the past.
745 if (isset($valid_input['custom_button_icon']) && $valid_input['custom_button_icon'] !== 'custom-image') {
746 $valid_input['custom_image'] = '';
747 }
748
749 // @todo custom image url validation
750 if (! isset($input['custom_image']) || empty($input['custom_image'])) {
751 $valid_input['custom_image'] = '';
752 }
753
754 if (! isset($input['custom_button_text'])) {
755 $valid_input['custom_button_text'] = 'custom-text';
756 }
757
758 // @todo validate optional custom text
759 if (! isset($input['custom_text'])) {
760 $valid_input['custom_text'] = 'Print Friendly';
761 } else {
762 $valid_input['custom_text'] = $this->esc_html_if_needed($valid_input['custom_text']);
763 }
764
765 // Custom button selected, but no url nor text given, reset button type to default
766 if ('custom-button' === $valid_input['button_type'] && ( '' === $valid_input['custom_image'] && '' === $input['custom_text'] )) {
767 $valid_input['button_type'] = 'buttons/printfriendly-pdf-button.png';
768 add_settings_error($this->option_name, 'invalid_custom_image', __('No valid custom image url received, please enter a valid url to use a custom image.', 'printfriendly'));
769 }
770
771 $valid_input['text_size'] = (int) $input['text_size'];
772 if (! isset($valid_input['text_size']) || 0 === $valid_input['text_size']) {
773 $valid_input['text_size'] = 14;
774 } elseif (25 < $valid_input['text_size'] || 9 > $valid_input['text_size']) {
775 $valid_input['text_size'] = 14;
776 add_settings_error($this->option_name, 'invalid_text_size', __('The text size you entered is invalid, please stay between 9px and 25px', 'printfriendly'));
777 }
778
779 if (! isset($input['text_color'])) {
780 $valid_input['text_color'] = $this->getVal('text_color');
781 } elseif (! preg_match('/^#[a-f0-9]{3,6}$/i', $input['text_color'])) {
782 // Revert to previous setting and throw error.
783 $valid_input['text_color'] = $this->getVal('text_color');
784 add_settings_error($this->option_name, 'invalid_color', __('The color you entered is not valid, it must be a valid hexadecimal RGB font color.', 'printfriendly'));
785 }
786
787 /* Section 2 options */
788 if (! isset($input['enable_css']) || 'off' !== $input['enable_css']) {
789 $valid_input['enable_css'] = 'on';
790 }
791
792 if (! isset($input['content_position']) || ! in_array($input['content_position'], array('left', 'center', 'right'), true)) {
793 $valid_input['content_position'] = 'left';
794 }
795
796 if (! isset($input['button_alignment_method']) || ! in_array($input['button_alignment_method'], array('default', 'css'), true)) {
797 $valid_input['button_alignment_method'] = 'default';
798 }
799
800 if (! isset($input['button_alignment_method']) || 'css' !== $input['button_alignment_method']) {
801 $valid_input['content_position_css'] = '';
802 } elseif (isset($input['content_position_css'])) {
803 // Strip all HTML/PHP tags (including any "</style>" breakout) before
804 // storing, mirroring the Custom CSS sanitization below. A bare ">"
805 // (CSS child combinator) is preserved because strip_tags() only removes
806 // complete "<...>" sequences.
807 $position_css = wp_strip_all_tags($input['content_position_css']);
808 $valid_input['content_position_css'] = sanitize_textarea_field($position_css);
809 } else {
810 $valid_input['content_position_css'] = '';
811 }
812
813 if (! isset($input['content_placement']) || ! in_array($input['content_placement'], array('before', 'after'), true)) {
814 $valid_input['content_placement'] = 'after';
815 }
816
817 foreach (array('margin_top', 'margin_right', 'margin_bottom', 'margin_left') as $opt) {
818 // if margin is not defined, don't throw a PHP notice
819 if (isset($input[ $opt ])) {
820 $valid_input[ $opt ] = (int) $input[ $opt ];
821 }
822 }
823
824 unset($opt);
825
826 /* Section 3 options */
827 foreach (array('show_on_posts', 'show_on_pages', 'show_on_homepage', 'show_on_categories', 'show_on_taxonomies') as $opt) {
828 if (! isset($input[ $opt ]) || 'on' !== $input[ $opt ]) {
829 unset($valid_input[ $opt ]);
830 }
831 }
832 unset($opt);
833
834 if (isset($input['category_ids'])) {
835 unset($valid_input['category_ids']);
836 }
837
838 /* Section 4 options */
839 if (! isset($input['logo']) || ! in_array($input['logo'], array('favicon', 'upload-an-image'), true)) {
840 $valid_input['logo'] = 'favicon';
841 }
842
843 // @todo custom logo url validation
844 if (! isset($input['image_url']) || empty($input['image_url'])) {
845 $valid_input['image_url'] = '';
846 }
847
848 // @todo validate optional tagline text
849 if (! isset($input['tagline'])) {
850 $valid_input['tagline'] = '';
851 }
852
853 // Custom logo selected, but no valid url given, reset logo to default
854 if ('upload-an-image' === $valid_input['logo'] && '' === $valid_input['image_url']) {
855 $valid_input['logo'] = 'favicon';
856 add_settings_error($this->option_name, 'invalid_custom_logo', __('No valid custom logo url received, please enter a valid url to use a custom logo.', 'printfriendly'));
857 }
858
859 if (! isset($input['image-style']) || ! in_array($input['image-style'], array('right', 'left', 'none', 'block'), true)) {
860 $valid_input['image-style'] = 'block';
861 }
862
863 foreach (array('click_to_delete', 'email', 'pdf', 'print') as $opt) {
864 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
865 if (! isset($input[ $opt ]) || ! in_array($input[ $opt ], array('0', '1'))) {
866 $valid_input[ $opt ] = '0';
867 }
868 }
869 unset($opt);
870
871 if (! isset($input['images-size']) || ! in_array($input['images-size'], array('full-size', 'remove-images', 'large', 'medium', 'small'), true)) {
872 $valid_input['images-size'] = 'full-size';
873 }
874
875 if (! isset($input['css_include_via']) || ! in_array($input['css_include_via'], array('file', 'inline_tag'), true)) {
876 $valid_input['css_include_via'] = 'inline_tag';
877 }
878
879 $css = $input['custom_css'];
880
881 // Strip all HTML/script tags before further sanitization.
882 $css = wp_strip_all_tags($css);
883 $valid_input['custom_css'] = sanitize_textarea_field($css);
884
885
886 // if empty CSS is provided in the box
887 // check if this version is upgraded from the version that was using url instead of textbox
888 // and reuse it
889 if (empty($valid_input['custom_css']) && isset($this->options['custom_css_url'])) {
890 if (empty($this->getVal('custom_css_url'))) {
891 unset($valid_input['custom_css_url']);
892 } else {
893 $valid_input['custom_css_url'] = $this->getVal('custom_css_url');
894 }
895 } else {
896 if (empty($valid_input['custom_css'])) {
897 unset($valid_input['custom_css_url_pro']);
898 } else {
899 if ($valid_input['css_include_via'] === 'file') {
900 $file = $this->generate_custom_css_file($valid_input['custom_css']);
901 $valid_input['custom_css_url_pro'] = $file;
902 }
903 }
904 }
905
906 /* Section 5 options */
907 if (! isset($input['password_protected']) || ! in_array($input['password_protected'], array('no', 'yes'), true)) {
908 $valid_input['password_protected'] = 'no';
909 }
910 if (! isset($input['show_hidden_content']) || ! in_array($input['show_hidden_content'], array('no', 'yes'), true)) {
911 $valid_input['show_hidden_content'] = 'no';
912 }
913
914 /*Analytics Options */
915 if (! isset($input['enable_google_analytics']) || ! in_array($input['enable_google_analytics'], array('no', 'yes'), true)) {
916 $valid_input['enable_google_analytics'] = 'no';
917 }
918
919 if (! isset($input['pf_algo']) || ! in_array($input['pf_algo'], array('wp', 'pf', 'css'), true)) {
920 $valid_input['pf_algo'] = 'wp';
921 }
922
923 if ($valid_input['pf_algo'] === 'css' && empty($valid_input['pf_algo_css_content']) && ! empty($valid_input['css-content'])) {
924 $valid_input['pf_algo_css_content'] = 'original';
925 }
926
927 if (! isset($valid_input['custom_image_width'])) {
928 $valid_input['custom_image_width'] = 0;
929 }
930
931 if (! isset($valid_input['custom_image_height'])) {
932 $valid_input['custom_image_height'] = 0;
933 }
934
935 if (empty($valid_input['custom_image'])) {
936 unset($valid_input['custom_image_width']);
937 unset($valid_input['custom_image_height']);
938 }
939
940 /* Database version */
941 $valid_input['db_version'] = $this->db_version;
942
943 // set the current tab from where the settings were saved from
944 if (isset($_POST['tab'])) {
945 // Nonce already verified via check_admin_referer() at the top of this method.
946 set_transient('pf-tab', sanitize_text_field(wp_unslash($_POST['tab'])), 5);
947 }
948
949 // save the categories as comma-separated
950 if (isset($valid_input['show_on_cat']) && ! empty($valid_input['show_on_cat'])) {
951 $valid_input['show_on_cat'] = implode(',', $valid_input['show_on_cat']);
952 }
953
954 return $valid_input;
955 }
956
957 /**
958 * Register the config page for all users that have the manage_options capability
959 *
960 * @since 3.0
961 */
962 function add_config_page()
963 {
964 $this->settings_page = add_options_page(__('PrintFriendly Options', 'printfriendly'), __('Print Friendly & PDF', 'printfriendly'), 'manage_options', $this->hook, array(&$this, 'config_page'));
965
966 // register callback gets call prior your own page gets rendered
967 add_action('load-' . $this->settings_page, array(&$this, 'on_load_printfriendly'));
968 }
969
970 /**
971 * Enqueue the scripts for the admin settings page
972 *
973 * @since 3.0
974 * @param string $screen_id check whether the current page is the PrintFriendly settings page.
975 */
976 function admin_enqueue_scripts($screen_id)
977 {
978 if ($this->settings_page === $screen_id) {
979 if (! did_action('wp_enqueue_media')) {
980 wp_enqueue_media();
981 }
982
983 if (! wp_script_is('clipboard')) {
984 // Use the clipboard library bundled with WordPress core (registered since 4.9).
985 wp_enqueue_script('clipboard');
986 }
987
988 if (! wp_script_is('select2')) {
989 wp_enqueue_script('select2', plugins_url('assets/js/lib/select2.min.js', __FILE__));
990 }
991
992 wp_register_script('pf-admin', plugins_url('assets/js/admin.js', __FILE__), array( 'jquery', 'jquery-ui-tabs', 'jquery-ui-accordion', 'media-upload', 'wp-color-picker', 'clipboard', 'select2' ), $this->plugin_version);
993 wp_localize_script(
994 'pf-admin',
995 'pf_config',
996 array(
997 'i10n' => array(
998 'upload_window_title' => __('Custom Image', 'printfriendly'),
999 'upload_window_button_title' => __('Use Image', 'printfriendly'),
1000 'invalid_image_url' => __('Invalid Image URL', 'printfriendly'),
1001 ),
1002 )
1003 );
1004
1005 wp_register_script('pf-admin-pro', plugins_url('assets/js/admin_pro.js', __FILE__), array( 'pf-admin' ), $this->plugin_version);
1006
1007 wp_localize_script(
1008 'pf-admin-pro',
1009 'pf_config',
1010 array(
1011 'nonce' => wp_create_nonce($this->hook . $this->plugin_version),
1012 'action' => $this->hook,
1013 'i10n' => array(
1014 'activation' => __('Activation', 'printfriendly'),
1015 'check_status' => __('Checking status', 'printfriendly'),
1016 'activate' => __('Activate', 'printfriendly'),
1017 'active_trial' => __('Active Trial', 'printfriendly'),
1018 'active' => __('Active', 'printfriendly'),
1019 'expired' => __('Expired', 'printfriendly'),
1020 'connection' => __('Please check Internet connection.', 'printfriendly'),
1021 ),
1022 )
1023 );
1024 wp_enqueue_script('pf-admin-pro');
1025
1026 wp_enqueue_style('pf-bulma', plugins_url('assets/css/lib/bulma.prefixed.min.css', __FILE__), array( 'wp-color-picker' ), $this->plugin_version);
1027 wp_enqueue_style('pf-select2', plugins_url('assets/css/lib/select2.min.css', __FILE__), array(), $this->plugin_version);
1028 wp_enqueue_style('pf-admin', plugins_url('assets/css/admin.css', __FILE__), array( 'pf-select2', 'pf-bulma' ), $this->plugin_version);
1029 }
1030 }
1031
1032 /**
1033 * Register the settings link for the plugins page
1034 *
1035 * @since 3.0
1036 * @param array $links the links for the plugins.
1037 * @param string $file filename to check against plugins filename.
1038 * @return array $links the links with the settings link added to it if appropriate.
1039 */
1040 function filter_plugin_actions($links, $file)
1041 {
1042 // Static so we don't call plugin_basename on every plugin row.
1043 static $this_plugin;
1044 if (! $this_plugin) {
1045 $this_plugin = plugin_basename(__FILE__);
1046 }
1047
1048 if ($file === $this_plugin) {
1049 $settings_link = '<a href="options-general.php?page=' . $this->hook . '">' . __('Settings', 'printfriendly') . '</a>';
1050 array_unshift($links, $settings_link); // before other links
1051 }
1052
1053 return $links;
1054 }
1055
1056 /**
1057 * Register the additional link for the plugins page in the plugin description column.
1058 *
1059 * @since ?
1060 * @param array $links the links for the plugins.
1061 * @param string $file filename to check against plugins filename.
1062 * @return array $links the links with the links added to it if appropriate.
1063 */
1064 function additional_links($links, $file)
1065 {
1066 // Static so we don't call plugin_basename on every plugin row.
1067 static $this_plugin;
1068 if (! $this_plugin) {
1069 $this_plugin = plugin_basename(__FILE__);
1070 }
1071
1072 if ($file === $this_plugin) {
1073 $new_links = array( '<a href="https://support.printfriendly.com/wordpress/customize-content-selection/" target="_new">' . __('Documentation', 'printfriendly') . '</a>' );
1074 $links = array_merge($links, $new_links);
1075 }
1076 return $links;
1077 }
1078
1079 /**
1080 * Set default values for the plugin. If old, as in pre 1.0, settings are there, use them and then delete them.
1081 *
1082 * @since 3.0
1083 */
1084 function set_defaults()
1085 {
1086 // Set some defaults
1087 $this->options = array(
1088 'button_type' => 'buttons/printfriendly-pdf-button.png',
1089 'content_position' => 'left',
1090 'button_alignment_method' => 'default',
1091 'content_placement' => 'after',
1092 'custom_button_icon' => 'https://cdn.printfriendly.com/icons/printfriendly-icon-md.png',
1093 'custom_button_text' => 'custom-text',
1094 'custom_image' => '',
1095 'custom_text' => 'PrintFriendly',
1096 'enable_css' => 'on',
1097 'margin_top' => '12',
1098 'margin_right' => '12',
1099 'margin_bottom' => '12',
1100 'margin_left' => '12',
1101 'show_on_posts' => 'on',
1102 'show_on_pages' => 'on',
1103 'text_color' => '#3AAA11',
1104 'text_size' => 14,
1105 'logo' => 'favicon',
1106 'image_url' => '',
1107 'tagline' => '',
1108 'click_to_delete' => '0', // 0 - allow, 1 - do not allow
1109 'hide-images' => '0', // 0 - show images, 1 - hide images
1110 'image-style' => 'block', // 'right', 'left', 'none', 'block'
1111 'email' => '0', // 0 - allow, 1 - do not allow
1112 'pdf' => '0', // 0 - allow, 1 - do not allow
1113 'print' => '0', // 0 - allow, 1 - do not allow
1114 'password_protected' => 'no',
1115 'enable_google_analytics' => 'no',
1116 'enable_error_reporting' => 'yes',
1117 'pf_algo' => 'wp',
1118 'images-size' => 'full-size',
1119 'show_hidden_content' => 'no',
1120 'css_include_via' => 'inline_tag',
1121 );
1122
1123 // Check whether the old badly named singular options are there, if so, use the data and delete them.
1124 foreach (array_keys($this->options) as $opt) {
1125 $old_opt = get_option('pf_' . $opt);
1126 if ($old_opt !== false) {
1127 $this->options[ $opt ] = $old_opt;
1128 delete_option('pf_' . $opt);
1129 }
1130 }
1131
1132 // This should always be set to the latest immediately when defaults are pushed in.
1133 $this->options['db_version'] = $this->db_version;
1134
1135 update_option($this->option_name, $this->options);
1136 }
1137
1138 /**
1139 * Upgrades the stored options, used to add new defaults if needed etc.
1140 *
1141 * @since 3.0
1142 */
1143 function upgrade()
1144 {
1145 // update options to version 2
1146 if ($this->options['db_version'] < 2) {
1147 $additional_options = array(
1148 'enable_css' => 'on',
1149 'logo' => 'favicon',
1150 'image_url' => '',
1151 'tagline' => '',
1152 'click_to_delete' => '0',
1153 'password_protected' => 'no',
1154 );
1155
1156 // correcting badly named option
1157 if (isset($this->options['disable_css'])) {
1158 $additional_options['enable_css'] = $this->options['disable_css'];
1159 unset($this->options['disable_css']);
1160 }
1161
1162 // check whether image we do not list any more was used
1163 if (in_array($this->options['button_type'], array('button-print-whgn20.png', 'pf_button_sq_qry_m.png', 'pf_button_sq_qry_l.png', 'pf_button_sq_grn_m.png', 'pf_button_sq_grn_l.png'), true)) {
1164 // previous version had a bug with button name
1165 if (in_array($this->options['button_type'], array('pf_button_sq_qry_m.png', 'pf_button_sq_qry_l.png'), true)) {
1166 $this->options['button_type'] = str_replace('qry', 'gry', $this->options['button_type']);
1167 }
1168
1169 $image_address = 'https://cdn.printfriendly.com/' . $this->options['button_type'];
1170 $this->options['button_type'] = 'custom-image';
1171 $this->options['custom_text'] = '';
1172 $this->options['custom_image'] = $image_address;
1173 }
1174
1175 $this->options = array_merge($this->options, $additional_options);
1176 }
1177
1178 // update options to version 3
1179 if ($this->options['db_version'] < 3) {
1180 $old_show_on = $this->options['show_list'];
1181 // 'manual' setting
1182 $additional_options = array('custom_css_url' => '');
1183
1184 if ($old_show_on === 'all') {
1185 $additional_options = array(
1186 'show_on_pages' => 'on',
1187 'show_on_posts' => 'on',
1188 'show_on_homepage' => 'on',
1189 'show_on_categories' => 'on',
1190 'show_on_taxonomies' => 'on',
1191 );
1192 }
1193
1194 if ($old_show_on === 'single') {
1195 $additional_options = array(
1196 'show_on_pages' => 'on',
1197 'show_on_posts' => 'on',
1198 );
1199 }
1200
1201 if ($old_show_on === 'posts') {
1202 $additional_options = array(
1203 'show_on_posts' => 'on',
1204 );
1205 }
1206
1207 unset($this->options['show_list']);
1208
1209 $this->options = array_merge($this->options, $additional_options);
1210 }
1211
1212 // update options to version 4
1213 if ($this->options['db_version'] < 4) {
1214 $additional_options = array(
1215 'email' => '0',
1216 'pdf' => '0',
1217 'print' => '0',
1218 );
1219
1220 $this->options = array_merge($this->options, $additional_options);
1221 }
1222
1223 // update options to version 6
1224 // Replacement for db version 5 - should also be run for those already upgraded
1225 if ($this->options['db_version'] < 6) {
1226 unset($this->options['category_ids']);
1227 }
1228
1229 if ($this->options['db_version'] < 7) {
1230 $additional_options = array(
1231 'hide-images' => '0',
1232 'image-style' => 'right',
1233 );
1234
1235 $this->options = array_merge($this->options, $additional_options);
1236 }
1237
1238 if ($this->options['db_version'] < 8) {
1239 $this->options['enable_google_analytics'] = 'no';
1240 }
1241
1242 if ($this->options['db_version'] < 9) {
1243 $this->options['pf_algo'] = 'wp';
1244 }
1245
1246 if ($this->options['db_version'] < 10) {
1247 $this->options['enable_error_reporting'] = 'yes';
1248 }
1249
1250 if ($this->options['db_version'] < 11) {
1251 if (! isset($this->options['custom_css_url'])) {
1252 $this->options['custom_css_url'] = '';
1253 }
1254 }
1255
1256 if ($this->options['db_version'] < 12) {
1257 // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
1258 $this->options['images-size'] = $this->options['hide-images'] == '1' ? 'remove-images' : 'full-size';
1259 }
1260
1261 if ($this->options['db_version'] < 13) {
1262 switch ($this->options['button_type']) {
1263 case 'pf-button.gif':
1264 $this->options['button_type'] = 'buttons/printfriendly-button.png';
1265 break;
1266 case 'pf-button-both.gif':
1267 $this->options['button_type'] = 'buttons/printfriendly-pdf-button.png';
1268 break;
1269 case 'pf-button-big.gif':
1270 $this->options['button_type'] = 'buttons/printfriendly-button-lg.png';
1271 break;
1272 case 'pf-button-print-pdf-mail.png':
1273 $this->options['button_type'] = 'buttons/printfriendly-pdf-email-button-notext.png';
1274 break;
1275 case 'button-print-grnw20.png':
1276 $this->options['button_type'] = 'buttons/print-button.png';
1277 break;
1278 case 'button-print-blu20.png':
1279 $this->options['button_type'] = 'buttons/print-button-nobg.png';
1280 break;
1281 case 'button-print-gry20.png':
1282 $this->options['button_type'] = 'buttons/print-button-gray.png';
1283 break;
1284 case 'pf-icon-small.gif':
1285 case 'pf-icon.gif':
1286 $this->options['button_type'] = 'custom-button';
1287 $this->options['custom_button_icon'] = 'icons/printfriendly-icon-md.png';
1288 $this->options['custom_button_text'] = 'custom-text';
1289 $this->options['custom_image'] = 'icons/printfriendly-icon-md.png';
1290 break;
1291 case 'text-only':
1292 $this->options['button_type'] = 'custom-button';
1293 $this->options['custom_button_icon'] = 'no-image';
1294 $this->options['custom_button_text'] = 'custom-text';
1295 break;
1296 }
1297 }
1298
1299 if ($this->options['db_version'] < 14) {
1300 if ($this->options['button_type'] === 'pf-icon-both.gif') {
1301 $this->options['button_type'] = 'buttons/printfriendly-pdf-button-nobg.png';
1302 }
1303 }
1304
1305 if ($this->options['db_version'] < 15) {
1306 if ($this->options['button_type'] === 'custom-image') {
1307 $this->options['button_type'] = 'custom-button';
1308
1309 switch ($this->options['custom_image']) {
1310 case 'icons/printfriendly-icon-sm.png':
1311 case 'icons/printfriendly-icon-md.png':
1312 case 'icons/printfriendly-icon-lg.png':
1313 $this->options['custom_button_icon'] = $this->options['custom_image'];
1314 break;
1315 case '':
1316 $this->options['custom_button_icon'] = 'no-image';
1317 break;
1318 default:
1319 $this->options['custom_button_icon'] = 'custom-image';
1320 }
1321
1322 if ($this->options['custom_text'] === '') {
1323 $this->options['custom_button_text'] = 'no-text';
1324 } else {
1325 $this->options['custom_button_text'] = 'custom-text';
1326 }
1327 }
1328 }
1329
1330 if ($this->options['db_version'] < 16) {
1331 if ($this->options['custom_button_icon'] === 'icons/printfriendly-icon-md.png') {
1332 $this->options['custom_button_icon'] = 'https://cdn.printfriendly.com/icons/printfriendly-icon-md.png';
1333 }
1334 }
1335
1336 if ($this->options['db_version'] < 17) {
1337 $this->options['pro_email'] = get_bloginfo('admin_email');
1338
1339 $url = get_bloginfo('url');
1340 $parsed_url = wp_parse_url($url);
1341 $this->options['pro_domain'] = $parsed_url['host'];
1342 }
1343
1344 if ($this->options['db_version'] < 21) {
1345 if (empty($this->getVal('custom_css'))) {
1346 $this->options['css_include_via'] = 'inline_tag';
1347 } else {
1348 $this->options['css_include_via'] = 'file';
1349 }
1350 }
1351
1352 $this->options['db_version'] = $this->db_version;
1353
1354 update_option($this->option_name, $this->options);
1355 }
1356
1357 /**
1358 * Displays radio button in the admin area
1359 *
1360 * @since 3.0
1361 * @param string $name the name of the radio button to generate.
1362 * @param boolean $br whether or not to add an HTML <br> tag, defaults to true.
1363 * @param boolean $value if this is null, will have the same value as $name.
1364 */
1365 function radio($name, $br = false, $value = null)
1366 {
1367 if (is_null($value)) {
1368 $value = $name;
1369 }
1370
1371 $var = '<input id="' . esc_attr($name) . '" class="radio" name="' . esc_attr($this->option_name) . '[button_type]" type="radio" value="' . esc_attr($value) . '" ' . $this->checked('button_type', $value, false) . '/>';
1372 $button = $this->button($name);
1373 if (! empty($button)) {
1374 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $name escaped via esc_attr(); $var and $button are pre-escaped HTML fragments.
1375 echo '<label for="' . esc_attr($name) . '">' . $var . $button . '</label>';
1376 } else {
1377 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Pre-escaped HTML fragment assembled above.
1378 echo $var;
1379 }
1380
1381 if ($br) {
1382 echo '<br>';
1383 }
1384 }
1385
1386 /**
1387 * Displays radio button in the admin area
1388 *
1389 * @since 3.12.0
1390 * @param string $value the value of the radio button to generate.
1391 */
1392 function radio_custom_image($value)
1393 {
1394 $button = $this->button($value);
1395 $value = "https://cdn.printfriendly.com/{$value}";
1396 ?>
1397 <label for="<?php echo esc_attr($value); ?>">
1398 <input type="radio" id="<?php echo esc_attr($value); ?>" name="<?php echo esc_attr($this->option_name); ?>[custom_button_icon]" value="<?php echo esc_url($value); ?>" <?php $this->checked('custom_button_icon', $value); ?>>
1399 <?php
1400 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Button markup built with esc_url()/esc_html() inside button().
1401 echo $button;
1402 ?>
1403 </label>
1404 <?php
1405 }
1406
1407 /**
1408 * Displays button image in the admin area
1409 *
1410 * @since 3.0
1411 * @param string $name the name of the button to generate.
1412 */
1413 function button($name = false)
1414 {
1415 if (! $name) {
1416 $name = $this->getVal('button_type');
1417 }
1418
1419 $img_path = 'https://cdn.printfriendly.com/';
1420
1421 $return = '';
1422 $imgStyle = '';
1423
1424 if ($name === 'custom-button') {
1425 if ($this->getVal('custom_button_icon') === 'custom-image' && '' !== trim($this->getVal('custom_image', ''))) {
1426 $width = $this->getVal('custom_image_width');
1427 $height = $this->getVal('custom_image_height');
1428 if (! empty($width)) {
1429 $imgStyle .= sprintf('width: %dpx;', $width);
1430 }
1431 if (! empty($height)) {
1432 $imgStyle .= sprintf('height: %dpx;', $height);
1433 }
1434
1435 $return = '<img src="' . esc_url($this->getVal('custom_image')) . '" alt="Print Friendly, PDF & Email" class="pf-button-img" style="' . $imgStyle . '" />';
1436 } elseif ($this->getVal('custom_button_icon') !== 'no-image') {
1437 $icon = str_replace($img_path, '', $this->getVal('custom_button_icon'));
1438 $attributes = self::$_buttons[ $icon ];
1439 $atts = '';
1440 $width = $attributes['width'];
1441 $height = $attributes['height'];
1442 if (! empty($width)) {
1443 $imgStyle .= sprintf('width: %dpx;', $width);
1444 }
1445 if (! empty($height)) {
1446 $imgStyle .= sprintf('height: %dpx;', $height);
1447 }
1448
1449 $return = '<img src="' . esc_url($this->getVal('custom_button_icon')) . '" alt="Print Friendly, PDF & Email" class="pf-button-img" style="' . $imgStyle . '" />';
1450 }
1451
1452 if ($this->getVal('custom_button_text') === 'custom-text') {
1453 $return .= sprintf('<span id="printfriendly-text2" class="pf-button-text">%s</span>', $this->esc_html_if_needed($this->getVal('custom_text')));
1454 }
1455
1456 return $return;
1457 } elseif ($name === 'custom-btn') {
1458 return __('Custom Button', 'printfriendly');
1459 } else {
1460 $attributes = self::$_buttons[ $name ];
1461 $atts = '';
1462 $width = $attributes['width'];
1463 $height = $attributes['height'];
1464 if (! empty($width)) {
1465 $imgStyle .= sprintf('width: %dpx;', $width);
1466 }
1467 if (! empty($height)) {
1468 $imgStyle .= sprintf('height: %dpx;', $height);
1469 }
1470
1471 return '<img class="pf-button-img" src="' . $img_path . $name . '" alt="Print Friendly, PDF & Email" style="' . $imgStyle . '" />';
1472 }
1473 }
1474
1475 /**
1476 * Convenience function to output a value custom button preview elements
1477 *
1478 * @since 3.0.9
1479 */
1480 function custom_button_preview()
1481 {
1482 $img = $url = $button_text = $style = $width = $height = '';
1483 switch ($this->getVal('custom_button_icon')) {
1484 case 'no-image':
1485 break;
1486 case 'custom-image':
1487 $url = $this->getVal('custom_image');
1488 $width = $this->getVal('custom_image_width');
1489 $height = $this->getVal('custom_image_height');
1490 break;
1491 default:
1492 $url = $this->getVal('custom_button_icon');
1493 break;
1494 }
1495
1496 if (! empty($url)) {
1497 $imgStyle = '';
1498 if (! empty($width)) {
1499 $imgStyle .= sprintf('width: %dpx;', $width);
1500 }
1501 if (! empty($height)) {
1502 $imgStyle .= sprintf('height: %dpx;', $height);
1503 }
1504 $img = sprintf('<img src="%s" style="%s" alt="Print Friendly, PDF & Email">', esc_url($url), $imgStyle);
1505 }
1506
1507 if ($this->getVal('custom_button_text') !== 'no-text') {
1508 $button_text = $this->getVal('custom_text');
1509 }
1510
1511 if ('' !== $this->getVal('text_color', '')) {
1512 $style .= 'color: ' . $this->getVal('text_color') . ';';
1513 }
1514
1515 $button_preview = sprintf('<span><span id="pf-custom-button-preview" class="pf-button-img">%s</span><span id="printfriendly-text2" class="pf-button-text" style="%s">%s</span></span>', $img, $style, $this->esc_html_if_needed($button_text));
1516
1517 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Assembled from esc_url()/esc_html_if_needed() parts and a hex-validated text_color.
1518 echo $button_preview;
1519 }
1520
1521 function echoVal($option, $default = '')
1522 {
1523 // Always escape for the HTML attribute context this helper is used in.
1524 echo esc_attr($this->getVal($option, $default));
1525 }
1526
1527 function getVal($option, $default = null)
1528 {
1529 $value = $default;
1530 if (isset($this->options[ $option ])) {
1531 $value = $this->options[ $option ];
1532 }
1533
1534 return $value;
1535 }
1536
1537
1538 /**
1539 * Like the WordPress checked() function but it doesn't throw notices when the array key isn't set and uses the plugins options array.
1540 *
1541 * @since 3.0
1542 * @param mixed $val value to check.
1543 * @param mixed $check_against value to check against.
1544 * @param boolean $echo whether or not to echo the output.
1545 * @return string checked, when true, empty, when false.
1546 */
1547 function checked($val, $check_against = true, $echo = true)
1548 {
1549 $result = '';
1550 if (! isset($this->options[ $val ])) {
1551 return $result;
1552 }
1553
1554 // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
1555 if ($this->options[ $val ] == $check_against) {
1556 $result = ' checked="checked" ';
1557 if ($echo) {
1558 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static ' checked="checked" ' attribute string.
1559 echo $result;
1560 }
1561 }
1562 return $result;
1563 }
1564
1565 /**
1566 * Helper for creating checkboxes.
1567 *
1568 * @since 3.1.5
1569 * @param string $name string used for various parts of checkbox.
1570 */
1571 function create_checkbox($name, $label = '', $labelid = '')
1572 {
1573 // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
1574 $label = ( ! empty($label) ? $label : __(ucfirst($name), 'printfriendly') );
1575 echo '<label' . ( ! empty($labelid) ? ' id="' . esc_attr($labelid) . '"' : '' ) . '><input type="checkbox" class="show_list" name="' . esc_attr($this->option_name) . '[show_on_' . esc_attr($name) . ']" value="on" ';
1576 $this->checked('show_on_' . $name, 'on');
1577 echo ' />' . esc_html($label) . "</label>\r\n";
1578 }
1579
1580
1581 /**
1582 * Like the WordPress selected() function but it doesn't throw notices when the array key isn't set and uses the plugins options array.
1583 *
1584 * @since 3.0.9
1585 * @param mixed $val value to check.
1586 * @param mixed $check_against value to check against.
1587 * @return string checked, when true, empty, when false.
1588 */
1589 function selected($val, $check_against = true)
1590 {
1591 if (! isset($this->options[ $val ])) {
1592 return;
1593 }
1594
1595 return selected($this->options[ $val ], $check_against);
1596 }
1597
1598 /**
1599 * For use with page metabox.
1600 *
1601 * @since 3.2.2
1602 */
1603 function get_page_post_type()
1604 {
1605 $post_types = get_post_types(array('name' => 'page'), 'object');
1606 // echo '<pre>'.print_r($post_types,1).'</pre>';
1607 // die;
1608
1609 return $post_types['page'];
1610 }
1611
1612
1613 /**
1614 * Helper that checks if wp versions is above 3.0.
1615 *
1616 * @since 3.2.2
1617 * @return boolean true wp version is above 3.0
1618 */
1619 function wp_version_gt30()
1620 {
1621 global $wp_version;
1622 return version_compare($wp_version, '3.0', '>=');
1623 }
1624
1625
1626 /**
1627 * Create box for picking individual categories.
1628 *
1629 * @since 3.2.2
1630 */
1631 function create_category_metabox()
1632 {
1633 $obj = new stdClass();
1634 $obj->ID = 0;
1635 do_meta_boxes('settings_page_' . $this->hook, 'normal', $obj);
1636 }
1637
1638
1639 /**
1640 * Load metaboxes advanced button display settings.
1641 *
1642 * @since 3.2.2
1643 */
1644 function on_load_printfriendly()
1645 {
1646 global $wp_version;
1647 if ($this->wp_version_gt30()) {
1648 // require_once(dirname(__FILE__).'/includes/meta-boxes.php');
1649 // require_once(dirname(__FILE__).''includes/nav-menu.php');
1650 wp_enqueue_script('post');
1651
1652 add_meta_box('categorydiv', __('Only display when post is in:', 'printfriendly'), 'post_categories_meta_box', 'settings_page_' . $this->hook, 'normal', 'core');
1653 }
1654 }
1655
1656 /**
1657 * Previously, users with capability unfiltered_html were allowed to use raw HTML.
1658 * But now, the input for all users is sanitized.
1659 */
1660 function esc_html_if_needed($input)
1661 {
1662 $final = esc_html(wp_strip_all_tags($input));
1663 return $final;
1664 }
1665
1666 /**
1667 * Returns if the user is a pro user.
1668 */
1669 function is_pro($feature = null)
1670 {
1671 $licensed = true;//$this->getVal('license_status') === 'pro';
1672
1673 switch ($feature) {
1674 case 'custom-css':
1675 // custom css needs to be available for all irrespective of the license.
1676 return true;
1677 }
1678
1679 return $licensed;
1680 }
1681
1682 function get_custom_css_tag()
1683 {
1684 $custom_css = $this->getVal('custom_css');
1685 if (!empty($custom_css) && 'inline_tag' === $this->getVal('css_include_via')) {
1686 return sprintf('<printfriendly-css style="display: none;">%s</printfriendly-css>', esc_html($custom_css));
1687 }
1688 }
1689
1690 function get_custom_css_js_var()
1691 {
1692 $css_url = $this->get_custom_css_url();
1693 if (!empty($css_url)) {
1694 return sprintf('var pfCustomCSS = "%s";', esc_js(esc_url($css_url)));
1695 }
1696 }
1697
1698 function get_custom_css_url()
1699 {
1700 $css_url = $this->getVal('custom_css_url', '');
1701
1702 if (empty($css_url) && 'file' === $this->getVal('css_include_via')) {
1703 // It could be unset because custom css is empty
1704 $css_url = $this->getVal('custom_css_url_pro');
1705 if (!empty($css_url)) {
1706 $dirs = wp_get_upload_dir();
1707 $css_url = $dirs['baseurl'] . '/' . $css_url;
1708 }
1709 }
1710 return $css_url;
1711 }
1712
1713
1714 /**
1715 * Generates the custom css file from the CSS block.
1716 */
1717 function generate_custom_css_file($css)
1718 {
1719 $file = $this->getVal('custom_css_url_pro');
1720
1721 $custom_css_old = $this->getVal('custom_css');
1722
1723 // return the old file if the CSS has not changed.
1724 if ($custom_css_old === $css && ! empty($file)) {
1725 return $file;
1726 }
1727
1728 $dirs = wp_get_upload_dir();
1729
1730 // delete old file, if it exists
1731 if (! empty($file)) {
1732 wp_delete_file($dirs['basedir'] . '/' . $file);
1733 }
1734
1735 // add a comment so that users know whence this file came.
1736 $date = date_i18n(get_option('date_format') . ' ' . get_option('time_format'));
1737 $comment = sprintf('/* DO NOT EDIT - FILE AUTO-GENERATED BY PRINTFRIENDLY v%s ON %s */', $this->plugin_version, $date);
1738 $css = $comment . PHP_EOL . PHP_EOL . $css;
1739
1740 // create new file, suffixed with the current time.
1741 $file = sprintf('%s_%s.css', $this->hook, time());
1742 include_once ABSPATH . 'wp-admin/includes/file.php';
1743 WP_Filesystem();
1744 global $wp_filesystem;
1745 $wp_filesystem->put_contents(
1746 $dirs['basedir'] . '/' . $file,
1747 $css,
1748 FS_CHMOD_FILE
1749 );
1750
1751 // return the new file name
1752 return $file;
1753 }
1754
1755 /**
1756 * If upgrading from a previous version that was using urls instead of the textarea
1757 * it will return an appropriate message for the user.
1758 */
1759 function get_custom_css_upgrade_message()
1760 {
1761 // upgrading from a version that was using urls instead of the textarea?
1762 $css_url = $this->getVal('custom_css_url');
1763 if (! empty($css_url)) {
1764 // translators: %1$s is an opening <a> tag, %2$s is the CSS file URL, %3$s is the closing </a> tag.
1765 return sprintf(__('You are currently using %1$s%2$s%3$s. You can copy its contents into the textbox if you want to update the styles.', 'printfriendly'), '<a href="' . esc_url($css_url) . '" target="_blank">', esc_html($css_url), '</a>');
1766 }
1767
1768 return null;
1769 }
1770
1771 /**
1772 * Output the config page
1773 *
1774 * @since 3.0
1775 */
1776 function config_page()
1777 {
1778
1779 // Since WP 3.2 outputs these errors by default, only display them when we're on versions older than 3.2 that do support the settings errors.
1780 global $wp_version;
1781 if (version_compare($wp_version, '3.2', '<') && $this->wp_version_gt30()) {
1782 settings_errors();
1783 }
1784
1785 $customCssOptionCode = $this->getSelectorsFromCustomCSS();
1786 include_once PRINTFRIENDLY_BASEPATH . '/views/settings.php';
1787 }
1788
1789 /**
1790 * Returns the current tab to activate.
1791 *
1792 * @since 4.0.1
1793 */
1794 function is_tab($tab_id)
1795 {
1796 $tab = get_transient('pf-tab');
1797 // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
1798 return empty($tab) && $tab_id == 0 ? true : $tab == $tab_id;
1799 }
1800 }
1801 $printfriendly = new PrintFriendly_WordPress();
1802 }
1803
1804 // Add shortcode for printfriendly button
1805 add_shortcode(
1806 'printfriendly',
1807 /**
1808 * Show the printfriendly button.
1809 *
1810 * @param array $atts The attributes of the shortcode.
1811 * If current="yes", the whole page will be printed (pf_current_page_button is called).
1812 * The default behaviour is to call pf_default_button.
1813 *
1814 * @since 3.0
1815 *
1816 * @return string The button.
1817 */
1818 function ($atts) {
1819 $atts = shortcode_atts(
1820 array(
1821 'current' => '', // yes => print the whole page
1822 ),
1823 $atts,
1824 'printfriendly'
1825 );
1826
1827 if (empty($atts['current'])) {
1828 return pf_default_button();
1829 } else {
1830 return pf_current_page_button();
1831 }
1832
1833 return null;
1834 }
1835 );
1836
1837
1838 /**
1839 * Convenience function for use in templates.
1840 *
1841 * @deprecated Use pf_default_button instead.
1842 *
1843 * @since 3.0
1844 *
1845 * @return string returns a button to be printed.
1846 */
1847 function pf_show_link()
1848 {
1849 return pf_default_button();
1850 }
1851
1852 /**
1853 * Convenience function for use in templates.
1854 *
1855 * @since 5.1
1856 *
1857 * @return string returns a button to be printed.
1858 */
1859 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Public template tag (since 3.0); renaming would break themes and shortcodes that call it.
1860 function pf_default_button()
1861 {
1862 global $printfriendly;
1863 return $printfriendly->getButton(true);
1864 }
1865
1866 /**
1867 * Convenience function for use in templates.
1868 *
1869 * @since 5.1
1870 *
1871 * @return string returns a button that prints the entire page it is on.
1872 */
1873 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Public template tag (since 5.1); renaming would break themes and shortcodes that call it.
1874 function pf_current_page_button()
1875 {
1876 global $printfriendly;
1877 return $printfriendly->getButton(true, '', true);
1878 }
1879