wp-print-friendly.php 29.9 KB
Newer Older
1
2
3
4
5
6
<?php
/*
Plugin Name: WP Print Friendly
Plugin URI: http://www.thinkoomph.com/plugins-modules/wp-print-friendly/
Description: Extends WordPress' template system to support printer-friendly templates. Works with permalink structures to support nice URLs.
Author: Erick Hitter (Oomph, Inc.)
7
Version: 0.5.1
8
9
10
11
12
Author URI: http://www.thinkoomph.com/
*/

class wp_print_friendly {
	var $query_var = 'print';
13

14
	var $ns = 'wp_print_friendly';
15

16
17
18
19
20
21
22
23
24
25
26
27
	var $settings_key = 'wpf';
	var $settings_defaults = array(
		'auto' => false,
		'placement' => 'below',
		'post_types' => array( 'post', 'page' ),
		'print_text' => 'Print this entry',
		'print_text_page' => 'Print this page',
		'css_class' => 'print_link',
		'link_target' => 'same',
		'endnotes' => true,
		'endnotes_label' => 'Endnotes:'
	);
28

29
	var $notice_key = 'wpf_admin_notice_dismissed';
30

Erick Hitter's avatar
Erick Hitter committed
31
	/**
32
	 * Register deactivation hook and filter.
Erick Hitter's avatar
Erick Hitter committed
33
	 *
34
35
36
	 * @uses register_deactivation_hook, add_filter
	 * @return null
	 */
Erick Hitter's avatar
Erick Hitter committed
37
	public function __construct() {
38
39
40
		register_deactivation_hook( __FILE__, array( $this, 'deactivation_hook' ) );
		add_action( 'plugins_loaded', array( $this, 'action_plugins_loaded' ) );
	}
41

Erick Hitter's avatar
Erick Hitter committed
42
	/**
43
	 * Clean up after plugin deactivation.
Erick Hitter's avatar
Erick Hitter committed
44
	 *
45
46
47
48
	 * @uses flush_rewrite_rules, delete_option
	 * @action register_deactivation_hook
	 * @return null
	 */
Erick Hitter's avatar
Erick Hitter committed
49
	public function deactivation_hook() {
50
		flush_rewrite_rules();
51

52
53
54
		delete_option( $this->settings_key );
		delete_option( $this->notice_key );
	}
55

Erick Hitter's avatar
Erick Hitter committed
56
	/**
57
	 * Register actions and filters.
Erick Hitter's avatar
Erick Hitter committed
58
	 *
59
60
61
62
	 * @uses add_action, add_filter, get_option
	 * @action plugins_loaded
	 * @return null
	 */
Erick Hitter's avatar
Erick Hitter committed
63
	public function action_plugins_loaded() {
64
65
66
67
68
69
		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
70
		add_filter( 'redirect_canonical', array( $this, 'filter_redirect_canonical' ) );
71
72
73
74
		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 );
75

Erick Hitter's avatar
Erick Hitter committed
76
		if ( ! get_option( $this->notice_key ) )
77
78
			add_action( 'admin_notices', array( $this, 'action_admin_notices_activation' ) );
	}
79

Erick Hitter's avatar
Erick Hitter committed
80
	/**
81
	 * Add print endpoint and rewrite rules for term taxonomy archives
Erick Hitter's avatar
Erick Hitter committed
82
83
	 *
	 * @uses add_rewrite_endpoint, $wp_rewrite, get_taxonomies, add_rewrite_rule, trailingslashit
84
85
86
	 * @action init
	 * @return null
	 */
Erick Hitter's avatar
Erick Hitter committed
87
	public function action_init() {
88
		add_rewrite_endpoint( $this->query_var, EP_ALL );
89

90
		global $wp_rewrite;
91
92

		//Taxonomies, since they aren't covered by add_rewrite_endpoint
Erick Hitter's avatar
Erick Hitter committed
93
		if ( $wp_rewrite->permalink_structure ) {
94
			$taxonomies = get_taxonomies( array(), 'objects' );
Erick Hitter's avatar
Erick Hitter committed
95
96
			foreach ( $taxonomies as $taxonomy => $args ) {
				if ( $args->rewrite == false || 'post_format' == $taxonomy )
97
					continue;
98

99
				$taxonomy_slug = '';
Erick Hitter's avatar
Erick Hitter committed
100
				if ( $args->rewrite[ 'with_front' ] && $wp_rewrite->front != '/' ) $taxonomy_slug .= $wp_rewrite->front;
101
				$taxonomy_slug .= $args->rewrite[ 'slug' ];
102

103
				$query_var = $args->query_var ? $args->query_var : 'taxonomy=' . $taxonomy . '&term';
104
105

				add_rewrite_rule( $taxonomy_slug . '/(.+)/' . $this->query_var . '(/([0-9]*))?/?$', $wp_rewrite->index . '?' . $query_var . '=$matches[1]&' . $this->query_var . '=$matches[3]', 'top' );
106
107
			}
		}
108
109
110

		//Extra rules needed if verbose page rules are requested
		if ( $wp_rewrite->use_verbose_page_rules ) {
111
			//Build regex
112
113
114
115
			$regex = substr( str_replace( $wp_rewrite->rewritecode, $wp_rewrite->rewritereplace, $wp_rewrite->permalink_structure ), 1 );
			$regex = trailingslashit( $regex );
			$regex .= $this->query_var . '(/([0-9]*))?/?$';

116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
			//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' );
133
		}
134
	}
135

Erick Hitter's avatar
Erick Hitter committed
136
	/**
137
	 * Register plugin option and disable rewrite rule flush warning.
Erick Hitter's avatar
Erick Hitter committed
138
	 *
139
140
141
142
	 * @uses register_setting, update_option
	 * @action admin_init
	 * @return null
	 */
Erick Hitter's avatar
Erick Hitter committed
143
	public function action_admin_init() {
144
		register_setting( $this->settings_key, $this->settings_key, array( $this, 'admin_options_validate' ) );
145

Erick Hitter's avatar
Erick Hitter committed
146
		if ( isset( $_GET[ $this->notice_key ] ) )
147
148
			update_option( $this->notice_key, 1 );
	}
149

Erick Hitter's avatar
Erick Hitter committed
150
	/**
151
	 * Determine if print template is being requested.
Erick Hitter's avatar
Erick Hitter committed
152
	 *
153
	 * @global $wp_query
154
155
	 * @return bool
	 */
Erick Hitter's avatar
Erick Hitter committed
156
	public function is_print() {
157
158
		global $wp_query;
		return is_array( $wp_query->query ) && array_key_exists( $this->query_var, $wp_query->query );
159
	}
160

Erick Hitter's avatar
Erick Hitter committed
161
	/**
162
163
	 * 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
164
	 *
165
166
167
	 * @uses get_queried_object, is_home, is_front_page, locate_template
	 * @return array or false
	 */
Erick Hitter's avatar
Erick Hitter committed
168
	public function template_chooser() {
169
170
		//Get queried object to check post type
		$queried_object = get_queried_object();
171

172
173
		//Get plugin path
		$pluginpath = dirname( __FILE__ );
174

Erick Hitter's avatar
Erick Hitter committed
175
		if ( ( is_home() || is_front_page() ) && ( '' !== ( $path = locate_template( 'wpf-home.php', false ) ) ) ) {
176
177
178
179
180
			$template = array(
				'name' => 'wpf-home',
				'path' => $path
			);
		}
Erick Hitter's avatar
Erick Hitter committed
181
		elseif (
182
183
184
			is_object( $queried_object ) &&
			property_exists( $queried_object, 'taxonomy' ) &&
			property_exists( $queried_object, 'slug' ) &&
185
			( '' !== ( $path = locate_template( array( 'wpf-' . $queried_object->taxonomy . '-' . $queried_object->slug . '.php', 'wpf-' . $queried_object->taxonomy . '.php' ), false ) ) )
186
187
188
189
190
		)
			$template = array(
				'name' => 'wpf-' . $queried_object->taxonomy,
				'path' => $path
			);
Erick Hitter's avatar
Erick Hitter committed
191
		elseif (
192
193
194
195
196
197
198
199
200
			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 ) ) )
		)
			$template = array(
				'name' => 'wpf-' . $queried_object->post_type,
				'path' => $path
			);
Erick Hitter's avatar
Erick Hitter committed
201
		elseif (
202
203
204
205
206
207
208
209
			is_object( $queried_object ) &&
			property_exists( $queried_object, 'post_name' ) &&
			( '' !== ( $path = locate_template( 'wpf-' . $queried_object->post_name . '.php', false ) ) )
		)
			$template = array(
				'name' => 'wpf-' . $queried_object->post_name,
				'path' => $path
			);
Erick Hitter's avatar
Erick Hitter committed
210
		elseif ( '' !== ( $path = locate_template( 'wpf.php', false ) ) )
211
212
213
214
			$template = array(
				'name' => 'wpf-default',
				'path' => $path
			);
Erick Hitter's avatar
Erick Hitter committed
215
		elseif ( file_exists( $pluginpath . '/default-template.php' ) )
216
217
218
219
			$template = array(
				'name' => 'wpf-plugin-default',
				'path' => $pluginpath . '/default-template.php'
			);
220

221
222
		return isset( $template ) ? $template : false;
	}
223

Erick Hitter's avatar
Erick Hitter committed
224
225
226
	/**
	 * Detect request for print stylesheet on the homepage and reset query variables.
	 *
227
228
229
230
	 * @param array $qv
	 * @filter request
	 * @return array
	 */
Erick Hitter's avatar
Erick Hitter committed
231
232
	public function filter_request( $qv ) {
		if ( array_key_exists( 'pagename', $qv ) && $qv[ 'pagename' ] == $this->query_var ) {
233
234
235
236
			$qv[ $this->query_var ] = '';
			unset( $qv[ 'page' ] );
			unset( $qv[ 'pagename' ] );
		}
237

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

241
242
		return $qv;
	}
243

Erick Hitter's avatar
Erick Hitter committed
244
	/**
245
	 * Filter query when request to print specific page is made.
Erick Hitter's avatar
Erick Hitter committed
246
	 *
247
248
249
250
	 * @param object $query
	 * @action pre_get_posts
	 * @return object
	 */
Erick Hitter's avatar
Erick Hitter committed
251
252
	public function action_pre_get_posts( $query ) {
		if ( array_key_exists( $this->query_var, $query->query_vars ) && ! empty( $query->query_vars[ $this->query_var ] ) ) {
253
			$qv = explode( '/', $query->query_vars[ $this->query_var ] );
254

Erick Hitter's avatar
Erick Hitter committed
255
			if ( array_key_exists( 1, $qv ) && is_numeric( $qv[ 1 ] ) )
256
257
				$query->query_vars[ 'page' ] = (int)$qv[ 1 ];
		}
258

259
260
		return $query;
	}
261

Erick Hitter's avatar
Erick Hitter committed
262
	/**
263
	 * Filter template include to return print template if requested.
Erick Hitter's avatar
Erick Hitter committed
264
	 *
265
266
267
268
	 * @param string $template
	 * @filter template_include
	 * @return string
	 */
Erick Hitter's avatar
Erick Hitter committed
269
270
	public function filter_template_include( $template ) {
		if ( $this->is_print() && ( $print_template = $this->template_chooser() ) )
271
			$template = $print_template[ 'path' ];
272

273
274
		return $template;
	}
275

Erick Hitter's avatar
Erick Hitter committed
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
	/**
	 * 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;
	}

	/**
292
	 * Filter body classes to include references to print template.
Erick Hitter's avatar
Erick Hitter committed
293
	 *
294
295
296
297
	 * @param array $classes
	 * @filter body_class
	 * @return array
	 */
Erick Hitter's avatar
Erick Hitter committed
298
299
300
	public function filter_body_class( $classes ) {
		if ( $this->is_print() && ( $print_template = $this->template_chooser() ) ) {
			if ( $print_template[ 'name' ] == 'default' )
301
302
303
304
				$classes[] = 'wpf';
			else
				$classes[] = $print_template[ 'name' ];
		}
305

306
307
		return $classes;
	}
308

Erick Hitter's avatar
Erick Hitter committed
309
	/**
310
	 * Filter post content to support printing entire post on one page.
Erick Hitter's avatar
Erick Hitter committed
311
	 *
312
313
314
315
316
	 * @param string $content
	 * @uses get_query_var
	 * @filter the_content
	 * @return string
	 */
Erick Hitter's avatar
Erick Hitter committed
317
318
	public function filter_the_content( $content ) {
		if ( $this->is_print() ) {
319
			$print = get_query_var( $this->query_var );
320

Erick Hitter's avatar
Erick Hitter committed
321
			if ( $print == 'all' || $print == '/all' || empty( $print ) ) {
322
				global $post;
323

324
325
326
327
328
329
330
				$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);
			}
		}
331

332
333
		return $content;
	}
334

Erick Hitter's avatar
Erick Hitter committed
335
336
337
	/**
	 * Filter the content if automatic inclusion is selected.
	 *
338
339
340
341
342
	 * @param string $content
	 * @uses $this::get_options, $post, $this::print_url, get_query_var, apply_filters
	 * @filter the_content
	 * @return string
	 */
Erick Hitter's avatar
Erick Hitter committed
343
	public function filter_the_content_auto( $content ) {
344
		$options = $this->get_options();
345

346
		global $post;
347

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

351
352
			//Basic URL
			$print_url = $this->print_url();
353

354
			//Page URL, if necessary
Erick Hitter's avatar
Erick Hitter committed
355
			if ( ! empty( $print_text_page ) && strpos( $post->post_content, '<!--nextpage-->' ) !== false ) {
356
357
				$page = get_query_var( 'page' );
				$page = $page ? $page : 1;
358

359
360
				$print_url_page = $this->print_url( false, $page );
			}
361

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

Erick Hitter's avatar
Erick Hitter committed
365
			if ( isset( $print_url_page ) ) {
366
367
368
				$link .= ' | ';
				$link .= '<a class="' . $css_class . ' ' . $css_class . '_cur" href="' . $print_url_page . '"' . ( $link_target == 'new' ? ' target="_blank"' : '' ) . '>' . $print_text_page . '</a>';
			}
369

370
			$link .= '</p><!-- .wpf_wrapper -->';
371

372
			//Place link(s)
Erick Hitter's avatar
Erick Hitter committed
373
			if ( $placement == 'above' )
374
				$content = $link . $content;
Erick Hitter's avatar
Erick Hitter committed
375
			elseif ( $placement == 'below' )
376
				$content = $content . $link;
Erick Hitter's avatar
Erick Hitter committed
377
			elseif ( $placement == 'both' )
378
379
				$content = $link . $content . $link;
		}
380

381
382
		return $content;
	}
383

Erick Hitter's avatar
Erick Hitter committed
384
385
386
	/**
	 * Convert links to endnotes if desired.
	 *
387
388
389
390
391
	 * @param string $content
	 * @uses $this::is_print, $this::get_options
	 * @filter the_content
	 * @return string
	 */
Erick Hitter's avatar
Erick Hitter committed
392
393
	public function filter_the_content_late( $content ) {
		if ( $this->is_print() ) {
394
			global $post;
395

396
			$options = $this->get_options();
397

398
			//Endnotes
Erick Hitter's avatar
Erick Hitter committed
399
			if ( $options[ 'endnotes' ] ) {
400
401
				$links = array();
				$i = 1;
402

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

Erick Hitter's avatar
Erick Hitter committed
406
				if (
407
					isset( $matches ) && is_array( $matches ) &&
Erick Hitter's avatar
Erick Hitter committed
408
409
410
					array_key_exists( 0, $matches ) && ! empty( $matches[ 0 ] ) &&
					array_key_exists( 2, $matches ) && ! empty( $matches[ 2 ] ) &&
					array_key_exists( 5, $matches ) && ! empty( $matches[ 5 ] )
411
412
413
				) {
					//Format matches for replacement in content
					$replacements = array();
Erick Hitter's avatar
Erick Hitter committed
414
					foreach ( $matches[ 0 ] as $key => $match ) {
415
416
417
418
419
						$replacements[ $match ] = array(
							'url' => $matches[ 2 ][ $key ],
							'title' => $matches[ 5 ][ $key ]
						);
					}
420

421
					//Replace links with endnote markers
Erick Hitter's avatar
Erick Hitter committed
422
					foreach ( $replacements as $match => $args ) {
423
424
425
426
						$content = str_replace( $match, $args[ 'title' ] . '[' . $i . ']', $content );
						$links[ $i ] = $args;
						$i++;
					}
427

428
429
430
431
					//Output endnotes
					$endnotes = '<div class="wpf-endnotes">';
					$endnotes .= '<strong>' . $options[ 'endnotes_label' ] . '</strong>';
					$endnotes .= '<ol>';
Erick Hitter's avatar
Erick Hitter committed
432
					foreach ( $links as $link ) {
433
434
435
436
437
						$endnotes .= '<li>';
						$endnotes .=  preg_replace( '#<img(.*)>#', '[Image]', $link[ 'title' ] ) . ': ' . esc_url( $link[ 'url' ] );
						$endnotes .= '</li>';
					}
					$endnotes .= '</ol></div><!-- .wpf-endnotes -->';
438

439
440
441
442
					$content .= $endnotes;
				}
			}
		}
443

444
445
		return $content;
	}
446

Erick Hitter's avatar
Erick Hitter committed
447
448
449
	/**
	 * Generate URL for post's printer-friendly format.
	 *
450
451
452
453
454
	 * @param int $post_id
	 * @param int $page
	 * @uses is_view_all, is_home, is_front_page, home_url, $post, get_permalink, is_category, get_category_link, is_tag, get_tag_link, is_date, get_query_var, get_day_link, get_month_link, get_year_link, is_tax, get_queried_object, get_term_link, $wp_rewrite, path_join, trailingslashit, add_query_arg
	 * @return string or bool
	 */
Erick Hitter's avatar
Erick Hitter committed
455
456
	public function print_url( $post_id = false, $page = false ) {
		if ( $page === true )
457
			return false;
458

Erick Hitter's avatar
Erick Hitter committed
459
		if ( function_exists( 'is_view_all' ) && is_view_all() )
460
			$page = false;
461

462
		$link = false;
463

464
		//Get link base specific to page type being viewed
Erick Hitter's avatar
Erick Hitter committed
465
466
		if ( is_singular() || in_the_loop() ) {
			if ( $post_id == false ) {
467
468
469
				global $post;
				$post_id = $post->ID;
			}
470

Erick Hitter's avatar
Erick Hitter committed
471
			if ( ! $post_id )
472
				return false;
473

474
475
			$link = get_permalink( $post_id );
		}
Erick Hitter's avatar
Erick Hitter committed
476
		elseif ( is_home() || is_front_page() )
477
			$link = home_url( '/' );
Erick Hitter's avatar
Erick Hitter committed
478
		elseif ( is_category() )
479
			$link = get_category_link( get_query_var( 'cat' ) );
Erick Hitter's avatar
Erick Hitter committed
480
		elseif ( is_tag() )
481
482
			$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
483
		elseif ( is_date() ) {
484
485
486
			$year = get_query_var( 'year' );
			$monthnum = get_query_var( 'monthnum' );
			$day = get_query_var( 'day' );
487

Erick Hitter's avatar
Erick Hitter committed
488
			if ( $day )
489
				$link = get_day_link( $year, $monthnum, $day );
Erick Hitter's avatar
Erick Hitter committed
490
			elseif ( $monthnum )
491
492
493
494
				$link = get_month_link( $year, $monthnum );
			else
				$link = get_year_link( $year );
		}*/
Erick Hitter's avatar
Erick Hitter committed
495
		elseif ( is_tax() ) {
496
			$queried_object = get_queried_object();
497

Erick Hitter's avatar
Erick Hitter committed
498
			if ( is_object( $queried_object ) && property_exists( $queried_object, 'taxonomy' ) && property_exists( $queried_object, 'term_id' ) )
499
500
				$link = get_term_link( (int)$queried_object->term_id, $queried_object->taxonomy );
		}
501

502
		//If link base is set, build link
Erick Hitter's avatar
Erick Hitter committed
503
		if ( $link !== false ) {
504
			global $wp_rewrite;
505

506
			$page = intval( $page );
507

Erick Hitter's avatar
Erick Hitter committed
508
			if ( $wp_rewrite->using_permalinks() ) {
509
				$link = path_join( $link, $this->query_var );
510

Erick Hitter's avatar
Erick Hitter committed
511
				if ( $page )
512
					$link = path_join( $link, intval( $page ) );
513

Erick Hitter's avatar
Erick Hitter committed
514
				if ( $wp_rewrite->use_trailing_slashes )
515
516
517
518
					$link = trailingslashit( $link );
			}
			else {
				$link = add_query_arg( $this->query_var, is_numeric( $page ) ? intval( $page ) : 'all', $link );
519

Erick Hitter's avatar
Erick Hitter committed
520
				if ( $page )
521
522
523
					$link = add_query_arg( 'page', is_numeric( $page ) ? intval( $page ) : 'all', $link );
			}
		}
524

525
526
		return $link;
	}
527

Erick Hitter's avatar
Erick Hitter committed
528
529
530
	/**
	 * Add menu item for options page.
	 *
531
532
533
534
	 * @uses add_options_page
	 * @action admin_menu
	 * @return null
	 */
Erick Hitter's avatar
Erick Hitter committed
535
	public function action_admin_menu() {
536
537
		add_options_page( 'WP Print Friendly Options', 'WP Print Friendly', 'manage_options', $this->ns, array( $this, 'admin_options' ) );
	}
538

Erick Hitter's avatar
Erick Hitter committed
539
540
541
	/**
	 * Render options page.
	 *
542
543
544
	 * @uses settings_fields, $this::get_options, _e, checked, esc_attr
	 * @return html
	 */
Erick Hitter's avatar
Erick Hitter committed
545
	public function admin_options() {
546
547
548
	?>
		<div class="wrap">
			<h2>WP Print Friendly</h2>
549

550
551
552
553
			<form action="options.php" method="post">
				<?php
					settings_fields( $this->settings_key );
					$options = $this->get_options();
554

555
556
					$post_types = $this->post_types_array();
				?>
557

558
				<h3>Display Options</h3>
559

560
561
				<table class="form-table">
					<tr>
Erick Hitter's avatar
Erick Hitter committed
562
						<th scope="row"><?php _e( 'Automatically add print links based on settings below?', 'wp_print_friendly' ); ?></th>
563
						<td>
Erick Hitter's avatar
Erick Hitter committed
564
565
							<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>
566
567
568
						</td>
					</tr>
					<tr>
Erick Hitter's avatar
Erick Hitter committed
569
						<th scope="row"><?php _e( 'Automatically place link:', 'wp_print_friendly' ); ?></th>
570
						<td>
Erick Hitter's avatar
Erick Hitter committed
571
572
573
							<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>
574
575
576
						</td>
					</tr>
					<tr>
Erick Hitter's avatar
Erick Hitter committed
577
						<th scope="row"><?php _e( 'Display automatically on:', 'wp_print_friendly' ); ?></th>
578
						<td>
Erick Hitter's avatar
Erick Hitter committed
579
580
							<?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 />
581
582
583
584
							<?php endforeach; ?>
						</td>
					</tr>
				</table>
585

586
				<h3>Link Options</h3>
587

588
589
				<table class="form-table">
					<tr>
Erick Hitter's avatar
Erick Hitter committed
590
						<th scope="row"><?php _e( 'Text for link to print entire item:', 'wp_print_friendly' ); ?></th>
591
						<td>
592
							<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%;" />
593
594
595
						</td>
					</tr>
					<tr>
Erick Hitter's avatar
Erick Hitter committed
596
						<th scope="row"><?php _e( 'Text for link to print current page:', 'wp_print_friendly' ); ?></th>
597
						<td>
598
599
							<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
600
601
							<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>
602
603
604
						</td>
					</tr>
					<tr>
Erick Hitter's avatar
Erick Hitter committed
605
						<th scope="row"><?php _e( 'CSS for print links:', 'wp_print_friendly' ); ?></th>
606
						<td>
607
608
							<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
609
610
							<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>
611
612
613
						</td>
					</tr>
					<tr>
Erick Hitter's avatar
Erick Hitter committed
614
						<th scope="row"><?php _e( 'Open print-friendly views:', 'wp_print_friendly' ); ?></th>
615
						<td>
Erick Hitter's avatar
Erick Hitter committed
616
617
							<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>
618
619
620
						</td>
					</tr>
				</table>
621

622
				<h3>Endnote Options</h3>
623

624
625
				<table class="form-table">
					<tr>
Erick Hitter's avatar
Erick Hitter committed
626
						<th scope="row"><?php _e( 'Include endnotes for links found in content?', 'wp_print_friendly' ); ?></th>
627
						<td>
Erick Hitter's avatar
Erick Hitter committed
628
							<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>
629

Erick Hitter's avatar
Erick Hitter committed
630
							<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>
631
632
633
						</td>
					</tr>
					<tr>
Erick Hitter's avatar
Erick Hitter committed
634
						<th scope="row"><label for="endnotes-label"><?php _e( 'Endnotes heading:', 'wp_print_friendly' ); ?></label></th>
635
						<td>
636
637
							<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
638
							<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>
639
640
641
						</td>
					</tr>
				</table>
642

643
644
645
646
				<p class="submit">
					<input type="submit" class="button-primary" value="Save Changes" />
				</p>
			</form>
647

648
649
650
		</div><!-- .wrap -->
	<?php
	}
651

Erick Hitter's avatar
Erick Hitter committed
652
	/**
653
	 * Validate options
Erick Hitter's avatar
Erick Hitter committed
654
	 *
655
656
657
658
	 * @param array $options
	 * @uses $this::get_options, $this::post_types_array, delete_option, sanitize_text_field
	 * @return array
	 */
Erick Hitter's avatar
Erick Hitter committed
659
	public function admin_options_validate( $options ) {
660
661
662
		$new_options = array(
			'endnotes' => false
		);
663

Erick Hitter's avatar
Erick Hitter committed
664
665
		if ( is_array( $options ) ) {
			foreach ( $options as $key => $value ) {
666
667
668
669
670
				switch( $key ) {
					case 'auto':
					case 'endnotes':
						$new_options[ $key ] = (bool)$value;
					break;
671

672
673
674
675
676
677
					case 'placement':
						$placements = array(
							'above',
							'below',
							'both'
						);
678

679
680
						$new_options[ $key ] = in_array( $value, $placements ) ? $value : 'below';
					break;
681

682
683
					case 'post_types':
						$post_types = $this->post_types_array();
684

685
						$new_options[ $key ] = array();
686

Erick Hitter's avatar
Erick Hitter committed
687
688
689
						if ( is_array( $value ) && is_array( $post_types ) ) {
							foreach ( $post_types as $post_type ) {
								if ( in_array( $post_type->name, $value ) )
690
691
692
693
									$new_options[ $key ][] = $post_type->name;
							}
						}
					break;
694

695
696
697
698
699
					case 'print_text':
					case 'print_text_page':
					case 'css_class':
					case 'endnotes_label':
						$value = sanitize_text_field( $value );
700

Erick Hitter's avatar
Erick Hitter committed
701
						if ( $key == 'print_text' && empty( $value ) )
702
							$value = 'Print this entry';
703

704
705
						$new_options[ $key ] = $value;
					break;
706

707
708
709
					case 'link_target':
						$new_options[ $key ] = $value == 'new' ? 'new' : 'same';
					break;
710

711
712
713
714
715
716
					default:
						continue;
					break;
				}
			}
		}
717

718
719
		return $new_options;
	}
720

Erick Hitter's avatar
Erick Hitter committed
721
722
723
	/**
	 * Return plugin options array parsed with default options.
	 *
724
725
726
	 * @uses wp_parse_args, get_option
	 * @return array
	 */
Erick Hitter's avatar
Erick Hitter committed
727
	public function get_options() {
728
		$options = get_option( $this->settings_key, $this->settings_defaults );
729

Erick Hitter's avatar
Erick Hitter committed
730
		if ( ! array_key_exists( 'post_types', $options ) )
731
			$options[ 'post_types' ] = array();
732

733
734
		return wp_parse_args( $options, $this->settings_defaults );
	}
735

Erick Hitter's avatar
Erick Hitter committed
736
737
738
	/**
	 * Build array of available post types, excluding all builtin ones except 'post' and 'page'.
	 *
739
740
741
	 * @uses get_post_types
	 * @return array
	 */
Erick Hitter's avatar
Erick Hitter committed
742
	public function post_types_array() {
743
		$post_types = array();
Erick Hitter's avatar
Erick Hitter committed
744
745
		foreach ( get_post_types( array(), 'objects' ) as $post_type ) {
			if ( $post_type->_builtin == false || $post_type->name == 'post' || $post_type->name == 'page' )
746
747
				$post_types[] = $post_type;
		}
748

749
750
		return $post_types;
	}
751

Erick Hitter's avatar
Erick Hitter committed
752
	/**
753
	 * Display admin notice regarding rewrite rules flush.
Erick Hitter's avatar
Erick Hitter committed
754
	 *
755
756
757
758
	 * @uses get_option, _e, __, admin_url, add_query_arg
	 * @action admin_notices
	 * @return html or null
	 */
Erick Hitter's avatar
Erick Hitter committed
759
760
	public function action_admin_notices_activation() {
		if ( ! get_option( $this->notice_key ) ):
761
		?>
762

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

Erick Hitter's avatar
Erick Hitter committed
766
			<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' ), admin_url( 'options-permalink.php' ) ); ?></p>
767

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

771
772
773
		<?php
		endif;
	}
774

Erick Hitter's avatar
Erick Hitter committed
775
	/**
776
	 * Render page numbers, such as "Page 1 of 5."
Erick Hitter's avatar
Erick Hitter committed
777
	 *
778
779
780
781
782
783
784
	 * @param int $post_id
	 * @param string $before
	 * @param string $separator
	 * @param string $after
	 * @uses $this::is_print, get_query_var, get_post_field
	 * @return string or false
	 */
Erick Hitter's avatar
Erick Hitter committed
785
786
	public function page_numbers( $post_id = false, $before = 'Page ', $separator = ' of ', $after = '' ) {
		if ( ! $this->is_print() )
787
			return false;
788

789
790
		//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
791
		if ( $print == 'all' || $print == '/all' || empty( $print ) )
792
			return false;
793

794
795
		//Get post ID and post content, or return false it either fails validation
		$post_id = intval( $post_id );
796

Erick Hitter's avatar
Erick Hitter committed
797
		if ( ! $post_id ) {
798
799
800
801
			global $post;
			$post_id = $post->ID;
			$post_content = $post->post_content;
		}
802

803
		$post_id = intval( $post_id );
804

Erick Hitter's avatar
Erick Hitter committed
805
		if ( ! $post_id )
806
			return false;
807

Erick Hitter's avatar
Erick Hitter committed
808
		if ( ! isset( $post_content ) || empty( $post_content ) )
809
			$post_content = get_post_field( 'post_content', $post_id );
810

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

814
815
816
		//Get current page
		$page = get_query_var( $this->query_var );
		$page = $page ? $page : 1;
817

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

Erick Hitter's avatar
Erick Hitter committed
821
		if ( is_int( $num_pages ) && $num_pages > 0 )
822
823
824
			$num_pages = $num_pages + 1;
		else
			return false;
825

826
827
828
829
830
831
832
		//Having made it this far, return the specified string
		return $before . $page . $separator . $num_pages . $after;
	}
}
global $wpf;
$wpf = new wp_print_friendly;

Erick Hitter's avatar
Erick Hitter committed
833
/**
834
 * Shortcut to function for generating post's printer-friendly format URL
Erick Hitter's avatar
Erick Hitter committed
835
 *
836
837
838
839
840
841
842
 * @param int $post_id
 * @param int $page
 * @uses $wpf
 * @return string or bool
 */
function wpf_get_print_url( $post_id = false, $page = false ) {
	global $wpf;
Erick Hitter's avatar
Erick Hitter committed
843
	if ( ! is_a( $wpf, 'wp_print_friendly' ) )
844
		$wpf = new wp_print_friendly;
845

846
847
848
	return $wpf->print_url( intval( $post_id ), intval( $page ) );
}

Erick Hitter's avatar
Erick Hitter committed
849
/**
850
 * Output link to printer-friendly post format.
Erick Hitter's avatar
Erick Hitter committed
851
 *
852
853
854
855
856
857
858
859
860
861
862
863
864
 * @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
 * @uses $post, wpf_get_print_url, esc_attr, esc_url, get_query_var
 * @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;
	$url = wpf_get_print_url( $post->ID );
865

866
	$page_link = (bool)$page_link;
867

Erick Hitter's avatar
Erick Hitter committed
868
	if ( function_exists( 'is_view_all' ) && is_view_all() )
869
		$page_link = false;
870