PluginProbe
Manual Related Posts / 3.3.0
Manual Related Posts v3.3.0
3.1.4 3.1.5 3.2.0 3.3.0 3.4.0 3.4.1 3.5.0 4.0.0 trunk 3.0.0 3.0.1 3.0.2 3.1.0 3.1.1 3.1.2 3.1.3
related / related.php

related.php in Manual Related Posts 3.3.0, at related.php

552 lines 16.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: Manual Related Posts
4 Plugin URI: https://wordpress.org/plugins/related/
5 Description: A simple 'related posts' plugin that lets you select related posts manually.
6 Version: 3.3.0
7 Author: Marcel Pol
8 Author URI: https://timelord.nl
9 Text Domain: related
10 Domain Path: /lang/
11
12
13 Copyright 2010 - 2012 Matthias Siegel (email: matthias.siegel@gmail.com)
14 Copyright 2013 - 2023 Marcel Pol (email: marcel@timelord.nl)
15
16 This program is free software; you can redistribute it and/or modify
17 it under the terms of the GNU General Public License as published by
18 the Free Software Foundation; either version 2 of the License, or
19 (at your option) any later version.
20
21 This program is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
25
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, write to the Free Software
28 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29 */
30
31
32 /*
33 * Todo:
34 *
35 * - Add shortcode, so it can be used in the middle of a post as well.
36 * - Consider adding a filter with option for the_excerpt as well.
37 * - Add vice-versa linking through an option:
38 * https://wordpress.org/support/topic/automatic-vice-versa-linking/
39 *
40 */
41
42 if ( ! class_exists('Related')) {
43 class Related {
44
45 /*
46 * __construct
47 * Constructor
48 */
49 public function __construct() {
50
51 // Set some helpful constants
52 $this->define_constants();
53
54 // Register hook to save the related posts when saving the post
55 add_action('save_post', array( &$this, 'save' ));
56
57 // Start the plugin
58 add_action('admin_menu', array( &$this, 'start' ), 10);
59
60 // Load the scripts
61 add_action('admin_enqueue_scripts', array( &$this, 'admin_scripts' ));
62
63 // Load the CSS
64 add_action('admin_enqueue_scripts', array( &$this, 'admin_css' ));
65 add_action('wp_enqueue_scripts', array( &$this, 'frontend_css' ));
66
67 // Add the related posts to the content, if set in options
68 add_filter( 'the_content', array( $this, 'related_content_filter' ), 22 );
69
70 // Add the related posts to the RSS Feed, if set in options
71 add_filter( 'the_excerpt_rss', array( $this, 'related_content_rss' ), 22 );
72 add_filter( 'the_content', array( $this, 'related_content_rss' ), 22 );
73 }
74
75
76 /*
77 * defineConstants
78 * Defines a few static helper values we might need
79 */
80 protected function define_constants() {
81 define('RELATED_VERSION', '3.3.0');
82 define('RELATED_FILE', plugin_basename(dirname(__FILE__)));
83 define('RELATED_ABSPATH', str_replace('\\', '/', WP_PLUGIN_DIR . '/' . plugin_basename(dirname(__FILE__))));
84 define('RELATED_URLPATH', plugins_url() . '/' . plugin_basename(dirname(__FILE__)));
85 }
86
87
88 /*
89 * start
90 * Main function
91 */
92 public function start() {
93
94 // Adds a meta box for related posts to the edit screen of each post type in WordPress
95 $related_show = get_option('related_show');
96 $related_show = json_decode( $related_show );
97 if ( empty( $related_show ) ) {
98 $related_show = array();
99 $related_show[] = 'any';
100 }
101 if ( in_array( 'any', $related_show ) ) {
102 foreach (get_post_types() as $post_type) {
103 $post_type = sanitize_text_field( $post_type );
104 add_meta_box($post_type . '-related-posts-box', esc_html__('Related posts', 'related' ), array( &$this, 'display_metabox' ), $post_type, 'normal', 'high');
105 }
106 } else {
107 foreach ($related_show as $post_type) {
108 $post_type = sanitize_text_field( $post_type );
109 add_meta_box($post_type . '-related-posts-box', esc_html__('Related posts', 'related' ), array( &$this, 'display_metabox' ), $post_type, 'normal', 'high');
110 }
111 }
112
113 }
114
115
116 /*
117 * Load JavaScript for Admin
118 */
119 public function admin_scripts() {
120 wp_enqueue_script('jquery-ui-core');
121 wp_enqueue_script('jquery-ui-sortable');
122 wp_enqueue_script('related-scripts', RELATED_URLPATH . '/js/scripts.js', false, RELATED_VERSION, true);
123 wp_enqueue_script('related-chosen', RELATED_URLPATH . '/chosen/chosen.jquery.js', false, RELATED_VERSION, true);
124 }
125
126
127 /*
128 * Load CSS for Admin
129 */
130 public function admin_css() {
131 wp_enqueue_style('related-admin-css', RELATED_URLPATH . '/css/admin-style.css', false, RELATED_VERSION, 'all');
132 wp_enqueue_style('related-chosen-css', RELATED_URLPATH . '/chosen/chosen.min.css', false, RELATED_VERSION, 'all');
133 }
134
135
136 /*
137 * Load CSS for Frontend
138 */
139 public function frontend_css() {
140 wp_enqueue_style('related-frontend-css', RELATED_URLPATH . '/css/frontend-style.css', false, RELATED_VERSION, 'all');
141 }
142
143 /*
144 * save
145 * Save related posts when saving the post
146 *
147 * @param $post_id int ID of the current post.
148 */
149 public function save( $post_id ) {
150 global $pagenow;
151
152 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
153 return;
154 if ( defined( 'DOING_AJAX' ) && DOING_AJAX )
155 return;
156 if ( defined( 'DOING_CRON' ) && DOING_CRON )
157 return;
158
159 /* Check Nonce */
160 $verified = false;
161 if ( isset($_POST['related_nonce']) ) {
162 $verified = wp_verify_nonce( $_POST['related_nonce'], 'related_nonce' );
163 }
164 if ( $verified == false ) {
165 // Nonce is invalid.
166 return;
167 }
168
169 if ( isset($_POST['related-posts']) ) {
170 $related_posts = $_POST['related-posts'];
171 $related_posts_new = array();
172 foreach ( $related_posts as $related_post ) {
173 if ( $post_id == (int) $related_post ) { continue; }
174 $related_posts_new[] = (int) $related_post; // cast to int for security.
175 }
176 update_post_meta( $post_id, 'related_posts', $related_posts_new );
177 }
178 /* Only delete on post.php page, not on Quick Edit. */
179 if ( empty($_POST['related-posts']) ) {
180 if ( $pagenow == 'post.php' ) {
181 delete_post_meta($post_id, 'related_posts');
182 }
183 }
184 }
185
186
187 /*
188 * display_metabox
189 * Creates the output on the post screen
190 */
191 public function display_metabox() {
192 global $post;
193 $post_id = $post->ID;
194
195 /* Nonce */
196 $nonce = wp_create_nonce( 'related_nonce' );
197 echo '<input type="hidden" id="related_nonce" name="related_nonce" value="' . esc_attr( $nonce ) . '" />';
198
199 echo '<p>' . esc_html__('Choose related posts. You can drag-and-drop them into the desired order:', 'related' ) . '</p><div id="related-posts">';
200
201 // Get related posts if existing
202 $related = get_post_meta($post_id, 'related_posts', true);
203
204 if ( ! empty($related)) {
205 foreach ($related as $r) {
206 if ( $post_id == $r ) { continue; }
207 $p = get_post( (int) $r );
208
209 if ( is_object( $p ) ) {
210 if ($p->post_status !== 'trash') {
211 echo '
212 <div class="related-post" id="related-post-' . (int) $r . '">
213 <input type="hidden" name="related-posts[]" value="' . (int) $r . '">
214 <span class="related-post-title">' . esc_html( $p->post_title ) . ' (' . ucfirst(get_post_type($p->ID)) . ')</span>
215 <a href="#">' . esc_html__('Delete', 'related' ) . '</a>
216 </div>';
217 }
218 }
219
220 }
221 }
222
223 /* First option should be empty with a data placeholder for text.
224 * The jQuery call allow_single_deselect makes it possible to empty the selection
225 */
226 echo '
227 </div>
228 <p class="related-posts-select">
229 <select class="related-posts-select chosen-select" name="related-posts-select" data-placeholder="' . esc_html__('Choose a related post... ', 'related' ) . '">';
230
231 echo '<option value="0"></option>';
232
233
234 $related_list = get_option('related_list');
235 $related_list = json_decode( $related_list );
236
237 if ( empty( $related_list ) || in_array( 'any', $related_list ) ) {
238 // list all the post_types
239 $related_list = array();
240
241 $post_types = get_post_types( '', 'names' );
242 foreach ( $post_types as $post_type ) {
243 if ( $post_type === 'revision' || $post_type === 'nav_menu_item' ) {
244 continue;
245 }
246 $related_list[] = $post_type;
247 }
248 }
249
250 foreach ( $related_list as $post_type ) {
251
252 if ( is_post_type_hierarchical($post_type) ) {
253 $orderby = 'title';
254 $order = 'ASC';
255 } else {
256 $orderby = 'date';
257 $order = 'DESC';
258 }
259 echo '<optgroup label="' . ucwords($post_type) . ' ' . sprintf( esc_html__('(sorted on %s)', 'related'), $orderby ) . '">';
260
261 /* Use suppress_filters to support WPML, only show posts in the right language. */
262 $query_args = array(
263 'nopaging' => true,
264 'posts_per_page' => 500,
265 'orderby' => $orderby,
266 'order' => $order,
267 'post_type' => $post_type,
268 'suppress_filters' => 0,
269 'post_status' => 'publish, inherit',
270 'exclude' => array( $post_id ),
271 );
272
273 $posts = get_posts( $query_args );
274
275 if ( ! empty( $posts ) ) {
276 $args = array( $posts, 0, $query_args );
277
278 $walker = new Walker_RelatedDropdown();
279 echo call_user_func_array( array( $walker, 'walk' ), $args );
280 }
281
282 echo '</optgroup>';
283
284 } // endforeach
285
286 wp_reset_postdata();
287
288 echo '
289 </select>
290 </p>';
291
292 }
293
294
295 /*
296 * show
297 * The frontend function that is used to display the related post list.
298 *
299 * Parameters:
300 * - Post ID: the post ID with the list of related posts.
301 * - Return: If true, returns a simple array of related posts to do as you please.
302 * If false (default), it will return a string with formatted HTML.
303 */
304 public function show( $id, $return = false ) {
305
306 global $wpdb;
307
308 /* Compatibility for Qtranslate, Qtranslate-X and Qtranslate-XT, and the get_permalink function */
309 if ( function_exists( 'qtrans_convertURL' ) ) {
310 add_filter('post_type_link', 'qtrans_convertURL');
311 }
312 if ( function_exists( 'qtranxf_convertURL' ) ) {
313 add_filter('post_type_link', 'qtranxf_convertURL');
314 }
315
316 if ( ! empty($id) && is_numeric($id)) {
317 $related = get_post_meta($id, 'related_posts', true);
318
319 if ( ! empty($related)) {
320 $rel = array();
321 foreach ($related as $r) {
322 $p = get_post( (int) $r );
323 $rel[] = $p;
324 }
325
326 // If value should be returned as array, return it.
327 if ($return) {
328 return $rel;
329 }
330
331 // Otherwise return a formatted list
332 if ( is_array( $rel ) && count( $rel ) > 0 ) {
333 $extended_view = get_option('related_content_extended', 0);
334 if ( $extended_view ) {
335 $list = '
336 <ul class="related-posts extended_view">';
337 } else {
338 $list = '
339 <ul class="related-posts">';
340 }
341 foreach ($rel as $r) {
342 if ( is_object( $r ) ) {
343 if ($r->post_status !== 'trash') {
344 if ( $extended_view ) {
345 $thumb_id = get_post_thumbnail_id($r->ID);
346 $tn_size = apply_filters( 'related_show_post_tn_size', 'medium' );
347 $tn = wp_get_attachment_image_src( $thumb_id, sanitize_text_field( $tn_size ) );
348 $image_url = '';
349 if ( isset($tn[0]) ) {
350 $image_url = $tn[0];
351 }
352 $image_alt = get_post_meta( $thumb_id, '_wp_attachment_image_alt', true );
353 if ( strlen($image_alt) == 0 ) { // No alt set for featured image, use the related post title.
354 $image_alt = get_the_title($r->ID);
355 }
356 $image = '';
357 if ( strlen($image_url) > 0 ) {
358 $image = '<img src="' . $image_url . '" alt="' . $image_alt . '" class="related-post-image" />';
359 }
360 $related_post = '
361 <li class="related-post extended_view">
362 <a href="' . get_permalink($r->ID) . '" class="related-post-link">
363 ' . $image
364 . '<span class="related-post-title">' . get_the_title($r->ID) . '</span>
365 </a>
366 </li>';
367 /*
368 * Filter for developers, where you can change content, or add content.
369 */
370 $related_post = apply_filters( 'related_show_post', $related_post, $r );
371 $list .= $related_post;
372 } else {
373 $related_post = '
374 <li class="related-post">
375 <a href="' . get_permalink($r->ID) . '">' . get_the_title($r->ID) . '</a>
376 </li>';
377 /*
378 * Filter for developers, where you can change content, or add content.
379 */
380 $related_post = apply_filters( 'related_show_post', $related_post, $r );
381 $list .= $related_post;
382 }
383 }
384 }
385 }
386 $list .= '
387 </ul>
388 <div style="clear:both;"></div>';
389
390 /*
391 * Filter for developers, where you can change content, or add content.
392 */
393 $list = apply_filters( 'related_show_post_list', $list, $rel );
394
395 return $list;
396 }
397 } else {
398 return false;
399 }
400 } else {
401 return esc_html__('Invalid post ID specified', 'related' );
402 }
403 }
404
405
406 /*
407 * Add the plugin data to the content, if it is set in the options.
408 */
409 public function related_content_filter( $content ) {
410 if ( is_feed() ) {
411 return $content;
412 }
413 if ( (get_option( 'related_content', 0 ) == 1 && is_singular() ) || get_option( 'related_content_all', 0 ) == 1 ) {
414 global $related;
415 $related_posts = $related->show( get_the_ID() );
416 if ( $related_posts ) {
417 $content .= '<div class="related_content" style="clear:both;">';
418 $filtered_title = '<h3 class="widget-title">';
419 $filtered_title .= stripslashes(get_option('related_content_title', esc_html__('Related Posts', 'related')));
420 $filtered_title .= '</h3>';
421 $content .= apply_filters( 'related_content_title', $filtered_title );
422 $content .= $related_posts;
423 $content .= '</div>
424 ';
425 }
426 }
427 // otherwise returns the old content
428 return $content;
429 }
430
431
432 /*
433 * Add the plugin data to the content of the RSS Feed.
434 */
435 public function related_content_rss( $content ) {
436 if ( is_feed() && get_option( 'related_content_rss', 0 ) == 1 ) {
437 global $related;
438 $related_posts = $related->show( get_the_ID() );
439 if ( $related_posts ) {
440 $content .= '<div class="related_content" style="clear:both;">';
441 $filtered_title = '<h3 class="widget-title">';
442 $filtered_title .= stripslashes(get_option('related_content_title', esc_html__('Related Posts', 'related')));
443 $filtered_title .= '</h3>';
444 $content .= apply_filters( 'related_content_title', $filtered_title );
445 $content .= $related_posts;
446 $content .= '</div>
447 ';
448 }
449 }
450 // otherwise returns the old content
451 return $content;
452 }
453 }
454
455 }
456
457
458 /**
459 * Create HTML dropdown list of hierarchical post_types.
460 * Returns the list of <option>'s for the select dropdown.
461 */
462 class Walker_RelatedDropdown extends Walker {
463 /**
464 * @see Walker::$tree_type
465 * @since 2.1.0
466 * @var string
467 */
468 public $tree_type = 'page';
469
470 /**
471 * @see Walker::$db_fields
472 * @since 2.1.0
473 * @todo Decouple this
474 * @var array
475 */
476 public $db_fields = array ( 'parent' => 'post_parent', 'id' => 'ID' );
477
478 /**
479 * @see Walker::start_el()
480 * @since 2.1.0
481 *
482 * @param string $output Passed by reference. Used to append additional content.
483 * @param object $page Page data object.
484 * @param int $depth Depth of page in reference to parent pages. Used for padding.
485 * @param int $id
486 */
487 public function start_el( &$output, $page, $depth = 0, $args = array(), $id = 0 ) {
488 $pad = str_repeat('&nbsp;', $depth * 3);
489
490 $output .= "\t<option class=\"level-$depth\" value=\"" . esc_attr( $page->ID ) . "\">";
491
492 $title = $page->post_title;
493 if ( '' === $title ) {
494 $title = sprintf( esc_html__( '#%d (no title)', 'related' ), $page->ID );
495 }
496
497 /**
498 * Filter the page title when creating an HTML drop-down list of pages.
499 *
500 * @since 3.1.0
501 *
502 * @param string $title Page title.
503 * @param object $page Page data object.
504 */
505 $title = apply_filters( 'list_pages', $title, $page );
506 $output .= $pad . esc_html( $title );
507 $output .= "</option>\n";
508 }
509 }
510
511
512 /*
513 * related_links
514 * Add Settings link to the main plugin page
515 *
516 */
517 function related_links( $links, $file ) {
518 if ( $file === plugin_basename( dirname(__FILE__).'/related.php' ) ) {
519 $links[] = '<a href="' . esc_url( admin_url( 'options-general.php?page=related.php' ) ) . '">' . esc_html__( 'Settings', 'related' ) . '</a>';
520 }
521 return $links;
522 }
523 add_filter( 'plugin_action_links', 'related_links', 10, 2 );
524
525
526 /* Include Settings page */
527 require_once 'adminpages/page-related.php';
528
529 /* Include widget */
530 require_once 'widgets/related-widget.php';
531
532 /* Include Double Up plugin */
533 require_once 'related_du.php';
534
535
536 /*
537 * related_init
538 * Function called at initialisation.
539 * - Loads language files
540 * - Make an instance of Related()
541 */
542
543 function related_init() {
544 load_plugin_textdomain('related', false, dirname( plugin_basename( __FILE__ ) ) . '/lang/');
545
546 // Start the plugin
547 global $related;
548 $related = new Related();
549 }
550 add_action('plugins_loaded', 'related_init');
551
552