PluginProbe
Manual Related Posts / 3.4.1
Manual Related Posts v3.4.1
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.4.1, at related.php

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