max_num_pages; } // Don't print empty markup if there's only one page. if ( $max_num_pages < 2 ) { return; } ?> %2$s'; $time_string = sprintf( $time_string, esc_attr( get_the_date( 'c' ) ), esc_html( get_the_date() ), esc_attr( get_the_modified_date( 'c' ) ), esc_html( get_the_modified_date() ) ); $posted_on = sprintf( /* translators: %s: date */ esc_html_x( 'Posted on %s', 'post date', 'timber-lite' ), '' . $time_string . '' ); echo '' . $posted_on . ''; // phpcs:ignore } endif; if ( ! function_exists( 'timber_lite_entry_footer' ) ) : /** * Prints HTML with meta information for the categories, tags and comments. */ function timber_lite_entry_footer() { // Hide category and tag text for pages. if ( 'post' == get_post_type() ) { echo '
'; echo '
'; $tags_list = get_the_tag_list(); if ( $tags_list ) { /* translators: %s: tag list */ printf( '' . esc_html__( 'Tags: %s', 'timber-lite' ) . '', $tags_list ); // phpcs:ignore } } if ( ! is_single() && ! post_password_required() && ( comments_open() || get_comments_number() ) ) { echo ''; /* translators: %s: Number of comments */ comments_popup_link( esc_html__( 'Leave a comment', 'timber-lite' ), esc_html__( '1 Comment', 'timber-lite' ), esc_html__( '% Comments', 'timber-lite' ) ); echo ''; } edit_post_link( esc_html__( 'Edit', 'timber-lite' ), '', '' ); } endif; /** * Returns true if a blog has more than 1 category. * * @return bool */ function timber_lite_categorized_blog() { if ( false === ( $all_the_cool_cats = get_transient( 'timber_categories' ) ) ) { // Create an array of all the categories that are attached to posts. $all_the_cool_cats = get_categories( array( 'fields' => 'ids', 'hide_empty' => 1, // We only need to know if there is more than one category. 'number' => 2, ) ); // Count the number of categories that are attached to the posts. $all_the_cool_cats = count( $all_the_cool_cats ); set_transient( 'timber_categories', $all_the_cool_cats ); } if ( $all_the_cool_cats > 1 ) { // This blog has more than 1 category so timber_lite_categorized_blog should return true. return true; } else { // This blog has only 1 category so timber_lite_categorized_blog should return false. return false; } } /** * Flush out the transients used in timber_lite_categorized_blog. */ function timber_lite_category_transient_flusher() { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } // Like, beat it. Dig? delete_transient( 'timber_categories' ); } add_action( 'edit_category', 'timber_lite_category_transient_flusher' ); add_action( 'save_post', 'timber_lite_category_transient_flusher' ); if ( ! function_exists( 'timber_lite_get_custom_excerpt' ) ) : /** * Generate a custom post excerpt suited to both latin alphabet languages and multibyte ones, like Chinese of Japanese * * @param int|WP_Post $id Optional. Post ID or post object. * @return string The custom excerpt */ function timber_lite_get_custom_excerpt( $post_id = null ) { $post = get_post( $post_id ); if ( empty( $post ) ) { return ''; } //so we need to generate a custom excerpt // //the problem arises when we are dealing with multibyte characters //in this case we need to do a multibyte character length excerpt not the regular, number of words excerpt //but first we need to detect such a case //the excerpt returned by WordPress $excerpt = get_the_excerpt(); //now we try to truncate the default excerpt with the length = number of words * 6 - the average word length in English $mb_excerpt = timber_lite_truncate( $excerpt, ( apply_filters( 'excerpt_length', 55 ) * 6 ) ); //if the multibyte excerpt's length is smaller then the regular excerpt's length divided by 1.8 (this is a conservative number) //then it's quite clear that the default one is no good //else leave things like they used to work if ( mb_strlen( $mb_excerpt ) < mb_strlen( $excerpt ) / 1.8 ) { $excerpt = $mb_excerpt; } return $excerpt; } endif; if ( ! function_exists( 'timber_lite_post_excerpt' ) ) : /** * Display the post excerpt, either with the tag or regular excerpt * * @param int|WP_Post $id Optional. Post ID or post object. */ function timber_lite_post_excerpt( $post_id = null ) { $post = get_post( $post_id ); if ( empty( $post ) ) { return; } // Check the content for the more text $has_more = strpos( $post->post_content, '' . PHP_EOL; } } } //SECOND, split by iframes and embeds that were not wrapped by Jetpack //this will use recursion to process content between the videos $tags = implode( '|', array( 'video', 'audio', 'iframe', 'embed' ) ); $num_matches = preg_match_all( '#<(?P' . $tags . ')[^<]*?(?:>[\s\S]*?<\/(?P=tag)>|\s*\/>)#', $content, $matches ); //if no videos found, continue to processing images and text if ( $num_matches > 0 ) { for ( $idx = 0; $idx < $num_matches; $idx++ ) { //first let's see if there is some content before the current match $pos = strpos( $content, $matches[0][ $idx ] ); $before_content = trim( substr( $content, 0, $pos ) ); //process the before content recursively $markup .= timber_lite_process_partial_content( $before_content, $ignore_text, $ignore_videos, false, $image_callback ); //delete everything in front of the current match including it $content = trim( substr( $content, $pos + strlen( $matches[0][ $idx ] ) ) ); if ( false == $ignore_videos ) { //now let's handle the current video match $markup .= '
' . $matches[0][ $idx ] . '
' . PHP_EOL; } } } //THIRD, once done with the videos, we are left with the content after the last video (it there was any) //split this content by images (,
) $num_matches = preg_match_all( "!(?:<\s*p\s?[^>]*>\s*)?(?:<\s*figure\s?.*>\s*)?(?:<\s*?a\s?.*>\s*)?<\s*img\s?.*src=[\"|']([^\"']*)[\"|'].*alt=[\"|']([^\"']*)[\"|'].*/>(?:\s*)?(?:\s*<\s*figcaption\s?[^>]*>([^>]*)\s*)?(?:\s*
)?(?:\s*)?!i", $content, $matches ); for ( $idx = 0; $idx < $num_matches; $idx++ ) { //first let's see if there is some content before the current match $pos = strpos( $content, $matches[0][ $idx ] ); $before_content = trim( substr( $content, 0, $pos ) ); if ( ! $ignore_text && ! empty( $before_content ) ) { //first a little bit of safety - better safe than sorry $before_content = balanceTags( $before_content, true ); //a quick text to see if we have some actual content $temp_content = strip_tags($before_content, 'img'); if ( ! empty( $temp_content ) ) { //let's make a text box $markup .= '
' . $before_content . '
' . PHP_EOL; } } //delete everything in front of the current match including it $content = trim( substr( $content, $pos + strlen( $matches[0][ $idx ] ) ) ); //now let's handle the current match //let's see if we have a caption $caption = ""; if ( ! empty( $matches[3][ $idx ] ) ) { $caption = $matches[3][ $idx ]; } //first try and get an attachment ID - we may fail because it is an external image $attachment_id = timber_lite_attachment_url_to_postid( $matches[1][ $idx ] ); if ( $attachment_id ) { $markup .= call_user_func( $image_callback, $attachment_id, $caption ); } } if ( ! $ignore_text && ! empty( $content ) ) { //we have one last text box //first a little bit of safety - better safe than sorry $content = balanceTags( $content, true ); //a quick text to see if we have some actual content (we don't strip images as that is content) $temp_content = strip_tags($content, 'img'); if ( ! empty( $temp_content ) ) { //let's make a text box $markup .= '
' . $content . '
' . PHP_EOL; } } return $markup; } endif; if ( ! function_exists( 'timber_lite_get_film_strip_image' ) ) : /** * Return markup for a single image in the film strip * * @param int $id Optional. Attachment ID * @param string $caption Optional. The caption * @param string $class * * @return string The image markup */ function timber_lite_get_film_strip_image( $id = null, $caption = "", $class = '' ) { $markup = ''; //do nothing if we have no ID if ( empty( $id ) ) { return $markup; } if ( empty( $caption ) ) { //try to get the caption from the attachment metadata $caption = timber_lite_get_img_caption( $id ); } /* * Since we get an array without keys, we need to rely on the order (src, width, height, is_intermediate) * @see wp_get_attachment_image_src() */ $image_small_size = wp_get_attachment_image_src( $id, 'timber-small-image' ); $image_small_size_url = ''; $image_small_size_width = ''; $image_small_size_height = ''; if ( false !== $image_small_size ) { if ( is_array( $image_small_size ) ) { $image_small_size_url = reset( $image_small_size ); } if ( ! empty( $image_small_size[1] ) ) { $image_small_size_width = $image_small_size[1]; } if ( ! empty( $image_small_size[2] ) ) { $image_small_size_height = $image_small_size[2]; } } $image_large_size = wp_get_attachment_image_src( $id, 'timber-large-image' ); $image_large_size_url = ''; if ( false !== $image_large_size && is_array( $image_large_size ) ) { $image_large_size_url = reset( $image_large_size ); } $image_full_size = wp_get_attachment_image_src( $id, 'timber-fullview-image' ); $image_full_size_url = ''; if ( false !== $image_full_size && is_array( $image_full_size ) ) { $image_full_size_url = reset( $image_full_size ); } $image_width = ''; $image_height = ''; // Get directly the raw metadata because Jetpack Photon ruins stuff for us - no dimensions are returned by wp_get_attachment_image_src() $image_data = wp_get_attachment_metadata( $id ); if ( false !== $image_data ) { if ( ! empty( $image_data['width'] ) ) { $image_width = absint( $image_data['width'] ); } if ( ! empty( $image_data['height'] ) ) { $image_height = absint( $image_data['height'] ); } } $markup .= '
' . PHP_EOL; return $markup; } endif; if ( ! function_exists( 'timber_lite_get_slider_image' ) ) : /** * Return markup for a single image in the slider * * @param int $id Optional. Attachment ID * @param string $caption Optional. The caption * @return string The image markup */ function timber_lite_get_slider_image( $id = null, $caption = "" ) { $markup = ''; //do nothing if we have no ID if ( empty( $id ) ) { return $markup; } if ( empty( $caption ) ) { //try to get the caption from the attachment metadata $caption = timber_lite_get_img_caption( $id ); } $image_full_size = wp_get_attachment_image_src( $id, 'full' ); //get directly the raw metadata because Jetpack Photon ruins stuff for us - no dimensions are returned by wp_get_attachment_image_src() $image_data = wp_get_attachment_metadata( $id ); $markup .= '
' . '' . esc_attr( timber_lite_get_img_alt( $id ) ) . ''; if ( ! empty( $caption ) ) { $markup .= '
' . wp_kses_post( $caption ) . '
'; } $markup .= '
' . PHP_EOL; return $markup; } endif; if ( ! function_exists( 'timber_lite_get_post_galleries' ) ) : /** * Retrieves galleries from the passed post's content. * Modified version of the core get_post_galleries() because we wanted the matched string also returned * * * @param int|WP_Post $post Post ID or object. * @param bool $html Optional. Whether to return HTML or data in the array. Default true. * @return array A list of arrays, each containing gallery data and srcs parsed * from the expanded shortcode. */ function timber_lite_get_post_galleries( $post, $html = true ) { if ( ! $post = get_post( $post ) ) return array(); if ( ! has_shortcode( $post->post_content, 'gallery' ) ) return array(); $galleries = array(); if ( preg_match_all( '/' . get_shortcode_regex() . '/s', $post->post_content, $matches, PREG_SET_ORDER ) ) { foreach ( $matches as $shortcode ) { if ( 'gallery' === $shortcode[2] ) { $srcs = array(); $gallery = do_shortcode_tag( $shortcode ); if ( $html ) { $galleries[] = $gallery; } else { preg_match_all( '#src=([\'"])(.+?)\1#is', $gallery, $src, PREG_SET_ORDER ); if ( ! empty( $src ) ) { foreach ( $src as $s ) $srcs[] = $s[2]; } $data = shortcode_parse_atts( $shortcode[3] ); $data['src'] = array_values( array_unique( $srcs ) ); //add the matched string also $data['original'] = $shortcode[0]; $galleries[] = $data; } } } } /** * Filter the list of all found galleries in the given post. * * @since 3.6.0 * * @param array $galleries Associative array of all found post galleries. * @param WP_Post $post Post object. */ return apply_filters( 'get_post_galleries', $galleries, $post ); } endif; /** * Handles the output of the media for audio attachment posts. This should be used within The Loop. * * @return string */ function timber_lite_audio_attachment() { return hybrid_media_grabber( array( 'type' => 'audio', 'split_media' => true ) ); } /** * Handles the output of the media for video attachment posts. This should be used within The Loop. * * @return string */ function timber_lite_video_attachment() { return hybrid_media_grabber( array( 'type' => 'video', 'split_media' => true ) ); } /** * Prints HTML with the category of a certain post, with the most posts in it * The most important category of a post * * @param int|WP_Post $post_ID Optional. Post ID or post object. */ function timber_lite_first_category( $post_ID = null ) { global $wp_rewrite; //use the current post ID is none given if ( empty( $post_ID ) ) { $post_ID = get_the_ID(); } //obviously pages don't have categories if ( 'page' == get_post_type( $post_ID ) ) { return; } //first get all categories ordered by count $all_categories = get_categories( array( 'orderby' => 'count', 'order' => 'DESC', ) ); //get the post's categories $categories = get_the_category( $post_ID ); //now intersect them so that we are left with e descending ordered array of the post's categories $categories = array_uintersect( $all_categories, $categories, 'timber_lite_compare_categories' ); //remove the Uncategorized category if ( ! empty( $categories ) ) { $first_category = reset( $categories ); while ( ! empty( $first_category ) && $first_category->term_id == get_option( 'default_category' ) ) { $first_category = array_shift( $categories ); } } if ( ! empty ( $categories ) ) { $first_category = array_shift( $categories ); $rel = ( is_object( $wp_rewrite ) && $wp_rewrite->using_permalinks() ) ? 'rel="category tag"' : 'rel="category"'; echo '
' . esc_html( $first_category->name ) . ''; // phpcs:ignore } } #function function timber_lite_compare_categories( $a1, $a2 ) { if ( $a1->term_id == $a2->term_id ) { return 0; //we are only interested by equality but PHP wants the whole thing } if ( $a1->term_id > $a2->term_id ) { return 1; } return -1; } /** * Prints HTML with the list of project types (categories) * * @param int|WP_Post $post_ID Optional. Post ID or post object. */ function timber_lite_the_project_types( $post_ID = null, $before = '', $after = '' ) { //use the current post ID is none given if ( empty( $post_ID ) ) { $post_ID = get_the_ID(); } /* * Project category list */ $separate_meta = esc_html_x( ', ', 'Used between list items, there is a space after the comma.', 'timber-lite' ); $terms_list = get_the_term_list( $post_ID, 'jetpack-portfolio-type', $before , $separate_meta, $after ); // $terms_list will turn into an wp_error when the taxonomy is missing so check it first if ( ! is_wp_error( $terms_list ) ) { echo $terms_list; // phpcs:ignore } } if ( ! function_exists( 'timber_lite_get_post_format_link' ) ) : /** * Returns HTML with the post format link * * @param int|WP_Post $post_ID Optional. Post ID or post object. */ function timber_lite_get_post_format_link( $post_ID = null, $before = '', $after = '' ) { //use the current post ID is none given if ( empty( $post_ID ) ) { $post_ID = get_the_ID(); } $post_format = get_post_format( $post_ID ); if ( empty( $post_format ) || 'standard' == $post_format ) { return ''; } return $before . ' ' . get_post_format_string( $post_format ) . ' ' . $after; } #function endif; if ( ! function_exists( 'timber_lite_post_format_link' ) ) : /** * Prints HTML with the post format link * * @param int|WP_Post $post_ID Optional. Post ID or post object. */ function timber_lite_post_format_link( $post_ID = null, $before = '', $after = '' ) { echo timber_lite_get_post_format_link( $post_ID, $before, $after ); // phpcs:ignore } #function endif; function timber_lite_get_post_gallery_count( $post_ID = null ) { //use the current post ID is none given if ( empty( $post_ID ) ) { $post_ID = get_the_ID(); } $images = get_post_gallery_images( $post_ID ); if ( ! empty($images) ) { return count( $images ); } return false; } /** * Get a number of random attachments attached to the jetpack-portfolio CPT * * @param int $maxnum Optional. Max number of random projects images to return * @return array List of images */ function timber_lite_get_random_projects_images( $maxnum = 5 ) { $projects = get_posts( array( 'post_type' => 'jetpack-portfolio', 'posts_per_page' => -1, 'fields' => 'ids' ) ); if ( ! empty($projects) ) { $args = array( 'post_parent__in' => $projects, 'post_type' => 'attachment', 'numberposts' => $maxnum, 'post_status' => 'any', 'post_mime_type' => 'image', 'orderby' => 'rand', ); $images = get_posts( $args ); return $images; } return array(); } /** * Print a JSON encoded array of a number of random attachments srcs from those attached to the jetpack-portfolio CPT. * * @param int $maxnum Optional. Max number of random projects images srcs to return */ function timber_lite_the_random_projects_images_srcs( $maxnum = 5 ) { $random_images = timber_lite_get_random_projects_images( $maxnum ); $image_srcs = array(); if ( ! empty( $random_images ) ) { foreach ($random_images as $key => $image) { $thumbnail = wp_get_attachment_image_src( $image->ID, 'thumbnail' ); if ( $thumbnail ) { $image_srcs[] = $thumbnail[0]; } } } echo json_encode( $image_srcs ); } if ( ! function_exists( 'timber_lite_body_attributes' ) ): function timber_lite_body_attributes() { //we use this so we can generate links with post id //right now we use it to change the Edit Post link in the admin bar $data_currentID = ''; $data_currentEditString = ''; $data_currentTaxonomy = ''; $current_object = get_queried_object(); if (!empty($current_object->post_type) && ($post_type_object = get_post_type_object($current_object->post_type)) && current_user_can('edit_post', $current_object->ID) && $post_type_object->show_ui && $post_type_object->show_in_admin_bar ) { $data_currentID = 'data-curpostid="' . esc_attr( $current_object->ID ) . '" '; if (isset($post_type_object->labels) && isset($post_type_object->labels->edit_item)) { $data_currentEditString = 'data-curpostedit="' . esc_attr( $post_type_object->labels->edit_item ) . '" '; } } elseif (!empty($current_object->taxonomy) && ($tax = get_taxonomy($current_object->taxonomy)) && current_user_can($tax->cap->edit_terms) && $tax->show_ui ) { $data_currentID = 'data-curpostid="' . esc_attr( $current_object->term_id ) . '" '; $data_currentTaxonomy = 'data-curtaxonomy="' . esc_attr( $current_object->taxonomy ) . '" '; if ( isset($tax->labels ) && isset( $tax->labels->edit_item ) ) { $data_currentEditString = 'data-curpostedit="' . esc_attr( $tax->labels->edit_item ) . '" '; } } echo $data_currentID . $data_currentEditString . $data_currentTaxonomy; // phpcs:ignore } endif; /** * Display the classes for the post title div. * * @param string|array $class One or more classes to add to the class list. * @param int|WP_Post $post_id Optional. Post ID or post object. * @return string */ function timber_lite_get_post_title_class_attr( $class = '', $post_id = null ) { // Separates classes with a single space, collates classes for post title return 'class="' . esc_attr( join( ' ', timber_lite_get_post_title_class( $class, $post_id ) ) ) . '"'; } if ( ! function_exists( 'timber_lite_get_post_title_class' ) ) : /** * Retrieve the classes for the post title, * depending on the length of the title * * @param string|array $class One or more classes to add to the class list. * @return array Array of classes. */ function timber_lite_get_post_title_class( $class = '', $post_id = null ) { $post = get_post( $post_id ); $classes = array(); if ( empty( $post ) ) { return $classes; } $classes[] = 'entry-header'; // .entry-header--[short|medium|long] depending on the title length // 0-29 chars = short // 30-59 = medium // 60+ = long $title_length = mb_strlen( get_the_title( $post ) ); if ( $title_length < 30 ) { $classes[] = 'entry-header--short'; } elseif ( $title_length < 60 ) { $classes[] = 'entry-header--medium'; } else { $classes[] = 'entry-header--long'; } if ( ! empty($class) ) { if ( ! is_array( $class ) ) { $class = preg_split( '#\s+#', $class ); } $classes = array_merge( $classes, $class ); } $classes = array_map( 'esc_attr', $classes ); /** * Filter the list of CSS classes for the current post title. * * @param array $classes An array of post classes. * @param string $class A comma-separated list of additional classes added to the post. * @param int $post_id The post ID. */ $classes = apply_filters( 'timber_post_title_class', $classes, $class, $post->ID ); return array_unique( $classes ); } #function endif; if ( ! function_exists( 'timber_lite_first_site_title_character' ) ) : /** * Returns the first UTF-8 character of the site title * returns empty string if nothing found * * @param bool $data_attribute * * @return string */ function timber_lite_first_site_title_character() { $title = get_bloginfo( 'name' ); if ( empty( $title ) ) { return ''; } $first_letter = ''; //find the first alphanumeric character - multibyte //suppress warnings and errors since this might fail if there is no appropiate UTF8 PHP support @preg_match( '/[\p{Xan}]/u', $title, $results ); if ( isset( $results ) && ! empty( $results[0] ) ) { $first_letter = $results[0]; } else { //lets try the old fashion way //find the first alphanumeric character - non-multibyte preg_match( '/[a-zA-Z\d]/', $title, $results ); if ( isset( $results ) && ! empty( $results[0] ) ) { $first_letter = $results[0]; } }; return $first_letter; } endif; if ( ! function_exists( 'timber_lite_get_portfolio_page_link' ) ) : // Return the slug of the page with the page-templates/custom-portfolio-archive.php template or the post type archive if no page was found function timber_lite_get_portfolio_page_link() { $pages = get_pages( array( 'sort_order' => 'DESC', 'sort_column' => 'ID', 'meta_key' => '_wp_page_template', 'meta_value' => 'page-templates/custom-portfolio-page.php', 'parent' => -1, 'suppress_filters' => false, //allow filters - like WPML ) ); if ( ! empty( $pages ) ) { //find the page with the Portfolio Archive option selected foreach ( $pages as $page ) { $custom_portfolio_page_type = get_post_meta( timber_lite_get_post_id( $page->ID, 'page' ), 'custom_portfolio_page_type', true); if ( 'portfolio' === $custom_portfolio_page_type ) { //found it return get_page_link( timber_lite_get_post_id( $page->ID, 'page' ) ); } } } //fallback to the archive slug return get_post_type_archive_link( 'jetpack-portfolio' ); } endif; if ( ! function_exists( 'timber_lite_footer_the_copyright' ) ) { /** * Display the footer copyright. */ function timber_lite_footer_the_copyright() { $output = ''; $output .= ''; echo apply_filters( 'pixelgrade_footer_the_copyright', $output ); } } if ( ! function_exists( 'wp_body_open' ) ) : /** * Fire the wp_body_open action. * * Added for backwards compatibility to support pre 5.2.0 WordPress versions. * * @since Timber Lite 1.0.3 */ function wp_body_open() { /** * Triggered after the opening tag. * * @since Timber Lite 1.0.3 */ do_action( 'wp_body_open' ); } endif;