| 1 |
<?php |
| 2 |
/** |
| 3 |
* Gutenberg Block implementation. |
| 4 |
* |
| 5 |
* @package categoryposts. |
| 6 |
* |
| 7 |
* @since 4.9 |
| 8 |
*/ |
| 9 |
|
| 10 |
namespace categoryPosts; |
| 11 |
|
| 12 |
/** |
| 13 |
* Build the widget instance data from block attributes. |
| 14 |
* |
| 15 |
* @param array $attributes The block attributes. |
| 16 |
* |
| 17 |
* @return array The widget instance data. |
| 18 |
*/ |
| 19 |
function build_block_instance( $attributes ) { |
| 20 |
$instance = array(); |
| 21 |
|
| 22 |
if ( ! is_array( $attributes ) ) { |
| 23 |
return $instance; |
| 24 |
} |
| 25 |
|
| 26 |
$instance['title'] = isset( $attributes['title'] ) ? $attributes['title'] : ''; |
| 27 |
$instance['title_link'] = isset( $attributes['titleLink'] ) ? $attributes['titleLink'] : false; |
| 28 |
$instance['title_link_target'] = isset( $attributes['titleLinkTarget'] ) ? $attributes['titleLinkTarget'] : false; |
| 29 |
$instance['title_level'] = isset( $attributes['titleLevel'] ) ? $attributes['titleLevel'] : 'Initial'; |
| 30 |
$instance['title_link_url'] = isset( $attributes['titleLinkUrl'] ) ? $attributes['titleLinkUrl'] : ''; |
| 31 |
$instance['hide_title'] = isset( $attributes['hideTitle'] ) ? $attributes['hideTitle'] : false; |
| 32 |
$instance['category_suggestions'] = isset( $attributes['categorySuggestions'] ) ? $attributes['categorySuggestions'] : array(); |
| 33 |
$instance['select_categories'] = isset( $attributes['selectCategories'] ) ? $attributes['selectCategories'] : array(); |
| 34 |
$instance['cat'] = isset( $attributes['categories'] ) ? $attributes['categories'] : ''; |
| 35 |
$instance['num'] = isset( $attributes['num'] ) ? $attributes['num'] : 10; |
| 36 |
$instance['offset'] = isset( $attributes['offset'] ) ? $attributes['offset'] : 1; |
| 37 |
$instance['sort_by'] = isset( $attributes['orderBy'] ) ? $attributes['orderBy'] : 'date'; |
| 38 |
$instance['status'] = isset( $attributes['status'] ) ? $attributes['status'] : 'publish'; |
| 39 |
$instance['asc_sort_order'] = isset( $attributes['order'] ) ? $attributes['order'] : true; |
| 40 |
$instance['exclude_current_post'] = isset( $attributes['excludeCurrentPost'] ) ? $attributes['excludeCurrentPost'] : false; |
| 41 |
$instance['hide_no_thumb'] = isset( $attributes['hideNoThumb'] ) ? $attributes['hideNoThumb'] : false; |
| 42 |
$instance['sticky'] = isset( $attributes['sticky'] ) ? $attributes['sticky'] : false; |
| 43 |
$instance['footer_link_text'] = isset( $attributes['footerLinkText'] ) ? $attributes['footerLinkText'] : ''; |
| 44 |
$instance['footer_link'] = isset( $attributes['footerLink'] ) ? $attributes['footerLink'] : ''; |
| 45 |
$instance['footer_link_target'] = isset( $attributes['footerLinkTarget'] ) ? $attributes['footerLinkTarget'] : false; |
| 46 |
$instance['item_title_level'] = isset( $attributes['itemTitleLevel'] ) ? $attributes['itemTitleLevel'] : 'Inline'; |
| 47 |
$instance['item_title_lines'] = isset( $attributes['itemTitleLines'] ) ? $attributes['itemTitleLines'] : 2; |
| 48 |
$instance['thumb_w'] = isset( $attributes['thumbW'] ) ? $attributes['thumbW'] : 150; |
| 49 |
$instance['thumb_fluid_width'] = isset( $attributes['thumbFluidWidth'] ) ? $attributes['thumbFluidWidth'] : 100; |
| 50 |
$instance['thumb_h'] = isset( $attributes['thumbH'] ) ? $attributes['thumbH'] : 150; |
| 51 |
$instance['thumb_hover'] = isset( $attributes['thumbHover'] ) ? $attributes['thumbHover'] : 'none'; |
| 52 |
$instance['thumb_symbols'] = isset( $attributes['thumbSymbols'] ) ? $attributes['thumbSymbols'] : false; |
| 53 |
$instance['hide_post_titles'] = isset( $attributes['hidePostTitles'] ) ? $attributes['hidePostTitles'] : false; |
| 54 |
$instance['excerpt_radio'] = isset( $attributes['excerptRadio'] ) ? $attributes['excerptRadio'] : 'excerpt'; |
| 55 |
$instance['excerpt_lines'] = isset( $attributes['excerptLines'] ) ? $attributes['excerptLines'] : 4; |
| 56 |
$instance['excerpt_length'] = isset( $attributes['excerptLength'] ) ? $attributes['excerptLength'] : 0; |
| 57 |
$instance['excerpt_more_text'] = isset( $attributes['excerptMoreText'] ) ? $attributes['excerptMoreText'] : ''; |
| 58 |
$instance['excerpt_filters'] = isset( $attributes['excerptFilters'] ) ? $attributes['excerptFilters'] : false; |
| 59 |
$instance['comment_num'] = isset( $attributes['commentNum'] ) ? $attributes['commentNum'] : false; |
| 60 |
$instance['disable_css'] = isset( $attributes['disableCss'] ) ? $attributes['disableCss'] : false; |
| 61 |
$instance['disable_font_styles'] = isset( $attributes['disableFontStyles'] ) ? $attributes['disableFontStyles'] : false; |
| 62 |
$instance['disable_theme_styles'] = isset( $attributes['disableThemeStyles'] ) ? $attributes['disableThemeStyles'] : false; |
| 63 |
$instance['show_post_format'] = isset( $attributes['showPostFormat'] ) ? $attributes['showPostFormat'] : 'none'; |
| 64 |
$instance['no_cat_childs'] = isset( $attributes['noCatChilds'] ) ? $attributes['noCatChilds'] : false; |
| 65 |
$instance['everything_is_link'] = isset( $attributes['everythingIsLink'] ) ? $attributes['everythingIsLink'] : false; |
| 66 |
$instance['preset_date_format'] = isset( $attributes['presetDateFormat'] ) ? $attributes['presetDateFormat'] : 'sitedate'; |
| 67 |
$instance['date_format'] = isset( $attributes['dateFormat'] ) ? $attributes['dateFormat'] : ''; |
| 68 |
$instance['date_past_time'] = isset( $attributes['datePastTime'] ) ? $attributes['datePastTime'] : '0'; |
| 69 |
$instance['template'] = isset( $attributes['template'] ) ? $attributes['template'] : "%title%\n\n%thumb%\n%excerpt%"; |
| 70 |
$instance['text_do_not_wrap_thumb'] = isset( $attributes['textDoNotWrapThumb'] ) ? $attributes['textDoNotWrapThumb'] : false; |
| 71 |
$instance['enable_loadmore'] = isset( $attributes['enableLoadmore'] ) ? $attributes['enableLoadmore'] : false; |
| 72 |
$instance['loadmore_scrollTo'] = isset( $attributes['loadmoreScrollTo'] ) ? $attributes['loadmoreScrollTo'] : false; |
| 73 |
$instance['loadmore_text'] = isset( $attributes['loadmoreText'] ) ? $attributes['loadmoreText'] : 'Load More (%step%/%all%)'; |
| 74 |
$instance['loading_text'] = isset( $attributes['loadingText'] ) ? $attributes['loadingText'] : 'Loading...'; |
| 75 |
$instance['date_range'] = isset( $attributes['dateRange'] ) ? $attributes['dateRange'] : 'off'; |
| 76 |
$instance['start_date'] = isset( $attributes['startDate'] ) ? $attributes['startDate'] : ''; |
| 77 |
$instance['end_date'] = isset( $attributes['endDate'] ) ? $attributes['endDate'] : ''; |
| 78 |
$instance['days_ago'] = isset( $attributes['daysAgo'] ) ? $attributes['daysAgo'] : 30; |
| 79 |
$instance['no_match_handling'] = isset( $attributes['noMatchHandling'] ) ? $attributes['noMatchHandling'] : 'nothing'; |
| 80 |
$instance['no_match_text'] = isset( $attributes['noMatchText'] ) ? $attributes['noMatchText'] : ''; |
| 81 |
$instance['default_thunmbnail'] = isset( $attributes['defaultThunmbnail'] ) ? $attributes['defaultThunmbnail'] : 0; |
| 82 |
$instance['ver'] = isset( $attributes['ver'] ) ? $attributes['ver'] : '5.1.0'; |
| 83 |
$instance['context'] = CONTEXT_BLOCK; |
| 84 |
|
| 85 |
return $instance; |
| 86 |
} |
| 87 |
|
| 88 |
/** |
| 89 |
* Get a stable ID for a block instance's load-more requests. |
| 90 |
* |
| 91 |
* Uses the `instanceId` block attribute (frozen in by the editor the first |
| 92 |
* time the block is inserted, see src/edit.js) instead of wp_unique_id(). |
| 93 |
* wp_unique_id() only counts up within a single PHP request, so it produces |
| 94 |
* colliding or diverging IDs when a block is rendered more than once per |
| 95 |
* request or when several instances are each rendered via their own request |
| 96 |
* (e.g. ServerSideRender previews in the block editor, excerpt generation, |
| 97 |
* REST API output, full-page caching). Falling back to wp_unique_id() keeps |
| 98 |
* older content saved before this attribute existed working. |
| 99 |
* |
| 100 |
* @param array $attributes The block attributes. |
| 101 |
* |
| 102 |
* @return string The block load-more ID. |
| 103 |
*/ |
| 104 |
function get_block_loadmore_id( $attributes ) { |
| 105 |
if ( is_array( $attributes ) && isset( $attributes['instanceId'] ) && '' !== $attributes['instanceId'] ) { |
| 106 |
return 'block-' . $attributes['instanceId']; |
| 107 |
} |
| 108 |
|
| 109 |
return wp_unique_id( 'block-' ); |
| 110 |
} |
| 111 |
|
| 112 |
/** |
| 113 |
* Store block settings for load-more requests. |
| 114 |
* |
| 115 |
* @param string $id The block load-more ID. |
| 116 |
* @param array $attributes The block attributes. |
| 117 |
* |
| 118 |
* @return void |
| 119 |
*/ |
| 120 |
function store_block_loadmore_settings( $id, $attributes ) { |
| 121 |
if ( empty( $id ) || ! is_array( $attributes ) ) { |
| 122 |
return; |
| 123 |
} |
| 124 |
set_transient( 'cat_posts_block_' . $id, $attributes, HOUR_IN_SECONDS ); |
| 125 |
} |
| 126 |
|
| 127 |
/** |
| 128 |
* Retrieve stored block settings for load-more requests. |
| 129 |
* |
| 130 |
* @param string $id The block load-more ID. |
| 131 |
* |
| 132 |
* @return array|false Stored attributes or false. |
| 133 |
*/ |
| 134 |
function get_block_loadmore_settings( $id ) { |
| 135 |
if ( empty( $id ) ) { |
| 136 |
return false; |
| 137 |
} |
| 138 |
$settings = get_transient( 'cat_posts_block_' . $id ); |
| 139 |
return is_array( $settings ) ? $settings : false; |
| 140 |
} |
| 141 |
|
| 142 |
/** |
| 143 |
* Collect everything the editor preview has to apply on its own. |
| 144 |
* |
| 145 |
* On the front end equal_cover_content_height() prints a script on wp_footer/ |
| 146 |
* admin_footer that refines the server-side "wrap the excerpt around the thumbnail" |
| 147 |
* guess once the actual rendered heights are known. Neither hook runs for the REST |
| 148 |
* request the ServerSideRender preview is built from, so the same flags are handed |
| 149 |
* to src/edit.js as data instead, which applies the refinement itself. |
| 150 |
* |
| 151 |
* The conditions have to stay in sync with equal_cover_content_height(). |
| 152 |
* |
| 153 |
* @param array $instance The widget instance data. |
| 154 |
* |
| 155 |
* @return array The wrap-text/image-ratio handling flags for the preview. |
| 156 |
*/ |
| 157 |
function block_preview_config( $instance ) { |
| 158 |
$wrap_text = ! ( isset( $instance['text_do_not_wrap_thumb'] ) && $instance['text_do_not_wrap_thumb'] ); |
| 159 |
|
| 160 |
// No ratio calculation if one or more dimensions is set to 0. |
| 161 |
$img_size = isset( $instance['thumb_w'] ) && 0 !== intval( $instance['thumb_w'] ) && |
| 162 |
isset( $instance['thumb_h'] ) && 0 !== intval( $instance['thumb_h'] ); |
| 163 |
|
| 164 |
return array( |
| 165 |
'wrapText' => $wrap_text, |
| 166 |
'imgSize' => $img_size, |
| 167 |
); |
| 168 |
} |
| 169 |
|
| 170 |
/** |
| 171 |
* Renders the `tiptip/category-posts-block` on server. |
| 172 |
* |
| 173 |
* @see WP_Widget_Archives |
| 174 |
* |
| 175 |
* @param array $attributes The block attributes. |
| 176 |
* |
| 177 |
* @return string Returns the post content with archives added. |
| 178 |
*/ |
| 179 |
function render_category_posts_block( $attributes ) { |
| 180 |
global $attr, $before_title, $after_title; |
| 181 |
|
| 182 |
$attr = $attributes; |
| 183 |
|
| 184 |
// Get HTML |
| 185 |
$widget = new Widget(); |
| 186 |
$instance = build_block_instance( $attributes ); |
| 187 |
$instance = upgrade_settings( $instance ); |
| 188 |
|
| 189 |
$block_id = get_block_loadmore_id( $attributes ); |
| 190 |
store_block_loadmore_settings( $block_id, $attributes ); |
| 191 |
$widget->number = $block_id; |
| 192 |
|
| 193 |
|
| 194 |
$current_post_id = ''; |
| 195 |
if ( is_singular() ) { |
| 196 |
$current_post_id = get_the_ID(); |
| 197 |
} |
| 198 |
|
| 199 |
$items = $widget->get_elements_HTML( $instance, $current_post_id, 0, 0 ); |
| 200 |
$ret = ''; |
| 201 |
|
| 202 |
// Id of the wrapper element. $widget->number has to keep its 'block-' prefix |
| 203 |
// because loadMoreHTML() relies on it, so use a separate variable for the DOM id. |
| 204 |
$dom_id = WIDGET_BASE_ID . '-' . $block_id; |
| 205 |
|
| 206 |
if ( ( 'nothing' === $instance['no_match_handling'] ) || ! empty( $items ) ) { |
| 207 |
$ret = $widget->titleHTML( $before_title, $after_title, $instance ); |
| 208 |
|
| 209 |
$ret .= '<ul>' . implode( $items ) . '</ul>'; |
| 210 |
|
| 211 |
// Load more only if we think we have more items. |
| 212 |
if ( count( $items ) === (int) $instance['num'] ) { |
| 213 |
$ret .= $widget->loadMoreHTML( $instance ); |
| 214 |
} |
| 215 |
|
| 216 |
$ret .= $widget->footerHTML( $instance ); |
| 217 |
|
| 218 |
// The 'cpwp-wrap-text' class the excerpt-lines CSS relies on, and the |
| 219 |
// 'cpwp-wrap-text-stage' wrapper, are added by this script at runtime. |
| 220 |
// Without it the excerpt lines and image ratio settings have no effect. |
| 221 |
if ( isset( $instance['template'] ) && preg_match( '/%thumb%|%excerpt%/', $instance['template'] ) ) { |
| 222 |
wp_enqueue_script( 'jquery' ); // Just in case the theme or other plugins did not enqueue it. |
| 223 |
add_action( |
| 224 |
'wp_footer', |
| 225 |
function () use ( $dom_id, $instance ) { |
| 226 |
equal_cover_content_height( $dom_id, $instance ); |
| 227 |
}, |
| 228 |
100 |
| 229 |
); |
| 230 |
// Gutenberg Editor does not run wp_footer, so we also need to enqueue the script in the admin footer. |
| 231 |
add_action( |
| 232 |
'admin_footer', |
| 233 |
function () use ( $dom_id, $instance ) { |
| 234 |
equal_cover_content_height( $dom_id, $instance ); |
| 235 |
}, |
| 236 |
100 |
| 237 |
); |
| 238 |
} |
| 239 |
|
| 240 |
// Everything above hooks into wp_footer/admin_footer, which never run for the |
| 241 |
// REST request the editor preview is built from - and a <script> element |
| 242 |
// inside the returned markup would not be executed by React either |
| 243 |
// (dangerouslySetInnerHTML does not run embedded scripts). So hand the editor |
| 244 |
// the same wrap-text/image-ratio flags as inert JSON, src/edit.js applies |
| 245 |
// them to the preview itself. |
| 246 |
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { |
| 247 |
$ret .= '<script type="application/json" class="cpwp-block-preview-config">' . |
| 248 |
wp_json_encode( block_preview_config( $instance ) ) . |
| 249 |
'</script>'; |
| 250 |
} |
| 251 |
} elseif ( 'text' === $instance['no_match_handling'] ) { |
| 252 |
$ret = $widget->titleHTML( $before_title, $after_title, $instance ); |
| 253 |
$ret .= '<span class="cat-post-no-match">' . wp_kses_post( $instance['no_match_text'] ) . '</span>'; |
| 254 |
$ret .= $widget->footerHTML( $instance ); |
| 255 |
} |
| 256 |
|
| 257 |
if ( '' === $ret ) { |
| 258 |
return ''; // Nothing to show, so no style island either. |
| 259 |
} |
| 260 |
|
| 261 |
// The CSS rules depend on the settings of this specific block, so they can not be |
| 262 |
// part of the static stylesheet. wp_head has long been sent by the time a block is |
| 263 |
// rendered, so the rules are emitted as a style island right here. |
| 264 |
$css = ''; |
| 265 |
|
| 266 |
static $styled = array(); // Identical attributes produce an identical id, emit once. |
| 267 |
if ( ! isset( $styled[ $dom_id ] ) ) { |
| 268 |
$styled[ $dom_id ] = true; |
| 269 |
|
| 270 |
$virtual = new Virtual_Widget( $dom_id, WIDGET_BASE_ID . '-block', $instance ); |
| 271 |
$rules = array(); |
| 272 |
$virtual->getCSSRules( CONTEXT_BLOCK, $rules ); |
| 273 |
|
| 274 |
foreach ( $rules as $group ) { |
| 275 |
$css .= implode( "\n", $group ) . "\n"; |
| 276 |
} |
| 277 |
} |
| 278 |
|
| 279 |
// get_block_wrapper_attributes() is what actually applies the block-supports |
| 280 |
// classes (e.g. "aligncenter" from the toolbar's alignment option) to a |
| 281 |
// dynamic block's markup - core never adds them on its own, they only ever |
| 282 |
// end up in $attributes and are otherwise silently dropped by a hardcoded |
| 283 |
// wrapper like the one this used to build. |
| 284 |
$wrapper_attributes = get_block_wrapper_attributes( |
| 285 |
array( |
| 286 |
'id' => $dom_id, |
| 287 |
'class' => WIDGET_BASE_ID . '-block', |
| 288 |
) |
| 289 |
); |
| 290 |
|
| 291 |
return ( '' !== $css ? '<style>' . $css . '</style>' : '' ) . |
| 292 |
'<div ' . $wrapper_attributes . '>' . $ret . '</div>'; |
| 293 |
} |
| 294 |
|
| 295 |
/** |
| 296 |
* Registers all block assets so that they can be enqueued through the block editor |
| 297 |
* in the corresponding context. |
| 298 |
* |
| 299 |
* @see https://developer.wordpress.org/block-editor/tutorials/block-tutorial/applying-styles-with-stylesheets/ |
| 300 |
*/ |
| 301 |
function category_posts_block_init() { |
| 302 |
$dir = __DIR__; |
| 303 |
|
| 304 |
$script_asset_path = "$dir/build/index.asset.php"; |
| 305 |
if ( ! file_exists( $script_asset_path ) ) { |
| 306 |
throw new \Error( |
| 307 |
'You need to run `npm start` or `npm run build` for the "tiptip/category-posts-block" block first.' |
| 308 |
); |
| 309 |
} |
| 310 |
$index_js = 'build/index.js'; |
| 311 |
$script_asset = require( $script_asset_path ); |
| 312 |
wp_register_script( |
| 313 |
'tiptip-category-posts-block-editor', |
| 314 |
plugins_url( $index_js, __FILE__ ), |
| 315 |
$script_asset['dependencies'], |
| 316 |
$script_asset['version'] |
| 317 |
); |
| 318 |
wp_set_script_translations( 'tiptip-category-posts-block-editor', 'category-posts' ); |
| 319 |
|
| 320 |
register_block_type_from_metadata( |
| 321 |
__DIR__, |
| 322 |
array( |
| 323 |
'render_callback' => __NAMESPACE__ . '\render_category_posts_block', |
| 324 |
) |
| 325 |
); |
| 326 |
} |
| 327 |
add_action( 'init', __NAMESPACE__ . '\category_posts_block_init' ); |
| 328 |
|