PluginProbe
Manual Related Posts / 3.4.0
Manual Related Posts v3.4.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.4.0, at related.php

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