PluginProbe
Category Posts Block / 5.1.0
Category Posts Block v5.1.0
5.1.0 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 All 63 releases
category-posts / cat-posts.php

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

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