wp-print-friendly.php 32.6 KB
Newer Older
1 2 3
<?php
/*
Plugin Name: WP Print Friendly
Erick Hitter's avatar
Erick Hitter committed
4
Plugin URI: http://oomphinc.com/plugins-modules/wp-print-friendly/
5
Description: Extends WordPress' template system to support printer-friendly templates. Works with permalink structures to support nice URLs.
6
Author: Erick Hitter, Steven K Word, and Oomph, Inc.
7
Version: 0.6.2
Erick Hitter's avatar
Erick Hitter committed
8
Author URI: http://oomphinc.com/
9
Text Domain: wp_print_friendly
Erick Hitter's avatar
Erick Hitter committed
10 11 12 13 14 15 16 17 18 19 20 21 22 23

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
24 25 26
*/

class wp_print_friendly {
27 28 29 30 31 32 33 34
	/**
	 * Singleton
	 */
	private static $__instance = null;

	/**
	 * Class variables
	 */
35
	private $query_var = 'print';
36

37
	private $ns = 'wp_print_friendly';
38

39
	private $settings_key = 'wpf';
40
	private $settings_defaults = null; // populated in this::action_plugins_loaded to facilitate translation.
41

42
	private $notice_key = 'wpf_admin_notice_dismissed';
43

Erick Hitter's avatar
Erick Hitter committed
44
	/**
45 46 47 48
	 * Silence is golden!
	 */
	private function __construct() {}

49 50 51 52 53 54 55 56 57 58 59 60
	/**
	 * Since class variables are private as of v0.6, expose them for external reference
	 *
	 * @return mixed
	 */
	public function __get( $name ) {
		if ( property_exists( $this, $name ) )
			return $this->$name;
		else
			return null;
	}

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
	/**
	 * Implement singleton
	 *
	 * @uses self::setup
	 * @return self
	 */
	public static function get_instance() {
		if ( ! is_a( self::$__instance, __CLASS__ ) ) {
			self::$__instance = new self;

			self::$__instance->setup();
		}

		return self::$__instance;
	}

	/**
	 * Register deactivation hook and tie balance of plugin hooks to `plugins_loaded`.
Erick Hitter's avatar
Erick Hitter committed
79
	 *
80 81
	 * @uses register_deactivation_hook
	 * @uses add_filter
82 83
	 * @return null
	 */
84
	private function setup() {
85 86 87
		register_deactivation_hook( __FILE__, array( $this, 'deactivation_hook' ) );
		add_action( 'plugins_loaded', array( $this, 'action_plugins_loaded' ) );
	}
88

Erick Hitter's avatar
Erick Hitter committed
89
	/**
90
	 * Clean up after plugin deactivation.
Erick Hitter's avatar
Erick Hitter committed
91
	 *
92 93
	 * @uses flush_rewrite_rules
	 * @uses delete_option
94 95 96
	 * @action register_deactivation_hook
	 * @return null
	 */
Erick Hitter's avatar
Erick Hitter committed
97
	public function deactivation_hook() {
98
		flush_rewrite_rules();
99

100 101 102
		delete_option( $this->settings_key );
		delete_option( $this->notice_key );
	}
103

Erick Hitter's avatar
Erick Hitter committed
104
	/**
105
	 * Register actions and filters.
Erick Hitter's avatar
Erick Hitter committed
106
	 *
107
	 * @uses __
108 109 110
	 * @uses add_action
	 * @uses add_filter
	 * @uses get_option
111 112 113
	 * @action plugins_loaded
	 * @return null
	 */
Erick Hitter's avatar
Erick Hitter committed
114
	public function action_plugins_loaded() {
115 116 117 118 119 120 121 122 123 124 125 126 127 128
		// Populate default settings, with translation support
		$this->settings_defaults = array(
			'auto'            => false,
			'placement'       => 'below',
			'post_types'      => array( 'post', 'page' ),
			'print_text'      => __( 'Print this entry', 'wp_print_friendly' ),
			'print_text_page' => __( 'Print this page', 'wp_print_friendly' ),
			'css_class'       => 'print_link',
			'link_target'     => 'same',
			'endnotes'        => true,
			'endnotes_label'  => __( 'Endnotes:', 'wp_print_friendly' ),
		);

		// Register plugin's remaining actions and filters
129 130 131 132 133 134
		add_action( 'init', array( $this, 'action_init' ), 20 );
		add_action( 'admin_init', array( $this, 'action_admin_init' ) );
		add_action( 'admin_menu', array( $this, 'action_admin_menu' ) );
		add_filter( 'request', array( $this, 'filter_request' ) );
		add_action( 'pre_get_posts', array( $this, 'action_pre_get_posts' ) );
		add_filter( 'template_include', array( $this, 'filter_template_include' ) );
Erick Hitter's avatar
Erick Hitter committed
135
		add_filter( 'redirect_canonical', array( $this, 'filter_redirect_canonical' ) );
136 137 138 139
		add_filter( 'body_class', array( $this, 'filter_body_class' ) );
		add_filter( 'the_content', array( $this, 'filter_the_content' ), 0 );
		add_filter( 'the_content', array( $this, 'filter_the_content_auto' ) );
		add_filter( 'the_content', array( $this, 'filter_the_content_late' ), 99 );
140

Erick Hitter's avatar
Erick Hitter committed
141
		if ( ! get_option( $this->notice_key ) )
142 143
			add_action( 'admin_notices', array( $this, 'action_admin_notices_activation' ) );
	}
144

Erick Hitter's avatar
Erick Hitter committed
145
	/**
146
	 * Add print endpoint and rewrite rules for term taxonomy archives
Erick Hitter's avatar
Erick Hitter committed
147
	 *
148 149 150 151 152
	 * @global $wp_rewrite
	 * @uses add_rewrite_endpoint
	 * @uses get_taxonomies
	 * @uses add_rewrite_rule
	 * @uses trailingslashit
153 154 155
	 * @action init
	 * @return null
	 */
Erick Hitter's avatar
Erick Hitter committed
156
	public function action_init() {
157
		add_rewrite_endpoint( $this->query_var, EP_ALL );
158

159
		global $wp_rewrite;
160 161

		//Taxonomies, since they aren't covered by add_rewrite_endpoint
Erick Hitter's avatar
Erick Hitter committed
162
		if ( $wp_rewrite->permalink_structure ) {
163
			$taxonomies = get_taxonomies( array(), 'objects' );
Erick Hitter's avatar
Erick Hitter committed
164 165
			foreach ( $taxonomies as $taxonomy => $args ) {
				if ( $args->rewrite == false || 'post_format' == $taxonomy )
166
					continue;
167

168
				$taxonomy_slug = '';
Erick Hitter's avatar
Erick Hitter committed
169
				if ( $args->rewrite[ 'with_front' ] && $wp_rewrite->front != '/' ) $taxonomy_slug .= $wp_rewrite->front;
170
				$taxonomy_slug .= $args->rewrite[ 'slug' ];
171

172
				$query_var = $args->query_var ? $args->query_var : 'taxonomy=' . $taxonomy . '&term';
173 174

				add_rewrite_rule( $taxonomy_slug . '/(.+)/' . $this->query_var . '(/([0-9]*))?/?$', $wp_rewrite->index . '?' . $query_var . '=$matches[1]&' . $this->query_var . '=$matches[3]', 'top' );
175 176
			}
		}
177 178 179

		//Extra rules needed if verbose page rules are requested
		if ( $wp_rewrite->use_verbose_page_rules ) {
180
			//Build regex
181 182 183 184
			$regex = substr( str_replace( $wp_rewrite->rewritecode, $wp_rewrite->rewritereplace, $wp_rewrite->permalink_structure ), 1 );
			$regex = trailingslashit( $regex );
			$regex .= $this->query_var . '(/([0-9]*))?/?$';

185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
			//Build corresponding query string
			$query = substr( str_replace( $wp_rewrite->rewritecode, $wp_rewrite->queryreplace, $wp_rewrite->permalink_structure ), 1 );
			$query = explode( '/', $query );
			$query = array_filter( $query );

			$i = 1;
			foreach ( $query as $key => $qv ) {
				$query[ $key ] .= '$matches[' . $i . ']';
				$i++;
			}

			$query[] = $this->query_var . '=$matches[' . ( $i + 1 ) . ']';

			$query = implode( '&', $query );

			//Add rule
			add_rewrite_rule( $regex, $wp_rewrite->index . '?' . $query, 'top' );
202
		}
203
	}
204

Erick Hitter's avatar
Erick Hitter committed
205
	/**
206
	 * Register plugin option and disable rewrite rule flush warning.
Erick Hitter's avatar
Erick Hitter committed
207
	 *
208 209
	 * @uses register_setting
	 * @uses update_option
210 211 212
	 * @action admin_init
	 * @return null
	 */
Erick Hitter's avatar
Erick Hitter committed
213
	public function action_admin_init() {
214
		register_setting( $this->settings_key, $this->settings_key, array( $this, 'admin_options_validate' ) );
215

Erick Hitter's avatar
Erick Hitter committed
216
		if ( isset( $_GET[ $this->notice_key ] ) )
217 218
			update_option( $this->notice_key, 1 );
	}
219

Erick Hitter's avatar
Erick Hitter committed
220
	/**
221
	 * Determine if print template is being requested.
Erick Hitter's avatar
Erick Hitter committed
222
	 *
223 224
	 * If WordPress determined this request is a 404, do nothing and let the main theme handle things.
	 *
225
	 * @global $wp_query
226
	 * @uses is_404
227 228
	 * @return bool
	 */
Erick Hitter's avatar
Erick Hitter committed
229
	public function is_print() {
230
		global $wp_query;
231
		return is_array( $wp_query->query ) && array_key_exists( $this->query_var, $wp_query->query ) && ! is_404();
232
	}
233

Erick Hitter's avatar
Erick Hitter committed
234
	/**
235 236
	 * Select appropriate template based on post type and available templates.
	 * Returns an array with name and path keys for available template or false if no template is found.
Erick Hitter's avatar
Erick Hitter committed
237
	 *
238 239 240 241
	 * @uses get_queried_object
	 * @uses is_home
	 * @uses is_front_page
	 * @uses locate_template
242 243
	 * @return array or false
	 */
Erick Hitter's avatar
Erick Hitter committed
244
	public function template_chooser() {
245 246
		//Get queried object to check post type
		$queried_object = get_queried_object();
247

248 249
		//Get plugin path
		$pluginpath = dirname( __FILE__ );
250

Erick Hitter's avatar
Erick Hitter committed
251
		if ( ( is_home() || is_front_page() ) && ( '' !== ( $path = locate_template( 'wpf-home.php', false ) ) ) ) {
252 253 254 255
			$template = array(
				'name' => 'wpf-home',
				'path' => $path
			);
256
		} elseif (
257 258 259
			is_object( $queried_object ) &&
			property_exists( $queried_object, 'taxonomy' ) &&
			property_exists( $queried_object, 'slug' ) &&
260
			( '' !== ( $path = locate_template( array( 'wpf-' . $queried_object->taxonomy . '-' . $queried_object->slug . '.php', 'wpf-' . $queried_object->taxonomy . '.php' ), false ) ) )
261
		) {
262 263 264 265
			$template = array(
				'name' => 'wpf-' . $queried_object->taxonomy,
				'path' => $path
			);
266
		} elseif (
267 268 269 270
			is_object( $queried_object ) &&
			property_exists( $queried_object, 'post_type' ) &&
			property_exists( $queried_object, 'post_name' ) &&
			( '' !== ( $path = locate_template( array( 'wpf-' . $queried_object->post_type . '-' . $queried_object->post_name . '.php', 'wpf-' . $queried_object->post_type . '.php' ), false ) ) )
271
		) {
272 273 274 275
			$template = array(
				'name' => 'wpf-' . $queried_object->post_type,
				'path' => $path
			);
276
		} elseif (
277 278 279
			is_object( $queried_object ) &&
			property_exists( $queried_object, 'post_name' ) &&
			( '' !== ( $path = locate_template( 'wpf-' . $queried_object->post_name . '.php', false ) ) )
280
		) {
281 282 283 284
			$template = array(
				'name' => 'wpf-' . $queried_object->post_name,
				'path' => $path
			);
285
		} elseif ( '' !== ( $path = locate_template( 'wpf.php', false ) ) ) {
286 287 288 289
			$template = array(
				'name' => 'wpf-default',
				'path' => $path
			);
290
		} elseif ( file_exists( $pluginpath . '/default-template.php' ) ) {
291 292 293 294
			$template = array(
				'name' => 'wpf-plugin-default',
				'path' => $pluginpath . '/default-template.php'
			);
295
		}
296

297 298
		return isset( $template ) ? $template : false;
	}
299

Erick Hitter's avatar
Erick Hitter committed
300 301 302
	/**
	 * Detect request for print stylesheet on the homepage and reset query variables.
	 *
303 304 305 306
	 * @param array $qv
	 * @filter request
	 * @return array
	 */
Erick Hitter's avatar
Erick Hitter committed
307 308
	public function filter_request( $qv ) {
		if ( array_key_exists( 'pagename', $qv ) && $qv[ 'pagename' ] == $this->query_var ) {
309 310 311 312
			$qv[ $this->query_var ] = '';
			unset( $qv[ 'page' ] );
			unset( $qv[ 'pagename' ] );
		}
313

Erick Hitter's avatar
Erick Hitter committed
314
		if ( array_key_exists( $this->query_var, $qv ) && is_numeric( $qv[ $this->query_var ] ) )
315
			$qv[ 'page' ] = intval( $qv[ $this->query_var ] );
316

317 318
		return $qv;
	}
319

Erick Hitter's avatar
Erick Hitter committed
320
	/**
321
	 * Filter query when request to print specific page is made.
Erick Hitter's avatar
Erick Hitter committed
322
	 *
323 324 325 326
	 * @param object $query
	 * @action pre_get_posts
	 * @return object
	 */
Erick Hitter's avatar
Erick Hitter committed
327 328
	public function action_pre_get_posts( $query ) {
		if ( array_key_exists( $this->query_var, $query->query_vars ) && ! empty( $query->query_vars[ $this->query_var ] ) ) {
329
			$qv = explode( '/', $query->query_vars[ $this->query_var ] );
330

Erick Hitter's avatar
Erick Hitter committed
331
			if ( array_key_exists( 1, $qv ) && is_numeric( $qv[ 1 ] ) )
332 333
				$query->query_vars[ 'page' ] = (int)$qv[ 1 ];
		}
334

335 336
		return $query;
	}
337

Erick Hitter's avatar
Erick Hitter committed
338
	/**
339
	 * Filter template include to return print template if requested.
Erick Hitter's avatar
Erick Hitter committed
340
	 *
341 342 343 344
	 * @param string $template
	 * @filter template_include
	 * @return string
	 */
Erick Hitter's avatar
Erick Hitter committed
345
	public function filter_template_include( $template ) {
346
		if ( $this->is_print() && ( $print_template = $this->template_chooser() ) )
347
			$template = $print_template[ 'path' ];
348

349 350
		return $template;
	}
351

Erick Hitter's avatar
Erick Hitter committed
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
	/**
	 * Prevent canonical redirect if print URL is requested.
	 *
	 * @param string $url
	 * @uses this::is_print
	 * @filter redirect_canonical
	 * @return string or false
	 */
	public function filter_redirect_canonical( $url ) {
		if ( $this->is_print() )
			$url = false;

		return $url;
	}

	/**
368
	 * Filter body classes to include references to print template.
Erick Hitter's avatar
Erick Hitter committed
369
	 *
370 371 372 373
	 * @param array $classes
	 * @filter body_class
	 * @return array
	 */
Erick Hitter's avatar
Erick Hitter committed
374 375 376
	public function filter_body_class( $classes ) {
		if ( $this->is_print() && ( $print_template = $this->template_chooser() ) ) {
			if ( $print_template[ 'name' ] == 'default' )
377 378 379 380
				$classes[] = 'wpf';
			else
				$classes[] = $print_template[ 'name' ];
		}
381

382 383
		return $classes;
	}
384

Erick Hitter's avatar
Erick Hitter committed
385
	/**
386
	 * Filter post content to support printing entire post on one page.
Erick Hitter's avatar
Erick Hitter committed
387
	 *
388
	 * @param string $content
389
	 * @uses post_password_required
390 391 392 393
	 * @uses get_query_var
	 * @filter the_content
	 * @return string
	 */
Erick Hitter's avatar
Erick Hitter committed
394
	public function filter_the_content( $content ) {
395
		if ( $this->is_print() && ! post_password_required() ) {
396
			$print = get_query_var( $this->query_var );
397

Erick Hitter's avatar
Erick Hitter committed
398
			if ( $print == 'all' || $print == '/all' || empty( $print ) ) {
399
				global $post;
400

401 402 403 404 405 406 407
				$content = $post->post_content;
				$content = str_replace("\n<!--nextpage-->\n", "\n\n", $content);
				$content = str_replace("\n<!--nextpage-->", "\n", $content);
				$content = str_replace("<!--nextpage-->\n", "\n", $content);
				$content = str_replace("<!--nextpage-->", ' ', $content);
			}
		}
408

409 410
		return $content;
	}
411

Erick Hitter's avatar
Erick Hitter committed
412 413 414
	/**
	 * Filter the content if automatic inclusion is selected.
	 *
415
	 * @global $post
416
	 * @param string $content
417 418 419 420
	 * @uses this::get_options
	 * @uses this::print_url
	 * @uses get_query_var
	 * @uses apply_filters
421 422 423
	 * @filter the_content
	 * @return string
	 */
Erick Hitter's avatar
Erick Hitter committed
424
	public function filter_the_content_auto( $content ) {
425
		$options = $this->get_options();
426

427
		global $post;
428

Erick Hitter's avatar
Erick Hitter committed
429
		if ( is_array( $options ) && array_key_exists( 'auto', $options ) && $options[ 'auto' ] == true && in_array( $post->post_type, $options[ 'post_types' ] ) && ! $this->is_print() ) {
430
			extract( $options );
431

432 433
			//Basic URL
			$print_url = $this->print_url();
434

435
			//Page URL, if necessary
Erick Hitter's avatar
Erick Hitter committed
436
			if ( ! empty( $print_text_page ) && strpos( $post->post_content, '<!--nextpage-->' ) !== false ) {
437 438
				$page = get_query_var( 'page' );
				$page = $page ? $page : 1;
439

440 441
				$print_url_page = $this->print_url( false, $page );
			}
442

443 444
			//Build link(s)
			$link = '<p class="wpf_wrapper"><a class="' . $css_class . '" href="' . $print_url . '"' . ( $link_target == 'new' ? ' target="_blank"' : '' ) . '>' . $print_text . '</a>';
445

Erick Hitter's avatar
Erick Hitter committed
446
			if ( isset( $print_url_page ) ) {
447 448 449
				$link .= ' | ';
				$link .= '<a class="' . $css_class . ' ' . $css_class . '_cur" href="' . $print_url_page . '"' . ( $link_target == 'new' ? ' target="_blank"' : '' ) . '>' . $print_text_page . '</a>';
			}
450

451
			$link .= '</p><!-- .wpf_wrapper -->';
452

453
			//Place link(s)
Erick Hitter's avatar
Erick Hitter committed
454
			if ( $placement == 'above' )
455
				$content = $link . $content;
Erick Hitter's avatar
Erick Hitter committed
456
			elseif ( $placement == 'below' )
457
				$content = $content . $link;
Erick Hitter's avatar
Erick Hitter committed
458
			elseif ( $placement == 'both' )
459 460
				$content = $link . $content . $link;
		}
461

462 463
		return $content;
	}
464

Erick Hitter's avatar
Erick Hitter committed
465 466 467
	/**
	 * Convert links to endnotes if desired.
	 *
468
	 * @param string $content
469 470 471
	 * @uses this::is_print
	 * @uses post_password_required
	 * @uses this::get_options
472 473 474
	 * @filter the_content
	 * @return string
	 */
Erick Hitter's avatar
Erick Hitter committed
475
	public function filter_the_content_late( $content ) {
476
		if ( $this->is_print() && ! post_password_required() ) {
477
			global $post;
478

479
			$options = $this->get_options();
480

481
			//Endnotes
Erick Hitter's avatar
Erick Hitter committed
482
			if ( $options[ 'endnotes' ] ) {
483 484
				$links = array();
				$i = 1;
485

486 487
				//Build array of links
				preg_match_all( '#<a href=(["\'{1}])([^"\']+)(["\'{1}])([^>]*)>(.*?)</a>#i', $content, $matches );
488

Erick Hitter's avatar
Erick Hitter committed
489
				if (
490
					isset( $matches ) && is_array( $matches ) &&
Erick Hitter's avatar
Erick Hitter committed
491 492 493
					array_key_exists( 0, $matches ) && ! empty( $matches[ 0 ] ) &&
					array_key_exists( 2, $matches ) && ! empty( $matches[ 2 ] ) &&
					array_key_exists( 5, $matches ) && ! empty( $matches[ 5 ] )
494 495 496
				) {
					//Format matches for replacement in content
					$replacements = array();
Erick Hitter's avatar
Erick Hitter committed
497
					foreach ( $matches[ 0 ] as $key => $match ) {
498 499 500 501 502
						$replacements[ $match ] = array(
							'url' => $matches[ 2 ][ $key ],
							'title' => $matches[ 5 ][ $key ]
						);
					}
503

504
					//Replace links with endnote markers
Erick Hitter's avatar
Erick Hitter committed
505
					foreach ( $replacements as $match => $args ) {
506 507 508 509
						$content = str_replace( $match, $args[ 'title' ] . '[' . $i . ']', $content );
						$links[ $i ] = $args;
						$i++;
					}
510

511 512 513 514
					//Output endnotes
					$endnotes = '<div class="wpf-endnotes">';
					$endnotes .= '<strong>' . $options[ 'endnotes_label' ] . '</strong>';
					$endnotes .= '<ol>';
Erick Hitter's avatar
Erick Hitter committed
515
					foreach ( $links as $link ) {
516 517 518 519 520
						$endnotes .= '<li>';
						$endnotes .=  preg_replace( '#<img(.*)>#', '[Image]', $link[ 'title' ] ) . ': ' . esc_url( $link[ 'url' ] );
						$endnotes .= '</li>';
					}
					$endnotes .= '</ol></div><!-- .wpf-endnotes -->';
521

522 523 524 525
					$content .= $endnotes;
				}
			}
		}
526

527 528
		return $content;
	}
529

Erick Hitter's avatar
Erick Hitter committed
530 531 532
	/**
	 * Generate URL for post's printer-friendly format.
	 *
533 534
	 * @global $post
	 * @global $wp_rewrite
535 536
	 * @param int $post_id
	 * @param int $page
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
	 * @uses is_view_all
	 * @uses is_home
	 * @uses is_front_page
	 * @uses home_url
	 * @uses get_permalink
	 * @uses is_category
	 * @uses get_category_link
	 * @uses is_tag
	 * @uses get_tag_link
	 * @uses is_date
	 * @uses get_query_var
	 * @uses get_day_link
	 * @uses get_month_link
	 * @uses get_year_link
	 * @uses is_tax
	 * @uses get_queried_object
	 * @uses get_term_link
	 * @uses path_join
	 * @uses trailingslashit
	 * @uses add_query_arg
557 558
	 * @return string or bool
	 */
Erick Hitter's avatar
Erick Hitter committed
559 560
	public function print_url( $post_id = false, $page = false ) {
		if ( $page === true )
561
			return false;
562

Erick Hitter's avatar
Erick Hitter committed
563
		if ( function_exists( 'is_view_all' ) && is_view_all() )
564
			$page = false;
565

566
		$link = false;
567

568
		//Get link base specific to page type being viewed
Erick Hitter's avatar
Erick Hitter committed
569 570
		if ( is_singular() || in_the_loop() ) {
			if ( $post_id == false ) {
571 572 573
				global $post;
				$post_id = $post->ID;
			}
574

Erick Hitter's avatar
Erick Hitter committed
575
			if ( ! $post_id )
576
				return false;
577

578 579
			$link = get_permalink( $post_id );
		}
Erick Hitter's avatar
Erick Hitter committed
580
		elseif ( is_home() || is_front_page() )
581
			$link = home_url( '/' );
Erick Hitter's avatar
Erick Hitter committed
582
		elseif ( is_category() )
583
			$link = get_category_link( get_query_var( 'cat' ) );
Erick Hitter's avatar
Erick Hitter committed
584
		elseif ( is_tag() )
585 586
			$link = get_tag_link( get_query_var( 'tag_id' ) );
		/* DISABLED FOR NOW AS PRINTING OF DATE-BASED ARCHIVES DOESN'T WORK YET
Erick Hitter's avatar
Erick Hitter committed
587
		elseif ( is_date() ) {
588 589 590
			$year = get_query_var( 'year' );
			$monthnum = get_query_var( 'monthnum' );
			$day = get_query_var( 'day' );
591

Erick Hitter's avatar
Erick Hitter committed
592
			if ( $day )
593
				$link = get_day_link( $year, $monthnum, $day );
Erick Hitter's avatar
Erick Hitter committed
594
			elseif ( $monthnum )
595 596 597 598
				$link = get_month_link( $year, $monthnum );
			else
				$link = get_year_link( $year );
		}*/
Erick Hitter's avatar
Erick Hitter committed
599
		elseif ( is_tax() ) {
600
			$queried_object = get_queried_object();
601

Erick Hitter's avatar
Erick Hitter committed
602
			if ( is_object( $queried_object ) && property_exists( $queried_object, 'taxonomy' ) && property_exists( $queried_object, 'term_id' ) )
603 604
				$link = get_term_link( (int)$queried_object->term_id, $queried_object->taxonomy );
		}
605

606
		//If link base is set, build link
Erick Hitter's avatar
Erick Hitter committed
607
		if ( $link !== false ) {
608
			global $wp_rewrite;
609

610
			$page = intval( $page );
611

Erick Hitter's avatar
Erick Hitter committed
612
			if ( $wp_rewrite->using_permalinks() ) {
613
				$link = path_join( $link, $this->query_var );
614

Erick Hitter's avatar
Erick Hitter committed
615
				if ( $page )
616
					$link = path_join( $link, intval( $page ) );
617

Erick Hitter's avatar
Erick Hitter committed
618
				if ( $wp_rewrite->use_trailing_slashes )
619 620 621 622
					$link = trailingslashit( $link );
			}
			else {
				$link = add_query_arg( $this->query_var, is_numeric( $page ) ? intval( $page ) : 'all', $link );
623

Erick Hitter's avatar
Erick Hitter committed
624
				if ( $page )
625 626 627
					$link = add_query_arg( 'page', is_numeric( $page ) ? intval( $page ) : 'all', $link );
			}
		}
628

629 630
		return $link;
	}
631

Erick Hitter's avatar
Erick Hitter committed
632 633 634
	/**
	 * Add menu item for options page.
	 *
635 636 637 638
	 * @uses add_options_page
	 * @action admin_menu
	 * @return null
	 */
Erick Hitter's avatar
Erick Hitter committed
639
	public function action_admin_menu() {
640 641
		add_options_page( 'WP Print Friendly Options', 'WP Print Friendly', 'manage_options', $this->ns, array( $this, 'admin_options' ) );
	}
642

Erick Hitter's avatar
Erick Hitter committed
643 644 645
	/**
	 * Render options page.
	 *
646 647 648 649 650
	 * @uses settings_fields
	 * @uses this::get_options
	 * @uses _e
	 * @uses checked
	 * @uses esc_attr
651 652
	 * @return html
	 */
Erick Hitter's avatar
Erick Hitter committed
653
	public function admin_options() {
654 655 656
	?>
		<div class="wrap">
			<h2>WP Print Friendly</h2>
657

658 659 660 661
			<form action="options.php" method="post">
				<?php
					settings_fields( $this->settings_key );
					$options = $this->get_options();
662

663 664
					$post_types = $this->post_types_array();
				?>
665

666
				<h3>Display Options</h3>
667

668 669
				<table class="form-table">
					<tr>
Erick Hitter's avatar
Erick Hitter committed
670
						<th scope="row"><?php _e( 'Automatically add print links based on settings below?', 'wp_print_friendly' ); ?></th>
671
						<td>
Erick Hitter's avatar
Erick Hitter committed
672 673
							<input type="radio" name="<?php echo esc_attr( $this->settings_key ); ?>[auto]" id="auto-true" value="1"<?php checked( $options[ 'auto' ], true, true ); ?> /> <label for="auto-true"><?php _e( 'Yes', 'wp_print_friendly' ); ?></label><br />
							<input type="radio" name="<?php echo esc_attr( $this->settings_key ); ?>[auto]" id="auto-false" value="0"<?php checked( $options[ 'auto' ], false, true ); ?> /> <label for="auto-false"><?php _e( 'No', 'wp_print_friendly' ); ?></label>
674 675 676
						</td>
					</tr>
					<tr>
Erick Hitter's avatar
Erick Hitter committed
677
						<th scope="row"><?php _e( 'Automatically place link:', 'wp_print_friendly' ); ?></th>
678
						<td>
Erick Hitter's avatar
Erick Hitter committed
679 680 681
							<input type="radio" name="<?php echo esc_attr( $this->settings_key ); ?>[placement]" id="placement-above" value="above"<?php checked( $options[ 'placement' ], 'above', true ); ?> /> <label for="placement-above"><?php _e( 'Above content', 'wp_print_friendly' ); ?></label><br />
							<input type="radio" name="<?php echo esc_attr( $this->settings_key ); ?>[placement]" id="placement-below" value="below"<?php checked( $options[ 'placement' ], 'below', true ); ?> /> <label for="placement-below"><?php _e( 'Below content', 'wp_print_friendly' ); ?></label><br />
							<input type="radio" name="<?php echo esc_attr( $this->settings_key ); ?>[placement]" id="placement-both" value="both"<?php checked( $options[ 'placement' ], 'both', true ); ?> /> <label for="placement-both"><?php _e( 'Above and below content', 'wp_print_friendly' ); ?></label>
682 683 684
						</td>
					</tr>
					<tr>
Erick Hitter's avatar
Erick Hitter committed
685
						<th scope="row"><?php _e( 'Display automatically on:', 'wp_print_friendly' ); ?></th>
686
						<td>
Erick Hitter's avatar
Erick Hitter committed
687 688
							<?php foreach ( $post_types as $post_type ): ?>
								<input type="checkbox" name="<?php echo esc_attr( $this->settings_key ); ?>[post_types][]" id="pt-<?php echo $post_type->name; ?>" value="<?php echo $post_type->name; ?>"<?php if ( in_array( $post_type->name, $options[ 'post_types' ] ) ) echo ' checked="checked"'; ?> /> <label for="pt-<?php echo $post_type->name; ?>"><?php echo $post_type->labels->name; ?></label><br />
689 690 691 692
							<?php endforeach; ?>
						</td>
					</tr>
				</table>
693

694
				<h3>Link Options</h3>
695

696 697
				<table class="form-table">
					<tr>
Erick Hitter's avatar
Erick Hitter committed
698
						<th scope="row"><?php _e( 'Text for link to print entire item:', 'wp_print_friendly' ); ?></th>
699
						<td>
700
							<input type="text" name="<?php echo esc_attr( $this->settings_key ); ?>[print_text]" id="print_text" value="<?php echo esc_attr( $options[ 'print_text' ] ); ?>" style="width: 40%;" />
701 702 703
						</td>
					</tr>
					<tr>
Erick Hitter's avatar
Erick Hitter committed
704
						<th scope="row"><?php _e( 'Text for link to print current page:', 'wp_print_friendly' ); ?></th>
705
						<td>
706 707
							<input type="text" name="<?php echo esc_attr( $this->settings_key ); ?>[print_text_page]" id="print_text_page" value="<?php echo esc_attr( $options[ 'print_text_page' ] ); ?>" style="width: 40%;" />

Erick Hitter's avatar
Erick Hitter committed
708 709
							<p class="description"><?php _e( 'If viewing a multipage post (set by using the &lt;!--nextpage--&gt; tag), the text above is used for a link to print just the current page.', 'wp_print_friendly' ); ?></p>
							<p class="description"><?php _e( '<strong>To hide this link,</strong> clear the field\'s contents.', 'wp_print_friendly' ); ?></p>
710 711 712
						</td>
					</tr>
					<tr>
Erick Hitter's avatar
Erick Hitter committed
713
						<th scope="row"><?php _e( 'CSS for print links:', 'wp_print_friendly' ); ?></th>
714
						<td>
715 716
							<input type="text" name="<?php echo esc_attr( $this->settings_key ); ?>[css_class]" id="css_class" value="<?php echo esc_attr( $options[ 'css_class' ] ); ?>" style="width: 40%;" />

Erick Hitter's avatar
Erick Hitter committed
717 718
							<p class="description"><?php _e( 'For page-specific print links, a second class, created by appending <strong>_cur</strong> to the above text, is added to each link.', 'wp_print_friendly' ); ?></p>
							<p class="description"><?php _e( 'Be aware that Internet Explorer will only interpret the first two CSS classes, so if multiple classes are entered above, the page-specific class may not be available in IE.', 'wp_print_friendly' ); ?></p>
719 720 721
						</td>
					</tr>
					<tr>
Erick Hitter's avatar
Erick Hitter committed
722
						<th scope="row"><?php _e( 'Open print-friendly views:', 'wp_print_friendly' ); ?></th>
723
						<td>
Erick Hitter's avatar
Erick Hitter committed
724 725
							<input type="radio" name="<?php echo esc_attr( $this->settings_key ); ?>[link_target]" id="target-same" value="same"<?php checked( $options[ 'link_target' ], 'same', true ); ?> /> <label for="target-same"><?php _e( 'In the same window', 'wp_print_friendly' ); ?></label><br />
							<input type="radio" name="<?php echo esc_attr( $this->settings_key ); ?>[link_target]" id="target-new" value="new"<?php checked( $options[ 'link_target' ], 'new', true ); ?> /> <label for="target-new"><?php _e( 'In a new window', 'wp_print_friendly' ); ?></label>
726 727 728
						</td>
					</tr>
				</table>
729

730
				<h3>Endnote Options</h3>
731

732 733
				<table class="form-table">
					<tr>
Erick Hitter's avatar
Erick Hitter committed
734
						<th scope="row"><?php _e( 'Include endnotes for links found in content?', 'wp_print_friendly' ); ?></th>
735
						<td>
Erick Hitter's avatar
Erick Hitter committed
736
							<input type="checkbox" name="<?php echo esc_attr( $this->settings_key ); ?>[endnotes]" id="endnotes" value="1"<?php checked( $options[ 'endnotes' ], true, true ); ?> /> <label for="endnotes"><?php _e( 'Yes', 'wp_print_friendly' ); ?></label>
737

Erick Hitter's avatar
Erick Hitter committed
738
							<p class="description"><?php _e( 'If enabled, content is automatically scanned for links and an endnote is added for each link found. This can be helpful for users if your content includes many links.', 'wp_print_friendly' ); ?></p>
739 740 741
						</td>
					</tr>
					<tr>
Erick Hitter's avatar
Erick Hitter committed
742
						<th scope="row"><label for="endnotes-label"><?php _e( 'Endnotes heading:', 'wp_print_friendly' ); ?></label></th>
743
						<td>
744 745
							<input type="text" name="<?php echo esc_attr( $this->settings_key ); ?>[endnotes_label]" class="regular-text code" id="endnotes-label" value="<?php echo esc_attr( $options[ 'endnotes_label' ] ); ?>" />

Erick Hitter's avatar
Erick Hitter committed
746
							<p class="description"><?php _e( 'If endnotes are enabled, the text entered above will be output above the list of links.', 'wp_print_friendly' ); ?></p>
747 748 749
						</td>
					</tr>
				</table>
750

751 752 753 754
				<p class="submit">
					<input type="submit" class="button-primary" value="Save Changes" />
				</p>
			</form>
755

756 757 758
		</div><!-- .wrap -->
	<?php
	}
759

Erick Hitter's avatar
Erick Hitter committed
760
	/**
761
	 * Validate options
Erick Hitter's avatar
Erick Hitter committed
762
	 *
763
	 * @param array $options
764 765 766 767
	 * @uses this::get_options
	 * @uses this::post_types_array
	 * @uses delete_option
	 * @uses sanitize_text_field
768 769
	 * @return array
	 */
Erick Hitter's avatar
Erick Hitter committed
770
	public function admin_options_validate( $options ) {
771 772 773
		$new_options = array(
			'endnotes' => false
		);
774

Erick Hitter's avatar
Erick Hitter committed
775 776
		if ( is_array( $options ) ) {
			foreach ( $options as $key => $value ) {
777 778 779 780 781
				switch( $key ) {
					case 'auto':
					case 'endnotes':
						$new_options[ $key ] = (bool)$value;
					break;
782

783 784 785 786 787 788
					case 'placement':
						$placements = array(
							'above',
							'below',
							'both'
						);
789

790 791
						$new_options[ $key ] = in_array( $value, $placements ) ? $value : 'below';
					break;
792

793 794
					case 'post_types':
						$post_types = $this->post_types_array();
795

796
						$new_options[ $key ] = array();
797

Erick Hitter's avatar
Erick Hitter committed
798 799 800
						if ( is_array( $value ) && is_array( $post_types ) ) {
							foreach ( $post_types as $post_type ) {
								if ( in_array( $post_type->name, $value ) )
801 802 803 804
									$new_options[ $key ][] = $post_type->name;
							}
						}
					break;
805

806 807 808 809 810
					case 'print_text':
					case 'print_text_page':
					case 'css_class':
					case 'endnotes_label':
						$value = sanitize_text_field( $value );
811

Erick Hitter's avatar
Erick Hitter committed
812
						if ( $key == 'print_text' && empty( $value ) )
813
							$value = 'Print this entry';
814

815 816
						$new_options[ $key ] = $value;
					break;
817

818 819 820
					case 'link_target':
						$new_options[ $key ] = $value == 'new' ? 'new' : 'same';
					break;
821

822
					default:
823
						continue 2;
824 825 826 827
					break;
				}
			}
		}
828

829 830
		return $new_options;
	}
831

Erick Hitter's avatar
Erick Hitter committed
832 833 834
	/**
	 * Return plugin options array parsed with default options.
	 *
835 836
	 * @uses wp_parse_args
	 * @uses get_option
837 838
	 * @return array
	 */
Erick Hitter's avatar
Erick Hitter committed
839
	public function get_options() {
840
		$options = get_option( $this->settings_key, $this->settings_defaults );
841

Erick Hitter's avatar
Erick Hitter committed
842
		if ( ! array_key_exists( 'post_types', $options ) )
843
			$options[ 'post_types' ] = array();
844

845 846
		return wp_parse_args( $options, $this->settings_defaults );
	}
847

Erick Hitter's avatar
Erick Hitter committed
848 849 850
	/**
	 * Build array of available post types, excluding all builtin ones except 'post' and 'page'.
	 *
851 852 853
	 * @uses get_post_types
	 * @return array
	 */
Erick Hitter's avatar
Erick Hitter committed
854
	public function post_types_array() {
855
		$post_types = array();
Erick Hitter's avatar
Erick Hitter committed
856 857
		foreach ( get_post_types( array(), 'objects' ) as $post_type ) {
			if ( $post_type->_builtin == false || $post_type->name == 'post' || $post_type->name == 'page' )
858 859
				$post_types[] = $post_type;
		}
860

861 862
		return $post_types;
	}
863

Erick Hitter's avatar
Erick Hitter committed
864
	/**
865
	 * Display admin notice regarding rewrite rules flush.
Erick Hitter's avatar
Erick Hitter committed
866
	 *
867 868 869 870 871
	 * @uses get_option
	 * @uses _e
	 * @uses __
	 * @uses admin_url
	 * @uses add_query_arg
872 873 874
	 * @action admin_notices
	 * @return html or null
	 */
Erick Hitter's avatar
Erick Hitter committed
875 876
	public function action_admin_notices_activation() {
		if ( ! get_option( $this->notice_key ) ):
877
		?>
878

879
		<div id="wpf-rewrite-flush-warning" class="error fade">
Erick Hitter's avatar
Erick Hitter committed
880
			<p><strong><?php _e( 'WP Print Friendly', 'wp_print_friendly' ); ?></strong></p>
881

Erick Hitter's avatar
Erick Hitter committed
882
			<p><?php printf( __( 'You must refresh your site\'s permalinks before WP Print Friendly is fully activated. To do so, go to <a href="%s">Permalinks</a> and click the <strong><em>Save Changes</em></strong> button at the bottom of the screen.', 'wp_print_friendly' ), esc_url( admin_url( 'options-permalink.php' ) ) ); ?></p>
883

Erick Hitter's avatar
Erick Hitter committed
884
			<p><?php printf( __( 'When finished, click <a href="%s">here</a> to hide this message.', 'wp_print_friendly' ), esc_url( admin_url( add_query_arg( $this->notice_key, 1, 'index.php' ) ) ) ); ?></p>
885
		</div>
886

887 888 889
		<?php
		endif;
	}
890

Erick Hitter's avatar
Erick Hitter committed
891
	/**
892
	 * Render page numbers, such as "Page 1 of 5."
Erick Hitter's avatar
Erick Hitter committed
893
	 *
894 895 896 897
	 * @param int $post_id
	 * @param string $before
	 * @param string $separator
	 * @param string $after
898 899 900 901
	 * @uses this::is_print
	 * @uses post_password_required
	 * @uses get_query_var
	 * @uses get_post_field
902 903
	 * @return string or false
	 */
Erick Hitter's avatar
Erick Hitter committed
904
	public function page_numbers( $post_id = false, $before = 'Page ', $separator = ' of ', $after = '' ) {
905
		if ( ! $this->is_print() || post_password_required() )
906
			return false;
907

908 909
		//Don't display on views that include all pages of a post
		$print = get_query_var( $this->query_var );
Erick Hitter's avatar
Erick Hitter committed
910
		if ( $print == 'all' || $print == '/all' || empty( $print ) )
911
			return false;
912

913 914
		//Get post ID and post content, or return false it either fails validation
		$post_id = intval( $post_id );
915

Erick Hitter's avatar
Erick Hitter committed
916
		if ( ! $post_id ) {
917 918 919 920
			global $post;
			$post_id = $post->ID;
			$post_content = $post->post_content;
		}
921

922
		$post_id = intval( $post_id );
923

Erick Hitter's avatar
Erick Hitter committed
924
		if ( ! $post_id )
925
			return false;
926

Erick Hitter's avatar
Erick Hitter committed
927
		if ( ! isset( $post_content ) || empty( $post_content ) )
928
			$post_content = get_post_field( 'post_content', $post_id );
929

Erick Hitter's avatar
Erick Hitter committed
930
		if ( ! is_string( $post_content ) || empty( $post_content ) || strpos( $post_content, '<!--nextpage-->' ) === false )
931
			return false;
932

933 934 935
		//Get current page
		$page = get_query_var( $this->query_var );
		$page = $page ? $page : 1;
936

937 938
		//Get total number of pages, or return false if total cannot be determined
		$num_pages = substr_count( $post_content, '<!--nextpage-->' );
939

Erick Hitter's avatar
Erick Hitter committed
940
		if ( is_int( $num_pages ) && $num_pages > 0 )
941 942 943
			$num_pages = $num_pages + 1;
		else
			return false;
944

945 946 947 948
		//Having made it this far, return the specified string
		return $before . $page . $separator . $num_pages . $after;
	}
}
949 950 951 952 953 954
wp_print_friendly::get_instance();

/**
 * Alias global variable used to hold instantiated plugin prior to singleton's introduction in version 0.6.
 */
$GLOBALS['wpf'] = wp_print_friendly::get_instance();
955

Erick Hitter's avatar
Erick Hitter committed
956
/**
957
 * Shortcut to function for generating post's printer-friendly format URL
Erick Hitter's avatar
Erick Hitter committed
958
 *
959 960
 * @param int $post_id
 * @param int $page
961
 * @uses wp_print_friendly::get_instance
962 963 964
 * @return string or bool
 */
function wpf_get_print_url( $post_id = false, $page = false ) {
965
	return wp_print_friendly::get_instance()->print_url( intval( $post_id ), intval( $page ) );
966 967
}

Erick Hitter's avatar
Erick Hitter committed
968
/**
969
 * Output link to printer-friendly post format.
Erick Hitter's avatar
Erick Hitter committed
970
 *
971
 * @global $post
972 973 974 975 976 977 978
 * @param string $link_text
 * @param string $class
 * @param int $post_id
 * @param bool $page_link
 * @param string $page_link_separator
 * @param string $page_link_text
 * @param string $link_target
979 980 981 982
 * @uses wpf_get_print_url
 * @uses esc_attr
 * @uses esc_url
 * @uses get_query_var
983 984 985 986
 * @return string or null
 */
function wpf_the_print_link( $page_link = false, $link_text = 'Print this post', $class = 'print_link', $page_link_separator = ' | ', $page_link_text = 'Print this page', $link_target = 'same' ) {
	global $post;
987

988
	$url = wpf_get_print_url( $post->ID );
989

990
	$page_link = (bool)$page_link;
991

Erick Hitter's avatar
Erick Hitter committed
992
	if ( function_exists( 'is_view_all' ) && is_view_all() )
993
		$page_link = false;
994

Erick Hitter's avatar
Erick Hitter committed
995
	if ( $url ) {
996
		$link = '<a ' . ( $class ? 'class="' . esc_attr( $class ) . '"' : '' ) . ' href="' . esc_url( $url ) . '"' . ( $link_target == 'new' ? ' target="_blank"' : '' ) . '>' . $link_text . '</a>';
997

Erick Hitter's avatar
Erick Hitter committed
998
		if ( $page_link && strpos( $post->post_content, '<!--nextpage-->' ) !== false ) {
999 1000 1001 1002
			$page = get_query_var( 'page' );
			$page = $page ? $page : 1;
			$link .= $page_link_separator . '<a ' . ( $class ? 'class="' . esc_attr( $class ) . '_cur ' . esc_attr( $class ) . '"' : '' ) . ' href="' . esc_url( wpf_get_print_url( $post->ID, $page ) ) . '"' . ( $link_target == 'new' ? ' target="_blank"' : '' ) . '>' . $page_link_text . '</a>';
		}
1003

1004 1005 1006 1007
		echo $link;
	}
}

Erick Hitter's avatar
Erick Hitter committed
1008
/**
1009
 * Display page numbers, such as "Page 1 of 5."
Erick Hitter's avatar
Erick Hitter committed
1010
 *
1011 1012 1013 1014
 * @param int $post_id
 * @param string $before
 * @param string $separator
 * @param string $after
1015
 * @uses wp_print_friendly::get_instance
1016 1017 1018
 * @return string or false
 */
function wpf_the_page_numbers( $post_id = false, $before = 'Page ', $separator = ' of ', $after = '' ) {
1019
	echo wp_print_friendly::get_instance()->page_numbers( intval( $post_id ), $before, $separator, $after );
1020 1021
}

Erick Hitter's avatar
Erick Hitter committed
1022 1023
if ( ! function_exists( 'is_print' ) ) {
	/**
1024
	 * Conditional tag indicating if printer-friendly format was requested.
Erick Hitter's avatar
Erick Hitter committed
1025
	 *
1026
	 * @uses wp_print_friendly::get_instance
1027 1028 1029
	 * @return bool
	 */
	function is_print() {
1030
		return wp_print_friendly::get_instance()->is_print();
1031 1032
	}
}