PluginProbe
Category Posts Block / 4.9.9
Category Posts Block v4.9.9
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.9, at cat-posts.php

1,153 lines 33.9 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.9
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.9';
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 } else {
338 if ( version_compare( '4.9.8', $settings['ver']) ) {
339 if ( isset( $settings['preset_date_format'] ) && 'sincepublished' === $settings['preset_date_format'] ) {
340 $settings['date_past_time'] = 999;
341 $settings['preset_date_format'] = 'sitedate';
342 }
343 }
344 }
345
346 $settings['ver'] = VERSION;
347
348 // Make sure all "empty" settings have default value.
349 $settings = wp_parse_args( $settings, default_settings() );
350
351 return $settings;
352 }
353
354 /**
355 * Convert pre 4.8 settings into template
356 *
357 * @param array $instance Array which contains the various settings.
358 *
359 * @since 4.8
360 */
361 function convert_settings_to_template( $instance ) {
362
363 $template = '';
364
365 if ( isset( $instance['thumb'] ) && $instance['thumb'] ) {
366 if ( isset( $instance['thumbTop'] ) && $instance['thumbTop'] ) {
367 $template .= "%thumb%\n\n";
368 if ( ! ( isset( $instance['hide_post_titles'] ) && $instance['hide_post_titles'] ) ) {
369 $template .= "%title%\n";
370 }
371 } elseif ( isset( $instance['date'] ) && $instance['date'] ) {
372 if ( ! ( isset( $instance['hide_post_titles'] ) && $instance['hide_post_titles'] ) ) {
373 $template .= "%title%\n\n";
374 }
375 $template .= "%date%\n\n";
376 $template .= "%thumb%\n";
377 } elseif ( ! ( isset( $instance['hide_post_titles'] ) && $instance['hide_post_titles'] ) ) {
378 $template .= "%thumb%\n%title%\n";
379 }
380 } else {
381 if ( ! ( isset( $instance['hide_post_titles'] ) && $instance['hide_post_titles'] ) ) {
382 $template .= "%title%\n";
383 }
384 if ( isset( $instance['date'] ) && $instance['date'] ) {
385 $template .= "%date%\n\n";
386 }
387 }
388 if ( isset( $instance['excerpt'] ) && $instance['excerpt'] ) {
389 $template .= '%excerpt%';
390 }
391 if ( isset( $instance['comment_num'] ) && $instance['comment_num'] ) {
392 $template .= "%commentnum%\n\n";
393 }
394 if ( isset( $instance['author'] ) && $instance['author'] ) {
395 $template .= "%author%\n\n";
396 }
397
398 return $template;
399 }
400
401 /*
402 * Plugin action links section
403 */
404
405 /**
406 * Applied to the list of links to display on the plugins page (beside the activate/deactivate links).
407 *
408 * @return array of the widget links
409 *
410 * @since 4.6.3
411 */
412 add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), __NAMESPACE__ . '\add_action_links' );
413
414 /**
415 * Handle the add links filter, add our links to the links displayed under the plugin_basename
416 * in the plugin admin screen.
417 *
418 * @param array $links The current links about to be displayed.
419 */
420 function add_action_links( $links ) {
421
422 if ( ! class_exists( '\\termcategoryPostsPro\\Widget' ) ) {
423 $pro_link = array(
424 '<a target="_blank" href="' . esc_url( PRO_URL ) . '">' . esc_html__( 'Get the Pro version', 'category-posts' ) . '</a>',
425 );
426
427 $links = array_merge( $pro_link, $links );
428 }
429
430 return $links;
431 }
432
433 /**
434 * Register the widget.
435 */
436 function register_widget() {
437 return \register_widget( __NAMESPACE__ . '\Widget' );
438 }
439
440 add_action( 'widgets_init', __NAMESPACE__ . '\register_widget' );
441
442 /**
443 * Output js code to set the image height if float text around image.
444 *
445 * @param int $number The widget number used to identify the specific list.
446 * @param array $widgetsettings The "instance" parameters of the widget.
447 *
448 * @since 4.9
449 **/
450 function equal_cover_content_height( $number, $widgetsettings ) {
451
452 if ( isset( $widgetsettings['template'] ) && preg_match( '/%thumb%|%excerpt%/', $widgetsettings['template'] ) ) :
453 ?>
454 <script type="text/javascript">
455 if (typeof jQuery !== 'undefined') {
456
457 var cat_posts_namespace = window.cat_posts_namespace || {};
458 cat_posts_namespace.layout_wrap_text = cat_posts_namespace.layout_wrap_text || {};
459 cat_posts_namespace.layout_img_size = cat_posts_namespace.layout_img_size || {};
460
461 cat_posts_namespace.layout_wrap_text = {
462 <?php /* Handle item */ echo "\r\n"; ?>
463 preWrap : function (widget) {
464 // var _widget = jQuery(widget);
465 jQuery(widget).find('.cat-post-item').each(function(){
466 var _that = jQuery(this);
467 _that.find('p.cpwp-excerpt-text').addClass('cpwp-wrap-text');
468 _that.find('p.cpwp-excerpt-text').closest('div').wrap('<div class="cpwp-wrap-text-stage"></div>');;
469 });
470 return;
471 },
472 <?php /* Handle add class */ echo "\r\n"; ?>
473 add : function(_this){
474 var _that = jQuery(_this);
475 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"; ?>
476 _that.find('p.cpwp-excerpt-text').closest('.cpwp-wrap-text-stage').removeClass( "cpwp-wrap-text" );
477 _that.find('p.cpwp-excerpt-text').addClass( "cpwp-wrap-text" ); <?php /* don't do the CSS hack, just set the class */ echo "\r\n"; ?>
478 }else{ <?php /* add the CSS hack, it's needed, text is wrapping, line's height is higher as thumb */ echo "\r\n"; ?>
479 _that.find('p.cpwp-excerpt-text').removeClass( "cpwp-wrap-text" );
480 _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"; ?>
481 }
482 return;
483 },
484 <?php /* Wait for image is loaded */ echo "\r\n"; ?>
485 handleLazyLoading : function(_this) {
486 var width = jQuery(_this).find('img').width();
487 <?php /* image is loaded */ echo "\r\n"; ?>
488 if( 0 !== width ){
489 cat_posts_namespace.layout_wrap_text.add(_this);
490 } else {
491 jQuery(_this).find('img').one("load", function(){
492 cat_posts_namespace.layout_wrap_text.add(_this);
493 });
494 }
495 return;
496 },
497 <?php /* Handle post items */ echo "\r\n"; ?>
498 setClass : function (widget) {
499 // var _widget = jQuery(widget);
500 jQuery(widget).find('.cat-post-item').each(function(){
501 cat_posts_namespace.layout_wrap_text.handleLazyLoading(this);
502 });
503 return;
504 }
505 }
506 cat_posts_namespace.layout_img_size = {
507 <?php /* Handle replace */ echo "\r\n"; ?>
508 replace : function(_this){
509 var _that = jQuery(_this),
510 resp_w = _that.width(),
511 resp_h = _that.height(),
512 orig_w = _that.data('cat-posts-width'),
513 orig_h = _that.data('cat-posts-height');
514 <?php /* replace height */ echo "\r\n"; ?>
515 if( resp_w < orig_w ){
516 _that.height( resp_w * orig_h / orig_w );
517 } else {
518 _that.height( '' );
519 }
520 return;
521 },
522 <?php /* Wait for image is loaded */ echo "\r\n"; ?>
523 handleLazyLoading : function(_this) {
524 var width = jQuery(_this).width();
525 <?php /* image is loaded */ echo "\r\n"; ?>
526 if( 0 !== width ){
527 cat_posts_namespace.layout_img_size.replace(_this);
528 } else {
529 jQuery(_this).one("load", function(){
530 cat_posts_namespace.layout_img_size.replace(_this);
531 });
532 }
533 return;
534 },
535 setHeight : function (widget) {
536 jQuery(widget).find('.cat-post-item img').each(function(){
537 cat_posts_namespace.layout_img_size.handleLazyLoading(this);
538 });
539 return;
540 }
541 }
542
543 jQuery( document ).ready(function () {
544 var widget = jQuery('#<?php echo esc_attr( $number ); ?>');
545
546 <?php /* do once on document ready */ echo "\r\n"; ?>
547 cat_posts_namespace.layout_wrap_text.preWrap(widget);
548
549 <?php /* do on page load or on resize the browser window */ echo "\r\n"; ?>
550 jQuery(window).on('load resize', function() {
551 cat_posts_namespace.layout_wrap_text.setClass(widget);
552 <?php /* No ratio calculation if one or more dimensions is set to 0 */ echo "\r\n"; ?>
553 <?php if ( isset( $widgetsettings['thumb_w'] ) && 0 !== intval( $widgetsettings['thumb_w'] ) &&
554 isset( $widgetsettings['thumb_h'] ) && 0 !== intval( $widgetsettings['thumb_h'] ) ) : echo "\r\n"; ?>
555 cat_posts_namespace.layout_img_size.setHeight(widget);
556 <?php endif; echo "\r\n"; ?>
557 });
558 });
559 }
560 </script>
561 <?php
562 endif;
563 }
564
565 /*
566 * shortcode section.
567 */
568
569 /**
570 * Get shortcode settings taking into account if it is being customized
571 *
572 * When not customized returns the settings as stored in the meta, but when
573 * it is customized returns the setting stored in the virtual option used by the customizer
574 *
575 * @param string $pid The ID of the post in which the shortcode is.
576 * @param string $name The name of the shortcode to retun, empty string indicates the nameless.
577 *
578 * @return array The shortcode settings if a short code name exists or is an empty string,
579 * empty array if name not found.
580 *
581 * @since 4.6
582 */
583 function shortcode_settings( $pid, $name ) {
584 $meta = get_post_meta( $pid, SHORTCODE_META, true );
585
586 if ( ! empty( $meta ) && ! is_array( reset( $meta ) ) ) {
587 $meta = array( '' => $meta ); // the conversion.
588 }
589
590 if ( ! isset( $meta[ $name ] ) ) { // name do not exists? return empty array.
591 return array();
592 }
593
594 $instance = $meta[ $name ];
595 if ( is_customize_preview() ) {
596 $o = get_option( '_virtual-' . WIDGET_BASE_ID );
597 if ( is_array( $o ) ) {
598 $instance = $o[ $pid ][ $name ];
599 $instance['ver'] = VERSION;
600 }
601 }
602
603 if ( isset( $instance['template'] ) && $instance['template'] ) {
604 ;
605 } else {
606 $instance['template'] = convert_settings_to_template( $instance );
607 }
608
609 $instance = upgrade_settings( $instance );
610
611 return $instance;
612 }
613
614 /**
615 * Handle the shortcode
616 *
617 * @param array $attr Array of the attributes to the short code, none is expected.
618 * @param string $content The content enclosed in the shortcode, none is expected.
619 *
620 * @return string An HTML of the "widget" based on its settings, actual or customized
621 */
622 function shortcode( $attr, $content = null ) {
623 $repository = new Virtual_Widgets_Repository();
624
625 $shortcodes = $repository->getShortcodes();
626
627 $name = '';
628 if ( isset( $attr['name'] ) ) {
629 $name = $attr['name'];
630 }
631
632 if ( is_singular() ) {
633 if ( isset( $shortcodes[ $name ] ) ) {
634 return $shortcodes[ $name ]->getHTML();
635 }
636 }
637
638 return '';
639 }
640
641 add_shortcode( SHORTCODE_NAME, __NAMESPACE__ . '\shortcode' );
642
643 /**
644 * Find if a specific shortcode is used in a content
645 *
646 * @param string $shortcode_name The name of the shortcode.
647 * @param string $content The content to look at.
648 *
649 * @return array An array containing the name attributes of the shortcodes. Empty array is
650 * an indication there were no shourcodes
651 *
652 * @since 4.7
653 */
654 function shortcode_names( $shortcode_name, $content ) {
655
656 $names = array();
657
658 $regex_pattern = get_shortcode_regex();
659 if ( preg_match_all( '/' . $regex_pattern . '/s', $content, $matches ) ) {
660 foreach ( $matches[2] as $k => $shortcode ) {
661 if ( SHORTCODE_NAME === $shortcode ) {
662 $name = '';
663 $atts = shortcode_parse_atts( $matches[3][ $k ] );
664 if ( ! empty( $atts['name'] ) ) {
665 $name = $atts['name'];
666 }
667 $names[] = $name;
668 }
669 }
670 }
671
672 return $names;
673 }
674
675 /**
676 * Organized way to have the default widget settings accessible
677 *
678 * @since 4.6
679 */
680 function default_settings() {
681 return array(
682 'title' => __( 'Recent Posts', 'category-posts' ),
683 'title_link' => false,
684 'title_link_url' => '',
685 'hide_title' => false,
686 'cat' => 0,
687 'num' => get_option( 'posts_per_page' ),
688 'offset' => 1,
689 'sort_by' => 'date',
690 'status' => 'publish',
691 'asc_sort_order' => false,
692 'exclude_current_post' => false,
693 'hideNoThumb' => false,
694 'footer_link_text' => '',
695 'footer_link' => '',
696 'item_title_level' => 'Inline',
697 'item_title_lines' => 2,
698 'thumb_w' => get_option( 'thumbnail_size_w', 150 ),
699 'thumb_fluid_width' => 100,
700 'thumb_h' => get_option( 'thumbnail_size_h', 150 ),
701 'thumb_hover' => 'none',
702 'hide_post_titles' => false,
703 'excerpt_lines' => 4,
704 'excerpt_length' => 0,
705 'excerpt_more_text' => __( '', 'category-posts' ),
706 'excerpt_filters' => false,
707 'comment_num' => false,
708 'disable_css' => false,
709 'disable_font_styles' => false,
710 'disable_theme_styles' => false,
711 'show_post_format' => 'none',
712 'no_cat_childs' => false,
713 'everything_is_link' => false,
714 'preset_date_format' => 'sitedate',
715 'date_format' => '',
716 'date_past_time' => '0',
717 'template' => "%title%\n\n%thumb%",
718 'text_do_not_wrap_thumb' => false,
719 'enable_loadmore' => false,
720 'loadmore_text' => sprintf( __( 'Load More (%s/%s)', 'category-posts' ), '%step%', '%all%'),
721 'loading_text' => __( 'Loading...', 'category-posts' ),
722 'date_range' => 'off',
723 'start_date' => '',
724 'end_date' => '',
725 'days_ago' => 30,
726 'no_match_handling' => 'nothing',
727 'no_match_text' => '',
728 'default_thunmbnail' => 0,
729 'ver' => VERSION,
730 );
731 }
732
733 /**
734 * Manipulate the relevant meta related to the short code when a post is save
735 *
736 * If A post has a short code, a meta holder is created, If it does not the meta holder is deleted
737 *
738 * @param integer $pid The post ID of the post being saved.
739 * @param WP_Post $post The post being saved.
740 * @return void
741 *
742 * @since 4.6
743 */
744 function save_post( $pid, $post ) {
745
746 // ignore revisions and auto saves.
747 if ( wp_is_post_revision( $pid ) || wp_is_post_autosave( $pid ) ) {
748 return;
749 }
750
751 $meta = get_post_meta( $pid, SHORTCODE_META, true );
752 if ( empty( $meta ) ) {
753 $meta = array();
754 }
755
756 // check if only one shortcode format - non array of arrays, and convert it.
757 if ( ! empty( $meta ) && ! is_array( reset( $meta ) ) ) {
758 $meta = array( '' => $meta ); // the conversion.
759 }
760
761 $old_names = array_keys( $meta ); // keep list of current shortcode names to delete lter whatever was deleted.
762 $names = shortcode_names( SHORTCODE_NAME, $post->post_content );
763
764 // remove setting for unused names.
765 $to_delete = array_diff( $old_names, $names );
766 foreach ( $to_delete as $k ) {
767 unset( $meta[ $k ] );
768 }
769
770 foreach ( $names as $name ) {
771 if ( ! isset( $meta[ $name ] ) ) {
772 $meta[ $name ] = default_settings();
773 }
774 }
775
776 delete_post_meta( $pid, SHORTCODE_META );
777 if ( ! empty( $meta ) ) {
778 add_post_meta( $pid, SHORTCODE_META, $meta, true );
779 }
780 }
781
782 add_action( 'save_post', __NAMESPACE__ . '\save_post', 10, 2 );
783
784 /**
785 * Called on Customizer init to do related registrations.
786 *
787 * @param mixed $wp_customize The customizer object.
788 */
789 function customize_register( $wp_customize ) {
790
791 require_once __DIR__ . '/class-shortcode-control.php';
792
793 $args = array(
794 'post_type' => 'any',
795 'post_status' => 'any',
796 'posts_per_page' => -1,
797 'update_post_term_cache' => false,
798 'meta_query' => array(
799 array(
800 'key' => SHORTCODE_META,
801 'compare' => 'EXISTS',
802 ),
803 ),
804
805 );
806 $posts = get_posts( $args );
807
808 if ( count( $posts ) > 0 ) {
809 $wp_customize->add_panel(
810 __NAMESPACE__,
811 array(
812 'title' => __( 'Category Posts Shortcode', 'category-posts' ),
813 'priority' => 300,
814 'capability' => 'edit_theme_options',
815 )
816 );
817
818 foreach ( $posts as $p ) {
819 $widget = new Widget();
820 $meta = get_post_meta( $p->ID, SHORTCODE_META, true );
821 if ( ! is_array( $meta ) ) {
822 continue;
823 }
824
825 if ( ! is_array( reset( $meta ) ) ) { // 4.6 format.
826 $meta = array( '' => $meta );
827 }
828
829 foreach ( $meta as $k => $m ) {
830
831 if ( isset( $m['template'] ) && $m['template'] ) {
832 ;
833 } else {
834 $m['template'] = convert_settings_to_template( $m );
835 }
836
837 if ( 0 === count( $meta ) ) { // new widget, use defaults.
838 ;
839 } else { // updated widgets come from =< 4.6 excerpt filter is on.
840 if ( ! isset( $m['excerpt_filters'] ) ) {
841 $m['excerpt_filters'] = 'on';
842 }
843 }
844
845 $m = upgrade_settings( $m );
846
847 $section_title = $k;
848 if ( '' === $section_title ) {
849 $section_title = __( '[shortcode]', 'category-posts' );
850 }
851
852 $wp_customize->add_section(
853 __NAMESPACE__ . '-' . $p->id . '-' . $k,
854 array(
855 'title' => $section_title,
856 'priority' => 10,
857 'capability' => 'edit_theme_options',
858 'panel' => __NAMESPACE__,
859 )
860 );
861
862 ob_start();
863
864 // 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.
865 if ( '' !== $k ) {
866 $widget->number = $p->ID . '_' . md5( $k );
867 } else {
868 $widget->number = $p->ID . '_';
869 }
870
871 $widget->form( $m );
872 $form = ob_get_clean();
873 $form = preg_replace_callback(
874 '/<(input|select|textarea)\s+.*name=("|\').*\[\w*\]\[([^\]]*)\][^>]*>/',
875 function ( $matches ) use ( $p, $wp_customize, $m, $k ) {
876 $setting = '_virtual-' . WIDGET_BASE_ID . '[' . $p->ID . '][' . $k . '][' . $matches[3] . ']';
877 if ( ! isset( $m[ $matches[3] ] ) ) {
878 $m[ $matches[3] ] = null;
879 }
880 $wp_customize->add_setting(
881 $setting,
882 array(
883 'default' => $m[ $matches[3] ], // set default to current value.
884 'type' => 'option',
885 )
886 );
887
888 return str_replace( '<' . $matches[1], '<' . $matches[1] . ' data-customize-setting-link="' . $setting . '"', $matches[0] );
889 },
890 $form
891 );
892
893 $args = array(
894 'label' => __( 'Layout', 'twentyfourteen' ),
895 'section' => __NAMESPACE__ . '-' . $p->id . '-' . $k,
896 'form' => $form,
897 'settings' => '_virtual-' . WIDGET_BASE_ID . '[' . $p->ID . '][' . $k . '][title]',
898 'active_callback' => function () use ( $p ) {
899 return is_singular() && ( get_the_ID() === $p->ID );
900 },
901 );
902
903 if ( get_option( 'page_on_front' ) === $p->ID ) {
904 $args['active_callback'] = function () {
905 return is_front_page();
906 };
907 }
908
909 $sc = new ShortCode_Control(
910 $wp_customize,
911 '_virtual-' . WIDGET_BASE_ID . '[' . $p->ID . '][' . $k . '][title]',
912 $args
913 );
914
915 if ( '' !== $k ) {
916 $sc->title_postfix = ' ' . $k;
917 }
918 $wp_customize->add_control( $sc );
919 }
920 }
921 }
922 }
923
924 add_action( 'customize_register', __NAMESPACE__ . '\customize_register' );
925
926 /**
927 * Save the virtual option used by the customizer into the proper meta values.
928 *
929 * The customizer actually saves only the changed values, so a merge needs to be done.
930 * After everything is updated the virtual option is deleted to leave a clean slate
931 *
932 * @return void
933 *
934 * @since 4.6
935 */
936 function customize_save_after() {
937 $virtual = get_option( '_virtual-' . WIDGET_BASE_ID );
938
939 if ( is_array( $virtual ) ) {
940 foreach ( $virtual as $pid => $instance ) {
941 $meta = get_post_meta( $pid, SHORTCODE_META, true );
942 if ( ! empty( $meta ) && ! is_array( reset( $meta ) ) ) {
943 $meta = array( '' => $meta ); // the conversion.
944 }
945
946 foreach ( $instance as $name => $new ) {
947 if ( isset( $meta[ $name ] ) ) { // unlikely but maybe that short code was deleted by other session.
948 $meta[ $name ] = array_merge( $meta[ $name ], $new );
949
950 $meta[ $name ] = upgrade_settings( $meta[ $name ] );
951 }
952 }
953 }
954 update_post_meta( $pid, SHORTCODE_META, $meta );
955 }
956
957 delete_option( '_virtual-' . WIDGET_BASE_ID );
958 }
959
960 add_action( 'customize_save_after', __NAMESPACE__ . '\customize_save_after', 100 );
961
962 /*
963 * tinymce related functions.
964 */
965
966 /**
967 * Uninstall handler, cleanup DB from options and meta
968 *
969 * @return void
970 *
971 * @since 4.7
972 */
973 function uninstall() {
974 delete_option( 'widget-' . WIDGET_BASE_ID ); // delete the option storing the widget options.
975 delete_post_meta_by_key( SHORTCODE_META ); // delete the meta storing the shortcode.
976 delete_metadata( 'user', 0, __NAMESPACE__, '', true ); // delete all user metadata.
977 }
978
979 register_uninstall_hook( __FILE__, __NAMESPACE__ . 'uninstall' );
980
981 /**
982 * Register the tinymce shortcode plugin
983 *
984 * @param array $plugin_array An array containing the current plugins to be used by tinymce.
985 *
986 * @return array An array containing the plugins to be used by tinymce, our plugin added to the $plugin_array parameter
987 *
988 * @since 4.7
989 */
990 function mce_external_plugins( $plugin_array ) {
991 if ( current_user_can( 'edit_theme_options' ) ) { // don't load the code if the user can not customize the shortcode.
992 // enqueue TinyMCE plugin script with its ID.
993 $meta = get_user_meta( get_current_user_id(), __NAMESPACE__, true );
994 if ( is_array( $meta ) && isset( $meta['editor'] ) ) {
995 ;
996 } else {
997 $plugin_array[ __NAMESPACE__ ] = plugins_url( 'js/admin/tinymce.min.js?ver=' . VERSION, __FILE__ );
998 }
999 }
1000
1001 return $plugin_array;
1002 }
1003
1004 add_filter( 'mce_external_plugins', __NAMESPACE__ . '\mce_external_plugins' );
1005
1006 /**
1007 * Register the tinymce buttons for the add shortcode
1008 *
1009 * @param array $buttons An array containing the current buttons to be used by tinymce.
1010 *
1011 * @return array An array containing the buttons to be used by tinymce, our button added to the $buttons parameter
1012 *
1013 * @since 4.7
1014 */
1015 function mce_buttons( $buttons ) {
1016 if ( current_user_can( 'edit_theme_options' ) ) { // don't load the code if the user can not customize the shortcode
1017 // register buttons with their id.
1018 $meta = get_user_meta( get_current_user_id(), __NAMESPACE__, true );
1019 if ( is_array( $meta ) && isset( $meta['editor'] ) ) {
1020 ;
1021 } else {
1022 array_push( $buttons, __NAMESPACE__ );
1023 }
1024 }
1025
1026 return $buttons;
1027 }
1028
1029 add_filter( 'mce_buttons', __NAMESPACE__ . '\mce_buttons' );
1030
1031 /**
1032 * Register the tinymcetranslation file
1033 *
1034 * @param array $locales An array containing the current translations to be used by tinymce.
1035 *
1036 * @return array An array containing the translations to be used by tinymce, our localization added to the $locale parameter
1037 *
1038 * @since 4.7
1039 */
1040 function mce_external_languages( $locales ) {
1041 if ( current_user_can( 'edit_theme_options' ) ) { // don't load the code if the user can not customize the shortcode.
1042 $meta = get_user_meta( get_current_user_id(), __NAMESPACE__, true );
1043 if ( is_array( $meta ) && isset( $meta['editor'] ) ) {
1044 ;
1045 } else {
1046 $locales['cat-posts'] = plugin_dir_path( __FILE__ ) . 'tinymce-translations.php';
1047 }
1048 }
1049
1050 return $locales;
1051 }
1052
1053 add_filter( 'mce_external_languages', __NAMESPACE__ . '\mce_external_languages' );
1054
1055 /*
1056 * user profile related functions.
1057 */
1058
1059 add_action( 'show_user_profile', __NAMESPACE__ . '\show_user_profile' );
1060 add_action( 'edit_user_profile', __NAMESPACE__ . '\show_user_profile' );
1061
1062 /**
1063 * Display the user specific setting on its profile page
1064 *
1065 * @param WP_user $user The user for which the profile page displays information.
1066 *
1067 * @since 4.7
1068 */
1069 function show_user_profile( $user ) {
1070
1071 if ( ! current_user_can( 'edit_user', $user->ID ) ) {
1072 return;
1073 }
1074
1075 if ( ! current_user_can( 'edit_theme_options', $user->ID ) ) {
1076 return;
1077 }
1078
1079 $meta = get_the_author_meta( __NAMESPACE__, $user->ID );
1080
1081 if ( empty( $meta ) ) {
1082 $meta = array();
1083 }
1084
1085 $accordion = false;
1086 if ( isset( $meta['panels'] ) ) {
1087 $accordion = true;
1088 }
1089
1090 $editor = false;
1091 if ( isset( $meta['editor'] ) ) {
1092 $editor = true;
1093 }
1094 ?>
1095 <h3 id="<?php echo __NAMESPACE__; ?>"><?php esc_html_e( 'Category Posts Widget behaviour settings', 'category-posts' ); ?></h3>
1096
1097 <table class="form-table">
1098 <tr>
1099 <th><label for="<?php echo __NAMESPACE__; ?>[panels]"><?php esc_html_e( 'Open panels behavior', 'category-posts' ); ?></label></th>
1100 <td>
1101 <input type="checkbox" name="<?php echo __NAMESPACE__; ?>[panels]" id="<?php echo __NAMESPACE__; ?>[panels]" <?php checked( $accordion ); ?>>
1102 <label for=<?php echo __NAMESPACE__; ?>[panels]><?php esc_html_e( 'Close the currently open panel when opening a new one', 'category-posts' ); ?></label>
1103 </td>
1104 </tr>
1105 <tr>
1106 <th><label for="<?php echo __NAMESPACE__; ?>[editor]"><?php esc_html_e( 'Visual editor button', 'category-posts' ); ?></label></th>
1107 <td>
1108 <input type="checkbox" name="<?php echo __NAMESPACE__; ?>[editor]" id="<?php echo __NAMESPACE__; ?>[editor]" <?php checked( $editor ); ?>>
1109 <label for="<?php echo __NAMESPACE__; ?>[editor]"><?php esc_html_e( 'Hide the "insert shortcode" button from the editor', 'category-posts' ); ?></label>
1110 </td>
1111 </tr>
1112 </table>
1113 <?php
1114 }
1115
1116 add_action( 'personal_options_update', __NAMESPACE__ . '\personal_options_update' );
1117 add_action( 'edit_user_profile_update', __NAMESPACE__ . '\personal_options_update' );
1118
1119 /**
1120 * Handles saving user related settings as was set in the profile page.
1121 *
1122 * @param int $user_id the ID of the user for which the data is saved..
1123 *
1124 * @since 4.7
1125 */
1126 function personal_options_update( $user_id ) {
1127
1128 if ( ! current_user_can( 'edit_user', $user_id ) ) {
1129 return false;
1130 }
1131
1132 if ( ! current_user_can( 'edit_theme_options', $user_id ) ) {
1133 return;
1134 }
1135
1136 if ( isset( $_POST[ __NAMESPACE__ ] ) ) {
1137 update_user_meta( $user_id, __NAMESPACE__, wp_unslash( $_POST[ __NAMESPACE__ ] ) );
1138 } else {
1139 delete_user_meta( $user_id, __NAMESPACE__ );
1140 }
1141 }
1142
1143 add_action( 'wp_loaded', __NAMESPACE__ . '\wp_loaded' );
1144
1145 /**
1146 * Run after WordPress finished bootstrapping, do whatever is needed at this stage
1147 * like registering the meta.
1148 */
1149 function wp_loaded() {
1150 register_meta( 'post', SHORTCODE_META, null, '__return_false' ); // do not allow access to the shortcode meta
1151 // use the pre 4.6 format for backward compatibility.
1152 }
1153