PluginProbe
Category Posts Block / 4.9.6
Category Posts Block v4.9.6
5.0.0 trunk 1.0 1.1 1.2 1.2.1 1.3 1.3.1 1.3.2 1.3.3 2.0 2.1 2.2 2.3 3.1 3.2 3.3 4.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 All 62 releases
category-posts / cat-posts.php

cat-posts.php in Category Posts Block 4.9.6, at cat-posts.php

1,142 lines 33.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Main file of the plugin
4 *
5 * @package categoryposts.
6 *
7 * @since 1.0
8 */
9
10 /*
11 Plugin Name: Category Posts Widget
12 Plugin URI: https://wordpress.org/plugins/category-posts/
13 Description: Adds a widget that shows the most recent posts from a single category.
14 Author: TipTopPress
15 Version: 4.9.6
16 Author URI: http://tiptoppress.com
17 Text Domain: category-posts
18 Domain Path: /languages
19 */
20
21 namespace categoryPosts;
22
23 // Don't call the file directly.
24 if ( ! defined( 'ABSPATH' ) ) {
25 exit;
26 }
27
28 const VERSION = '4.9.6';
29 const DOC_URL = 'http://tiptoppress.com/category-posts-widget/documentation-4-9/';
30 const PRO_URL = 'http://tiptoppress.com/term-and-category-based-posts-widget/';
31 const SUPPORT_URL = 'https://wordpress.org/support/plugin/category-posts/';
32 const SHORTCODE_NAME = 'catposts';
33 const SHORTCODE_META = 'categoryPosts-shorcode';
34 const WIDGET_BASE_ID = 'category-posts';
35
36 require_once __DIR__ . '/class-virtual-widget.php';
37 require_once __DIR__ . '/class-virtual-widgets-repository.php';
38 require_once __DIR__ . '/class-widget.php';
39 require_once __DIR__ . '/loadmore.php';
40 require_once __DIR__ . '/localizeddate.php';
41
42 /**
43 * Adds the "Customize" link to the Toolbar on edit mode.
44 *
45 * @since 4.6
46 */
47 function wp_admin_bar_customize_menu() {
48 global $wp_admin_bar;
49
50 if ( ! isset( $_GET['action'] ) || ( 'edit' !== $_GET['action'] ) ) {
51 return;
52 }
53
54 if ( ! current_user_can( 'customize' ) || ! is_admin() || ! is_user_logged_in() || ! is_admin_bar_showing() ) {
55 return;
56 }
57
58 $current_url = '';
59 if ( isset( $_GET['post'] ) && ( '' !== $_GET['post'] ) ) {
60 $current_url = get_permalink( absint( $_GET['post'] ) );
61 }
62 $customize_url = add_query_arg( 'url', rawurlencode( $current_url ), wp_customize_url() );
63
64 $p = isset( $_GET['post'] ) && ! empty( $_GET['post'] ) ? get_post( absint( $_GET['post'] ) ) : false;
65 $names = $p ? shortcode_names( SHORTCODE_NAME, $p->post_content ) : array();
66 if ( empty( $names ) ) {
67 return;
68 }
69
70 $wp_admin_bar->add_menu(
71 array(
72 'id' => 'customize',
73 'title' => __( 'Customize' ),
74 'href' => $customize_url,
75 'meta' => array(
76 'class' => 'hide-if-no-customize',
77 ),
78 )
79 );
80 add_action( 'wp_before_admin_bar_render', 'wp_customize_support_script' );
81 }
82
83 add_action( 'admin_bar_menu', __NAMESPACE__ . '\wp_admin_bar_customize_menu', 35 );
84
85 /**
86 * Print out required CSS, hooked on the wp_head hook.
87 */
88 function wp_head() {
89
90 $widget_repository = new Virtual_Widgets_Repository();
91
92 $styles = array();
93
94 foreach ( $widget_repository->getShortcodes() as $widget ) {
95 $widget->getCSSRules( true, $styles );
96 }
97
98 foreach ( $widget_repository->getWidgets() as $widget ) {
99 $widget->getCSSRules( false, $styles );
100 }
101
102 if ( ! empty( $styles ) ) {
103 ?>
104 <style>
105 <?php
106 foreach ( $styles as $rules ) {
107 foreach ( $rules as $rule ) {
108 echo "$rule\n"; // Xss ok. raw css output, can not be html escaped.
109 }
110 }
111 ?>
112 </style>
113 <?php
114 }
115 }
116
117 add_action( 'wp_head', __NAMESPACE__ . '\register_virtual_widgets', 0 );
118
119 /**
120 * Register virtual widgets for all widgets and shortcodes that are going to be displayed on the page
121 *
122 * @return void
123 *
124 * @since 4.7
125 */
126 function register_virtual_widgets() {
127 global $post;
128 global $wp_registered_widgets;
129
130 $repository = new Virtual_Widgets_Repository();
131
132 // check first for shortcode settings.
133 if ( is_singular() ) {
134 $names = shortcode_names( SHORTCODE_NAME, $post->post_content );
135
136 foreach ( $names as $name ) {
137 $meta = shortcode_settings( get_the_ID(), $name );
138 if ( is_array( $meta ) ) {
139 $id = WIDGET_BASE_ID . '-shortcode-' . get_the_ID(); // needed to make a unique id for the widget html element.
140 if ( '' !== $name ) { // if not default name append to the id.
141 $id .= '-' . sanitize_title( $name ); // sanitize to be on the safe side, not sure where when and how this will be used.
142 }
143 $repository->addShortcode( $name, new Virtual_Widget( $id, WIDGET_BASE_ID . '-shortcode', $meta ) );
144 }
145 }
146 }
147
148 $sidebars_widgets = wp_get_sidebars_widgets();
149
150 if ( is_array( $sidebars_widgets ) ) {
151 foreach ( $sidebars_widgets as $sidebar => $widgets ) {
152 if ( 'wp_inactive_widgets' === $sidebar || 'orphaned_widgets' === substr( $sidebar, 0, 16 ) ) {
153 continue;
154 }
155
156 if ( is_array( $widgets ) ) {
157 foreach ( $widgets as $widget ) {
158 $widget_base = _get_widget_id_base( $widget );
159 if ( WIDGET_BASE_ID === $widget_base ) {
160 $class = __NAMESPACE__ . '\Widget';
161 $widgetclass = new $class();
162 $allsettings = $widgetclass->get_settings();
163 $settings = isset( $allsettings[ str_replace( $widget_base . '-', '', $widget ) ] ) ? $allsettings[ str_replace( $widget_base . '-', '', $widget ) ] : false;
164 $repository->addWidget( $widget, new Virtual_Widget( $widget, $widget, $settings ) );
165 }
166 }
167 }
168 }
169 }
170 }
171
172 add_action( 'wp_head', __NAMESPACE__ . '\wp_head' );
173
174 /**
175 * Enqueue widget related scripts for the widget front-end
176 *
177 * @since 4.8
178 */
179 function frontend_script() {
180 $suffix = 'min.js';
181 if ( defined( 'WP_DEBUG' ) && WP_DEBUG === true ) {
182 $suffix = 'js';
183 }
184 wp_enqueue_script( 'cat-posts-frontend-js', plugins_url( 'js/frontend/category-posts-frontend.' . $suffix, __FILE__ ), array( 'jquery' ), VERSION, true );
185 }
186
187 /**
188 * Embed the front end JS in the HTML footer.
189 *
190 * @since 4.9
191 */
192 function embed_front_end_scripts() {
193 $suffix = 'min.js';
194 if ( defined( 'WP_DEBUG' ) && WP_DEBUG === true ) {
195 $suffix = 'js';
196 }
197 echo '<script>';
198 include __DIR__ . '/js/frontend/category-posts-frontend.' . $suffix;
199 echo '</script>';
200 }
201
202 /**
203 * Enqueue widget related scripts for the widget admin page and customizer.
204 *
205 * @param string $hook the name of the admin hook for which the function was triggered.
206 */
207 function admin_scripts( $hook ) {
208
209 if ( 'widgets.php' === $hook || 'post.php' === $hook ) { // enqueue only for widget admin and customizer. (add if post.php: fix make widget SiteOrigin Page Builder plugin, GH issue #181).
210
211 /*
212 * Add script to control admin UX.
213 */
214
215 // Use unminified version of JS when debuging, and minified when not.
216 $suffix = 'min.js';
217 if ( defined( 'WP_DEBUG' ) && WP_DEBUG === true ) {
218 $suffix = 'js';
219 }
220 wp_register_script( 'category-posts-widget-admin-js', plugins_url( 'js/admin/category-posts-widget.' . $suffix, __FILE__ ), array( 'jquery' ), VERSION, true );
221 wp_enqueue_script( 'category-posts-widget-admin-js' );
222
223 $js_data = array( 'accordion' => false );
224 $meta = get_user_meta( get_current_user_id(), __NAMESPACE__, true );
225 if ( is_array( $meta ) && isset( $meta['panels'] ) ) {
226 $js_data['accordion'] = true;
227 }
228 $js_data['template_tags'] = get_template_tags();
229 $js_data[ __NAMESPACE__ ] = $js_data; // To make accessing the data in JS easier to understand.
230
231 wp_localize_script( 'category-posts-widget-admin-js', 'tiptoppress', $js_data );
232 wp_enqueue_media();
233 wp_localize_script(
234 'category-posts-widget-admin-js',
235 'cwp_default_thumb_selection',
236 array(
237 'frame_title' => __( 'Select a default thumbnail', 'category-posts' ),
238 'button_title' => __( 'Select', 'category-posts' ),
239 'none' => __( 'None', 'category-posts' ),
240 )
241 );
242 }
243 }
244
245 add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\admin_scripts' ); // "called on widgets.php and costumizer since 3.9.
246
247 add_action( 'admin_init', __NAMESPACE__ . '\load_textdomain' );
248
249 /**
250 * Load plugin textdomain.
251 *
252 * @return void
253 *
254 * @since 4.1
255 **/
256 function load_textdomain() {
257 load_plugin_textdomain( 'category-posts', false, plugin_basename( dirname( __FILE__ ) ) . '/languages' );
258 }
259
260 /**
261 * Add styles for widget admin sections
262 *
263 * @since 4.1
264 **/
265 function admin_styles() {
266 wp_enqueue_style( 'cat-posts-admin-styles', plugins_url( 'styles/admin/category-posts-widget.css', __FILE__ ), array(), VERSION, false );
267 }
268
269 add_action( 'admin_print_styles-widgets.php', __NAMESPACE__ . '\admin_styles' );
270
271 // fix make widget SiteOrigin Page Builder plugin, GH issue #181.
272 add_action( 'siteorigin_panel_enqueue_admin_scripts', __NAMESPACE__ . '\admin_styles' );
273
274 /**
275 * Get the tags which might be used in the template.
276 *
277 * @since 4.8
278 *
279 * @return array Array of strings of the tags.
280 */
281 function get_template_tags() {
282 return array( 'author', 'title', 'date', 'thumb', 'excerpt', 'commentnum', 'post_tag', 'category', 'more-link' );
283 }
284
285 /**
286 * Get a regex to parse the template in order to find the tags used in it.
287 *
288 * @since 4.8
289 *
290 * @return string The template parsing regex.
291 */
292 function get_template_regex() {
293 $tags = get_template_tags();
294
295 $regexp = '';
296 foreach ( $tags as $t ) {
297 if ( ! empty( $regexp ) ) {
298 $regexp .= '|';
299 }
300 $regexp .= '%' . $t . '%';
301 }
302
303 $regexp = '@(' . $regexp . ')@i';
304
305 return $regexp;
306 }
307
308 /**
309 * The convert old data structure to current one.
310 *
311 * @param array $settings The settings to upgrade.
312 *
313 * @return array The settings matching current version standard.
314 *
315 * @since 4.8
316 */
317 function upgrade_settings( $settings ) {
318
319 if ( 0 === count( $settings ) ) {
320 return default_settings();
321 }
322
323 if ( ! isset( $settings['ver'] ) ) {
324 /*
325 * Pre 4.9 version.
326 */
327
328 // Upgrade the hide if empty option.
329 if ( isset( $settings['hide_if_empty'] ) && $settings['hide_if_empty'] ) {
330 $settings['no_match_handling'] = 'hide';
331 } else {
332 $settings['no_match_handling'] = 'nothing';
333 }
334 if ( isset( $settings['hide_if_empty'] ) ) {
335 unset( $settings['hide_if_empty'] );
336 }
337 }
338
339 $settings['ver'] = VERSION;
340
341 // Make sure all "empty" settings have default value.
342 $settings = wp_parse_args( $settings, default_settings() );
343
344 return $settings;
345 }
346
347 /**
348 * Convert pre 4.8 settings into template
349 *
350 * @param array $instance Array which contains the various settings.
351 *
352 * @since 4.8
353 */
354 function convert_settings_to_template( $instance ) {
355
356 $template = '';
357
358 if ( isset( $instance['thumb'] ) && $instance['thumb'] ) {
359 if ( isset( $instance['thumbTop'] ) && $instance['thumbTop'] ) {
360 $template .= "%thumb%\n\n";
361 if ( ! ( isset( $instance['hide_post_titles'] ) && $instance['hide_post_titles'] ) ) {
362 $template .= "%title%\n";
363 }
364 } elseif ( isset( $instance['date'] ) && $instance['date'] ) {
365 if ( ! ( isset( $instance['hide_post_titles'] ) && $instance['hide_post_titles'] ) ) {
366 $template .= "%title%\n\n";
367 }
368 $template .= "%date%\n\n";
369 $template .= "%thumb%\n";
370 } elseif ( ! ( isset( $instance['hide_post_titles'] ) && $instance['hide_post_titles'] ) ) {
371 $template .= "%thumb%\n%title%\n";
372 }
373 } else {
374 if ( ! ( isset( $instance['hide_post_titles'] ) && $instance['hide_post_titles'] ) ) {
375 $template .= "%title%\n";
376 }
377 if ( isset( $instance['date'] ) && $instance['date'] ) {
378 $template .= "%date%\n\n";
379 }
380 }
381 if ( isset( $instance['excerpt'] ) && $instance['excerpt'] ) {
382 $template .= '%excerpt%';
383 }
384 if ( isset( $instance['comment_num'] ) && $instance['comment_num'] ) {
385 $template .= "%commentnum%\n\n";
386 }
387 if ( isset( $instance['author'] ) && $instance['author'] ) {
388 $template .= "%author%\n\n";
389 }
390
391 return $template;
392 }
393
394 /*
395 * Plugin action links section
396 */
397
398 /**
399 * Applied to the list of links to display on the plugins page (beside the activate/deactivate links).
400 *
401 * @return array of the widget links
402 *
403 * @since 4.6.3
404 */
405 add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), __NAMESPACE__ . '\add_action_links' );
406
407 /**
408 * Handle the add links filter, add our links to the links displayed under the plugin_basename
409 * in the plugin admin screen.
410 *
411 * @param array $links The current links about to be displayed.
412 */
413 function add_action_links( $links ) {
414
415 if ( ! class_exists( '\\termcategoryPostsPro\\Widget' ) ) {
416 $pro_link = array(
417 '<a target="_blank" href="' . esc_url( PRO_URL ) . '">' . esc_html__( 'Get the Pro version', 'category-posts' ) . '</a>',
418 );
419
420 $links = array_merge( $pro_link, $links );
421 }
422
423 return $links;
424 }
425
426 /**
427 * Register the widget.
428 */
429 function register_widget() {
430 return \register_widget( __NAMESPACE__ . '\Widget' );
431 }
432
433 add_action( 'widgets_init', __NAMESPACE__ . '\register_widget' );
434
435 /**
436 * Output js code to set the image height if float text around image.
437 *
438 * @param int $number The widget number used to identify the specific list.
439 * @param array $widgetsettings The "instance" parameters of the widget.
440 *
441 * @since 4.9
442 **/
443 function equal_cover_content_height( $number, $widgetsettings ) {
444
445 if ( isset( $widgetsettings['template'] ) && preg_match( '/%thumb%|%excerpt%/', $widgetsettings['template'] ) ) :
446 ?>
447 <script type="text/javascript">
448 if (typeof jQuery !== 'undefined') {
449 jQuery( document ).ready(function () {
450 var cat_posts_namespace = window.cat_posts_namespace || {};
451 cat_posts_namespace.layout_wrap_text = cat_posts_namespace.layout_wrap_text || {};
452 cat_posts_namespace.layout_img_size = cat_posts_namespace.layout_img_size || {};
453
454 cat_posts_namespace.layout_wrap_text = {
455 <?php /* Handle item */ echo "\r\n"; ?>
456 preWrap : function (widget) {
457 // var _widget = jQuery(widget);
458 jQuery(widget).find('.cat-post-item').each(function(){
459 var _that = jQuery(this);
460 _that.find('p.cpwp-excerpt-text').addClass('cpwp-wrap-text');
461 _that.find('p.cpwp-excerpt-text').closest('div').wrap('<div class="cpwp-wrap-text-stage"></div>');;
462 });
463 return;
464 },
465 <?php /* Handle add class */ echo "\r\n"; ?>
466 add : function(_this){
467 var _that = jQuery(_this);
468 if (_that.find('p.cpwp-excerpt-text').height() < _that.find('.cat-post-thumbnail').height()) { <?php /* don't move class to do the CSS hack, line's height is smaller as thumb */ echo "\r\n"; ?>
469 _that.find('p.cpwp-excerpt-text').closest('.cpwp-wrap-text-stage').removeClass( "cpwp-wrap-text" );
470 _that.find('p.cpwp-excerpt-text').addClass( "cpwp-wrap-text" ); <?php /* don't do the CSS hack, just set the class */ echo "\r\n"; ?>
471 }else{ <?php /* add the CSS hack, it's needed, text is wrapping, line's height is higher as thumb */ echo "\r\n"; ?>
472 _that.find('p.cpwp-excerpt-text').removeClass( "cpwp-wrap-text" );
473 _that.find('p.cpwp-excerpt-text').closest('.cpwp-wrap-text-stage').addClass( "cpwp-wrap-text" ); <?php /* text is wrapping, do the CSS hack */ echo "\r\n"; ?>
474 }
475 return;
476 },
477 <?php /* Wait for image is loaded */ echo "\r\n"; ?>
478 handleLazyLoading : function(_this) {
479 var width = jQuery(_this).find('img').width();
480 <?php /* image is loaded */ echo "\r\n"; ?>
481 if( 0 !== width ){
482 cat_posts_namespace.layout_wrap_text.add(_this);
483 } else {
484 jQuery(_this).find('img').one("load", function(){
485 cat_posts_namespace.layout_wrap_text.add(_this);
486 });
487 }
488 return;
489 },
490 <?php /* Handle post items */ echo "\r\n"; ?>
491 setClass : function (widget) {
492 // var _widget = jQuery(widget);
493 jQuery(widget).find('.cat-post-item').each(function(){
494 cat_posts_namespace.layout_wrap_text.handleLazyLoading(this);
495 });
496 return;
497 }
498 }
499 cat_posts_namespace.layout_img_size = {
500 <?php /* Handle replace */ echo "\r\n"; ?>
501 replace : function(_this){
502 var _that = jQuery(_this),
503 resp_w = _that.width(),
504 resp_h = _that.height(),
505 orig_w = _that.data('cat-posts-width'),
506 orig_h = _that.data('cat-posts-height');
507 <?php /* replace height */ echo "\r\n"; ?>
508 if( resp_w < orig_w ){
509 _that.height( resp_w * orig_h / orig_w );
510 } else {
511 _that.height( '' );
512 }
513 return;
514 },
515 <?php /* Wait for image is loaded */ echo "\r\n"; ?>
516 handleLazyLoading : function(_this) {
517 var width = jQuery(_this).width();
518 <?php /* image is loaded */ echo "\r\n"; ?>
519 if( 0 !== width ){
520 cat_posts_namespace.layout_img_size.replace(_this);
521 } else {
522 jQuery(_this).one("load", function(){
523 cat_posts_namespace.layout_img_size.replace(_this);
524 });
525 }
526 return;
527 },
528 setHeight : function (widget) {
529 jQuery(widget).find('.cat-post-item img').each(function(){
530 cat_posts_namespace.layout_img_size.handleLazyLoading(this);
531 });
532 return;
533 }
534 }
535
536 var widget = jQuery('#<?php echo esc_attr( $number ); ?>');
537
538 <?php /* do once on document ready */ echo "\r\n"; ?>
539 cat_posts_namespace.layout_wrap_text.preWrap(widget);
540
541 <?php /* do on page load or on resize the browser window */ echo "\r\n"; ?>
542 jQuery(window).on('load resize', function() {
543 cat_posts_namespace.layout_wrap_text.setClass(widget);
544 <?php /* No ratio calculation if one or more dimensions is set to 0 */ echo "\r\n"; ?>
545 <?php if ( isset( $widgetsettings['thumb_w'] ) && 0 !== intval( $widgetsettings['thumb_w'] ) &&
546 isset( $widgetsettings['thumb_h'] ) && 0 !== intval( $widgetsettings['thumb_h'] ) ) : echo "\r\n"; ?>
547 cat_posts_namespace.layout_img_size.setHeight(widget);
548 <?php endif; echo "\r\n"; ?>
549 });
550 });
551 }
552 </script>
553 <?php
554 endif;
555 }
556
557 /*
558 * shortcode section.
559 */
560
561 /**
562 * Get shortcode settings taking into account if it is being customized
563 *
564 * When not customized returns the settings as stored in the meta, but when
565 * it is customized returns the setting stored in the virtual option used by the customizer
566 *
567 * @param string $pid The ID of the post in which the shortcode is.
568 * @param string $name The name of the shortcode to retun, empty string indicates the nameless.
569 *
570 * @return array The shortcode settings if a short code name exists or is an empty string,
571 * empty array if name not found.
572 *
573 * @since 4.6
574 */
575 function shortcode_settings( $pid, $name ) {
576 $meta = get_post_meta( $pid, SHORTCODE_META, true );
577
578 if ( ! empty( $meta ) && ! is_array( reset( $meta ) ) ) {
579 $meta = array( '' => $meta ); // the conversion.
580 }
581
582 if ( ! isset( $meta[ $name ] ) ) { // name do not exists? return empty array.
583 return array();
584 }
585
586 $instance = $meta[ $name ];
587 if ( is_customize_preview() ) {
588 $o = get_option( '_virtual-' . WIDGET_BASE_ID );
589 if ( is_array( $o ) ) {
590 $instance = $o[ $pid ][ $name ];
591 $instance['ver'] = VERSION;
592 }
593 }
594
595 if ( isset( $instance['template'] ) && $instance['template'] ) {
596 ;
597 } else {
598 $instance['template'] = convert_settings_to_template( $instance );
599 }
600
601 $instance = upgrade_settings( $instance );
602
603 return $instance;
604 }
605
606 /**
607 * Handle the shortcode
608 *
609 * @param array $attr Array of the attributes to the short code, none is expected.
610 * @param string $content The content enclosed in the shortcode, none is expected.
611 *
612 * @return string An HTML of the "widget" based on its settings, actual or customized
613 */
614 function shortcode( $attr, $content = null ) {
615 $repository = new Virtual_Widgets_Repository();
616
617 $shortcodes = $repository->getShortcodes();
618
619 $name = '';
620 if ( isset( $attr['name'] ) ) {
621 $name = $attr['name'];
622 }
623
624 if ( is_singular() ) {
625 if ( isset( $shortcodes[ $name ] ) ) {
626 return $shortcodes[ $name ]->getHTML();
627 }
628 }
629
630 return '';
631 }
632
633 add_shortcode( SHORTCODE_NAME, __NAMESPACE__ . '\shortcode' );
634
635 /**
636 * Find if a specific shortcode is used in a content
637 *
638 * @param string $shortcode_name The name of the shortcode.
639 * @param string $content The content to look at.
640 *
641 * @return array An array containing the name attributes of the shortcodes. Empty array is
642 * an indication there were no shourcodes
643 *
644 * @since 4.7
645 */
646 function shortcode_names( $shortcode_name, $content ) {
647
648 $names = array();
649
650 $regex_pattern = get_shortcode_regex();
651 if ( preg_match_all( '/' . $regex_pattern . '/s', $content, $matches ) ) {
652 foreach ( $matches[2] as $k => $shortcode ) {
653 if ( SHORTCODE_NAME === $shortcode ) {
654 $name = '';
655 $atts = shortcode_parse_atts( $matches[3][ $k ] );
656 if ( ! empty( $atts['name'] ) ) {
657 $name = $atts['name'];
658 }
659 $names[] = $name;
660 }
661 }
662 }
663
664 return $names;
665 }
666
667 /**
668 * Organized way to have the default widget settings accessible
669 *
670 * @since 4.6
671 */
672 function default_settings() {
673 return array(
674 'title' => __( 'Recent Posts', 'category-posts' ),
675 'title_link' => false,
676 'title_link_url' => '',
677 'hide_title' => false,
678 'cat' => 0,
679 'num' => get_option( 'posts_per_page' ),
680 'offset' => 1,
681 'sort_by' => 'date',
682 'status' => 'publish',
683 'asc_sort_order' => false,
684 'exclude_current_post' => false,
685 'hideNoThumb' => false,
686 'footer_link_text' => '',
687 'footer_link' => '',
688 'item_title_level' => 'Inline',
689 'thumb_w' => get_option( 'thumbnail_size_w', 150 ),
690 'thumb_fluid_width' => 100,
691 'thumb_h' => get_option( 'thumbnail_size_h', 150 ),
692 'thumb_hover' => 'none',
693 'hide_post_titles' => false,
694 'excerpt_lines' => 4,
695 'excerpt_length' => 0,
696 'excerpt_more_text' => __( '', 'category-posts' ),
697 'excerpt_filters' => false,
698 'comment_num' => false,
699 'date_link' => false,
700 'date_format' => '',
701 'disable_css' => false,
702 'disable_font_styles' => false,
703 'disable_theme_styles' => false,
704 'show_post_format' => 'none',
705 'no_cat_childs' => false,
706 'everything_is_link' => false,
707 'preset_date_format' => 'sitedateandtime',
708 'template' => "%title%\n\n%thumb%",
709 'text_do_not_wrap_thumb' => false,
710 'enable_loadmore' => false,
711 'loadmore_text' => __( 'Load More', 'category-posts' ),
712 'loading_text' => __( 'Loading...', 'category-posts' ),
713 'date_range' => 'off',
714 'start_date' => '',
715 'end_date' => '',
716 'days_ago' => 30,
717 'no_match_handling' => 'nothing',
718 'no_match_text' => '',
719 'default_thunmbnail' => 0,
720 'ver' => VERSION,
721 );
722 }
723
724 /**
725 * Manipulate the relevant meta related to the short code when a post is save
726 *
727 * If A post has a short code, a meta holder is created, If it does not the meta holder is deleted
728 *
729 * @param integer $pid The post ID of the post being saved.
730 * @param WP_Post $post The post being saved.
731 * @return void
732 *
733 * @since 4.6
734 */
735 function save_post( $pid, $post ) {
736
737 // ignore revisions and auto saves.
738 if ( wp_is_post_revision( $pid ) || wp_is_post_autosave( $pid ) ) {
739 return;
740 }
741
742 $meta = get_post_meta( $pid, SHORTCODE_META, true );
743 if ( empty( $meta ) ) {
744 $meta = array();
745 }
746
747 // check if only one shortcode format - non array of arrays, and convert it.
748 if ( ! empty( $meta ) && ! is_array( reset( $meta ) ) ) {
749 $meta = array( '' => $meta ); // the conversion.
750 }
751
752 $old_names = array_keys( $meta ); // keep list of current shortcode names to delete lter whatever was deleted.
753 $names = shortcode_names( SHORTCODE_NAME, $post->post_content );
754
755 // remove setting for unused names.
756 $to_delete = array_diff( $old_names, $names );
757 foreach ( $to_delete as $k ) {
758 unset( $meta[ $k ] );
759 }
760
761 foreach ( $names as $name ) {
762 if ( ! isset( $meta[ $name ] ) ) {
763 $meta[ $name ] = default_settings();
764 }
765 }
766
767 delete_post_meta( $pid, SHORTCODE_META );
768 if ( ! empty( $meta ) ) {
769 add_post_meta( $pid, SHORTCODE_META, $meta, true );
770 }
771 }
772
773 add_action( 'save_post', __NAMESPACE__ . '\save_post', 10, 2 );
774
775 /**
776 * Called on Customizer init to do related registrations.
777 *
778 * @param mixed $wp_customize The customizer object.
779 */
780 function customize_register( $wp_customize ) {
781
782 require_once __DIR__ . '/class-shortcode-control.php';
783
784 $args = array(
785 'post_type' => 'any',
786 'post_status' => 'any',
787 'posts_per_page' => -1,
788 'update_post_term_cache' => false,
789 'meta_query' => array(
790 array(
791 'key' => SHORTCODE_META,
792 'compare' => 'EXISTS',
793 ),
794 ),
795
796 );
797 $posts = get_posts( $args );
798
799 if ( count( $posts ) > 0 ) {
800 $wp_customize->add_panel(
801 __NAMESPACE__,
802 array(
803 'title' => __( 'Category Posts Shortcode', 'category-posts' ),
804 'priority' => 300,
805 'capability' => 'edit_theme_options',
806 )
807 );
808
809 foreach ( $posts as $p ) {
810 $widget = new Widget();
811 $meta = get_post_meta( $p->ID, SHORTCODE_META, true );
812 if ( ! is_array( $meta ) ) {
813 continue;
814 }
815
816 if ( ! is_array( reset( $meta ) ) ) { // 4.6 format.
817 $meta = array( '' => $meta );
818 }
819
820 foreach ( $meta as $k => $m ) {
821
822 if ( isset( $m['template'] ) && $m['template'] ) {
823 ;
824 } else {
825 $m['template'] = convert_settings_to_template( $m );
826 }
827
828 if ( 0 === count( $meta ) ) { // new widget, use defaults.
829 ;
830 } else { // updated widgets come from =< 4.6 excerpt filter is on.
831 if ( ! isset( $m['excerpt_filters'] ) ) {
832 $m['excerpt_filters'] = 'on';
833 }
834 }
835
836 $m = upgrade_settings( $m );
837
838 $section_title = $k;
839 if ( '' === $section_title ) {
840 $section_title = __( '[shortcode]', 'category-posts' );
841 }
842
843 $wp_customize->add_section(
844 __NAMESPACE__ . '-' . $p->id . '-' . $k,
845 array(
846 'title' => $section_title,
847 'priority' => 10,
848 'capability' => 'edit_theme_options',
849 'panel' => __NAMESPACE__,
850 )
851 );
852
853 ob_start();
854
855 // For the form method to handle generation gracefully, the number needs to be a simple string, and the name might include other chars as well, so for simplisity md5 it.
856 if ( '' !== $k ) {
857 $widget->number = $p->ID . '_' . md5( $k );
858 } else {
859 $widget->number = $p->ID . '_';
860 }
861
862 $widget->form( $m );
863 $form = ob_get_clean();
864 $form = preg_replace_callback(
865 '/<(input|select|textarea)\s+.*name=("|\').*\[\w*\]\[([^\]]*)\][^>]*>/',
866 function ( $matches ) use ( $p, $wp_customize, $m, $k ) {
867 $setting = '_virtual-' . WIDGET_BASE_ID . '[' . $p->ID . '][' . $k . '][' . $matches[3] . ']';
868 if ( ! isset( $m[ $matches[3] ] ) ) {
869 $m[ $matches[3] ] = null;
870 }
871 $wp_customize->add_setting(
872 $setting,
873 array(
874 'default' => $m[ $matches[3] ], // set default to current value.
875 'type' => 'option',
876 )
877 );
878
879 return str_replace( '<' . $matches[1], '<' . $matches[1] . ' data-customize-setting-link="' . $setting . '"', $matches[0] );
880 },
881 $form
882 );
883
884 $args = array(
885 'label' => __( 'Layout', 'twentyfourteen' ),
886 'section' => __NAMESPACE__ . '-' . $p->id . '-' . $k,
887 'form' => $form,
888 'settings' => '_virtual-' . WIDGET_BASE_ID . '[' . $p->ID . '][' . $k . '][title]',
889 'active_callback' => function () use ( $p ) {
890 return is_singular() && ( get_the_ID() === $p->ID );
891 },
892 );
893
894 if ( get_option( 'page_on_front' ) === $p->ID ) {
895 $args['active_callback'] = function () {
896 return is_front_page();
897 };
898 }
899
900 $sc = new ShortCode_Control(
901 $wp_customize,
902 '_virtual-' . WIDGET_BASE_ID . '[' . $p->ID . '][' . $k . '][title]',
903 $args
904 );
905
906 if ( '' !== $k ) {
907 $sc->title_postfix = ' ' . $k;
908 }
909 $wp_customize->add_control( $sc );
910 }
911 }
912 }
913 }
914
915 add_action( 'customize_register', __NAMESPACE__ . '\customize_register' );
916
917 /**
918 * Save the virtual option used by the customizer into the proper meta values.
919 *
920 * The customizer actually saves only the changed values, so a merge needs to be done.
921 * After everything is updated the virtual option is deleted to leave a clean slate
922 *
923 * @return void
924 *
925 * @since 4.6
926 */
927 function customize_save_after() {
928 $virtual = get_option( '_virtual-' . WIDGET_BASE_ID );
929
930 if ( is_array( $virtual ) ) {
931 foreach ( $virtual as $pid => $instance ) {
932 $meta = get_post_meta( $pid, SHORTCODE_META, true );
933 if ( ! empty( $meta ) && ! is_array( reset( $meta ) ) ) {
934 $meta = array( '' => $meta ); // the conversion.
935 }
936
937 foreach ( $instance as $name => $new ) {
938 if ( isset( $meta[ $name ] ) ) { // unlikely but maybe that short code was deleted by other session.
939 $meta[ $name ] = array_merge( $meta[ $name ], $new );
940 }
941 }
942 }
943 update_post_meta( $pid, SHORTCODE_META, $meta );
944 }
945
946 delete_option( '_virtual-' . WIDGET_BASE_ID );
947 }
948
949 add_action( 'customize_save_after', __NAMESPACE__ . '\customize_save_after', 100 );
950
951 /*
952 * tinymce related functions.
953 */
954
955 /**
956 * Uninstall handler, cleanup DB from options and meta
957 *
958 * @return void
959 *
960 * @since 4.7
961 */
962 function uninstall() {
963 delete_option( 'widget-' . WIDGET_BASE_ID ); // delete the option storing the widget options.
964 delete_post_meta_by_key( SHORTCODE_META ); // delete the meta storing the shortcode.
965 delete_metadata( 'user', 0, __NAMESPACE__, '', true ); // delete all user metadata.
966 }
967
968 register_uninstall_hook( __FILE__, __NAMESPACE__ . 'uninstall' );
969
970 /**
971 * Register the tinymce shortcode plugin
972 *
973 * @param array $plugin_array An array containing the current plugins to be used by tinymce.
974 *
975 * @return array An array containing the plugins to be used by tinymce, our plugin added to the $plugin_array parameter
976 *
977 * @since 4.7
978 */
979 function mce_external_plugins( $plugin_array ) {
980 if ( current_user_can( 'edit_theme_options' ) ) { // don't load the code if the user can not customize the shortcode.
981 // enqueue TinyMCE plugin script with its ID.
982 $meta = get_user_meta( get_current_user_id(), __NAMESPACE__, true );
983 if ( is_array( $meta ) && isset( $meta['editor'] ) ) {
984 ;
985 } else {
986 $plugin_array[ __NAMESPACE__ ] = plugins_url( 'js/admin/tinymce.min.js?ver=' . VERSION, __FILE__ );
987 }
988 }
989
990 return $plugin_array;
991 }
992
993 add_filter( 'mce_external_plugins', __NAMESPACE__ . '\mce_external_plugins' );
994
995 /**
996 * Register the tinymce buttons for the add shortcode
997 *
998 * @param array $buttons An array containing the current buttons to be used by tinymce.
999 *
1000 * @return array An array containing the buttons to be used by tinymce, our button added to the $buttons parameter
1001 *
1002 * @since 4.7
1003 */
1004 function mce_buttons( $buttons ) {
1005 if ( current_user_can( 'edit_theme_options' ) ) { // don't load the code if the user can not customize the shortcode
1006 // register buttons with their id.
1007 $meta = get_user_meta( get_current_user_id(), __NAMESPACE__, true );
1008 if ( is_array( $meta ) && isset( $meta['editor'] ) ) {
1009 ;
1010 } else {
1011 array_push( $buttons, __NAMESPACE__ );
1012 }
1013 }
1014
1015 return $buttons;
1016 }
1017
1018 add_filter( 'mce_buttons', __NAMESPACE__ . '\mce_buttons' );
1019
1020 /**
1021 * Register the tinymcetranslation file
1022 *
1023 * @param array $locales An array containing the current translations to be used by tinymce.
1024 *
1025 * @return array An array containing the translations to be used by tinymce, our localization added to the $locale parameter
1026 *
1027 * @since 4.7
1028 */
1029 function mce_external_languages( $locales ) {
1030 if ( current_user_can( 'edit_theme_options' ) ) { // don't load the code if the user can not customize the shortcode.
1031 $meta = get_user_meta( get_current_user_id(), __NAMESPACE__, true );
1032 if ( is_array( $meta ) && isset( $meta['editor'] ) ) {
1033 ;
1034 } else {
1035 $locales['cat-posts'] = plugin_dir_path( __FILE__ ) . 'tinymce-translations.php';
1036 }
1037 }
1038
1039 return $locales;
1040 }
1041
1042 add_filter( 'mce_external_languages', __NAMESPACE__ . '\mce_external_languages' );
1043
1044 /*
1045 * user profile related functions.
1046 */
1047
1048 add_action( 'show_user_profile', __NAMESPACE__ . '\show_user_profile' );
1049 add_action( 'edit_user_profile', __NAMESPACE__ . '\show_user_profile' );
1050
1051 /**
1052 * Display the user specific setting on its profile page
1053 *
1054 * @param WP_user $user The user for which the profile page displays information.
1055 *
1056 * @since 4.7
1057 */
1058 function show_user_profile( $user ) {
1059
1060 if ( ! current_user_can( 'edit_user', $user->ID ) ) {
1061 return;
1062 }
1063
1064 if ( ! current_user_can( 'edit_theme_options', $user->ID ) ) {
1065 return;
1066 }
1067
1068 $meta = get_the_author_meta( __NAMESPACE__, $user->ID );
1069
1070 if ( empty( $meta ) ) {
1071 $meta = array();
1072 }
1073
1074 $accordion = false;
1075 if ( isset( $meta['panels'] ) ) {
1076 $accordion = true;
1077 }
1078
1079 $editor = false;
1080 if ( isset( $meta['editor'] ) ) {
1081 $editor = true;
1082 }
1083 ?>
1084 <h3 id="<?php echo __NAMESPACE__; ?>"><?php esc_html_e( 'Category Posts Widget behaviour settings', 'category-posts' ); ?></h3>
1085
1086 <table class="form-table">
1087 <tr>
1088 <th><label for="<?php echo __NAMESPACE__; ?>[panels]"><?php esc_html_e( 'Open panels behavior', 'category-posts' ); ?></label></th>
1089 <td>
1090 <input type="checkbox" name="<?php echo __NAMESPACE__; ?>[panels]" id="<?php echo __NAMESPACE__; ?>[panels]" <?php checked( $accordion ); ?>>
1091 <label for=<?php echo __NAMESPACE__; ?>[panels]><?php esc_html_e( 'Close the currently open panel when opening a new one', 'category-posts' ); ?></label>
1092 </td>
1093 </tr>
1094 <tr>
1095 <th><label for="<?php echo __NAMESPACE__; ?>[editor]"><?php esc_html_e( 'Visual editor button', 'category-posts' ); ?></label></th>
1096 <td>
1097 <input type="checkbox" name="<?php echo __NAMESPACE__; ?>[editor]" id="<?php echo __NAMESPACE__; ?>[editor]" <?php checked( $editor ); ?>>
1098 <label for="<?php echo __NAMESPACE__; ?>[editor]"><?php esc_html_e( 'Hide the "insert shortcode" button from the editor', 'category-posts' ); ?></label>
1099 </td>
1100 </tr>
1101 </table>
1102 <?php
1103 }
1104
1105 add_action( 'personal_options_update', __NAMESPACE__ . '\personal_options_update' );
1106 add_action( 'edit_user_profile_update', __NAMESPACE__ . '\personal_options_update' );
1107
1108 /**
1109 * Handles saving user related settings as was set in the profile page.
1110 *
1111 * @param int $user_id the ID of the user for which the data is saved..
1112 *
1113 * @since 4.7
1114 */
1115 function personal_options_update( $user_id ) {
1116
1117 if ( ! current_user_can( 'edit_user', $user_id ) ) {
1118 return false;
1119 }
1120
1121 if ( ! current_user_can( 'edit_theme_options', $user_id ) ) {
1122 return;
1123 }
1124
1125 if ( isset( $_POST[ __NAMESPACE__ ] ) ) {
1126 update_user_meta( $user_id, __NAMESPACE__, wp_unslash( $_POST[ __NAMESPACE__ ] ) );
1127 } else {
1128 delete_user_meta( $user_id, __NAMESPACE__ );
1129 }
1130 }
1131
1132 add_action( 'wp_loaded', __NAMESPACE__ . '\wp_loaded' );
1133
1134 /**
1135 * Run after WordPress finished bootstrapping, do whatever is needed at this stage
1136 * like registering the meta.
1137 */
1138 function wp_loaded() {
1139 register_meta( 'post', SHORTCODE_META, null, '__return_false' ); // do not allow access to the shortcode meta
1140 // use the pre 4.6 format for backward compatibility.
1141 }
1142