├── .gitmodules ├── css ├── admin-post.css ├── admin-license.css └── admin-widgets.css ├── js ├── ie-unsupported.js ├── admin-post.js ├── jquery.fitvids.js ├── responsive-embeds.js ├── maps.js └── admin-widgets.js ├── README.md ├── includes ├── admin │ ├── meta-boxes.php │ ├── admin-enqueue-styles.php │ ├── admin-enqueue-scripts.php │ ├── admin-taxonomies.php │ ├── activation.php │ └── admin-widgets.php ├── body.php ├── mime-types.php ├── meta-data.php ├── posts.php ├── taxonomies.php ├── downloads.php ├── template-tags.php ├── people.php ├── locations.php ├── classes │ ├── walker-nav-menu-description.php │ ├── customize-controls.php │ ├── ctfw-theme-updater-class.php │ └── widget-galleries.php ├── page-nav.php ├── pages.php ├── background.php ├── comments.php ├── head.php ├── templates.php ├── fonts.php ├── images.php ├── sidebars.php ├── deprecated.php ├── colors.php ├── localization.php ├── maps.php ├── compatibility.php ├── conditions.php ├── embeds.php └── customize.php └── framework.php /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "includes/libraries/ct-meta-box"] 2 | path = includes/libraries/ct-meta-box 3 | url = https://github.com/churchthemes/ct-meta-box.git 4 | [submodule "includes/libraries/ct-recurrence"] 5 | path = includes/libraries/ct-recurrence 6 | url = https://github.com/churchthemes/ct-recurrence.git 7 | -------------------------------------------------------------------------------- /css/admin-post.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Admin Add/Edit Post CSS 3 | */ 4 | 5 | /* Gutenberg */ 6 | 7 | .gutenberg #ctfw-featured-image-note:first-child { 8 | margin-top: 0; 9 | margin-bottom: 1em; 10 | } 11 | 12 | .gutenberg #ctfw-featured-image-note:last-child { 13 | margin-top: 1em; 14 | margin-bottom: 0; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /css/admin-license.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Theme License 3 | */ 4 | 5 | /* Status */ 6 | 7 | .ctfw-license-active { 8 | color: #1ba31b; 9 | font-weight: bold; 10 | } 11 | 12 | .ctfw-license-inactive, 13 | .ctfw-license-expired, 14 | .ctfw-license-expiring-soon { 15 | color: #d52121; 16 | font-weight: bold; 17 | } 18 | 19 | /* Buttons */ 20 | 21 | .ctfw-license-button { 22 | margin-right: 10px !important; 23 | } -------------------------------------------------------------------------------- /js/ie-unsupported.js: -------------------------------------------------------------------------------- 1 | jQuery( document ).ready( function( $ ) { 2 | 3 | // Is old version of IE used? 4 | if ( navigator.userAgent.match( new RegExp( "MSIE [5-" + ctfw_ie_unsupported.version + "]", "gi" ) ) ) { 5 | 6 | // Hide content 7 | $( 'body' ) 8 | .empty() // remove content 9 | .css( 'background', 'none' ); // hide background 10 | 11 | // Tell user to upgrade to a modern browser 12 | alert( ctfw_ie_unsupported.message ); 13 | 14 | // Redirect to a site with upgrade details 15 | window.location = ctfw_ie_unsupported.redirect_url; 16 | 17 | } 18 | 19 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Church Theme Framework 2 | ====================== 3 | 4 | A library of code from [ChurchThemes.com](https://churchthemes.com) useful for assisting with the development of church WordPress themes using the [Church Content](https://github.com/churchthemes/church-theme-content) functionality plugin. 5 | 6 | Purpose 7 | ------- 8 | 9 | Church Theme Framework is a drop-in framework. In other words, it's a directory of includes containing functions, classes and other code useful to multiple, similar themes. 10 | 11 | * Faster development 12 | * Easier maintenance 13 | * Consistent features 14 | 15 | Full Details 16 | ------- 17 | 18 | For full details, see the [Developer's Guide](https://churchthemes.com/guides/developer/framework/) on ChurchThemes.com. -------------------------------------------------------------------------------- /includes/admin/meta-boxes.php: -------------------------------------------------------------------------------- 1 | ' + ctfw_post.featured_image_note + '

' ); 23 | $( '#ctfw-featured-image-note' ) 24 | .hide() 25 | .fadeIn( 'fast' ); 26 | 27 | clearInterval( interval ); 28 | 29 | } 30 | 31 | }, 1000 ); 32 | 33 | } 34 | 35 | } ); 36 | -------------------------------------------------------------------------------- /includes/body.php: -------------------------------------------------------------------------------- 1 | Functions 4 | * 5 | * @package Church_Theme_Framework 6 | * @subpackage Functions 7 | * @copyright Copyright (c) 2014 - 2019, ChurchThemes.com, LLC 8 | * @link https://github.com/churchthemes/church-theme-framework 9 | * @license GPLv2 or later 10 | * @since 1.1.2 11 | */ 12 | 13 | // No direct access 14 | if ( ! defined( 'ABSPATH' ) ) exit; 15 | 16 | /******************************************* 17 | * BODY CLASSES 18 | *******************************************/ 19 | 20 | /** 21 | * Add various helper classes to 22 | * 23 | * Enable with add_theme_support( 'ctfw-body-classes' ); 24 | * 25 | * IMPORTANT: Do not do client detection (mobile, browser, etc.) here. 26 | * Instead, do in theme's JS so works with caching plugins. 27 | * 28 | * @since 1.1.2 29 | * @param array $classes Classes currently being added to body tag 30 | * @return array Modified classes 31 | */ 32 | function ctfw_add_body_classes( $classes ) { 33 | 34 | // Theme supports body helper classes? 35 | if ( current_theme_supports( 'ctfw-body-classes' ) ) { 36 | 37 | // Page has loop for multiple entries (archive, search, etc.) 38 | if ( ctfw_has_loop_multiple() ) { 39 | $classes[] = 'ctfw-has-loop-multiple'; 40 | } else { 41 | $classes[] = 'ctfw-no-loop-multiple'; 42 | } 43 | 44 | } 45 | 46 | return $classes; 47 | 48 | } 49 | 50 | add_filter( 'body_class', 'ctfw_add_body_classes' ); 51 | -------------------------------------------------------------------------------- /includes/mime-types.php: -------------------------------------------------------------------------------- 1 | 'Image', 37 | 'audio' => 'Audio', 38 | 'video' => 'Video', 39 | 'application/pdf' => 'PDF', 40 | ); 41 | $mime_type_names = apply_filters( 'ctfw_mime_type_names', $mime_type_names ); 42 | 43 | // Check for match 44 | foreach ( $mime_type_names as $mime_type_match => $mime_type_name ) { 45 | 46 | // Match the first part and keep that name (e.g. image/jpeg matches image) 47 | if ( preg_match( '/^' . preg_quote( $mime_type_match, '/' ) . '/i', $mime_type ) ) { 48 | $friendly_name = $mime_type_name; 49 | break; 50 | } 51 | 52 | } 53 | 54 | return apply_filters( 'ctfw_mime_type_name', $friendly_name , $mime_type ); 55 | 56 | } -------------------------------------------------------------------------------- /includes/meta-data.php: -------------------------------------------------------------------------------- 1 | 23 | * @return array Modified array of classes 24 | */ 25 | function ctfw_add_post_classes( $classes ) { 26 | 27 | // Theme asks for this enhancement? 28 | if ( current_theme_supports( 'ctfw-post-classes' ) ) { 29 | 30 | // Has featured image? 31 | if ( has_post_thumbnail() ) { 32 | $classes[] = 'ctfw-has-image'; 33 | } else { 34 | $classes[] = 'ctfw-no-image'; 35 | } 36 | 37 | } 38 | 39 | return $classes; 40 | 41 | } 42 | 43 | add_filter( 'post_class', 'ctfw_add_post_classes' ); 44 | 45 | /** 46 | * Get first ordered post 47 | * 48 | * Get first post according to manual order 49 | * 50 | * @since 1.0.9 51 | * @param string $post_type Post type to use 52 | * @return Array Post data 53 | */ 54 | function ctfw_first_ordered_post( $post_type ) { 55 | 56 | $post = array(); 57 | 58 | // Get first post 59 | $posts = get_posts( array( 60 | 'post_type' => $post_type, 61 | 'orderby' => 'menu_order', // first manually ordered 62 | 'order' => 'ASC', 63 | 'numberposts' => 1, 64 | 'suppress_filters' => false // assist multilingual 65 | ) ); 66 | 67 | // Get post as array 68 | if ( isset( $posts[0] ) ) { 69 | $post = (array) $posts[0]; 70 | } 71 | 72 | // Return filtered 73 | return apply_filters( 'ctfw_first_ordered_post', $post ); 74 | 75 | } 76 | -------------------------------------------------------------------------------- /includes/admin/admin-enqueue-styles.php: -------------------------------------------------------------------------------- 1 | base ) { 35 | 36 | // CSS for add/edit post screen. 37 | wp_enqueue_style( 'ctfw-post', get_theme_file_uri( CTFW_CSS_DIR . '/admin-post.css' ), false, CTFW_THEME_VERSION ); // bust cache on update. 38 | 39 | } 40 | 41 | // Admin Widgets. 42 | if ( 'widgets' === $screen->base ) { 43 | 44 | // For color widget field type. 45 | // Improvement to enqueue only when there is a widget with color field? 46 | wp_enqueue_style( 'wp-color-picker' ); 47 | 48 | // CSS for admin widgets. 49 | // Framework also enqueues this for Customizer in framework/includes/customize.php. 50 | wp_enqueue_style( 'ctfw-widgets', get_theme_file_uri( CTFW_CSS_DIR . '/admin-widgets.css' ), false, CTFW_THEME_VERSION ); // bust cache on update. 51 | 52 | } 53 | 54 | // Theme License. 55 | if ( 'appearance_page_theme-license' == $screen->base ) { 56 | wp_enqueue_style( 'ctfw-license', get_theme_file_uri( CTFW_CSS_DIR . '/admin-license.css' ), false, CTFW_THEME_VERSION ); // bust cache on update. 57 | } 58 | 59 | } 60 | 61 | add_action( 'admin_enqueue_scripts', 'ctfw_admin_enqueue_styles' ); 62 | -------------------------------------------------------------------------------- /includes/admin/admin-enqueue-scripts.php: -------------------------------------------------------------------------------- 1 | base ) { // don't enqueue unless needed 31 | 32 | wp_enqueue_script( 'ctfw-admin-post', get_theme_file_uri( CTFW_JS_DIR . '/admin-post.js' ), array( 'jquery' ), CTFW_THEME_VERSION ); // bust cache on update 33 | wp_localize_script( 'ctfw-admin-post', 'ctfw_post', array( 34 | 'featured_image_note' => ctfw_featured_image_note(), // get note to show on current post type's Featured Image (Gutenberg). 35 | ) ); 36 | 37 | } 38 | 39 | // Widgets JavaScript 40 | // wp_enqueue_media() is run in classes/widget.php 41 | if ( 'widgets' === $screen->base ) { // don't enqueue unless needed 42 | 43 | // New media uploader in WP 3.5+ 44 | wp_enqueue_media(); 45 | 46 | // Color picker 47 | // Improvement to enqueue only when there is a widget with color field? 48 | wp_enqueue_script( 'wp-color-picker' ); 49 | 50 | // Main widgets script 51 | wp_enqueue_script( 'ctfw-admin-widgets', get_theme_file_uri( CTFW_JS_DIR . '/admin-widgets.js' ), array( 'jquery' ), CTFW_THEME_VERSION ); // bust cache on update 52 | wp_localize_script( 'ctfw-admin-widgets', 'ctfw_widgets', ctfw_admin_widgets_js_data() ); // see admin-widgets.php 53 | 54 | } 55 | 56 | } 57 | 58 | add_action( 'admin_enqueue_scripts', 'ctfw_admin_enqueue_scripts' ); // admin-end only 59 | -------------------------------------------------------------------------------- /includes/taxonomies.php: -------------------------------------------------------------------------------- 1 | show_ui ) ? true : false; 34 | 35 | // Return filterable 36 | return apply_filters( 'ctfw_ctc_taxonomy_supported', $supported, $taxonomy_name ); 37 | 38 | } 39 | 40 | /** 41 | * Taxonomy term options 42 | * 43 | * Returns ID/name pairs useful for creating select options and sanitizing on front-end. 44 | * 45 | * @since 0.9 46 | * @param string $taxonomy_name Taxonomy slug 47 | * @param array $prepend Array to start with such as "All" or similar 48 | * @return array ID/name pairs 49 | */ 50 | function ctfw_term_options( $taxonomy_name, $prepend = array() ) { 51 | 52 | $options = array(); 53 | 54 | if ( ! preg_match( '/^ctc_/', $taxonomy_name ) || ctfw_ctc_taxonomy_supported( $taxonomy_name ) ) { // make sure CTC taxonomy support 55 | 56 | $terms = $categories = get_terms( $taxonomy_name ); 57 | 58 | if ( ! empty( $prepend ) ) { 59 | $options = $prepend; 60 | } 61 | 62 | foreach ( $terms as $term ) { 63 | $options[$term->term_id] = $term->name; 64 | } 65 | 66 | } 67 | 68 | return apply_filters( 'ctfw_term_options', $options, $taxonomy_name, $prepend ); 69 | 70 | } 71 | -------------------------------------------------------------------------------- /includes/downloads.php: -------------------------------------------------------------------------------- 1 | tags use download="download" attribute to attempt "Save As". 27 | * As of October, 2019, most browsers support (not IE11 or iOS, which doesn't save files anyway). 28 | * 29 | * Prior to framework version 2.6, this would use ctfw_force_download_url() to force downloads via headers. 30 | * Now we're relying on download attribute which is simpler, safer and not error-prone. 31 | * 32 | * @since 1.7.2 33 | * @param string $url URL for file 34 | * @return string URL modified to force Save As if local or as is if external and has extension 35 | */ 36 | function ctfw_download_url( $url ) { 37 | 38 | $download_url = $url; 39 | 40 | // Dropbox URL? 41 | $is_dropbox = ''; 42 | if ( preg_match( '/dropbox/', $url ) ) { 43 | 44 | $is_dropbox = true; 45 | 46 | // Force ?dl=1 since download="download" attribute won't work with remote URL in most browser. 47 | $download_url = remove_query_arg( 'dl', $download_url ); 48 | $download_url = remove_query_arg( 'raw', $download_url ); 49 | $download_url = add_query_arg( 'dl', '1', $download_url ); 50 | 51 | } 52 | 53 | // Must have extension or be Dropbox URL to be downloadable. 54 | // It may be URL to SoundCloud, YouTube, etc. 55 | $filetype = wp_check_filetype( $download_url ); // remove any query string. 56 | if ( empty( $filetype['ext'] ) && ! $is_dropbox ) { 57 | $download_url = ''; // Return nothing, there is no file to download. 58 | } 59 | 60 | return apply_filters( 'ctfw_download_url', $download_url, $url ); 61 | 62 | } 63 | -------------------------------------------------------------------------------- /includes/admin/admin-taxonomies.php: -------------------------------------------------------------------------------- 1 | true, 36 | 'show_ui' => true // weed out post_format 37 | ) ); 38 | 39 | // Supported taxonomies (some cannot be re-ordered). 40 | $supported_taxonomies = array( 41 | 'category', 42 | 'ctc_person_group', 43 | 'ctc_sermon_speaker', 44 | 'ctc_event_category', 45 | ); 46 | 47 | // Add note to each 48 | foreach ($taxonomies as $taxonomy) { 49 | if (in_array($taxonomy, $supported_taxonomies)) { 50 | add_action( 'after-' . $taxonomy . '-table', 'ctfw_taxonomy_order_note' ); 51 | } 52 | } 53 | 54 | } 55 | 56 | add_action( 'admin_init', 'ctfw_taxonomy_order_notes' ); 57 | 58 | /** 59 | * Show custom ordering note 60 | * 61 | * @since 0.9 62 | * @param string $taxonomy Taxonomy to affect 63 | */ 64 | function ctfw_taxonomy_order_note( $taxonomy ) { 65 | 66 | // Only if theme requests this 67 | $support = get_theme_support( 'ctfw-taxonomy-order-note' ); 68 | if ($support) { // returns false if feature not supported 69 | 70 | // Get URL if not using default 71 | $url = isset( $support[0] ) ? $support[0] : 'https://churchthemes.com/go/taxonomy-order'; 72 | 73 | // Get taxonomy plural 74 | $taxonomy_obj = get_taxonomy( $taxonomy ); 75 | $taxonomy_plural = strtolower( $taxonomy_obj->labels->name ); 76 | 77 | // Show message 78 | echo '

'; 79 | printf( 80 | __( 'Custom Ordering: Try this plugin for custom ordering your %s.', 'church-theme-framework' ), 81 | $url, 82 | $taxonomy_plural 83 | ); 84 | echo '

'; 85 | 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /includes/template-tags.php: -------------------------------------------------------------------------------- 1 | false, 35 | 'today' => true, // show "Today" if post is from today 36 | 'yesterday' => true, // show "Yesterday" instead of yesterday's date 37 | 'date_format' => get_option( 'date_format' ), // from WordPress general settings 38 | 'abbreviate_date' => false, // true or pass arguments for ctfw_abbreviate_date_format() 39 | ) ); 40 | $options = wp_parse_args( $options, $defaults ); 41 | 42 | // Abbreviate date format 43 | // If $options['abbreviate_date'] is true, default arguments will be used (abbreviate) 44 | if ( $options['abbreviate_date'] ) { 45 | 46 | // Use the date format passed in already 47 | $abbreviate_date_args = array( 48 | 'date_format' => $options['date_format'], 49 | ); 50 | 51 | // Passing arguments for abbreviating date 52 | // Default both true: abbreviate_month, remove_year 53 | if ( is_array( $options['abbreviate_date'] ) ) { 54 | $abbreviate_date_args = array_merge( $options['abbreviate_date'], $abbreviate_date_args ); 55 | } 56 | 57 | $options['date_format'] = ctfw_abbreviate_date_format( $abbreviate_date_args ); 58 | 59 | } 60 | 61 | // Today and yesterday in local time 62 | $today_ymd = date_i18n( 'Y-m-d' ); 63 | $yesterday_ymd = date_i18n( 'Y-m-d', strtotime( $today_ymd ) - DAY_IN_SECONDS ); 64 | 65 | // Post date 66 | $date_timestamp = get_the_time( 'U', $post ); 67 | $date_ymd = date_i18n( 'Y-m-d', $date_timestamp ); 68 | 69 | // Show "Today" 70 | if ( $options['today'] && $today_ymd == $date_ymd ) { 71 | $date_formatted = __( 'Today', 'church-theme-framework' ); 72 | } 73 | 74 | // Show "Yesterday" 75 | elseif ( $options['yesterday'] && $yesterday_ymd == $date_ymd ) { 76 | $date_formatted = __( 'Yesterday', 'church-theme-framework' ); 77 | } 78 | 79 | // Show date 80 | else { 81 | $date_formatted = date_i18n( $options['date_format'], $date_timestamp ); // translated date 82 | } 83 | 84 | // Date filtering 85 | $date_formatted = apply_filters( 'ctfw_post_date', $date_formatted, $options ); 86 | 87 | // Output or return 88 | if ( $options['return'] ) { 89 | return $date_formatted; 90 | } else { 91 | echo $date_formatted; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /includes/people.php: -------------------------------------------------------------------------------- 1 | is_archive && ! empty( $query->query['ctc_person_group'] ) ) { 36 | $query->set( 'orderby', 'menu_order' ); 37 | $query->set( 'order', 'ASC' ); 38 | } 39 | 40 | } 41 | 42 | return $query; 43 | 44 | } 45 | 46 | add_filter( 'pre_get_posts' , 'ctfw_people_group_order' ); 47 | 48 | /********************************** 49 | * PEOPLE DATA 50 | **********************************/ 51 | 52 | /** 53 | * Get person data 54 | * 55 | * @since 0.9 56 | * @param int $post_id Post ID to get data for; null for current post 57 | * @return array Person data 58 | */ 59 | function ctfw_person_data( $post_id = null ) { 60 | 61 | // Get meta values 62 | $data = ctfw_get_meta_data( array( // without _ctc_person_ prefix 63 | 'position', 64 | 'phone', 65 | 'email', 66 | 'urls', 67 | ), $post_id ); 68 | 69 | // Return filtered 70 | return apply_filters( 'ctfw_person_data', $data ); 71 | 72 | } 73 | 74 | /********************************** 75 | * PEOPLE NAVIGATION 76 | **********************************/ 77 | 78 | /** 79 | * Prev/next people sorting 80 | * 81 | * This makes get_previous_post() and get_next_post() sort by manual order instead of Publish Date 82 | * 83 | * @since 0.9.0 84 | */ 85 | function ctfw_previous_next_person_sorting() { 86 | 87 | // Theme supports it? 88 | if ( ! current_theme_supports( 'ctfw-person-navigation' ) ) { 89 | return; 90 | } 91 | 92 | // While on single person, if theme supports People from Church Content 93 | // IMPORTANT: Without ! is_page(), is_singular() runs, somehow causing /page/#/ URL's on static front page to break 94 | if ( ! is_page() && is_singular( 'ctc_person' ) && current_theme_supports( 'ctc-people' ) ) { 95 | 96 | // SQL WHERE 97 | add_filter( 'get_previous_post_where', 'ctfw_previous_post_where' ); 98 | add_filter( 'get_next_post_where', 'ctfw_next_post_where' ); 99 | 100 | // SQL ORDER BY 101 | add_filter( 'get_previous_post_sort', 'ctfw_previous_post_sort' ); 102 | add_filter( 'get_next_post_sort', 'ctfw_next_post_sort' ); 103 | 104 | } 105 | 106 | } 107 | 108 | add_action( 'wp', 'ctfw_previous_next_person_sorting' ); // is_singular() not available until wp action (after posts_selection) 109 | -------------------------------------------------------------------------------- /includes/locations.php: -------------------------------------------------------------------------------- 1 | publish > 1 ) { 106 | $multiple = true; 107 | } 108 | 109 | return $multiple; 110 | 111 | } 112 | -------------------------------------------------------------------------------- /js/jquery.fitvids.js: -------------------------------------------------------------------------------- 1 | /*global jQuery */ 2 | /*jshint browser:true */ 3 | /*! 4 | * FitVids 1.1 5 | * 6 | * Copyright 2013, Chris Coyier - http://css-tricks.com + Dave Rupert - http://daverupert.com 7 | * Credit to Thierry Koblentz - http://www.alistapart.com/articles/creating-intrinsic-ratios-for-video/ 8 | * Released under the WTFPL license - http://sam.zoy.org/wtfpl/ 9 | * 10 | */ 11 | 12 | (function( $ ){ 13 | 14 | "use strict"; 15 | 16 | $.fn.fitVids = function( options ) { 17 | var settings = { 18 | customSelector: null, 19 | ignore: null 20 | }; 21 | 22 | if(!document.getElementById('fit-vids-style')) { 23 | // appendStyles: https://github.com/toddmotto/fluidvids/blob/master/dist/fluidvids.js 24 | var head = document.head || document.getElementsByTagName('head')[0]; 25 | var css = '.fluid-width-video-wrapper{width:100%;position:relative;padding:0;}.fluid-width-video-wrapper iframe,.fluid-width-video-wrapper object,.fluid-width-video-wrapper embed {position:absolute;top:0;left:0;width:100%;height:100%;}'; 26 | var div = document.createElement('div'); 27 | div.innerHTML = '

x

'; 28 | head.appendChild(div.childNodes[1]); 29 | } 30 | 31 | if ( options ) { 32 | $.extend( settings, options ); 33 | } 34 | 35 | return this.each(function(){ 36 | var selectors = [ 37 | "iframe[src*='player.vimeo.com']", 38 | "iframe[src*='youtube.com']", 39 | "iframe[src*='youtube-nocookie.com']", 40 | "iframe[src*='kickstarter.com'][src*='video.html']", 41 | "object", 42 | "embed" 43 | ]; 44 | 45 | if (settings.customSelector) { 46 | selectors.push(settings.customSelector); 47 | } 48 | 49 | var ignoreList = '.fitvidsignore'; 50 | 51 | if(settings.ignore) { 52 | ignoreList = ignoreList + ', ' + settings.ignore; 53 | } 54 | 55 | var $allVideos = $(this).find(selectors.join(',')); 56 | $allVideos = $allVideos.not("object object"); // SwfObj conflict patch 57 | $allVideos = $allVideos.not(ignoreList); // Disable FitVids on this video. 58 | 59 | $allVideos.each(function(){ 60 | var $this = $(this); 61 | if($this.parents(ignoreList).length > 0) { 62 | return; // Disable FitVids on this video. 63 | } 64 | if (this.tagName.toLowerCase() === 'embed' && $this.parent('object').length || $this.parent('.fluid-width-video-wrapper').length) { return; } 65 | if ((!$this.css('height') && !$this.css('width')) && (isNaN($this.attr('height')) || isNaN($this.attr('width')))) 66 | { 67 | $this.attr('height', 9); 68 | $this.attr('width', 16); 69 | } 70 | var height = ( this.tagName.toLowerCase() === 'object' || ($this.attr('height') && !isNaN(parseInt($this.attr('height'), 10))) ) ? parseInt($this.attr('height'), 10) : $this.height(), 71 | width = !isNaN(parseInt($this.attr('width'), 10)) ? parseInt($this.attr('width'), 10) : $this.width(), 72 | aspectRatio = height / width; 73 | if(!$this.attr('id')){ 74 | var videoID = 'fitvid' + Math.floor(Math.random()*999999); 75 | $this.attr('id', videoID); 76 | } 77 | $this.wrap('
').parent('.fluid-width-video-wrapper').css('padding-top', (aspectRatio * 100)+"%"); 78 | $this.removeAttr('height').removeAttr('width'); 79 | }); 80 | }); 81 | }; 82 | // Works with either jQuery or Zepto 83 | })( window.jQuery || window.Zepto ); -------------------------------------------------------------------------------- /js/responsive-embeds.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Responsive Embeds 3 | */ 4 | 5 | // Use FitVids.js for responsive videos and other embeds 6 | // Note: Rdio and Spotify are correct when loading at final size ( browser resize is bad demo ) 7 | ctfw_embed_fitvids_selectors = [ 8 | 9 | // Default (from fitVids.js) 10 | // We redefine these here so they can be hidden prior to FitVids.js 11 | "iframe[src*='player.vimeo.com']", 12 | "iframe[src*='youtube.com']", 13 | "iframe[src*='youtube-nocookie.com']", 14 | "iframe[src*='kickstarter.com'][src*='video.html']", 15 | "object", 16 | "embed", 17 | 18 | // Custom 19 | "iframe[src*='youtu.be']", 20 | "iframe[src*='blip.tv']", 21 | "iframe[src*='hulu.com']", 22 | "iframe[src*='dailymotion.com']", 23 | "iframe[src*='revision3.com']", 24 | "iframe[src*='slideshare.net']", 25 | "iframe[src*='scribed.com']", 26 | "iframe[src*='viddler.com']", 27 | "iframe[src*='rd.io']", 28 | "iframe[src*='rdio.com']", 29 | //"iframe[src*='spotify.com']", // has issues, not needed (see https://dev.churchthemes.local/exodus/sermons/tes/) 30 | "iframe[src*='soundcloud.com']:not([width$='%'])", // Jetpack soundcloud shortcode is already responsive with %, so exclude 31 | "iframe[src*='snd.sc']", 32 | "iframe[src*='livestream.com']", 33 | "iframe[src*='soundfaith.com']", 34 | "iframe[src*='ustream.tv']", 35 | "iframe[src*='sermon.net']:not([src*='/main']):not([src*='.sermon.net/embed'])", 36 | "iframe[src*='read.amazon.com']", 37 | 38 | ]; 39 | ctfw_embed_fitvids_selectors_list = ctfw_embed_fitvids_selectors.join(', '); 40 | 41 | // Other embedded media only need max-width: 100% ( height is static ) - MediaElement.js 42 | // Important: when done via stylesheet, MediaElement.js volume control flickers 43 | ctfw_embed_other_selectors_list = '.wp-video-shortcode, .wp-audio-shortcode'; 44 | 45 | // Hide videos before resizing 46 | // This keeps them from showing briefly at small size before showing at full width 47 | ctfw_embed_all_selectors_list = ctfw_embed_fitvids_selectors_list + ', ' + ctfw_embed_other_selectors_list; 48 | jQuery('head').prepend('' + "\n"); 49 | 50 | // Resize videos to 100% width 51 | jQuery(document).ready(function ($) { 52 | 53 | // Ignore those already being made responsive with WordPress. 54 | if (ctfw_responsive_embeds.wp_responsive_embeds) { 55 | 56 | // Loop selectors 57 | jQuery.each(ctfw_embed_fitvids_selectors, function (i, selector) { 58 | 59 | // Ignore FitVids if WP already making responsive. 60 | if (jQuery(selector).parents('.wp-has-aspect-ratio').length) { 61 | 62 | // Loop each element matching selector. 63 | jQuery.each(jQuery(selector), function (i, element) { 64 | 65 | // Not sermon media element (problem when also have media in content). 66 | if (jQuery(element).parents('[id$=-sermon-video-player]').length) { 67 | return; 68 | } 69 | 70 | jQuery(element).addClass('fitvidsignore'); 71 | 72 | }); 73 | 74 | } 75 | 76 | }); 77 | 78 | } 79 | 80 | // Remove element from Blip.tv (use iframe only) - creates a gap w/FitVid 81 | $("embed[src*='blip.tv']").remove(); 82 | 83 | // FitVids.js for most embeds 84 | $('body').fitVids({ 85 | customSelector: ctfw_embed_fitvids_selectors, 86 | }); 87 | 88 | // Other embeds (MediaElement.js) 89 | $(ctfw_embed_other_selectors_list).css('max-width', '100%'); 90 | 91 | // Show embeds after resize 92 | $('#ctfw-hide-responsive-embeds').remove(); 93 | 94 | 95 | }); 96 | -------------------------------------------------------------------------------- /includes/classes/walker-nav-menu-description.php: -------------------------------------------------------------------------------- 1 | classes ) ? array() : (array) $item->classes; 44 | $classes[] = 'menu-item-' . $item->ID; 45 | 46 | // Modification: Add class to top level links with no descriptions so dropdown can be moved up 47 | if ( 0 == $depth && empty ( $item->description ) ) { 48 | $classes[] = 'ctfw-header-menu-link-no-description'; 49 | } 50 | 51 | $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) ); 52 | $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : ''; 53 | 54 | $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args ); 55 | $id = $id ? ' id="' . esc_attr( $id ) . '"' : ''; 56 | 57 | $output .= $indent . ''; 58 | 59 | $attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : ''; 60 | $attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : ''; 61 | $attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : ''; 62 | $attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : ''; 63 | 64 | $item_output = $args->before; 65 | $item_output .= ''; 66 | 67 | // Original source from WordPress core 68 | //$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after; 69 | 70 | // Modified version of line above 71 | $link_content = apply_filters( 'the_title', $item->title, $item->ID ); 72 | if ( 0 == $depth ) { // top-level links only 73 | $link_content = ''; // wrap title portion 74 | if ( ! empty ( $item->description ) ) { // append description if available 75 | $link_content .= ''; // HTML5 allows div in a 76 | } 77 | $link_content = ''; // wrap title and description in inner container 78 | } 79 | $item_output .= $args->link_before . $link_content . $args->link_after; 80 | 81 | $item_output .= ''; 82 | $item_output .= $args->after; 83 | 84 | $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args ); 85 | 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /css/admin-widgets.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Admin Widgets 3 | * 4 | * Appearance > Widgets and Customizer's widget controls both use these styles. 5 | */ 6 | 7 | /* Field Container */ 8 | 9 | .widget-content { 10 | margin-top: 12px; 11 | } 12 | 13 | /* Fields */ 14 | 15 | .ctfw-widget-field { 16 | margin-bottom: 12px; 17 | } 18 | 19 | .ctfw-widget-field.ctfw-widget-no-bottom-margin { 20 | margin-bottom: 0; 21 | } 22 | 23 | .ctfw-widget-name { 24 | margin-top: 3px; 25 | } 26 | 27 | .ctfw-widget-name span { /* (Optional) or (Required) */ 28 | color: #595959; 29 | font-weight: normal; 30 | } 31 | 32 | 33 | .ctfw-widget-value input[type=text], /* can also use widefat class for 100% */ 34 | .ctfw-widget-value input[type=url], 35 | .ctfw-widget-value textarea { 36 | width: 100%; 37 | } 38 | 39 | .ctfw-widget-value textarea { 40 | height: 56px; 41 | } 42 | 43 | .ctfw-widget-value textarea.ctfw-widget-medium { 44 | height: 100px; 45 | } 46 | 47 | .ctfw-widget-value textarea.ctfw-widget-large { 48 | height: 200px; 49 | } 50 | 51 | .ctfw-widget-value select { 52 | max-width: 99%; 53 | } 54 | 55 | input[type=text].ctfw-widget-small, 56 | input[type=url].ctfw-widget-small, 57 | textarea.ctfw-widget-small { 58 | width: 100px; 59 | } 60 | 61 | input[type=text].ctfw-widget-tiny, 62 | input[type=url].ctfw-widget-tiny, 63 | textarea.ctfw-widget-tiny { 64 | width: 40px; 65 | } 66 | 67 | .ctfw-widget-radio-inline { 68 | display: inline-block; 69 | margin: 2px 12px 0 0; 70 | } 71 | 72 | .ctfw-widget-radio-inline:last-child { 73 | margin-right: 0; 74 | } 75 | 76 | .ctfw-widget-value .description { 77 | margin-top: 6px !important; 78 | padding: 0 !important; 79 | } 80 | 81 | /* Hidden Fields */ 82 | 83 | .ctfw-widget-hidden { 84 | display: none; 85 | } 86 | 87 | /* Image Field */ 88 | 89 | .ctfw-widget-image-preview img { 90 | max-width: 100%; 91 | height: auto; 92 | margin-top: 4px; 93 | } 94 | 95 | .ctfw-widget-image-choose, 96 | .ctfw-widget-image-remove { 97 | display: inline-block; 98 | margin-top: 4px !important; 99 | } 100 | 101 | .ctfw-widget-image-choose { 102 | margin-right: 5px !important; 103 | } 104 | 105 | .ctfw-widget-image-unset .ctfw-widget-image-preview, 106 | .ctfw-widget-image-unset .ctfw-widget-image-remove { 107 | display: none; 108 | } 109 | 110 | /* Upload Button */ 111 | 112 | .ctfw-widget-upload-file { 113 | margin-top: 4px !important; 114 | } 115 | 116 | /* Widget/Sidebar Restrictions */ 117 | 118 | .ctfw-widget-incompatible { 119 | display: none; /* initially */ 120 | margin: 12px 0; 121 | color: #a8010b; 122 | } 123 | 124 | /** 125 | * Customizer Dashicons 126 | * 127 | * http://melchoyce.github.io/dashicons/ 128 | * 129 | * WordPress does some of this automatically in wp-admin/css/customize-widgets 130 | * Here we're filling in the blanks or improving 131 | */ 132 | 133 | #available-widgets [class*="ctfw-highlight"] .widget-title:before { 134 | content: "\f128"; /* dashicon-format-image */ 135 | } 136 | 137 | #available-widgets [class*="ctfw-sermons"] .widget-title:before { 138 | content: "\f236"; /* dashicon-video-alt3 */ 139 | } 140 | 141 | #available-widgets [class*="ctfw-people"] .widget-title:before { 142 | content: "\f307"; /* dashicon-groups */ 143 | } 144 | 145 | #available-widgets [class*="ctfw-gallery"] .widget-title:before { 146 | content: "\f232"; /* dashicon-images-alt */ 147 | } 148 | 149 | #available-widgets [class*="ctfw-giving"] .widget-title:before { 150 | content: "\f174"; /* dashicon-cart */ 151 | } 152 | -------------------------------------------------------------------------------- /includes/admin/activation.php: -------------------------------------------------------------------------------- 1 | true, 27 | * 'replace_notice' => sprintf( __( 'Please follow the Next Steps now that the theme has been activated.', 'your-theme-textdomain' ), 'https://churchthemes.com/guides/user/getting-started/' ) 28 | * ) ); 29 | * 30 | * This does not affect the Customizer preview. 31 | * 32 | * @since 0.9 33 | */ 34 | function ctfw_after_activation() { 35 | 36 | // Does theme support this? 37 | $support = get_theme_support( 'ctfw-after-activation' ); 38 | if ( $support ) { 39 | 40 | // What to do 41 | $activation_tasks = isset( $support[0] ) ? $support[0] : array(); 42 | 43 | // Update .htaccess to make sure friendly URL's are in working order 44 | if ( ! empty( $activation_tasks['flush_rewrite_rules'] ) ) { 45 | flush_rewrite_rules(); 46 | } 47 | 48 | // Show notice to user 49 | if ( ! empty( $activation_tasks['notice'] ) ) { 50 | 51 | add_action( 'admin_notices', 'ctfw_activation_notice', 5 ); // show above other notices 52 | 53 | // Hide default notice 54 | if ( ! empty( $activation_tasks['hide_default_notice'] ) ) { 55 | add_action( 'admin_head', 'ctfw_hide_default_activation_notice' ); 56 | } 57 | 58 | // Remove other notices when showing activation notice -- keep it simple 59 | ctfw_activation_remove_notices(); 60 | 61 | } 62 | 63 | } 64 | 65 | } 66 | 67 | add_action( 'after_switch_theme', 'ctfw_after_activation' ); 68 | 69 | /******************************************** 70 | * NOTICES 71 | ********************************************/ 72 | 73 | /** 74 | * Message to show to user after activation 75 | * 76 | * Hooked in ctfw_after_activation(). 77 | * 78 | * @since 0.9 79 | */ 80 | function ctfw_activation_notice() { 81 | 82 | // Get notice if supported by theme 83 | $support = get_theme_support( 'ctfw-after-activation' ); 84 | $notice = ! empty( $support[0]['notice'] ) ? $support[0]['notice'] : ''; 85 | 86 | // Show notice if have it 87 | if ( $notice ) { 88 | 89 | ?> 90 |
91 |

92 | 93 |

94 |
95 | #message2{ display: none; }'; 109 | 110 | } 111 | 112 | /** 113 | * Remove activation notices 114 | * 115 | * Remove all other notices from theme as not to overwhelm user on activation. 116 | * This way only the success (go to Next Steps) or fail (old WP version) notice for activation shows. 117 | * 118 | * See ctfw_after_activation() above and compatibility.php for where this is used. 119 | * 120 | * @since 0.9.1 121 | */ 122 | function ctfw_activation_remove_notices() { 123 | 124 | remove_action( 'admin_notices', 'ctfw_edd_license_notice', 7 ); // Theme License 125 | remove_action( 'admin_notices', 'ctfw_ctc_plugin_notice' ); // Church Content 126 | 127 | } 128 | -------------------------------------------------------------------------------- /includes/page-nav.php: -------------------------------------------------------------------------------- 1 | '; 67 | } else { 68 | $op = '<'; 69 | } 70 | 71 | // SQL WHERE 72 | // Note that Order may not be a unique value, so in that case sorting by ID is also done. 73 | // Otherwise people with same Order would get skipped over. More details: http://bit.ly/15pUv2j 74 | $where = $wpdb->prepare( 75 | "WHERE 76 | ( 77 | ( 78 | p.menu_order = %s 79 | AND p.ID $op %d 80 | ) 81 | OR p.menu_order $op %s 82 | ) 83 | AND p.post_type = %s 84 | AND p.post_status = 'publish' 85 | ", 86 | $post->menu_order, 87 | get_the_ID(), 88 | $post->menu_order, 89 | get_post_type() 90 | ); 91 | 92 | return $where; 93 | 94 | } 95 | 96 | /** 97 | * SQL WHERE for manual order of previous post 98 | * 99 | * @since 0.9.1 100 | * @param string $where Current WHERE clause 101 | * @return string Custom WHERE clause 102 | */ 103 | function ctfw_previous_post_where( $where ) { 104 | return ctfw_previous_next_post_where( 'previous' ); 105 | } 106 | 107 | /** 108 | * SQL WHERE for manual order of for next post 109 | * 110 | * @since 0.9.1 111 | * @param string $where Current WHERE clause 112 | * @return string Custom WHERE clause 113 | */ 114 | function ctfw_next_post_where( $where ) { 115 | return ctfw_previous_next_post_where( 'next' ); 116 | } 117 | 118 | /** 119 | * SQL ORDER BY for manual order of previous post 120 | * 121 | * @since 0.9.1 122 | * @param string $sort Current ORDER BY clause 123 | * @return string Custom ORDER BY clause 124 | */ 125 | function ctfw_previous_post_sort( $sort ) { 126 | return "ORDER BY p.menu_order ASC LIMIT 1"; 127 | } 128 | 129 | /** 130 | * SQL ORDER BY for manual order of next post 131 | * 132 | * @since 0.9.1 133 | * @param string $sort Current ORDER BY clause 134 | * @return string Custom ORDER BY clause 135 | */ 136 | function ctfw_next_post_sort( $sort ) { 137 | return "ORDER BY p.menu_order DESC LIMIT 1"; 138 | } -------------------------------------------------------------------------------- /includes/pages.php: -------------------------------------------------------------------------------- 1 | 'page', 51 | 'nopaging' => true, 52 | 'posts_per_page' => 1, 53 | 'meta_key' => '_wp_page_template', 54 | 'meta_value' => $template, 55 | 'orderby' => 'ID', 56 | 'order' => 'DESC', 57 | 'no_found_rows' => true, // faster (no pagination) 58 | ) ); 59 | 60 | // Got one? 61 | if ( ! empty( $page_query->post ) ) { 62 | $page = $page_query->post; 63 | break; // if not check next template 64 | } 65 | 66 | } 67 | 68 | return apply_filters( 'ctfw_get_page_by_template', $page, $templates ); 69 | 70 | } 71 | 72 | /** 73 | * Get page ID by template 74 | * 75 | * Get newest page ID using a specific template file name. 76 | * 77 | * Multiple templates can be specified as array and the first match will be used. 78 | * This is handy when one template rather than the usual is used for primary content. 79 | * 80 | * @since 0.9 81 | * @param string|array $templates Template or array of templates (first match used) 82 | * @return int Page ID 83 | */ 84 | function ctfw_get_page_id_by_template( $templates ) { 85 | 86 | $page = ctfw_get_page_by_template( $templates ); 87 | 88 | $page_id = ! empty( $page->ID ) ? $page->ID : ''; 89 | 90 | return apply_filters( 'ctfw_get_page_id_by_template', $page_id, $templates ); 91 | 92 | } 93 | 94 | 95 | /** 96 | * Get page URL by template 97 | * 98 | * @since 1.7.1 99 | * @param string|array $templates Template or array of templates (first match used) 100 | * @return int Page URL, if page was found; otherwise empty 101 | */ 102 | function ctfw_get_page_url_by_template( $templates ) { 103 | 104 | $url = ''; 105 | 106 | $page = ctfw_get_page_by_template( $templates ); 107 | 108 | if ( $page ) { 109 | $url = get_permalink( $page ); 110 | } 111 | 112 | return apply_filters( 'ctfw_get_page_url_by_template', $url, $templates ); 113 | 114 | } 115 | 116 | /** 117 | * Page options 118 | * 119 | * Handy for making select options 120 | * 121 | * @since 0.9 122 | * @param bool $allow_none Whether or not to include option for none 123 | * @return array Page options 124 | */ 125 | function ctfw_page_options( $allow_none = true ) { 126 | 127 | $pages = get_pages( array( 128 | 'hierarchical' => false, 129 | ) ); 130 | 131 | $page_options = array(); 132 | 133 | if ( ! empty( $allow_none ) ) { 134 | $page_options[] = ''; 135 | } 136 | 137 | foreach ( $pages as $page ) { 138 | $page_options[$page->ID] = $page->post_title; 139 | } 140 | 141 | return $page_options; 142 | 143 | } 144 | -------------------------------------------------------------------------------- /includes/background.php: -------------------------------------------------------------------------------- 1 | $data ) { 44 | 45 | if ( ! empty( $data['thumb'] ) ) { 46 | 47 | // Thumbnail 48 | $backgrounds_clean[$file]['thumb'] = $data['thumb']; 49 | 50 | // WordPress core "Presets" link(); ?> value="" /> 39 | 40 | 43 | 44 |
    45 | 46 | $data ) { 50 | 51 | // URLs 52 | $url = $data['url']; // no SSL, save the same always 53 | $thumbnail_url = set_url_scheme( $data['thumb_url'] ); 54 | 55 | // Classes array 56 | $classes = array(); 57 | 58 | // Selected class 59 | if ( $value == $url ) { 60 | $classes[] = 'ctfw-customize-image-preset-selected'; 61 | } 62 | 63 | // Build class attribute 64 | $class_attr = ''; 65 | if ( $classes ) { 66 | $class_attr = 'class="' . esc_attr( implode( ' ', $classes ) ) . '" '; 67 | } 68 | 69 | ?> 70 | 71 |
  • data-customize-image-title="" 72 | data-customize-image-value="" 73 | data-customize-image-preset-preset="" 74 | data-customize-image-preset-colorable=""> 75 |
    76 |
  • 77 | 78 | 83 | 84 |
85 | 86 | 89 | 90 | presets = isset( $args['presets'] ) ? $args['presets'] : array(); 121 | 122 | // Add presets tab (multiple) 123 | if ( $this->presets ) { 124 | $this->add_tab( 'ctfw_presets', _x( 'Presets', 'customizer', 'church-theme-framework' ), array( $this, 'tab_preset_backgrounds' ) ); 125 | } 126 | 127 | // Remove "Default" tab - user can select first in presets 128 | $this->remove_tab( 'default' ); 129 | 130 | } 131 | 132 | /** 133 | * List multiple presets 134 | * 135 | * @since 0.9 136 | */ 137 | public function tab_preset_backgrounds() { 138 | 139 | foreach ( $this->presets as $file => $data ) { 140 | 141 | // Output mimics print_tab_image() output but adds preset data attributes 142 | 143 | $url = set_url_scheme( $data['url'] ); 144 | $thumbnail_url = set_url_scheme( $data['thumb_url'] ); 145 | 146 | ?> 147 | data-customize-image-title="" 148 | data-customize-image-value="" 149 | data-customize-image-preset-fullscreen="" 150 | data-customize-image-preset-repeat="" 151 | data-customize-image-preset-position="" 152 | data-customize-image-preset-attachment="" 153 | data-customize-image-preset-colorable=""> 154 |
155 |
156 | id ); 180 | 181 | ?> 182 | 186 | 206 | 210 | '', 40 | 'request_data' => array(), 41 | 'theme_slug' => get_template(), // use get_stylesheet() for child theme updates 42 | 'item_name' => '', 43 | 'license' => '', 44 | 'version' => '', 45 | 'author' => '', 46 | 'beta' => false, 47 | ); 48 | 49 | $args = wp_parse_args($args, $defaults); 50 | 51 | $this->license = $args['license']; 52 | $this->item_name = $args['item_name']; 53 | $this->version = $args['version']; 54 | $this->theme_slug = sanitize_key($args['theme_slug']); 55 | $this->author = $args['author']; 56 | $this->beta = $args['beta']; 57 | $this->remote_api_url = $args['remote_api_url']; 58 | $this->response_key = $this->theme_slug . '-' . $this->beta . '-update-response'; 59 | $this->strings = $strings; 60 | 61 | add_filter('site_transient_update_themes', array($this, 'theme_update_transient')); 62 | add_filter('delete_site_transient_update_themes', array($this, 'delete_theme_update_transient')); 63 | add_action('load-update-core.php', array($this, 'delete_theme_update_transient')); 64 | add_action('load-themes.php', array($this, 'delete_theme_update_transient')); 65 | add_action('load-themes.php', array($this, 'load_themes_screen')); 66 | } 67 | 68 | /** 69 | * Show the update notification when neecessary 70 | * 71 | * @return void 72 | */ 73 | function load_themes_screen() 74 | { 75 | add_thickbox(); 76 | add_action('admin_notices', array($this, 'update_nag')); 77 | } 78 | 79 | /** 80 | * Display the update notifications 81 | * 82 | * @return void 83 | */ 84 | function update_nag() 85 | { 86 | 87 | $strings = $this->strings; 88 | $theme = wp_get_theme($this->theme_slug); 89 | $api_response = get_transient($this->response_key); 90 | 91 | if (false === $api_response) { 92 | return; 93 | } 94 | 95 | $update_url = wp_nonce_url('update.php?action=upgrade-theme&theme=' . urlencode($this->theme_slug), 'upgrade-theme_' . $this->theme_slug); 96 | $update_onclick = ' onclick="if ( confirm(\'' . esc_js($strings['update-notice']) . '\') ) {return true;}return false;"'; 97 | 98 | if (version_compare($this->version, $api_response->new_version, '<')) { 99 | 100 | echo '
'; 101 | printf( 102 | $strings['update-available'], 103 | $theme->get('Name'), 104 | $api_response->new_version, 105 | '#TB_inline?width=640&inlineId=' . $this->theme_slug . '_changelog', 106 | $theme->get('Name'), 107 | $update_url, 108 | $update_onclick 109 | ); 110 | echo '
'; 111 | echo ''; 114 | } 115 | } 116 | 117 | /** 118 | * Update the theme update transient with the response from the version check 119 | * 120 | * @param array $value The default update values. 121 | * @return array|boolean If an update is available, returns the update parameters, if no update is needed returns false, if 122 | * the request fails returns false. 123 | */ 124 | function theme_update_transient($value) 125 | { 126 | $update_data = $this->check_for_update(); 127 | if ($update_data) { 128 | if (empty($value)) { 129 | $value = new stdClass(); 130 | } 131 | $value->response[$this->theme_slug] = $update_data; 132 | } 133 | return $value; 134 | } 135 | 136 | /** 137 | * Remove the update data for the theme 138 | * 139 | * @return void 140 | */ 141 | function delete_theme_update_transient() 142 | { 143 | delete_transient($this->response_key); 144 | } 145 | 146 | /** 147 | * Call the EDD SL API (using the URL in the construct) to get the latest version information 148 | * 149 | * @return array|boolean If an update is available, returns the update parameters, if no update is needed returns false, if 150 | * the request fails returns false. 151 | */ 152 | function check_for_update() 153 | { 154 | 155 | $update_data = get_transient($this->response_key); 156 | 157 | if (false === $update_data) { 158 | $failed = false; 159 | 160 | $remote_api_url = ctfw_edd_license_config('remote_api_url'); 161 | $remote_api_url = trailingslashit($remote_api_url); 162 | $response = wp_remote_post($remote_api_url, [ 163 | 'body' => [ 164 | 'edd_action' => 'get_version', 165 | 'license' => $this->license, 166 | 'name' => $this->item_name, 167 | 'slug' => $this->theme_slug, 168 | 'version' => $this->version, 169 | 'author' => $this->author, 170 | 'beta' => $this->beta, 171 | 'no_cache' => md5(microtime(true)), 172 | ], 173 | 'timeout' => 15, 174 | 'sslverify' => true, 175 | ]); 176 | 177 | // Make sure the response was successful 178 | if (is_wp_error($response) || 200 != wp_remote_retrieve_response_code($response)) { 179 | $failed = true; 180 | } 181 | 182 | $update_data = json_decode(wp_remote_retrieve_body($response)); 183 | 184 | if (! is_object($update_data)) { 185 | $failed = true; 186 | } 187 | 188 | // If the response failed, try again in 30 minutes 189 | if ($failed) { 190 | $data = new stdClass(); 191 | $data->new_version = $this->version; 192 | set_transient($this->response_key, $data, strtotime('+30 minutes', current_time('timestamp'))); 193 | return false; 194 | } 195 | 196 | // If the status is 'ok', return the update arguments 197 | if (! $failed) { 198 | $update_data->sections = maybe_unserialize($update_data->sections); 199 | set_transient($this->response_key, $update_data, strtotime('+12 hours', current_time('timestamp'))); 200 | } 201 | } 202 | 203 | if (version_compare($this->version, $update_data->new_version, '>=')) { 204 | return false; 205 | } 206 | 207 | return (array) $update_data; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /includes/images.php: -------------------------------------------------------------------------------- 1 | post_type ) && ! empty( $post_types[$screen->post_type] ) ) { 64 | 65 | // Get post type data 66 | $post_type_data = $post_types[$screen->post_type]; 67 | 68 | // Size and specific note specified. 69 | if ( is_array( $post_type_data ) && ! empty( $post_type_data[0] ) && ! empty( $post_type_data[1] ) ) { 70 | $size = $post_type_data[0]; 71 | $note = $post_type_data[1]; 72 | } 73 | 74 | // Only size specified (use default note). 75 | elseif ( ! empty( $post_types[$screen->post_type] ) ) { 76 | $size = $post_types[$screen->post_type]; 77 | $note = ! empty( $support[1] ) ? $support[1] : __( 'The target image size is %s.', 'church-theme-framework' ); // third argument, if any 78 | } 79 | 80 | // Build note. 81 | if ( isset( $size ) && isset( $note ) ) { 82 | 83 | // Get dimensions for size. 84 | $dimensions = ctfw_image_size_dimensions( $size ); 85 | if ( $dimensions ) { 86 | 87 | // Add dimensions to note. 88 | $note = sprintf( $note, $dimensions ); 89 | 90 | } 91 | 92 | } 93 | 94 | // Empty note. 95 | else { 96 | $note = ''; 97 | } 98 | 99 | } 100 | 101 | } 102 | 103 | // Return note. 104 | return apply_filters( 'ctfw_featured_image_note', $note ); 105 | 106 | } 107 | 108 | /** 109 | * Add note below Featured Image 110 | * 111 | * Each post type has a recommended image size. 112 | * 113 | * Note: This only affects classic editor. Gutenberg is handled by admin-post.js. 114 | * 115 | * @since 0.9 116 | * @param string $content Featured image meta box content 117 | * @return string Content with image size note appended 118 | */ 119 | function ctfw_featured_image_notes( $content ) { 120 | 121 | // Get note for current post type. 122 | // This only returns a value when ctfw-featured-image-notes feature supported. 123 | $note = ctfw_featured_image_note(); 124 | 125 | // Have note. 126 | if ( $note ) { 127 | 128 | // Apply the note for the appropriate post type 129 | $content .= '

' . esc_html( $note ) . '

'; 130 | 131 | } 132 | 133 | // Return content with note appended. 134 | return $content; 135 | 136 | } 137 | 138 | add_filter( 'admin_post_thumbnail_html', 'ctfw_featured_image_notes' ); 139 | 140 | /*********************************************** 141 | * RESIZING 142 | ***********************************************/ 143 | 144 | /** 145 | * Enable upscaling of images 146 | * 147 | * Normally WordPress will only generate resized/cropped images if the source is larger than the target. 148 | * This forces an image to be made for all sizes, even if the source is smaller than the target. 149 | * This makes responsive images work more consistently (automatic height via CSS, for example). 150 | * 151 | * This code is based on the core image_resize_dimensions() function in wp-content/media.php. 152 | * 153 | * Note: This framework feature must be enabled using add_theme_support( 'ctfw-image-upscaling' ) 154 | * 155 | * Second argument is array of arguments. You can exclude upscaling for certain image size names. 156 | * This is helpful for make sure a large background image version is not generated for every image uploaded. 157 | * 158 | * add_theme_support( 'ctfw-image-upscaling', array( 159 | * 'excluded_sizes' => array( 'themename-sizename' ), 160 | * ) ); 161 | * 162 | * @since 0.9 163 | * @param array $output Current output 164 | * @param int $orig_w Original width 165 | * @param int $orig_h Original height 166 | * @param int $dest_w New width 167 | * @param int $dest_h New height 168 | * @param bool $crop Whether or not to crop 169 | * @return array Modified $output 170 | * @global array $_wp_additional_image_sizes 171 | */ 172 | function ctfw_image_resize_dimensions_upscale( $output, $orig_w, $orig_h, $dest_w, $dest_h, $crop ) { 173 | 174 | global $_wp_additional_image_sizes; 175 | 176 | $support = get_theme_support( 'ctfw-image-upscaling' ); 177 | 178 | // force upscaling if theme supports it and crop is being done 179 | // otherwise $output = null causes regular behavior 180 | if ( $support && $crop ) { 181 | 182 | // check if size is excluded 183 | $exclude = false; 184 | if ( ! empty( $support[0]['excluded_sizes'] ) ) { 185 | 186 | $excluded_sizes = (array) $support[0]['excluded_sizes']; 187 | 188 | foreach ( $excluded_sizes as $excluded_size ) { 189 | 190 | // Stop upscaling if excluded image size matches destination size 191 | if ( $_wp_additional_image_sizes[$excluded_size]['width'] == $dest_w && $_wp_additional_image_sizes[$excluded_size]['height'] == $dest_h ) { 192 | 193 | $exclude = true; 194 | 195 | break; 196 | 197 | } 198 | 199 | } 200 | 201 | } 202 | 203 | // force upscaling 204 | if ( ! $exclude ) { 205 | 206 | // resize to target dimensions, upscaling if necessary 207 | $new_w = $dest_w; 208 | $new_h = $dest_h; 209 | 210 | $size_ratio = max( $new_w / $orig_w, $new_h / $orig_h ); 211 | 212 | $crop_w = round( $new_w / $size_ratio ); 213 | $crop_h = round( $new_h / $size_ratio ); 214 | 215 | $s_x = floor( ( $orig_w - $crop_w ) / 2 ); 216 | $s_y = floor( ( $orig_h - $crop_h ) / 2 ); 217 | 218 | // the return array matches the parameters to imagecopyresampled() 219 | // int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h 220 | $output = array( 0, 0, (int) $s_x, (int) $s_y, (int) $new_w, (int) $new_h, (int) $crop_w, (int) $crop_h ); 221 | 222 | } 223 | 224 | } 225 | 226 | return $output; 227 | 228 | } 229 | 230 | add_filter( 'image_resize_dimensions', 'ctfw_image_resize_dimensions_upscale', 10, 6 ); 231 | -------------------------------------------------------------------------------- /includes/sidebars.php: -------------------------------------------------------------------------------- 1 | $widgets ) { 103 | 104 | // Any widgets? 105 | if ( empty( $widgets ) ) { 106 | continue; 107 | } 108 | 109 | // Leave core sidebars like "Inactive" alone 110 | if ( preg_match( '/^wp_/', $sidebar_id ) ) { 111 | continue; 112 | } 113 | 114 | // Sidebar widget restrictions 115 | // (used for checking limit) 116 | $sidebar_widget_restrictions = ctfw_get_sidebar_widget_restrictions( 'sidebar_widget' ); 117 | $sidebar_limit = ! empty( $sidebar_widget_restrictions[$sidebar_id]['limit'] ) ? $sidebar_widget_restrictions[$sidebar_id]['limit'] : false; 118 | 119 | // Loop widgets in sidebar 120 | $widget_i = 0; 121 | foreach ( $widgets as $widget_key => $widget ) { 122 | 123 | $widget_i++; 124 | 125 | $remove = false; 126 | 127 | // Determine widget id of this instance 128 | $widget_id = substr( $widget, 0, strrpos( $widget, '-') ); // chop -# instance off end 129 | 130 | // Remove if either disallows the other 131 | // Front-end only because this can cause JavaScript error with WordPress 3.9 Customizer when "Add a Widget" clicked 132 | // The user will always see "Not compatible" in widget editors until they remove, but will never show on frontend 133 | if ( ! is_admin() && ! ctfw_sidebar_widget_compatible( $sidebar_id, $widget_id ) ) { 134 | $remove = true; 135 | } 136 | 137 | // Remove if widget limit for sidebar has been reached 138 | // Front-end only since no warning shown and don't want re-arranging to cause loss 139 | if ( ! is_admin() && $sidebar_limit && $widget_i > $sidebar_limit ) { 140 | $remove = true; 141 | } 142 | 143 | // Remove widget from sidebar 144 | if ( $remove ) { 145 | unset( $sidebars_widgets[$sidebar_id][$widget_key] ); 146 | } 147 | 148 | } 149 | 150 | } 151 | 152 | // Re-index so keys are 0, 1, 2, etc. (fill in the gaps from unset) 153 | if ( isset( $sidebars_widgets[$sidebar_id] ) ) { 154 | $sidebars_widgets[$sidebar_id] = array_values( $sidebars_widgets[$sidebar_id] ); 155 | } 156 | 157 | // Return pruned array 158 | return $sidebars_widgets; 159 | 160 | } 161 | 162 | add_filter( 'sidebars_widgets', 'ctfw_restrict_sidebars_widgets', 5 ); 163 | 164 | /********************************** 165 | * SIDEBAR DATA 166 | **********************************/ 167 | 168 | /** 169 | * Set current sidebar ID 170 | * 171 | * See ctfw_is_sidebar() in conditions.php which uses this global. 172 | * 173 | * @since 2.0 174 | * @param string $index Sidebar ID 175 | */ 176 | 177 | function ctfw_set_current_sidebar_id( $index ) { 178 | 179 | global $ctfw_current_sidebar_id; 180 | 181 | if ( ! empty( $index ) ) { 182 | $ctfw_current_sidebar_id = $index; 183 | } 184 | 185 | } 186 | 187 | add_action( 'dynamic_sidebar_before', 'ctfw_set_current_sidebar_id' ); 188 | 189 | /** 190 | * Unset current sidebar ID 191 | * 192 | * We unset so that this bool is not true even after leave the sidebar. 193 | * 194 | * See ctfw_set_current_sidebar_id() above for more. 195 | * 196 | * @since 2.0 197 | * @param string $index Sidebar ID 198 | */ 199 | function ctfw_unset_current_sidebar_id() { 200 | 201 | global $ctfw_current_sidebar_id; 202 | 203 | if ( isset( $ctfw_current_sidebar_id ) ) { 204 | unset( $ctfw_current_sidebar_id ); 205 | } 206 | 207 | } 208 | 209 | add_action( 'dynamic_sidebar_after', 'ctfw_unset_current_sidebar_id' ); 210 | -------------------------------------------------------------------------------- /includes/deprecated.php: -------------------------------------------------------------------------------- 1 | tag to be friendly 99 | * 100 | * An SEO plugin can be used to fine-tune the for various areas of the site. 101 | * 102 | * Deprecation: add_theme_support( 'title-tag' ) in WordPress 4.1 handles this nicely. 103 | * 104 | * @since 0.9 105 | * @deprecated 1.7.3 106 | * @param string $title Page title determined by WordPress core 107 | * @param string $sep Optional, default is '»'. How to separate the various items within the page title. 108 | * @param string $seplocation Optional. Direction to display title, 'right'. 109 | * @return string Formatted title 110 | */ 111 | function ctfw_head_title( $title, $sep, $seplocation ) { 112 | 113 | $new_title = $title; 114 | 115 | if ( current_theme_supports( 'ctfw-auto-title' ) ) { 116 | 117 | _deprecated_function( __FUNCTION__, '1.7.3', "add_theme_support( 'title-tag' )" ); 118 | 119 | // Feed 120 | if ( is_feed() ) { 121 | return $title; 122 | } 123 | 124 | // Page number 125 | $page_number = ''; 126 | $page = ctfw_page_num(); 127 | if ( $page > 1 ) { 128 | /* translators: page number in <title> */ 129 | $page_number = sprintf( _x( ' (Page %d)', 'head title', 'church-theme-framework' ), $page ); 130 | } 131 | 132 | // Homepage (site name - tagline ) 133 | if ( is_front_page() ) { 134 | $before = get_bloginfo( 'name', 'display' ); 135 | $after = $page <= 1 ? get_bloginfo( 'description', 'display' ) : ''; // show tagline if on first page (not showing page number) 136 | } 137 | 138 | // Subpage (page title - site name) 139 | else { 140 | $before = $title; 141 | $after = get_bloginfo( 'name' ); 142 | } 143 | 144 | // Build title 145 | $before = trim( $before ) . $page_number; 146 | $after = trim( $after ); 147 | $new_title = $before; 148 | if ( $after ) { 149 | /* translators: separator for <title> content */ 150 | $new_title .= _x( ' - ', 'head title', 'church-theme-framework' ) . $after; 151 | } 152 | 153 | } 154 | 155 | return $new_title; 156 | 157 | } 158 | 159 | add_filter( 'wp_title', 'ctfw_head_title', 10, 3 ); 160 | 161 | /** 162 | * Remove custom background from admin menu 163 | * 164 | * Note: This only has effect on WordPRess 4.0 and earlier because 4.1 removed background image screen 165 | * 166 | * Use add_theme_support( 'ctfw-force-customizer-background' ) to force users to edit 167 | * the custom background via the Customizer. 168 | * 169 | * @since 0.9 170 | */ 171 | function ctfw_admin_remove_menu_pages() { 172 | 173 | global $menu; 174 | 175 | // If theme supports this 176 | if ( current_theme_supports( 'ctfw-force-customizer-background' ) ) { 177 | 178 | _deprecated_function( __FUNCTION__, '1.7.3' ); 179 | 180 | if ( version_compare( get_bloginfo( 'version' ), '4.1', '<' ) ) { 181 | 182 | // Remove background link 183 | // Encourage access by Theme Customizer since it has Fullscreen and Preset enhancements 184 | remove_submenu_page( 'themes.php', 'custom-background' ); 185 | 186 | } 187 | 188 | } 189 | 190 | } 191 | 192 | add_action( 'admin_menu', 'ctfw_admin_remove_menu_pages', 11 ); // after add theme support for background 193 | 194 | /** 195 | * Redirect custom background to Customizer 196 | * 197 | * Note: This only has effect on WordPRess 4.0 and earlier because 4.1 removed background image screen 198 | * 199 | * Use add_theme_support( 'ctfw-force-customizer-background' ) to force users to edit 200 | * the custom background via the Customizer when trying to use background image screen. 201 | * 202 | * @since 0.9 203 | */ 204 | function ctfw_admin_redirect_background() { 205 | 206 | // If theme supports this 207 | if ( current_theme_supports( 'ctfw-force-customizer-background' ) ) { 208 | 209 | _deprecated_function( __FUNCTION__, '1.7.3' ); 210 | 211 | // We're on custom background page 212 | if ( 'themes.php?page=custom-background' == basename( $_SERVER['REQUEST_URI'] ) ) { 213 | 214 | // Redirect to Theme Customizer 215 | wp_redirect( admin_url( 'customize.php' ) ); 216 | exit; 217 | 218 | } 219 | 220 | } 221 | 222 | } 223 | 224 | add_action( 'admin_init', 'ctfw_admin_redirect_background' ); 225 | 226 | /** 227 | * @since 0.9 228 | * @deprecated 1.3 229 | */ 230 | function ctfw_edd_license_check_deactivation() { 231 | 232 | _deprecated_function( __FUNCTION__, '1.3', 'ctfw_edd_license_sync()' ); 233 | 234 | ctfw_edd_license_sync(); 235 | 236 | } 237 | 238 | /** 239 | * @since 0.9 240 | * @deprecated 1.3 241 | */ 242 | function ctfw_edd_license_auto_check_deactivation() { 243 | 244 | _deprecated_function( __FUNCTION__, '1.3', 'ctfw_edd_license_auto_sync()' ); 245 | 246 | ctfw_edd_license_sync(); 247 | 248 | } 249 | -------------------------------------------------------------------------------- /includes/colors.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Color Functions 4 | * 5 | * @package Church_Theme_Framework 6 | * @subpackage Functions 7 | * @copyright Copyright (c) 2013 - 2019, ChurchThemes.com, LLC 8 | * @link https://github.com/churchthemes/church-theme-framework 9 | * @license GPLv2 or later 10 | * @since 0.9 11 | */ 12 | 13 | // No direct access. 14 | if ( ! defined( 'ABSPATH' ) ) { 15 | exit; 16 | } 17 | 18 | /******************************************* 19 | * COLOR SCHEMES 20 | *******************************************/ 21 | 22 | /** 23 | * Return array of color schemes from colors directory 24 | * 25 | * If styles exist in child theme then use those instead of parent 26 | * 27 | * @since 0.9 28 | * @return array Color schemes 29 | */ 30 | function ctfw_colors() { 31 | 32 | $parent_styles_dir = CTFW_THEME_PATH . '/' . CTFW_THEME_COLOR_DIR; // parent theme 33 | $child_styles_dir = CTFW_THEME_CHILD_PATH . '/' . CTFW_THEME_COLOR_DIR; 34 | $styles_dir = file_exists( $child_styles_dir ) ? $child_styles_dir : $parent_styles_dir; // if a styles dir was made for child theme, use it 35 | 36 | $colors = array(); 37 | if ( file_exists( $styles_dir ) && $handle = opendir( $styles_dir ) ) { // if colors directory exists in child theme, use it 38 | while ( false !== ( $entry = readdir($handle) ) ) { // loop style schemes available in style directory 39 | if ( ! preg_match( '/\./', $entry ) ) { // directories only 40 | $style_name = str_replace( array( '-', '_' ), ' ', $entry ); // replace - and _ with space 41 | $style_name = ucwords( $style_name ); // capitalize words 42 | $colors[$entry] = $style_name; 43 | } 44 | } 45 | closedir( $handle ); 46 | } 47 | 48 | $colors = apply_filters( 'ctfw_colors', $colors ); 49 | 50 | return $colors; 51 | 52 | } 53 | 54 | /** 55 | * Check if color scheme is valid 56 | * 57 | * If none is provided, it will check the active color scheme. 58 | * 59 | * @since 0.9 60 | * @param string $color Color scheme; empty to use currently active color 61 | * @return bool True if color scheme exists 62 | */ 63 | function ctfw_valid_color( $color = false ) { 64 | 65 | $valid = false; 66 | 67 | // Use active if none given 68 | if ( empty( $color ) ) { 69 | $color = ctfw_customization( 'color' ); 70 | } 71 | 72 | $colors = ctfw_colors(); 73 | 74 | if ( ! empty( $colors[$color] ) ) { 75 | $valid = true; 76 | } 77 | 78 | return apply_filters( 'ctfw_valid_color', $valid, $color ); 79 | 80 | } 81 | 82 | /** 83 | * Retrieve URL of a file in color scheme 84 | * 85 | * Checks first in child (if exists), then parent. 86 | * If no color scheme given, active color scheme used. 87 | * 88 | * @since 0.9 89 | * @param string $file File in a color scheme 90 | * @param string $color Color scheme; empty to use currently active color 91 | * @return string URL of file in color scheme 92 | */ 93 | function ctfw_color_url( $file, $color = false ) { 94 | 95 | // Use active color scheme if none specified 96 | if ( empty( $color ) ) { 97 | $color = ctfw_customization( 'color' ); 98 | } 99 | 100 | // Validate color scheme 101 | // (even active one, to prevent any messing with cookies in front-end style customizer) 102 | if ( ctfw_valid_color( $color ) ) { 103 | $url = get_theme_file_uri( CTFW_THEME_COLOR_DIR . '/' . $color . '/' . ltrim( $file, '/' ) ); 104 | } else { 105 | $url = ''; 106 | } 107 | 108 | // Return filterable 109 | return apply_filters( 'ctfw_color_url', $url, $file, $color ); 110 | 111 | } 112 | 113 | /** 114 | * Color scheme stylesheet URL 115 | * 116 | * This can be used to enqueue the stylesheet. 117 | * 118 | * @since 0.9 119 | * @param string $theme 'child' or 'parent' 120 | * @return string URL of color scheme stylesheet 121 | */ 122 | function ctfw_color_style_url( $theme = false ) { 123 | 124 | $url = ''; 125 | 126 | // Make sure active color scheme is valid so nobody tries to mess with file path (ie. via front-end style picker cookie) 127 | if ( ctfw_valid_color() ) { 128 | 129 | $color = ctfw_customization( 'color' ); 130 | 131 | $color_rel = CTFW_THEME_COLOR_DIR . '/' . $color . '/style.css'; 132 | 133 | $color_parent_path = CTFW_THEME_PATH . '/' . $color_rel; 134 | $color_parent_url = CTFW_THEME_URL . '/' . $color_rel; 135 | 136 | $color_child_path = CTFW_THEME_CHILD_PATH . '/' . $color_rel; 137 | $color_child_url = CTFW_THEME_CHILD_URL . '/' . $color_rel; 138 | 139 | // Force parent version 140 | if ( 'parent' == $theme && file_exists( $color_parent_path ) ) { 141 | $url = $color_parent_url; 142 | } 143 | 144 | // Force child version 145 | else if ( 'child' == $theme && file_exists( $color_child_path ) ) { 146 | $url = $color_child_url; 147 | } 148 | 149 | // Auto-detect (default) 150 | // If parent or child not explicit, use default behavior (child if exists, otherwise parent) 151 | else { 152 | $url = get_theme_file_uri( $color_rel ); // use child theme version if provided 153 | } 154 | 155 | } 156 | 157 | // Return filtered. 158 | return apply_filters( 'ctfw_color_style_url', $url, $theme ); 159 | 160 | } 161 | 162 | /******************************************* 163 | * EDITOR COLORS 164 | *******************************************/ 165 | 166 | /** 167 | * Color styles. 168 | * 169 | * Gutenberg editor requires a class for every color specified via add_theme_support( 'editor-color-palette' ). 170 | * 171 | * @since 2.4.2 172 | * @param string $editor True if to be used in Gutenberg editor. 173 | * @return string <style> HTML tag. 174 | */ 175 | function ctfw_color_styles( $editor = false ) { 176 | 177 | // Get colors and proceed only if defined. 178 | $color_palette = get_theme_support( 'editor-color-palette' ); 179 | if ( empty( $color_palette[0] ) ) { 180 | return; 181 | } else { 182 | $color_palette = $color_palette[0]; 183 | } 184 | 185 | // Gutenberg editor class. 186 | $editor_prefix = ''; 187 | if ( ! empty( $editor ) ) { 188 | $editor_prefix = '.edit-post-visual-editor '; 189 | } 190 | 191 | // Loop colors to build styles. 192 | $styles = "\n<style type=\"text/css\">"; 193 | foreach ( $color_palette as $color ) { 194 | 195 | // Have color. 196 | if ( ! empty( $color['color'] ) ) { 197 | 198 | $slug_dashed = str_replace( ' ', '-', $color['slug'] ); 199 | 200 | $styles .= esc_html( $editor_prefix ) . '.has-' . esc_html( $slug_dashed ) . '-background-color,'; 201 | $styles .= esc_html( $editor_prefix ) . 'p.has-' . esc_html( $slug_dashed ) . '-background-color {'; 202 | $styles .= ' background-color: ' . esc_html( $color['color'] ) . '; '; 203 | $styles .= '}'; 204 | 205 | $styles .= esc_html( $editor_prefix ) . '.has-' . esc_html( $slug_dashed ) . '-color,'; 206 | $styles .= esc_html( $editor_prefix ) . 'p.has-' . esc_html( $slug_dashed ) . '-color {'; 207 | $styles .= ' color: ' . esc_html( $color['color'] ) . '; '; 208 | $styles .= '}'; 209 | 210 | } 211 | 212 | } 213 | $styles .= "</style>\n\n"; 214 | 215 | // Return. 216 | return apply_filters( 'ctfw_color_styles', $styles, $editor ); 217 | 218 | } 219 | 220 | /** 221 | * Output frontend color styles. 222 | * 223 | * Must use add_theme_support( 'editor-color-palette' ) for this to work. 224 | * 225 | * Related: This is done by 'ctfw-editor-styles' feature for Gutenberg editor. 226 | * 227 | * @since 2.4.2 228 | */ 229 | function ctfw_output_color_styles( $editor = false ) { 230 | 231 | // Only if theme supports automatically adding color styles. 232 | if ( ! current_theme_supports( 'ctfw-color-styles' ) ) { 233 | return; 234 | } 235 | 236 | // Output styles if colors are defined. 237 | echo ctfw_color_styles(); 238 | 239 | } 240 | 241 | add_action( 'wp_head', 'ctfw_output_color_styles' ); 242 | -------------------------------------------------------------------------------- /includes/localization.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Localization Functions 4 | * 5 | * @package Church_Theme_Framework 6 | * @subpackage Functions 7 | * @copyright Copyright (c) 2013 - 2019, ChurchThemes.com, LLC 8 | * @link https://github.com/churchthemes/church-theme-framework 9 | * @license GPLv2 or later 10 | * @since 0.9 11 | */ 12 | 13 | // No direct access 14 | if ( ! defined( 'ABSPATH' ) ) exit; 15 | 16 | 17 | /******************************************* 18 | * LOAD TEXTDOMAIN 19 | *******************************************/ 20 | 21 | /** 22 | * Load Theme Textdomain 23 | * 24 | * Language file should go in wp-content/languages/themes/textdomain-locale.mo. 25 | * There is no option for using languages folder in theme, because this is dangerous. 26 | * This folder is only for storing the .pot file and any pre-made translations. 27 | * It is absolutely best to keep it outside of theme. 28 | * 29 | * See http://core.trac.wordpress.org/changeset/22346 30 | * 31 | * @since 0.9 32 | */ 33 | function ctfw_load_theme_textdomain() { 34 | 35 | // Theme supports? 36 | if ( current_theme_supports( 'ctfw-load-translation' ) ) { 37 | 38 | // Textdomain same as theme's directory 39 | $textdomain = apply_filters( 'ctfw_theme_textdomain', CTFW_THEME_SLUG ); 40 | 41 | // First, check for language file from the 'languages' folder in theme (recommended only for pre-made translations coming with theme) 42 | // Secondarily, load custom language file from outside the theme at wp-content/languages/themes/textdomain-locale.mo (safe from theme updates) 43 | load_theme_textdomain( $textdomain, CTFW_THEME_LANG_DIR ); 44 | 45 | } 46 | 47 | } 48 | 49 | add_action( 'after_setup_theme', 'ctfw_load_theme_textdomain' ); 50 | 51 | 52 | /******************************************* 53 | * THEME TRANSLATION 54 | *******************************************/ 55 | 56 | /** 57 | * Use theme's translation file for framework text strings 58 | * 59 | * The framework's textdomain is 'church-theme-framework' while the theme has its own textdomain. 60 | * This makes it so one translation file (the theme's) can be used for both domains. 61 | * 62 | * These functions are based on work by Justin Tadlock in the Hybrid Core framework: 63 | * https://github.com/justintadlock/hybrid-core/blob/master/functions/i18n.php 64 | */ 65 | 66 | /** 67 | * Filter gettext to use theme's translation file for framework text strings 68 | * 69 | * @since 0.9 70 | * @param string $translated Translated text 71 | * @param string $text Original text 72 | * @param string $domain Textdomain 73 | * @return string Modified translated string 74 | */ 75 | function ctfw_gettext( $translated, $text, $domain ) { 76 | 77 | // Theme supports? 78 | if ( current_theme_supports( 'ctfw-load-translation' ) ) { 79 | 80 | // Framework textdomain? 81 | if ( 'church-theme-framework' == $domain ) { 82 | 83 | // Use theme's translation 84 | $translations = get_translations_for_domain( CTFW_THEME_SLUG ); // theme's directory name 85 | $translated = $translations->translate( $text ); 86 | 87 | } 88 | 89 | } 90 | 91 | return $translated; 92 | 93 | } 94 | 95 | add_filter( 'gettext', 'ctfw_gettext', 1, 3 ); 96 | 97 | /** 98 | * Filter gettext_with_context to use theme's translation file for framework text strings 99 | * 100 | * @since 1.1.3 101 | * @param string $translated Translated text 102 | * @param string $text Original text 103 | * @param string $context Context of the text 104 | * @param string $domain Textdomain 105 | * @return string Modified translated string 106 | */ 107 | function ctfw_gettext_with_context( $translated, $text, $context, $domain ) { 108 | 109 | // Theme supports? 110 | if ( current_theme_supports( 'ctfw-load-translation' ) ) { 111 | 112 | // Framework textdomain? 113 | if ( 'church-theme-framework' == $domain ) { 114 | 115 | // Use theme's translation 116 | $translations = get_translations_for_domain( CTFW_THEME_SLUG ); // theme's directory name 117 | $translated = $translations->translate( $text, $context ); 118 | 119 | } 120 | 121 | } 122 | 123 | return $translated; 124 | 125 | } 126 | 127 | add_filter( 'gettext_with_context', 'ctfw_gettext_with_context', 1, 4 ); 128 | 129 | /** 130 | * Filter ngettext to use theme's translation file for framework text strings 131 | * 132 | * @since 1.1.3 133 | * @param string $translated Translated text 134 | * @param string $single Singular form of original text 135 | * @param string $plural Plural form of original text 136 | * @param int $number Number determining whether singular or plural 137 | * @param string $domain Textdomain 138 | * @return string Modified translated string 139 | */ 140 | function ctfw_ngettext( $translated, $single, $plural, $number, $domain ) { 141 | 142 | // Theme supports? 143 | if ( current_theme_supports( 'ctfw-load-translation' ) ) { 144 | 145 | // Framework textdomain? 146 | if ( 'church-theme-framework' == $domain ) { 147 | 148 | // Use theme's translation 149 | $translations = get_translations_for_domain( CTFW_THEME_SLUG ); // theme's directory name 150 | $translated = $translations->translate_plural( $single, $plural, $number ); 151 | 152 | } 153 | 154 | } 155 | 156 | return $translated; 157 | 158 | } 159 | 160 | add_filter( 'ngettext', 'ctfw_ngettext', 1, 5 ); 161 | 162 | /** 163 | * Filter ngettext_with_context to use theme's translation file for framework text strings 164 | * 165 | * @since 1.1.3 166 | * @param string $translated Translated text 167 | * @param string $single Singular form of original text 168 | * @param string $plural Plural form of original text 169 | * @param int $number Number determining whether singular or plural 170 | * @param string $context Context of the text 171 | * @param string $domain Textdomain 172 | * @return string Modified translated string 173 | */ 174 | function ctfw_ngettext_with_context( $translated, $single, $plural, $number, $context, $domain ) { 175 | 176 | // Theme supports? 177 | if ( current_theme_supports( 'ctfw-load-translation' ) ) { 178 | 179 | // Framework textdomain? 180 | if ( 'church-theme-framework' == $domain ) { 181 | 182 | // Use theme's translation 183 | $translations = get_translations_for_domain( CTFW_THEME_SLUG ); // theme's directory name 184 | $translated = $translations->translate_plural( $single, $plural, $number, $context ); 185 | 186 | } 187 | 188 | } 189 | 190 | return $translated; 191 | 192 | } 193 | 194 | add_filter( 'ngettext_with_context', 'ctfw_ngettext_with_context', 1, 6 ); 195 | 196 | /******************************************* 197 | * REPLACE TEXT 198 | *******************************************/ 199 | 200 | /** 201 | * Replace WordPress core text strings 202 | * 203 | * WordPress core and its translations sometimes use text that is not preferred. 204 | * 205 | * Example: Spanish translation uses "Correo electrónico" on comment forms, which is too long 206 | * so "Email" would be better to use since it is just as valid in Spanish. 207 | * 208 | * Use with add_theme_support() like this: 209 | * 210 | * add_theme_support( 'ctfw-replace-wp-text', array( 211 | * 'Correo electrónico' => __( 'Email', 'textdomain' ), // Spanish: too long for comment form 212 | * ) ); 213 | * 214 | * @since 1.2.2 215 | * @param string Translated string 216 | * @param string Original string 217 | * @param string Textdomain 218 | * @return string Translated string, possibly modified 219 | */ 220 | function ctfw_replace_wp_text( $translated, $original, $domain ) { 221 | 222 | // Get theme support 223 | $support = get_theme_support( 'ctfw-replace-wp-text' ); 224 | 225 | // Feature is used 226 | if ( ! empty( $support[0] ) ) { 227 | 228 | // WordPress core text strings only 229 | if ( 'default' == $domain ) { 230 | 231 | // Get strings to replace 232 | $strings = $support[0]; 233 | 234 | // Replace original and translated strings 235 | if ( ! empty( $strings[$translated] ) ) { 236 | $translated = $strings[$translated]; 237 | } 238 | 239 | } 240 | 241 | } 242 | 243 | return $translated; 244 | 245 | } 246 | 247 | add_filter( 'gettext', 'ctfw_replace_wp_text', 10, 3 ); 248 | -------------------------------------------------------------------------------- /js/admin-widgets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Admin Widgets Page JavaScript 3 | * 4 | * Appearance > Widgets and Customizer both use this. 5 | */ 6 | 7 | jQuery(document).ready(function ($) { 8 | 9 | /******************************************* 10 | * IMAGE FIELD 11 | *******************************************/ 12 | 13 | // Choose Image 14 | $('body').on('click', '.ctfw-widget-image-choose', function (event) { 15 | 16 | var value_container, frame; 17 | 18 | // Stop click to URL 19 | event.preventDefault(); 20 | 21 | // Get field value container 22 | value_container = $(this).parent('.ctfw-widget-value'); 23 | 24 | // Media frame 25 | frame = wp.media({ 26 | title: ctfw_widgets.image_library_title, 27 | multiple: false, 28 | library: { type: 'image' }, 29 | button: { text: ctfw_widgets.image_library_button } 30 | }); 31 | 32 | // Open media frame and set current image 33 | // This information was useful: http://bit.ly/13FATWB 34 | frame.on('open', function () { 35 | 36 | var image_id, current_attachment; 37 | 38 | image_id = $('.ctfw-widget-image', value_container).val(); 39 | 40 | if (image_id) { 41 | current_attachment = wp.media.attachment(image_id); 42 | current_attachment.fetch(); 43 | frame.state().get('selection').add(current_attachment); 44 | } 45 | 46 | }).open(); // do open after binding 47 | 48 | // Set attachment ID and preview on click of "Use in Widget" 49 | // ( don't do on 'close' so user can cancel ) 50 | frame.on('select', function () { 51 | 52 | var attachments, attachment, attachment_id, attachment_preview_url; 53 | 54 | // Get attachment data 55 | attachments = frame.state().get('selection').toJSON(); 56 | attachment = attachments[0]; 57 | 58 | // An image is selected 59 | if (typeof attachment != 'undefined') { 60 | 61 | // Get attachment ID 62 | attachment_id = attachment.id; 63 | 64 | // Get medium size image for preview if exists 65 | attachment_preview_url = attachment.url; // use full if no medium 66 | if (typeof attachment.sizes.medium != 'undefined') { 67 | attachment_preview_url = attachment.sizes.medium.url; 68 | } 69 | 70 | // Have attachment ID and preview image 71 | if (attachment_id && attachment_preview_url) { 72 | 73 | // Set attachment ID on hidden input 74 | // Also trigger change event to make Customizer refresh preview 75 | $('.ctfw-widget-image', value_container) 76 | .val(attachment_id) 77 | .trigger('change'); 78 | 79 | // Set image preview 80 | $('.ctfw-widget-image-preview', value_container).html('<img src="' + attachment_preview_url + '" />'); 81 | 82 | // Set class on value container to tell image and remove button to show 83 | $(value_container) 84 | .removeClass('ctfw-widget-image-unset') 85 | .addClass('ctfw-widget-image-set'); 86 | 87 | } 88 | 89 | } 90 | 91 | }); 92 | 93 | }); 94 | 95 | // Remove Image 96 | $('body').on('click', '.ctfw-widget-image-remove', function (event) { 97 | 98 | var value_container; 99 | 100 | // Stop click to URL 101 | event.preventDefault(); 102 | 103 | // Get field value container 104 | value_container = $(this).parent('.ctfw-widget-value'); 105 | 106 | // Set attachment ID on hidden input 107 | // Also trigger change event to make Customizer refresh preview 108 | $('.ctfw-widget-image', value_container) 109 | .val('') 110 | .trigger('change'); 111 | 112 | // Set image preview 113 | $('.ctfw-widget-image-preview', value_container).empty(); 114 | 115 | // Set class on value container to tell image and remove button NOT to show 116 | $(value_container) 117 | .removeClass('ctfw-widget-image-set') 118 | .addClass('ctfw-widget-image-unset'); 119 | 120 | }); 121 | 122 | /************************************** 123 | * VIDEO FIELD 124 | **************************************/ 125 | 126 | // Open media uploader on button click 127 | $('body').on('click', '.ctfw-widget-upload-file', function (event) { 128 | 129 | var frame; 130 | 131 | // Stop click to URL 132 | event.preventDefault(); 133 | 134 | // Input element 135 | $input_element = $(this).prev('input'); 136 | 137 | // Media frame 138 | frame = wp.media({ 139 | title: $(this).attr('data-ctfw-widget-upload-title'), 140 | library: { type: $(this).attr('data-ctfw-widget-upload-type') }, 141 | multiple: false 142 | }); 143 | 144 | // Open media frame 145 | // To Do: Set current attachment after opening 146 | // ( How with only URL? For doing with ID, see this: http://bit.ly/Zut80f ) 147 | frame.open(); 148 | 149 | // Set attachment URL on click of button 150 | // (don't do on 'close' so user can cancel) 151 | frame.on('select', function () { 152 | 153 | var attachments, attachment; 154 | 155 | // Get attachment data 156 | attachments = frame.state().get('selection').toJSON(); 157 | attachment = attachments[0]; 158 | 159 | // An attachment is selected 160 | if (typeof attachment != 'undefined') { 161 | 162 | // Set attachment URL on input 163 | // Also trigger change event to make Customizer refresh preview 164 | if (attachment.url) { 165 | 166 | $input_element 167 | .val(attachment.url) // input is directly before button 168 | .trigger('change'); 169 | } 170 | 171 | } 172 | 173 | }); 174 | 175 | }); 176 | 177 | /******************************************* 178 | * COLORPICKER 179 | *******************************************/ 180 | 181 | // Add colorpicker 182 | $(document) 183 | 184 | // Init colorpicker 185 | .on('widget-added widget-updated', function (event, widget) { 186 | ctfw_init_widget_colorpicker(widget); 187 | }) 188 | 189 | // Persist after AJAX save, without this the field turns into a plain text input 190 | .ready(function () { 191 | 192 | // Init for each field 193 | $('#widgets-right .widget:has(.ctfw-widget-color)').each(function () { 194 | ctfw_init_widget_colorpicker($(this)); 195 | }); 196 | 197 | }); 198 | 199 | /******************************************* 200 | * RESTRICT WIDGETS/SIDEBARS 201 | *******************************************/ 202 | 203 | // Customizer: Pre-fill Search when "Add a Widget" clicked 204 | // Show only Slide for Slider widget area and same for Homepage Highlights 205 | if ($('.wp-customizer').length) { // on Customizer only 206 | 207 | // "Add a Widget" clicked 208 | $('.add-new-widget').on('click', function (e) { 209 | 210 | var accordion_section, accordion_section_id, sidebar, search; 211 | 212 | // Which widget area is open? 213 | accordion_section = $(this).parents('.accordion-section'); 214 | accordion_section_id = $(accordion_section).prop('id'); 215 | 216 | // Get widget area name 217 | sidebar = false; 218 | if (accordion_section_id) { 219 | sidebar = accordion_section_id.replace(/^accordion-section-sidebar-widgets-/, ''); 220 | } 221 | 222 | // Limit Slide and Highlight widget areas to their appropriate widgets 223 | search = ''; 224 | if (sidebar) { 225 | 226 | if (sidebar.match(/slider/)) { 227 | search = 'Slide'; 228 | } else if (sidebar.match(/highlights/)) { 229 | search = 'Highlight'; 230 | } 231 | 232 | } 233 | 234 | // Update Search input 235 | if (search) { 236 | 237 | setTimeout(function () { 238 | $('#widgets-search') 239 | .val(search) 240 | .trigger('change'); 241 | }, 100) 242 | 243 | } 244 | 245 | }); 246 | 247 | } 248 | 249 | }); 250 | 251 | /********************************************** 252 | * FUNCTIONS 253 | **********************************************/ 254 | 255 | // Init colorpicker 256 | function ctfw_init_widget_colorpicker(widget) { 257 | 258 | jQuery(widget).find('.ctfw-widget-color').wpColorPicker({ 259 | 260 | // Cause Customizer to refresh 261 | // Bug? With this, first color selection doesn't take effect 262 | change: _.throttle(function () { 263 | jQuery(this).trigger('change'); 264 | }, 3000) 265 | 266 | }); 267 | 268 | } 269 | -------------------------------------------------------------------------------- /includes/maps.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Google Maps Functions 4 | * 5 | * @package Church_Theme_Framework 6 | * @subpackage Functions 7 | * @copyright Copyright (c) 2013 - 2019, ChurchThemes.com, LLC 8 | * @link https://github.com/churchthemes/church-theme-framework 9 | * @license GPLv2 or later 10 | * @since 0.9 11 | */ 12 | 13 | // No direct access. 14 | if ( ! defined( 'ABSPATH' ) ) { 15 | exit; 16 | } 17 | 18 | /******************************************* 19 | * JAVASCRIPT MAP 20 | *******************************************/ 21 | 22 | /** 23 | * Display Google Map 24 | * 25 | * Only latitude and longitude are required. 26 | * 27 | * @since 0.9 28 | * @param array $options Options for showing map 29 | * @return string Google Maps HTML 30 | */ 31 | function ctfw_google_map( $options = false ) { 32 | 33 | $html = ''; 34 | 35 | if ( ! empty( $options['latitude'] ) && ! empty( $options['longitude'] ) ) { 36 | 37 | // Defaults 38 | $options['type'] = isset( $options['type'] ) ? strtoupper( $options['type'] ) : ''; 39 | $options['zoom'] = isset( $options['zoom'] ) ? (int) $options['zoom'] : ''; 40 | $options['container'] = isset( $options['container'] ) ? $options['container'] : true; // default true 41 | $options['responsive'] = isset( $options['responsive'] ) ? $options['responsive'] : true; // default true 42 | $options['marker'] = isset( $options['marker'] ) ? $options['marker'] : true; // default true 43 | $options['center_resize'] = isset( $options['center_resize'] ) ? $options['center_resize'] : true; // default true 44 | $options['callback_loaded'] = isset( $options['callback_loaded'] ) ? $options['callback_loaded'] : ''; 45 | $options['callback_resize'] = isset( $options['callback_resize'] ) ? $options['callback_resize'] : ''; 46 | 47 | // Unique ID for this map so can have multiple maps on a page 48 | // Can pass map_id as option for custom ID 49 | $google_map_id_default = 'ctfw-google-map-' . rand( 1000000, 9999999 ); 50 | $google_map_id = isset( $options['canvas_id'] ) ? $options['canvas_id'] : $google_map_id_default; 51 | 52 | // Classes for map canvas element 53 | $canvas_classes = array( 'ctfw-google-map' ); 54 | 55 | if ( ! empty( $options['canvas_class'] ) ) { 56 | $canvas_classes[] = $options['canvas_class']; 57 | } 58 | 59 | if ( $options['responsive'] ) { 60 | $canvas_classes[] = 'ctfw-google-map-responsive'; 61 | } 62 | 63 | $canvas_classes = implode( ' ', $canvas_classes ); 64 | 65 | // Height percentage of width? 66 | $map_style = ''; 67 | if ( ! empty( $options['height_percent'] ) ) { 68 | $options['height_percent'] = str_replace( '%', '', $options['height_percent'] ); 69 | $map_style = ' style="padding-bottom: ' . $options['height_percent'] . '%;"'; 70 | } 71 | 72 | // Data Attributes 73 | $data_latitude = esc_attr( $options['latitude'] ); 74 | $data_longitude = esc_attr( $options['longitude'] ); 75 | $data_type = esc_attr( $options['type'] ); 76 | $data_zoom = esc_attr( $options['zoom'] ); 77 | $data_marker = esc_attr( $options['marker'] ); 78 | $data_center_resize = esc_attr( $options['center_resize'] ); 79 | $data_callback_loaded = esc_attr( $options['callback_loaded'] ); 80 | $data_callback_resize = esc_attr( $options['callback_resize'] ); 81 | 82 | // Map canvas tag with attributes 83 | $html = '<div id="' . esc_attr( $google_map_id ) . '" class="' . $canvas_classes . '" data-ctfw-map-lat="' . esc_attr( $data_latitude ) . '" data-ctfw-map-lng="' . esc_attr( $data_longitude ) . '" data-ctfw-map-type="' . esc_attr( $data_type ) . '" data-ctfw-map-zoom="' . esc_attr( $data_zoom ) . '" data-ctfw-map-marker="' . esc_attr( $data_marker ) . '" data-ctfw-map-center-resize="' . esc_attr( $data_center_resize ) . '" data-ctfw-map-callback-loaded="' . esc_attr( $data_callback_loaded ) . '" data-ctfw-map-callback-resize="' . esc_attr( $data_callback_resize ) . '"' . $map_style . '></div>'; 84 | 85 | // Use container? 86 | if ( $options['container'] ) { 87 | $html = '<div class="ctfw-google-map-container">' . $html . '</div>'; 88 | } 89 | 90 | // Enqueue map scripts to handle Google Maps init 91 | // this way the scripts are loaded only when feature is used, not on every page 92 | wp_enqueue_script( 'ctfw-maps', get_theme_file_uri( CTFW_JS_DIR . '/maps.js' ), array( 'jquery' ), CTFW_VERSION ); // bust cache on theme update 93 | wp_enqueue_script( 'google-maps', '//maps.googleapis.com/maps/api/js?key=' . ctfw_google_maps_api_key() . '&callback=ctfw_load_maps', array( 'ctfw-maps' ), null ); // no version, generic name to share w/plugins 94 | 95 | } elseif ( ! empty( $options['show_error'] ) ) { 96 | $html = __( '<p><b>Google Map Error:</b> <i>latitude</i> and <i>longitude</i> attributes are required. See documentation for help.</p>', 'church-theme-framework' ); 97 | } 98 | 99 | return apply_filters( 'ctfw_google_map', $html, $options ); 100 | 101 | } 102 | 103 | /******************************************* 104 | * IMAGE MAP 105 | *******************************************/ 106 | 107 | /** 108 | * Google Map Image Tag 109 | * 110 | * Return a HiDPI-ready Google Map <img> URL. 111 | * 112 | * @since 1.0.9 113 | * @param array $options Options for showing map 114 | * @return string Google Maps HTML 115 | */ 116 | function ctfw_google_map_image( $options = array() ) { 117 | 118 | // Default arguments 119 | $options = wp_parse_args( $options, apply_filters( 'ctfw_google_map_image_options', array( 120 | 'latitude' => 31.768319, // Jerusalem 121 | 'longitude' => 35.213710, 122 | 'type' => 'road', 123 | 'zoom' => '14', 124 | 'width' => 480, 125 | 'height' => 320, 126 | 'alt' => '', 127 | 'scale' => 2, // double for HiDPI devices 128 | 'marker_color' => 'f2f2f2' // hex without # (light gray) 129 | ) ) ); 130 | 131 | // Extract options 132 | extract( $options, EXTR_SKIP ); 133 | 134 | // Clean options 135 | $type = strtolower( $type ); 136 | $marker_color = str_replace( '#', '', $marker_color ); 137 | 138 | // Start arguments 139 | $map_args = array(); 140 | 141 | // Required arguments 142 | $map_args['size'] = $width . 'x' . $height; 143 | $map_args['center'] = $latitude . ',' . $longitude; 144 | $map_args['scale'] = $scale; // double for Retina 145 | //$map_args['markers'] = 'color:0x' . $marker_color . '|' . $map_args['center']; 146 | $map_args['markers'] = 'color:0x' . $marker_color . '%7C' . $map_args['center']; // HTML5-valid: http://bit.ly/1xfv8yA 147 | $map_args['key'] = ctfw_google_maps_api_key(); // from Church Content plugin settings 148 | 149 | // Have zoom? 150 | if ( ! empty( $zoom ) ) { 151 | $map_args['zoom'] = $zoom; 152 | } 153 | 154 | // Have type? 155 | if ( ! empty( $type ) ) { 156 | $map_args['maptype'] = strtolower( $type ); 157 | } 158 | 159 | // Sensor last 160 | $map_args['sensor'] = 'false'; 161 | 162 | // Filter map arguments 163 | $map_args = apply_filters( 'ctfw_google_map_image_args', $map_args ); 164 | 165 | // Add arguments to URL 166 | $map_url = add_query_arg( $map_args, '//maps.googleapis.com/maps/api/staticmap' ); 167 | 168 | // Filter URL 169 | $map_args = apply_filters( 'ctfw_google_map_image_url', $map_args ); 170 | 171 | // Build image tag 172 | $img_tag = '<img src="' . esc_url( $map_url ) . '" class="ctfw-google-map-image" alt="' . esc_attr( $alt ) . '" width="' . esc_attr( $width ) . '" height="' . esc_attr( $height ) . '">'; 173 | 174 | // Return 175 | return apply_filters( 'ctfw_google_map_image', $img_tag ); 176 | 177 | } 178 | 179 | /******************************************* 180 | * HELPERS 181 | *******************************************/ 182 | 183 | /** 184 | * Build Google Maps directions URL from address 185 | * 186 | * @since 0.9 187 | * @param string $address Address to get directions URL for 188 | * @return string URL for directions on Google Maps 189 | */ 190 | function ctfw_directions_url( $address ) { 191 | 192 | $directions_url = ''; 193 | 194 | if ( $address ) { 195 | 196 | // Convert address to one line (replace newlines with commas) 197 | $directions_address = ctfw_address_one_line( $address ); 198 | 199 | // Build URL to Google Maps 200 | $directions_url = 'https://www.google.com/maps/dir//' . urlencode( $directions_address ) . '/'; // works with new and old maps 201 | 202 | } 203 | 204 | return apply_filters( 'ctfw_directions_url', $directions_url, $address ); 205 | 206 | } 207 | 208 | /** 209 | * Get API Key from Church Content settings 210 | * 211 | * It's set in Church Content settings because "Get From Address" button needs it. 212 | * Themes can use the key from the plugin in this way. 213 | * 214 | * @since 1.8 215 | * @return string Google Maps API Key 216 | */ 217 | function ctfw_google_maps_api_key() { 218 | 219 | $key = ''; 220 | 221 | // Make sure the plugin's function is available 222 | if ( function_exists( 'ctc_setting' ) ) { 223 | $key = ctc_setting( 'google_maps_api_key' ); 224 | } 225 | 226 | return apply_filters( 'ctfw_google_maps_api_key', $key ); 227 | 228 | } 229 | -------------------------------------------------------------------------------- /framework.php: -------------------------------------------------------------------------------- 1 | <?php 2 | 3 | /** 4 | * Church Theme Framework 5 | * 6 | * The framework provides code and assets common to multiple themes for more organized and efficient development/updates. 7 | * It is intended for use in themes that use the Church Content plugin. 8 | * 9 | * @package Church_Theme_Framework 10 | * @copyright Copyright (c) 2013 - 2025, ChurchThemes.com, LLC 11 | * @link https: //github.com/churchthemes/church-theme-framework 12 | * @license GPLv2 or later 13 | */ 14 | 15 | // No direct access 16 | if (! defined('ABSPATH')) { 17 | exit; 18 | } 19 | 20 | /******************************************** 21 | * CONSTANTS 22 | ********************************************/ 23 | 24 | /** 25 | * Get theme data 26 | * 27 | * If child theme, get parent theme data. 28 | */ 29 | $theme_data = wp_get_theme(); 30 | $theme_data = is_child_theme() ? wp_get_theme($theme_data->template) : $theme_data; 31 | 32 | /** 33 | * Framework constants 34 | */ 35 | if (! defined('CTFW_VERSION')) define('CTFW_VERSION', '2.9.5'); 36 | 37 | /** 38 | * Theme constants 39 | */ 40 | if (! defined('CTFW_THEME_VERSION')) define('CTFW_THEME_VERSION', $theme_data->Version); // parent theme version 41 | if (! defined('CTFW_THEME_NAME')) define('CTFW_THEME_NAME', $theme_data->Name); // parent theme name, specified in style.css 42 | if (! defined('CTFW_THEME_SLUG')) define('CTFW_THEME_SLUG', $theme_data->template); // parent theme's folder (theme slug) 43 | if (! defined('CTFW_THEME_AUTHOR')) define('CTFW_THEME_AUTHOR', strip_tags($theme_data->Author)); // parent theme's author 44 | if (! defined('CTFW_THEME_PATH')) define('CTFW_THEME_PATH', get_template_directory()); // parent theme path 45 | if (! defined('CTFW_THEME_URL')) define('CTFW_THEME_URL', get_template_directory_uri()); // parent theme URI 46 | if (! defined('CTFW_THEME_CHILD_PATH')) define('CTFW_THEME_CHILD_PATH', get_stylesheet_directory()); // child theme path 47 | if (! defined('CTFW_THEME_CHILD_URL')) define('CTFW_THEME_CHILD_URL', get_stylesheet_directory_uri()); // child theme URI 48 | 49 | /** 50 | * Theme directory constants 51 | * 52 | * Theme and framework structures mirror each other. 53 | */ 54 | if (! defined('CTFW_THEME_INC_DIR')) define('CTFW_THEME_INC_DIR', 'includes'); // includes directory 55 | if (! defined('CTFW_THEME_ADMIN_DIR')) define('CTFW_THEME_ADMIN_DIR', CTFW_THEME_INC_DIR . '/admin'); // admin directory 56 | if (! defined('CTFW_THEME_CLASS_DIR')) define('CTFW_THEME_CLASS_DIR', CTFW_THEME_INC_DIR . '/classes'); // classes directory 57 | if (! defined('CTFW_THEME_LIB_DIR')) define('CTFW_THEME_LIB_DIR', CTFW_THEME_INC_DIR . '/libraries'); // libraries directory 58 | if (! defined('CTFW_THEME_PAGE_TPL_DIR')) define('CTFW_THEME_PAGE_TPL_DIR', 'page-templates'); // page templates directory 59 | if (! defined('CTFW_THEME_PARTIAL_DIR')) define('CTFW_THEME_PARTIAL_DIR', 'partials'); // partials directory (re-usable template parts) 60 | if (! defined('CTFW_THEME_WIDGET_DIR')) define('CTFW_THEME_WIDGET_DIR', 'widget-templates'); // widget templates directory 61 | if (! defined('CTFW_THEME_CSS_DIR')) define('CTFW_THEME_CSS_DIR', 'css'); // stylesheets directory 62 | if (! defined('CTFW_THEME_JS_DIR')) define('CTFW_THEME_JS_DIR', 'js'); // JavaScript directory 63 | if (! defined('CTFW_THEME_IMG_DIR')) define('CTFW_THEME_IMG_DIR', 'images'); // images directory 64 | if (! defined('CTFW_THEME_BG_DIR')) define('CTFW_THEME_BG_DIR', CTFW_THEME_IMG_DIR . '/backgrounds'); // background images directory 65 | if (! defined('CTFW_THEME_COLOR_DIR')) define('CTFW_THEME_COLOR_DIR', 'colors'); // color schemes directory 66 | if (! defined('CTFW_THEME_LANG_DIR')) define('CTFW_THEME_LANG_DIR', 'languages'); // languages directory 67 | 68 | /** 69 | * Framework directory constants 70 | * 71 | * Note use of theme constants. Theme and framework structures mirror each other. 72 | */ 73 | if (! defined('CTFW_DIR')) define('CTFW_DIR', basename(dirname(__FILE__))); // framework directory (where this file is) 74 | if (! defined('CTFW_INC_DIR')) define('CTFW_INC_DIR', CTFW_DIR . '/' . CTFW_THEME_INC_DIR); // framework includes directory 75 | if (! defined('CTFW_ADMIN_DIR')) define('CTFW_ADMIN_DIR', CTFW_DIR . '/' . CTFW_THEME_ADMIN_DIR); // framework admin directory 76 | if (! defined('CTFW_CLASS_DIR')) define('CTFW_CLASS_DIR', CTFW_DIR . '/' . CTFW_THEME_CLASS_DIR); // framework classes directory 77 | if (! defined('CTFW_LIB_DIR')) define('CTFW_LIB_DIR', CTFW_DIR . '/' . CTFW_THEME_LIB_DIR); // framework libraries directory 78 | if (! defined('CTFW_CSS_DIR')) define('CTFW_CSS_DIR', CTFW_DIR . '/' . CTFW_THEME_CSS_DIR); // framework stylesheets directory 79 | if (! defined('CTFW_JS_DIR')) define('CTFW_JS_DIR', CTFW_DIR . '/' . CTFW_THEME_JS_DIR); // framework JavaScript directory 80 | if (! defined('CTFW_IMG_DIR')) define('CTFW_IMG_DIR', CTFW_DIR . '/' . CTFW_THEME_IMG_DIR); // framework images directory 81 | 82 | /******************************************** 83 | * INCLUDES 84 | ********************************************/ 85 | 86 | /** 87 | * Includes to load 88 | */ 89 | $ctfw_includes = array( 90 | 91 | // Frontend or Admin 92 | 'always' => array( 93 | 94 | // Functions 95 | CTFW_INC_DIR . '/archives.php', 96 | CTFW_INC_DIR . '/background.php', 97 | CTFW_INC_DIR . '/body.php', 98 | CTFW_INC_DIR . '/colors.php', 99 | CTFW_INC_DIR . '/comments.php', 100 | CTFW_INC_DIR . '/compatibility.php', 101 | CTFW_INC_DIR . '/conditions.php', 102 | CTFW_INC_DIR . '/content-types.php', 103 | CTFW_INC_DIR . '/customize.php', 104 | CTFW_INC_DIR . '/deprecated.php', 105 | CTFW_INC_DIR . '/downloads.php', 106 | CTFW_INC_DIR . '/embeds.php', 107 | CTFW_INC_DIR . '/events.php', 108 | CTFW_INC_DIR . '/fonts.php', 109 | CTFW_INC_DIR . '/gallery.php', 110 | CTFW_INC_DIR . '/head.php', 111 | CTFW_INC_DIR . '/helpers.php', 112 | CTFW_INC_DIR . '/images.php', 113 | CTFW_INC_DIR . '/localization.php', 114 | CTFW_INC_DIR . '/locations.php', 115 | CTFW_INC_DIR . '/maps.php', 116 | CTFW_INC_DIR . '/meta-data.php', 117 | CTFW_INC_DIR . '/mime-types.php', 118 | CTFW_INC_DIR . '/page-nav.php', 119 | CTFW_INC_DIR . '/pages.php', 120 | CTFW_INC_DIR . '/people.php', 121 | CTFW_INC_DIR . '/posts.php', 122 | CTFW_INC_DIR . '/taxonomies.php', 123 | CTFW_INC_DIR . '/template-tags.php', 124 | CTFW_INC_DIR . '/templates.php', 125 | CTFW_INC_DIR . '/sermons.php', 126 | CTFW_INC_DIR . '/sidebars.php', 127 | CTFW_INC_DIR . '/widgets.php', 128 | 129 | // Classes 130 | CTFW_CLASS_DIR . '/customize-controls.php', 131 | CTFW_CLASS_DIR . '/widget.php', 132 | 133 | // Libraries 134 | CTFW_LIB_DIR . '/ct-recurrence/ct-recurrence-load.php', // don't load ct-recurrence.php directly. 135 | 136 | ), 137 | 138 | // Admin Only 139 | 'admin' => array( 140 | 141 | // Functions 142 | CTFW_ADMIN_DIR . '/activation.php', 143 | CTFW_ADMIN_DIR . '/admin-enqueue-styles.php', 144 | CTFW_ADMIN_DIR . '/admin-enqueue-scripts.php', 145 | CTFW_ADMIN_DIR . '/admin-taxonomies.php', 146 | CTFW_ADMIN_DIR . '/admin-widgets.php', 147 | CTFW_ADMIN_DIR . '/edd-license.php', 148 | CTFW_ADMIN_DIR . '/editor.php', 149 | CTFW_ADMIN_DIR . '/import.php', 150 | CTFW_ADMIN_DIR . '/meta-boxes.php', 151 | 152 | // Libraries 153 | CTFW_LIB_DIR . '/ct-meta-box/ct-meta-box.php', 154 | 155 | ), 156 | 157 | // Frontend Only 158 | 'frontend' => array( 159 | 160 | // Classes 161 | CTFW_CLASS_DIR . '/breadcrumbs.php', 162 | CTFW_CLASS_DIR . '/walker-nav-menu-description.php', 163 | 164 | ), 165 | 166 | ); 167 | 168 | /** 169 | * Filter includes 170 | */ 171 | $ctfw_includes = apply_filters('ctfw_includes', $ctfw_includes); // make filterable 172 | 173 | /** 174 | * Load includes 175 | */ 176 | ctfw_load_includes($ctfw_includes); 177 | 178 | /** 179 | * Include loader function 180 | * 181 | * Used by framework above and functions.php for theme-specific includes. 182 | * If include exists in child theme, it will be used. Otherwise, parent theme file is used. 183 | * 184 | * @since 0.5 185 | * @param array $includes Files to include 186 | */ 187 | function ctfw_load_includes($includes) 188 | { 189 | 190 | // Loop conditions 191 | foreach ($includes as $condition => $files) { 192 | 193 | // Check condition 194 | $do_includes = false; 195 | switch ($condition) { 196 | 197 | // Admin Only 198 | case 'admin': 199 | 200 | if (is_admin()) { 201 | $do_includes = true; 202 | } 203 | 204 | break; 205 | 206 | // Frontend Only 207 | case 'frontend': 208 | 209 | if (! is_admin()) { 210 | $do_includes = true; 211 | } 212 | 213 | break; 214 | 215 | // Admin or Frontend (always) 216 | default: 217 | 218 | $do_includes = true; 219 | 220 | break; 221 | } 222 | 223 | // Loop files if condition met 224 | if ($do_includes) { 225 | 226 | foreach ($files as $file) { 227 | locate_template($file, true); // include from child theme first, then parent theme 228 | } 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /includes/compatibility.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Compatibility Functions 4 | * 5 | * Require minimum version of WordPress, Church Content plugin, Internet Explorer, etc. 6 | * 7 | * @package Church_Theme_Framework 8 | * @subpackage Functions 9 | * @copyright Copyright (c) 2013 - 2019, ChurchThemes.com, LLC 10 | * @link https://github.com/churchthemes/church-theme-framework 11 | * @license GPLv2 or later 12 | * @since 0.9 13 | */ 14 | 15 | // No direct access. 16 | if (! defined( 'ABSPATH' )) { 17 | exit; 18 | } 19 | 20 | /******************************************* 21 | * WORDPRESS VERSION 22 | *******************************************/ 23 | 24 | // Based on code from default Twenty Thirteen theme 25 | 26 | /** 27 | * Detect if old WordPress version used 28 | * 29 | * Use add_theme_support( 'ctfw-wordpress-version', 7 ); // 7 and under not supported 30 | * 31 | * @since 0.9 32 | * @return bool True if theme supports feature and version is old 33 | */ 34 | function ctfw_old_wp() { 35 | 36 | $old = false; 37 | 38 | // Theme uses this feature 39 | $support = get_theme_support( 'ctfw-wordpress-version' ); 40 | if (! empty( $support[0] )) { 41 | 42 | // Get minimum required version 43 | $required_version = $support[0]; 44 | 45 | // Is old version used? 46 | if (version_compare( $GLOBALS['wp_version'], $required_version, '<' )) { 47 | $old = true; 48 | } 49 | 50 | } 51 | 52 | return apply_filters( 'ctfw_old_wp', $old ); 53 | 54 | } 55 | 56 | /** 57 | * Message to show when old version used 58 | * 59 | * @since 0.9 60 | * @return string Message saying version is old 61 | */ 62 | function ctfw_old_wp_message() { 63 | return sprintf( __( '<b>Activation Failed:</b> %s requires a newer version of WordPress. Please update and try again.', 'church-theme-framework' ), CTFW_THEME_NAME ); 64 | } 65 | 66 | /** 67 | * Prevent switching to theme on old version of WordPress 68 | * 69 | * Switches to the previously activated theme or the default theme. 70 | * 71 | * @since 0.9 72 | * @param string $theme_name Theme slug 73 | * @param object $theme Theme object 74 | */ 75 | function ctfw_old_wp_switch_theme( $theme_name, $theme = false ) { 76 | 77 | // Old theme still exists 78 | if (! $theme) { 79 | return; 80 | } 81 | 82 | // Is WordPress version too old for theme? 83 | if (ctfw_old_wp()) { 84 | 85 | if (CTFW_THEME_SLUG != $theme->get_template()) { 86 | switch_theme( $theme->get_template(), $theme->get_stylesheet() ); 87 | } elseif (CTFW_THEME_SLUG != WP_DEFAULT_THEME) { 88 | switch_theme( WP_DEFAULT_THEME ); 89 | } 90 | 91 | unset( $_GET['activated'] ); 92 | 93 | // Don't show regular notices (license activation, CTC plugin, etc.) 94 | ctfw_activation_remove_notices(); 95 | 96 | // Show notice saying to update WP then try again 97 | add_action( 'admin_notices', 'ctfw_old_wp_switch_theme_notice' ); 98 | 99 | } 100 | 101 | } 102 | 103 | add_action( 'after_switch_theme', 'ctfw_old_wp_switch_theme', 10, 2 ); 104 | 105 | /** 106 | * Show notice if try to switch to theme while using old version of WordPress 107 | * 108 | * @since 0.9 109 | */ 110 | function ctfw_old_wp_switch_theme_notice() { 111 | 112 | ?> 113 | <div class="notice notice-error"> 114 | <p> 115 | <?php echo ctfw_old_wp_message(); ?> 116 | </p> 117 | </div> 118 | <?php 119 | 120 | } 121 | 122 | /***************************************************** 123 | * WORDPRESS FUNCTIONS 124 | *****************************************************/ 125 | 126 | /** 127 | * Make wp_body_open() work on all versions of WordPress. 128 | * 129 | * This makes the function available for versions of WordPress earlier than 5.2. 130 | * 131 | * https://make.wordpress.org/core/2019/04/24/miscellaneous-developer-updates-in-5-2/ 132 | * 133 | * @since 2.6.2 134 | */ 135 | if (! function_exists( 'wp_body_open' )) { 136 | 137 | function wp_body_open() { 138 | do_action( 'wp_body_open' ); 139 | } 140 | 141 | } 142 | 143 | /***************************************************** 144 | * CHURCH CONTENT PLUGIN 145 | *****************************************************/ 146 | 147 | /** 148 | * Plugin file 149 | * 150 | * @since 0.9 151 | * @return string Main plugin file relative to plugin directory 152 | */ 153 | function ctfw_ctc_plugin_file() { 154 | return 'church-theme-content/church-theme-content.php'; 155 | } 156 | 157 | /** 158 | * Plugin slug 159 | * 160 | * @since 0.9 161 | * @return string Plugin slug based on its directory 162 | */ 163 | function ctfw_ctc_plugin_slug() { 164 | return dirname( ctfw_ctc_plugin_file() ); 165 | } 166 | 167 | /** 168 | * Plugin is installed and has been activated 169 | * 170 | * @since 0.9 171 | * @return bool True if plugin installed and active 172 | */ 173 | function ctfw_ctc_plugin_active() { 174 | 175 | $activated = false; 176 | 177 | include_once ABSPATH . 'wp-admin/includes/plugin.php'; 178 | 179 | if (is_plugin_active( ctfw_ctc_plugin_file() )) { 180 | $activated = true; 181 | } 182 | 183 | return apply_filters( 'ctfw_ctc_plugin_active', $activated ); 184 | 185 | } 186 | 187 | /** 188 | * Plugin is installed but not necessarily activated 189 | * 190 | * @since 0.9 191 | * @return bool True if plugin is installed 192 | */ 193 | function ctfw_ctc_plugin_installed() { 194 | 195 | $installed = false; 196 | 197 | if (array_key_exists( ctfw_ctc_plugin_file(), get_plugins() )) { 198 | $installed = true; 199 | } 200 | 201 | return apply_filters( 'ctfw_ctc_plugin_installed', $installed ); 202 | 203 | } 204 | 205 | /** 206 | * Admin notice 207 | * 208 | * Show notice at top of admin until plugin is both installed and activated. 209 | * 210 | * @since 0.9 211 | */ 212 | function ctfw_ctc_plugin_notice() { 213 | 214 | // Show only on relevant pages as not to overwhelm the admin 215 | $screen = get_current_screen(); 216 | if (! in_array( $screen->base, array( 'dashboard', 'themes', 'plugins' ) )) { 217 | return; 218 | } 219 | 220 | // Prevent plugins (CTC add-ons) from showing similar notice 221 | // Make sure this is always after last return above, meaning a notice is being shown 222 | if (! empty( $GLOBALS['ctc_install_notice_sent'] )) { 223 | return; 224 | } else { 225 | $GLOBALS['ctc_install_notice_sent'] = true; 226 | } 227 | 228 | // Plugin not installed 229 | if (! ctfw_ctc_plugin_installed() && current_user_can( 'install_plugins' )) { 230 | 231 | $notice = sprintf( 232 | __( '<b>Plugin Required:</b> Please install and activate the <a href="%s">Church Content</a> plugin to use with the current theme.', 'church-theme-framework' ), 233 | esc_url( network_admin_url( 'plugin-install.php?s=' . rawurlencode( '"Church Content" ChurchThemes.com' ) . '&tab=search' ) ) 234 | ); 235 | 236 | } 237 | 238 | // Plugin installed but not activated 239 | elseif (! ctfw_ctc_plugin_active() && current_user_can( 'activate_plugins' )) { 240 | 241 | $notice = sprintf( 242 | __( 'Please <a href="%s">activate</a> the <b>Church Content</b> plugin required by the current theme.', 'church-theme-framework' ), 243 | wp_nonce_url( self_admin_url( 'plugins.php?action=activate&plugin=' . ctfw_ctc_plugin_file() ), 'activate-plugin_' . ctfw_ctc_plugin_file() ) 244 | ); 245 | 246 | } 247 | 248 | // Show notice 249 | if (isset( $notice )) { 250 | 251 | ?> 252 | <div class="notice notice-warning"> 253 | <p> 254 | <?php echo $notice; ?> 255 | </p> 256 | </div> 257 | <?php 258 | 259 | } 260 | 261 | } 262 | 263 | add_action( 'admin_notices', 'ctfw_ctc_plugin_notice', 9 ); // higher priority than notices coming from plugin (theme notice before plugin addon notices) 264 | 265 | /******************************************* 266 | * BROWSERS 267 | *******************************************/ 268 | 269 | /** 270 | * Unsupported Internet Explorer 271 | * 272 | * Theme can use add_theme_support( 'ctfw-ie-unsupported', 7 ) 273 | * to prompt Internet Explorer 7 users to upgrade to a modern browser. 274 | * 275 | * Note script is always loaded because caching (WP Engine, etc) can cause server-side 276 | * detection to fail (giving non-IE users the warning). 277 | * 278 | * @since 0.9 279 | */ 280 | function ctfw_enqueue_ie_unsupported() { 281 | 282 | // Only if theme requests this 283 | $support = get_theme_support( 'ctfw-ie-unsupported' ); 284 | if ($support) { 285 | 286 | // Default and valid version range 287 | // Currently specified version must be between 5 and 9 (10 could require more complex regex, but that may never be needed) 288 | $default_version = 7; 289 | $min_version = 5; 290 | $max_version = 9; 291 | 292 | // Get version 293 | $version = isset( $support[0] ) ? $support[0] : $default_version; 294 | 295 | // Check version range 296 | $version = absint( $version ); 297 | if ($version < $min_version || $version > $max_version) { 298 | $version = $default_version; 299 | } 300 | 301 | // Client-side JS to compare versions, alert and redirect 302 | wp_enqueue_script( 'ctfw-ie-unsupported', get_theme_file_uri( CTFW_JS_DIR . '/ie-unsupported.js' ), array( 'jquery' ), CTFW_THEME_VERSION ); // bust cache on theme update 303 | 304 | // Pass data 305 | wp_localize_script( 'ctfw-ie-unsupported', 'ctfw_ie_unsupported', array( // pass WP data into JS from this point on 306 | 'default_version' => $default_version, 307 | 'min_version' => $min_version, 308 | 'max_version' => $max_version, 309 | 'version' => $version, 310 | 'message' => __( 'You are using an outdated version of Internet Explorer. Please upgrade your browser to use this site.', 'church-theme-framework' ), 311 | 'redirect_url' => apply_filters( 'ctfw_upgrade_browser_url', 'https://browsehappy.com/' ) 312 | ) ); 313 | 314 | } 315 | 316 | } 317 | 318 | add_action( 'wp_enqueue_scripts', 'ctfw_enqueue_ie_unsupported', 1 ); // before all others 319 | -------------------------------------------------------------------------------- /includes/conditions.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Detect Conditions 4 | * 5 | * @package Church_Theme_Framework 6 | * @subpackage Functions 7 | * @copyright Copyright (c) 2013 - 2019, ChurchThemes.com, LLC 8 | * @link https://github.com/churchthemes/church-theme-framework 9 | * @license GPLv2 or later 10 | * @since 0.9 11 | */ 12 | 13 | // No direct access 14 | if (! defined( 'ABSPATH' )) exit; 15 | 16 | /******************************************* 17 | * CONTENT 18 | *******************************************/ 19 | 20 | /** 21 | * Is multipage 22 | * 23 | * @since 0.9 24 | * @global bool $multipage 25 | * @return bool True if current post has multiple pages 26 | */ 27 | function ctfw_is_multipage() { 28 | 29 | global $multipage; 30 | 31 | $is_multipage = false; 32 | 33 | if (! empty( $multipage )) { 34 | $is_multipage = true; 35 | } 36 | 37 | return apply_filters( 'ctfw_is_multipage', $is_multipage ); 38 | 39 | } 40 | 41 | /** 42 | * Has <!--more--> quicktag 43 | * 44 | * @since 0.9 45 | * @global object $post 46 | * @return bool True if uses more tag 47 | */ 48 | function ctfw_has_more_tag() { 49 | 50 | global $post; 51 | 52 | $has_more_tag = false; 53 | 54 | if (preg_match( '/<!--more(.*?)?-->/', $post->post_content )) { 55 | $has_more_tag = true; 56 | } 57 | 58 | return apply_filters( 'ctfw_has_more_tag', $has_more_tag ); 59 | 60 | } 61 | 62 | /** 63 | * Has title 64 | * 65 | * @since 0.9 66 | * @return bool True if has title 67 | */ 68 | function ctfw_has_title() { 69 | 70 | $has_title = false; 71 | 72 | if (trim( strip_tags( get_the_title() ) )) { 73 | $has_title = true; 74 | } 75 | 76 | return apply_filters( 'ctfw_has_title', $has_title ); 77 | 78 | } 79 | 80 | /** 81 | * Has content 82 | * 83 | * It strips tags because sometimes content-less tags are left behind (breaks). 84 | * This is useful for not outputting content wrapping tags when there is no content. 85 | * Content tags like img and iframe are preserved so user can add image content 86 | * with no text content and still have this return true. 87 | * 88 | * @since 0.9 89 | * @return bool True if has content 90 | */ 91 | function ctfw_has_content() { 92 | 93 | $has_content = false; 94 | 95 | // Check for content and allow certain tags if no content. 96 | // This way, for example, if has just an image tag, the content still renders. 97 | $content = trim( get_the_content() ); 98 | if (strip_tags( $content, '<img><iframe><script><embed><audio><video>' ) || preg_match( '/wp\:/', $content )) { 99 | $has_content = true; 100 | } 101 | 102 | // Check for page buiders like Elementor and Beaver Builder. 103 | if (ctfw_using_builder_plugin()) { 104 | $has_content = true; 105 | } 106 | 107 | return apply_filters( 'ctfw_has_content', $has_content ); 108 | 109 | } 110 | 111 | /** 112 | * Has excerpt 113 | * 114 | * @since 0.9 115 | * @return bool True if has manual or automatic excerpt 116 | */ 117 | function ctfw_has_excerpt() { 118 | 119 | $has_excerpt = false; 120 | 121 | if (trim( strip_tags( get_the_excerpt() ) )) { 122 | $has_excerpt = true; 123 | } 124 | 125 | return apply_filters( 'ctfw_has_excerpt', $has_excerpt ); 126 | 127 | } 128 | 129 | /** 130 | * Has manual excerpt 131 | * 132 | * @since 0.9 133 | * @global object $post 134 | * @return bool True if has manual excerpt 135 | */ 136 | function ctfw_has_manual_excerpt() { 137 | 138 | global $post; 139 | 140 | $bool = false; 141 | 142 | if (trim( strip_tags( $post->post_excerpt ) )) { 143 | $bool = true; 144 | } 145 | 146 | return apply_filters( 'ctfw_has_manual_excerpt', $bool ); 147 | 148 | } 149 | 150 | /** 151 | * Has excerpt or more tag 152 | * 153 | * @since 1.7.1 154 | * @global object $post 155 | * @return bool True if has manual excerpt 156 | */ 157 | function ctfw_has_excerpt_or_more() { 158 | 159 | $bool = false; 160 | 161 | if (ctfw_has_excerpt() || ctfw_has_more_tag()) { 162 | $bool = true; 163 | } 164 | 165 | return apply_filters( 'ctfw_has_excerpt_or_more', $bool ); 166 | 167 | } 168 | 169 | /** 170 | * Check if theme's "loop after content" used 171 | * 172 | * This tells if a loop is being output on a regular page, such as via page templates like Sermons, People, etc. 173 | * It helps ctfw_has_loop_multiple() below. 174 | * 175 | * Usage follows. Second argument is function to call for the check. 176 | * 177 | * add_theme_support( 'ctfw-loop-after-content-used', 'saved_loop_after_content_used' ); 178 | * 179 | * @since 2.1.1 180 | * @return bool True if given function is true 181 | */ 182 | function ctfw_loop_after_content_used() { 183 | 184 | $result = false; 185 | 186 | // Check theme support and function to call 187 | $support = get_theme_support( 'ctfw-loop-after-content-used' ); 188 | 189 | // Function given in theme support? 190 | if (! empty( $support[0] )) { 191 | 192 | // Get function 193 | $function = $support[0]; 194 | 195 | // Run function if exists 196 | if (function_exists( $function )) { 197 | $result = call_user_func( $function ); 198 | } 199 | 200 | } 201 | 202 | return apply_filters( 'ctfw_loop_after_content_used', $result ); 203 | 204 | } 205 | 206 | /** 207 | * Has loop for multiple entries 208 | * 209 | * This page is looping multiple entries 210 | * 211 | * @since 1.9.2 212 | * @return bool true if looping multiple entries 213 | */ 214 | function ctfw_has_loop_multiple() { 215 | 216 | $showing = false; 217 | 218 | // Loop being output on regular page, such as via page templates like Sermons, People, etc. 219 | // Requires 'ctfw-loop-after-content-used' via add_theme_support() 220 | if (ctfw_loop_after_content_used()) { 221 | $showing = true; 222 | } 223 | 224 | // Archives like Sermon Topics, People Groups, etc. 225 | // Also covers post type archives if a page with archive template isn't setup yet 226 | elseif (is_archive()) { 227 | $showing = true; 228 | } 229 | 230 | // Blog requires special handling with regard to Settings > Reading 231 | elseif (is_home()) { // is_home() returns blog page 232 | $showing = true; 233 | } 234 | 235 | // Search shows short entries 236 | elseif (is_search()) { 237 | $showing = true; 238 | } 239 | 240 | // Return filterable 241 | return apply_filters( 'ctfw_has_loop_multiple', $showing ); 242 | 243 | } 244 | 245 | /** 246 | * Is page template used 247 | * 248 | * An shorter way to determine if page is using a template 249 | * 250 | * Usage: ctfw_is_page_template( 'homepage' ) // homepage.php in template directory 251 | * 252 | * @since 1.9.3 253 | * @param $name Filename with or without .php and with or without path 254 | * @return bool True if current page is using that template 255 | */ 256 | function ctfw_is_page_template( $name ) { 257 | 258 | // Remove path and .php 259 | $name = basename( $name, '.php' ); 260 | 261 | // Check it 262 | $result = is_page_template( CTFW_THEME_PAGE_TPL_DIR . '/' . $name . '.php' ) ? true : false; 263 | 264 | // Return filtered 265 | return apply_filters( 'ctfw_is_page_template', $result, $name ); 266 | 267 | } 268 | 269 | /** 270 | * Using page builder plugin? 271 | * 272 | * This is useful for assisting ctfw_has_content() so that the_content() can be run. 273 | * 274 | * @since 2.6.3 275 | * @global object $post 276 | * @return bool True if so 277 | */ 278 | function ctfw_using_builder_plugin() { 279 | 280 | global $post; 281 | 282 | $result = false; 283 | 284 | // Elementor. 285 | if (did_action( 'elementor/loaded' ) && isset( $post->ID ) && \Elementor\Plugin::$instance->db->is_built_with_elementor( $post->ID )) { 286 | $result = true; 287 | } 288 | 289 | // Beaver Builder. 290 | elseif (method_exists( 'FLBuilderModel', 'is_builder_enabled' ) && FLBuilderModel::is_builder_enabled()) { 291 | $result = true; 292 | } 293 | 294 | return apply_filters( 'ctfw_using_builder_plugin', $result ); 295 | 296 | } 297 | 298 | /******************************************* 299 | * WIDGETS 300 | *******************************************/ 301 | 302 | /** 303 | * Determine if inside a particular sidebar / widget area 304 | * 305 | * This uses global set by ctfw_set_current_sidebar_id() in sidebars.php. 306 | * 307 | * @since 2.0 308 | * @param string $sidebar_id Sidebar ID / widget area 309 | * @return bool True if so 310 | */ 311 | function ctfw_is_sidebar( $sidebar_id ) { 312 | 313 | global $ctfw_current_sidebar_id; 314 | 315 | $is = false; 316 | 317 | if (isset( $ctfw_current_sidebar_id ) && $sidebar_id == $ctfw_current_sidebar_id) { 318 | $is = true; 319 | } 320 | 321 | return apply_filters( 'ctfw_is_sidebar', $is, $sidebar_id ); 322 | 323 | } 324 | 325 | /** 326 | * Determine if is first widget in sidebar 327 | * 328 | * This uses global set by ctfw_increment_widget_position() in widgets.php. 329 | * 330 | * @since 2.0 331 | * @return bool True if so 332 | */ 333 | function ctfw_is_first_widget() { 334 | 335 | global $ctfw_current_widget_position; 336 | 337 | $is_first = false; 338 | 339 | if (isset( $ctfw_current_widget_position ) && 1 == $ctfw_current_widget_position) { 340 | $is_first = true; 341 | } 342 | 343 | return apply_filters( 'ctfw_is_first_widget', $is_first ); 344 | 345 | } 346 | 347 | /******************************************* 348 | * USERS 349 | *******************************************/ 350 | 351 | /** 352 | * User can edit post 353 | * 354 | * @since 0.9 355 | * @global object $post 356 | * @return bool True if user can edit post 357 | */ 358 | function ctfw_can_edit_post() { 359 | 360 | global $post; 361 | 362 | $can_edit = false; 363 | 364 | if (! empty( $post )) { 365 | 366 | $post_type_object = get_post_type_object( $post->post_type ); 367 | 368 | if (! empty( $post_type_object )) { 369 | 370 | if (current_user_can( $post_type_object->cap->edit_post, $post->ID )) { 371 | $can_edit = true; 372 | } 373 | 374 | } 375 | 376 | } 377 | 378 | return apply_filters( 'ctfw_can_edit_post', $can_edit ); 379 | 380 | } 381 | 382 | /******************************************* 383 | * OTHER 384 | *******************************************/ 385 | 386 | /** 387 | * Is this the posts page? 388 | * 389 | * If a static front page is used and the "Posts page" is set, this is true. 390 | * 391 | * @since 0.9 392 | * @return bool True if is "Posts page" 393 | */ 394 | function ctfw_is_posts_page() { 395 | 396 | $bool = false; 397 | 398 | if (is_home() && ! is_front_page()) { 399 | $bool = true; 400 | } 401 | 402 | return apply_filters( 'ctfw_is_posts_page', $bool ); 403 | 404 | } 405 | -------------------------------------------------------------------------------- /includes/embeds.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Embed Functions 4 | * 5 | * @package Church_Theme_Framework 6 | * @subpackage Functions 7 | * @copyright Copyright (c) 2013 - 2019, ChurchThemes.com, LLC 8 | * @link https://github.com/churchthemes/church-theme-framework 9 | * @license GPLv2 or later 10 | * @since 0.9 11 | */ 12 | 13 | // No direct access. 14 | if (! defined( 'ABSPATH' )) { 15 | exit; 16 | } 17 | 18 | /*********************************************** 19 | * EMBEDS 20 | ***********************************************/ 21 | 22 | /** 23 | * Embed code based on audio/video URL or provided embed code 24 | * 25 | * If content is URL, use oEmbed to get embed code. If content is not URL, assume it is 26 | * embed code and run do_shortcode() in case of [video], [audio] or [embed] 27 | * 28 | * @since 0.9 29 | * @param string $content URL 30 | */ 31 | function ctfw_embed_code( $content ) { 32 | 33 | global $wp_embed; 34 | 35 | // Convert URL into media shortcode like [audio] or [video] 36 | if (ctfw_is_url( $content )) { 37 | 38 | $embed_code = ''; 39 | 40 | // Use native HTML 5 <audio> or <video> player. 41 | if (current_theme_supports( 'ctfw-native-player' )) { 42 | 43 | $url = $content; 44 | 45 | // Get file type. 46 | $filetype = wp_check_filetype( $url ); 47 | 48 | // Audio or video player? 49 | $tag = ''; 50 | if ( 51 | // Audio (ideally mp3) 52 | 'mp3' === $filetype['ext'] 53 | || 'wav' === $filetype['ext'] 54 | || ( 'mp4' === $filetype['ext'] && 'audio/mp4' === $filetype['type'] ) // can be audio or video (usually video) 55 | // ogg/audio and acc better off with MediaElementJS due to browser support 56 | ) { 57 | $tag = 'audio'; 58 | } elseif ( 59 | // Video (ideally mp4) 60 | 'mp4' === $filetype['ext'] // video if wasn't audio above 61 | // ogg/video and webm better off with MediaElementJS due to browser support 62 | ) { 63 | $tag = 'video'; 64 | } 65 | 66 | // Build tag. 67 | if ($tag) { 68 | $embed_code = '<' . $tag . ' controls="" controlsList="nodownload" src="' . esc_url( $url ) . '"></' . $tag . '>'; 69 | } 70 | 71 | } 72 | 73 | // If not using native player or could not detect an audio/video file type. 74 | if (! $embed_code) { 75 | 76 | // Make Dropbox URL's work. 77 | if (preg_match( '/dropbox/', $content )) { 78 | 79 | // URL. 80 | $url = $content; 81 | $url_no_qs = strtok( $url, '?' ); 82 | 83 | // Replace ?dl=0 (or ?dl=1) with ?raw=1. 84 | $url = remove_query_arg( 'dl', $url ); 85 | $url = add_query_arg( 'raw', '1', $url ); 86 | 87 | // Audio or video shortcode? 88 | $format = ''; 89 | $ext = pathinfo( $url_no_qs , PATHINFO_EXTENSION ); 90 | 91 | if (in_array( $ext, array( 'mp3', 'wav' ) )) { // Audio (ideally mp3) 92 | $format = 'audio'; 93 | } else if (in_array( $ext, array( 'mp4' ) )) { // Video (assuming mp4 is video, not audio) 94 | $format = 'video'; 95 | } 96 | 97 | // Build shortcode since WP won't auto-convert URL. 98 | if ($format) { 99 | 100 | $shortcode = '[' . $format . ' src="' . $url . '"]'; 101 | 102 | add_filter( 'wp_' . $format . '_extensions', 'ctfw_embed_allow_dropbox' ); // temporarily allow parameters like ?raw=1 in media URL 103 | $embed_code = do_shortcode( $shortcode ); 104 | remove_filter( 'wp_' . $format . '_extensions', 'ctfw_embed_allow_dropbox' ); 105 | 106 | } 107 | 108 | } 109 | 110 | // Process shortcode 111 | if (! $embed_code) { 112 | $embed_code = $wp_embed->shortcode( array(), $content ); 113 | } 114 | 115 | } 116 | 117 | } 118 | 119 | // HTML or shortcode embed may have been provided 120 | else { 121 | $embed_code = $content; 122 | } 123 | 124 | // Use video media item's featured image in shortcode like video block does. 125 | // Only for WP 4.4+ (2015) and on [video] shortcode not already having poster attribute. 126 | if (function_exists( 'get_the_post_thumbnail_url' ) && preg_match( '/^\[video /', $embed_code ) && ! preg_match( '/^poster="/', $embed_code )) { 127 | 128 | $poster_url = ''; 129 | 130 | // Get src URL from shortcode. 131 | preg_match( '/src="([^"]*)"/', $embed_code, $matches ); 132 | $src_url = isset( $matches[1] ) ? $matches[1] : ''; 133 | 134 | // Get video's media post ID from URL. 135 | $media_post_id = url_to_postid( $src_url ); 136 | $media_post_id = attachment_url_to_postid( $src_url ); 137 | 138 | // Get poster URL from media item's featured image. 139 | if ($media_post_id) { 140 | $poster_url = get_the_post_thumbnail_url( $media_post_id, 'full' ); 141 | } 142 | 143 | // Add poster attribute to shortcode if have URL. 144 | if ($poster_url) { 145 | $embed_code = str_replace( '[video ', '[video poster="' . $poster_url . '" ', $embed_code ); 146 | } 147 | 148 | } 149 | 150 | // Run shortcode 151 | // [video], [audio] or [embed] converted from URL or already existing in $content 152 | $embed_code = do_shortcode( $embed_code ); 153 | 154 | // Return filtered 155 | return apply_filters( 'ctfw_embed_code', $embed_code, $content ); 156 | 157 | } 158 | 159 | /** 160 | * Allow Dropbox URL's with ?raw=1 in shortcodes 161 | * 162 | * Used temporarily by ctfw_embed_code() for wp_audio_extensions and wp_video_extensions. 163 | * 164 | * This is a workaround until WordPress allows media URLs with query strings: 165 | * https://wordpress.stackexchange.com/questions/220572/how-can-i-get-the-video-shortcode-to-allow-query-string-parameters 166 | * 167 | * @param array $ext Array of extensions 168 | * @return array Modified extensions 169 | */ 170 | function ctfw_embed_allow_dropbox( $ext ) { 171 | 172 | // Allows any extension (ie. mp3?raw=1 for Dropbox) 173 | $ext[] = ''; 174 | 175 | return $ext; 176 | 177 | } 178 | 179 | /** 180 | * Responsive embeds JavaScript 181 | */ 182 | function ctfw_responsive_embeds_enqueue_scripts() { 183 | 184 | // If theme supports this feature 185 | if (current_theme_supports( 'ctfw-responsive-embeds' )) { 186 | 187 | // FitVids.js 188 | wp_enqueue_script( 'fitvids', get_theme_file_uri( CTFW_JS_DIR . '/jquery.fitvids.js' ), array( 'jquery' ), CTFW_THEME_VERSION ); // bust cache on theme update 189 | 190 | // Responsive embeds script 191 | if (! preg_match('/^sfwd/', get_post_type())) { // Not on LearnDash post types (HS 24879) 192 | wp_enqueue_script( 'ctfw-responsive-embeds', get_theme_file_uri( CTFW_JS_DIR . '/responsive-embeds.js' ), array( 'fitvids' ), CTFW_THEME_VERSION ); // bust cache on theme update 193 | wp_localize_script( 'ctfw-responsive-embeds', 'ctfw_responsive_embeds', array( 194 | 'wp_responsive_embeds' => current_theme_supports( 'responsive-embeds' ), 195 | ) ); 196 | } 197 | 198 | } 199 | 200 | } 201 | 202 | add_action( 'wp_enqueue_scripts', 'ctfw_responsive_embeds_enqueue_scripts' ); // front-end only (yes, wp_enqueue_scripts is correct for styles) 203 | 204 | /** 205 | * Generic embeds 206 | * 207 | * This helps make embeds more generic by setting parameters to remove. 208 | * related videos, set neutral colors, reduce branding, etc. 209 | * 210 | * Enable with: add_theme_support( 'ctfw-generic-embeds' ); 211 | * 212 | * @since 0.9 213 | * @param string $html Embed HTML code 214 | * @return string Modified embed HTML code 215 | */ 216 | function ctfw_generic_embeds( $html ) { 217 | 218 | // Does theme support this? 219 | if (current_theme_supports( 'ctfw-generic-embeds' )) { 220 | 221 | // Get frame source URL 222 | // Separating i from frame avoids Theme Check false positive 223 | preg_match_all( '/<i' . 'frame[^>]+src=([\'"])(.+?)\1[^>]*>/i', $html, $matches ); 224 | $url = ! empty( $matches[2][0] ) ? $matches[2][0] : ''; 225 | 226 | // URL found 227 | if ($url) { 228 | 229 | $new_url = ''; 230 | $source = ''; 231 | $args = array(); 232 | 233 | // YouTube 234 | if (preg_match( '/youtube/i', $url )) { 235 | $source = 'youtube'; 236 | $args = array( 237 | 'wmode' => 'transparent', 238 | 'rel' => '0', // don't show related videos at end 239 | 'showinfo' => '0', 240 | 'color' => 'white', 241 | 'modestbranding' => '1' 242 | ); 243 | } 244 | 245 | // Vimeo 246 | elseif (preg_match( '/vimeo/i', $url )) { 247 | $source = 'vimeo'; 248 | $args = array( 249 | 'title' => '0', 250 | 'byline' => '0', 251 | 'portrait' => '0', 252 | 'color' => 'ffffff' 253 | ); 254 | } 255 | 256 | // Modify URL 257 | $args = apply_filters( 'ctfw_generic_embeds_add_args', $args, $source ); 258 | $new_url = esc_url( add_query_arg( $args, $url ) ); 259 | 260 | // Replace source with modified URL 261 | if ($new_url != $url) { 262 | $html = str_replace( $url, $new_url, $html ); 263 | } 264 | 265 | } 266 | 267 | } 268 | 269 | return $html; 270 | 271 | } 272 | 273 | add_filter( 'embed_oembed_html', 'ctfw_generic_embeds' ); 274 | 275 | /** 276 | * HTML5 valid embeds 277 | * 278 | * This will correct YouTube embed code that is not HTML5 valid. 279 | * Other sources may be added later. 280 | * 281 | * Enable with add_theme_support( 'ctfw-valid-embeds' ); 282 | * 283 | * @since 1.7 284 | * @param string $html Embed HTML code 285 | * @return string Modified embed HTML code 286 | */ 287 | function ctfw_valid_embeds( $html ) { 288 | 289 | // Does theme support this? 290 | if (current_theme_supports( 'ctfw-valid-embeds' )) { 291 | 292 | // YouTube, Vimeo, etc. 293 | $html = str_replace( 'frameborder="0"', 'style="border: none;"', $html ); 294 | 295 | // Vimeo 296 | $html = preg_replace( '( webkitallowfullscreen| mozallowfullscreen)', '$1', $html ); 297 | 298 | } 299 | 300 | return $html; 301 | 302 | } 303 | 304 | add_filter( 'embed_oembed_html', 'ctfw_valid_embeds' ); 305 | 306 | /** 307 | * Clean media shortcode URLs 308 | * 309 | * Safari sometimes struggles with video shortcode's src URL when ?_=1 is appended. 310 | * Remove it with add_theme_support( 'ctfw-clean-media-shortcode-url' ); 311 | * 312 | * This may also help with firewall cache issues. 313 | * 314 | * @since 2.6.4 315 | * @param string $output Audio or video shortcode HTML output. 316 | * @param array $atts Array of video shortcode attributes. 317 | * @param string $media Audio or video file. 318 | * @param int $post_id Post ID. 319 | * @param string $library Media library used for the video shortcode. 320 | * @return string Filtered video output. 321 | */ 322 | function ctfw_clean_media_shortcode_url( $output, $atts, $media, $post_id, $library ) { 323 | 324 | // Theme supports this. 325 | if (current_theme_supports( 'ctfw-clean-media-shortcode-url' )) { 326 | 327 | // Remove ?_=1 from video URL. 328 | $output = str_replace( '?_=1', '', $output ); 329 | 330 | } 331 | 332 | return $output; 333 | 334 | } 335 | 336 | add_action( 'wp_audio_shortcode', 'ctfw_clean_media_shortcode_url', 10, 5 ); 337 | add_action( 'wp_video_shortcode', 'ctfw_clean_media_shortcode_url', 10, 5 ); 338 | -------------------------------------------------------------------------------- /includes/customize.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Theme Customizer 4 | * 5 | * Helper functions for theme customizer use. 6 | * 7 | * @package Church_Theme_Framework 8 | * @subpackage Functions 9 | * @copyright Copyright (c) 2013 - 2019, ChurchThemes.com, LLC 10 | * @link https://github.com/churchthemes/church-theme-framework 11 | * @license GPLv2 or later 12 | * @since 0.9 13 | */ 14 | 15 | // No direct access. 16 | if ( ! defined( 'ABSPATH' ) ) { 17 | exit; 18 | } 19 | 20 | /******************************************* 21 | * VALUES 22 | *******************************************/ 23 | 24 | /** 25 | * Customization option ID 26 | * 27 | * Used for storing and getting customizer option values from a master array. 28 | * The option name is based on the parent theme's name. 29 | * Settings API used instead of theme mod for greater flexibility. 30 | * 31 | * @since 0.9 32 | * @return string Option ID for customizations, unique to theme 33 | */ 34 | function ctfw_customize_option_id() { 35 | 36 | $option_id = CTFW_THEME_SLUG . '_customizations'; // unique to parent theme 37 | 38 | return apply_filters( 'ctfw_customize_option_id', $option_id ); // prefix with theme name so options are unique to theme 39 | 40 | } 41 | 42 | /** 43 | * Get customization value 44 | * 45 | * This gets a customization value for convenient use in templates, etc. 46 | * It automatically fills in default 47 | * 48 | * @since 0.9 49 | * @param string $option Customization option 50 | * @return string Option value 51 | */ 52 | function ctfw_customization( $option ) { 53 | 54 | $value = ''; 55 | 56 | // Get options array to pull value from 57 | $options = get_option( ctfw_customize_option_id() ); 58 | 59 | // Get default value 60 | $defaults = ctfw_customize_defaults(); 61 | $default = isset( $defaults[$option]['value'] ) ? $defaults[$option]['value'] : ''; 62 | 63 | // Option not saved - use default value 64 | if ( ! isset( $options[$option] ) ) { 65 | $value = $default; 66 | } 67 | 68 | // Option has been saved 69 | else { 70 | 71 | // Value is empty when not allowed, use default 72 | if ( empty( $options[$option] ) && ! empty( $defaults[$option]['no_empty'] ) ) { 73 | $value = $default; 74 | } 75 | 76 | // Otherwise, stick with current value 77 | else { 78 | $value = $options[$option]; 79 | } 80 | 81 | } 82 | 83 | // Replace psuedo shortcodes (used in Footer notice, Icon URLs, etc.) 84 | $value = str_replace( '[ctcom_site_name]', get_bloginfo( 'name' ), $value ); 85 | $value = str_replace( '[ctcom_current_year]', date( 'Y' ), $value ); 86 | $value = str_replace( '[ctcom_rss_url]', get_bloginfo( 'rss_url' ), $value ); 87 | 88 | // Return filtered 89 | return apply_filters( 'ctfw_customization', $value, $option ); 90 | 91 | } 92 | 93 | /** 94 | * Get raw customization value 95 | * 96 | * Get raw customization value without filling in of defaults 97 | * 98 | * @since 1.4.1 99 | * @param string $option Customization option 100 | * @return string Option value 101 | */ 102 | function ctfw_customization_raw( $option ) { 103 | 104 | $value = ''; 105 | 106 | // Get options array to pull value from 107 | $options = get_option( ctfw_customize_option_id() ); 108 | 109 | // Get value if set 110 | if ( isset( $options[$option] ) ) { 111 | $value = $options[$option]; 112 | } else { // empty if not set 113 | $value = ''; 114 | } 115 | 116 | // Return filtered 117 | return apply_filters( 'ctfw_customization_raw', $value, $option ); 118 | 119 | } 120 | 121 | /** 122 | * Update customization value 123 | * 124 | * This updates a customization value in the option's array of settings 125 | * 126 | * @since 1.4.1 127 | * @param string $option Customization option to update 128 | * @param mixed $value Value to update with 129 | */ 130 | function ctfw_update_customization( $option, $value ) { 131 | 132 | // Get array of options 133 | $options = get_option( ctfw_customize_option_id() ); 134 | 135 | // Update the value 136 | $options[$option] = $value; 137 | 138 | // Save modified array back to option 139 | update_option( ctfw_customize_option_id(), $options ); 140 | 141 | } 142 | 143 | /** 144 | * Unset customization 145 | * 146 | * This removes a customization from array of settings 147 | * 148 | * @since 1.4.1 149 | * @param string $option Customization option to remove from array 150 | */ 151 | function ctfw_unset_customization( $option ) { 152 | 153 | // Get array of options 154 | $options = get_option( ctfw_customize_option_id() ); 155 | 156 | // Update the value 157 | unset( $options[$option] ); 158 | 159 | // Save modified array back to option 160 | update_option( ctfw_customize_option_id(), $options ); 161 | 162 | } 163 | 164 | /** 165 | * Get Defaults 166 | * 167 | * Theme can make array of defaults available to framework via ctfw_customize_defaults filter. 168 | * This way they can be accessed via this function from anywhere. 169 | * 170 | * @since 0.9 171 | * @return array Customizer defaults 172 | */ 173 | function ctfw_customize_defaults() { 174 | 175 | // Get cached defaults. 176 | // Customization settings are gotten dozens of times per pageload 177 | // Caching them is a good idea in case default values every come from a function or query 178 | $transient = CTFW_THEME_SLUG . '_customize_defaults'; 179 | $defaults = get_transient( $transient ); 180 | 181 | // No cache; get defaults from theme, then cache. 182 | if ( empty( $defaults ) ) { 183 | 184 | // Get defaults from theme 185 | $defaults = apply_filters( 'ctfw_customize_defaults', array() ); 186 | 187 | // Cache defaults (if have them and not live preview) 188 | // 10 seconds good enough for one page load 189 | set_transient( $transient, $defaults, 10 ); 190 | 191 | } 192 | 193 | return $defaults; 194 | 195 | } 196 | 197 | /******************************************* 198 | * SANITIZATION 199 | *******************************************/ 200 | 201 | /** 202 | * Sanitize Checkbox 203 | * 204 | * This is useful for using directly with sanitize_callback and sanitize_js_callback. 205 | * 206 | * @since 1.1.4 207 | * @param string $input The user-entered value 208 | * @return string Sanitized value 209 | */ 210 | function ctfw_customize_sanitize_checkbox( $input, $object ) { 211 | 212 | // True or empty 213 | if ( 1 == $input ) { 214 | $output = $input; 215 | } else { 216 | $output = ''; 217 | } 218 | 219 | // Return sanitized, filterable 220 | return apply_filters( 'ctfw_customize_sanitize_checkbox', $output, $input, $object ); 221 | 222 | } 223 | 224 | /** 225 | * Sanitize Single Choice 226 | * 227 | * Sanitize radio or single select. 228 | * 229 | * Check if input matches choices given and if not use default value when empty value not permitted. 230 | * 231 | * @since 1.1.4 232 | * @param string $setting The setting being sanitized, as provided in defaults array 233 | * @param string $input The user-entered value 234 | * @param array $choices Valid choices to check against 235 | * @return string Sanitized value 236 | */ 237 | function ctfw_customize_sanitize_single_choice( $setting, $input, $choices ) { 238 | 239 | // Default values 240 | $defaults = ctfw_customize_defaults(); 241 | 242 | // Not valid choice; use default if empty value not permitted 243 | if ( ! isset( $choices[$input] ) && ! empty( $defaults[$setting]['no_empty'] ) ) { 244 | $output = $defaults[$setting]['value']; 245 | } else { 246 | $output = $input; // valid choice 247 | } 248 | 249 | // Return sanitized, filterable 250 | return apply_filters( 'ctfw_customize_sanitize_single_choice', $output, $setting, $input, $choices ); 251 | 252 | } 253 | 254 | /** 255 | * Sanitize Color 256 | * 257 | * If hex code empty or invalid, use default value when empty value is not permitted. 258 | * Add # to front of hex code if missing. 259 | * 260 | * @since 1.1.4 261 | * @param string $setting The setting being sanitized, as provided in defaults array 262 | * @param string $input The user-entered value 263 | * @return string Sanitized value 264 | */ 265 | function ctfw_customize_sanitize_color( $setting, $input ) { 266 | 267 | // Default values 268 | $defaults = ctfw_customize_defaults(); 269 | 270 | // Return null if hex code invalid 271 | $output = sanitize_hex_color( $input ); 272 | 273 | // If invalid or empty and empty value not permitted, use default 274 | if ( empty( $output ) && ! empty( $defaults[$setting]['no_empty'] ) ) { 275 | $output = $defaults[$setting]['value']; 276 | } 277 | 278 | // Add # if missing 279 | $output = maybe_hash_hex_color( $output ); 280 | 281 | // Return sanitized, filterable 282 | return apply_filters( 'ctfw_customize_sanitize_color', $output, $setting, $input ); 283 | 284 | } 285 | 286 | /** 287 | * Sanitize Google Font 288 | * 289 | * Check if input matches choices given and if not use default value when empty value not permitted. 290 | * 291 | * @since 1.1.4 292 | * @param string $setting The setting being sanitized, as provided in defaults array 293 | * @param string $input Unsanitized value submitted by user 294 | * @return string Sanitized value 295 | */ 296 | function ctfw_customize_sanitize_google_font( $setting, $input ) { 297 | 298 | // Valid choices 299 | $choices = ctfw_google_font_options_array( array( 'target' => $setting ) ); 300 | 301 | // Check input against options; use default if empty value not permitted 302 | // ctfw_customize_sanitize_single_choice() is for radio or single select 303 | $output = ctfw_customize_sanitize_single_choice( $setting, $input, $choices ); 304 | 305 | // Return sanitized, filterable 306 | return apply_filters( 'ctfw_customize_sanitize_google_font', $output, $setting, $input ); 307 | 308 | } 309 | 310 | /** 311 | * Also see ctfw_sanitize_url_list() in includes/helpers.php. 312 | * It is useful for social media URL lists in Customizer. 313 | */ 314 | 315 | 316 | /********************************************* 317 | * SCRIPTS & STYLES 318 | *********************************************/ 319 | 320 | /** 321 | * Enqueue JavaScript for customizer controls 322 | * 323 | * @since 1.2 324 | */ 325 | function ctfw_customize_enqueue_scripts() { 326 | 327 | // New media uploader in WP 3.5+ 328 | wp_enqueue_media(); 329 | 330 | // Main widgets script 331 | wp_enqueue_script( 'ctfw-admin-widgets', get_theme_file_uri( CTFW_JS_DIR . '/admin-widgets.js' ), array( 'jquery' ), CTFW_THEME_VERSION ); // bust cache on update 332 | wp_localize_script( 'ctfw-admin-widgets', 'ctfw_widgets', ctfw_admin_widgets_js_data() ); // see admin-widgets.php 333 | 334 | } 335 | 336 | add_action( 'customize_controls_print_scripts', 'ctfw_customize_enqueue_scripts' ); 337 | 338 | /** 339 | * Enqueue styles for customizer controls 340 | * 341 | * @since 1.2 342 | */ 343 | function ctfw_customize_enqueue_styles() { 344 | 345 | // Admin widgets 346 | // Same stylesheet used for Appearance > Widgets 347 | wp_enqueue_style( 'ctfw-widgets', get_theme_file_uri( CTFW_CSS_DIR . '/admin-widgets.css' ), false, CTFW_THEME_VERSION ); // bust cache on update 348 | 349 | } 350 | 351 | add_action( 'customize_controls_print_styles', 'ctfw_customize_enqueue_styles' ); 352 | -------------------------------------------------------------------------------- /includes/classes/widget-galleries.php: -------------------------------------------------------------------------------- 1 | <?php 2 | /** 3 | * Galleries Widget 4 | * 5 | * This lists all gallery pages. 6 | * 7 | * @package Church_Theme_Framework 8 | * @subpackage Classes 9 | * @copyright Copyright (c) 2013 - 2019, ChurchThemes.com, LLC 10 | * @link https://github.com/churchthemes/church-theme-framework 11 | * @license GPLv2 or later 12 | * @since 0.9 13 | */ 14 | 15 | // No direct access. 16 | if ( ! defined( 'ABSPATH' ) ) { 17 | exit; 18 | } 19 | 20 | /** 21 | * Galleries widget class 22 | * 23 | * @since 0.9 24 | */ 25 | class CTFW_Widget_Galleries extends CTFW_Widget { 26 | 27 | /** 28 | * Register widget with WordPress 29 | * 30 | * @since 0.9 31 | */ 32 | function __construct() { 33 | 34 | parent::__construct( 35 | 'ctfw-galleries', 36 | _x( 'CT Galleries', 'widget', 'church-theme-framework' ), 37 | array( 38 | 'description' => __( 'Shows list of gallery pages', 'church-theme-framework' ) 39 | ) 40 | ); 41 | 42 | } 43 | 44 | /** 45 | * Field configuration 46 | * 47 | * This is used by CTFW_Widget class for automatic field output, filtering, sanitization and saving. 48 | * 49 | * @since 0.9 50 | * @return array Fields for widget 51 | */ 52 | function ctfw_fields() { // prefix in case WP core adds method with same name 53 | 54 | // Fields 55 | $fields = array( 56 | 57 | // Example 58 | /* 59 | 'field_id' => array( 60 | 'name' => __( 'Field Name', 'church-theme-framework' ), 61 | 'after_name' => '', // (Optional), (Required), etc. 62 | 'desc' => __( 'This is the description below the field.', 'church-theme-framework' ), 63 | 'type' => 'text', // text, textarea, checkbox, radio, select, number, url, image, color 64 | 'checkbox_label' => '', //show text after checkbox 65 | 'radio_inline' => false, // show radio inputs inline or on top of each other 66 | 'number_min' => '', // lowest possible value for number type 67 | 'number_max' => '', // highest possible value for number type 68 | 'options' => array(), // array of keys/values for radio or select 69 | 'upload_button' => '', // for url field; text for button that opens media frame 70 | 'upload_title' => '', // for url field; title appearing at top of media frame 71 | 'upload_type' => '', // for url field; optional type of media to filter by (image, audio, video, application/pdf) 72 | 'default' => '', // value to pre-populate option with (before first save or on reset) 73 | 'no_empty' => false, // if user empties value, force default to be saved instead 74 | 'allow_html' => false, // allow HTML to be used in the value (text, textarea) 75 | 'attributes' => array(), // attributes to add to input element 76 | 'class' => '', // class(es) to add to input 77 | 'field_attributes' => array(), // attr => value array for field container 78 | 'field_class' => '', // class(es) to add to field container 79 | 'custom_sanitize' => '', // function to do additional sanitization (or array( &$this, 'method' )) 80 | 'custom_field' => '', // function for custom display of field input 81 | 'taxonomies' => array(), // hide field if taxonomies are not supported 82 | ); 83 | */ 84 | 85 | // Title 86 | 'title' => array( 87 | 'name' => _x( 'Title', 'galleries widget', 'church-theme-framework' ), 88 | 'after_name' => '', // (Optional), (Required), etc. 89 | 'desc' => '', 90 | 'type' => 'text', // text, textarea, checkbox, radio, select, number, url, image, color 91 | 'checkbox_label' => '', //show text after checkbox 92 | 'radio_inline' => false, // show radio inputs inline or on top of each other 93 | 'number_min' => '', // lowest possible value for number type 94 | 'number_max' => '', // highest possible value for number type 95 | 'options' => array(), // array of keys/values for radio or select 96 | 'upload_button' => '', // for url field; text for button that opens media frame 97 | 'upload_title' => '', // for url field; title appearing at top of media frame 98 | 'upload_type' => '', // for url field; optional type of media to filter by (image, audio, video, application/pdf) 99 | 'default' => _x( 'Galleries', 'galleries widget title default', 'church-theme-framework' ), // value to pre-populate option with (before first save or on reset) 100 | 'no_empty' => false, // if user empties value, force default to be saved instead 101 | 'allow_html' => false, // allow HTML to be used in the value (text, textarea) 102 | 'attributes' => array(), // attributes to add to input element 103 | 'class' => '', // class(es) to add to input 104 | 'field_attributes' => array(), // attr => value array for field container 105 | 'field_class' => '', // class(es) to add to field container 106 | 'custom_sanitize' => '', // function to do additional sanitization (or array( &$this, 'method' )) 107 | 'custom_field' => '', // function for custom display of field input 108 | 'taxonomies' => array(), // hide field if taxonomies are not supported 109 | ), 110 | 111 | // Order By 112 | 'orderby' => array( 113 | 'name' => _x( 'Order By', 'galleries widget', 'church-theme-framework' ), 114 | 'after_name' => '', // (Optional), (Required), etc. 115 | 'desc' => '', 116 | 'type' => 'select', // text, textarea, checkbox, radio, select, number, url, image, color 117 | 'checkbox_label' => '', //show text after checkbox 118 | 'radio_inline' => false, // show radio inputs inline or on top of each other 119 | 'number_min' => '', // lowest possible value for number type 120 | 'number_max' => '', // highest possible value for number type 121 | 'options' => array( // array of keys/values for radio or select 122 | 'date' => _x( 'Date Added', 'galleries widget order by', 'church-theme-framework' ), 123 | 'title' => _x( 'Title', 'galleries widget order by', 'church-theme-framework' ), 124 | ), 125 | 'upload_button' => '', // for url field; text for button that opens media frame 126 | 'upload_title' => '', // for url field; title appearing at top of media frame 127 | 'upload_type' => '', // for url field; optional type of media to filter by (image, audio, video, application/pdf) 128 | 'default' => 'date', // value to pre-populate option with (before first save or on reset) 129 | 'no_empty' => true, // if user empties value, force default to be saved instead 130 | 'allow_html' => false, // allow HTML to be used in the value (text, textarea) 131 | 'attributes' => array(), // attributes to add to input element 132 | 'class' => '', // class(es) to add to input 133 | 'field_attributes' => array(), // attr => value array for field container 134 | 'field_class' => 'ctfw-widget-no-bottom-margin', // class(es) to add to field container 135 | 'custom_sanitize' => '', // function to do additional sanitization (or array( &$this, 'method' )) 136 | 'custom_field' => '', // function for custom display of field input 137 | 'taxonomies' => array(), // hide field if taxonomies are not supported 138 | ), 139 | 140 | // Order 141 | 'order' => array( 142 | 'name' => '', 143 | 'after_name' => '', // (Optional), (Required), etc. 144 | 'desc' => '', 145 | 'type' => 'radio', // text, textarea, checkbox, radio, select, number, url, image, color 146 | 'checkbox_label' => '', // show text after checkbox 147 | 'radio_inline' => true, // show radio inputs inline or on top of each other 148 | 'number_min' => '', // lowest possible value for number type 149 | 'number_max' => '', // highest possible value for number type 150 | 'options' => array( // array of keys/values for radio or select 151 | 'asc' => _x( 'Low to High', 'galleries widget order', 'church-theme-framework' ), 152 | 'desc' => _x( 'High to Low', 'galleries widget order', 'church-theme-framework' ), 153 | ), 154 | 'upload_button' => '', // for url field; text for button that opens media frame 155 | 'upload_title' => '', // for url field; title appearing at top of media frame 156 | 'upload_type' => '', // for url field; optional type of media to filter by (image, audio, video, application/pdf) 157 | 'default' => 'desc', // value to pre-populate option with (before first save or on reset) 158 | 'no_empty' => true, // if user empties value, force default to be saved instead 159 | 'allow_html' => false, // allow HTML to be used in the value (text, textarea) 160 | 'attributes' => array(), // attributes to add to input element 161 | 'class' => '', // class(es) to add to input 162 | 'field_attributes' => array(), // attr => value array for field container 163 | 'field_class' => '', // class(es) to add to field container 164 | 'custom_sanitize' => '', // function to do additional sanitization (or array( &$this, 'method' )) 165 | 'custom_field' => '', // function for custom display of field input 166 | 'taxonomies' => array(), // hide field if taxonomies are not supported 167 | ), 168 | 169 | // Limit 170 | 'limit' => array( 171 | 'name' => _x( 'Limit', 'galleries widget', 'church-theme-framework' ), 172 | 'after_name' => '', // (Optional), (Required), etc. 173 | 'desc' => _x( 'Set to 0 for unlimited.', 'galleries widget', 'church-theme-framework' ), 174 | 'type' => 'number', // text, textarea, checkbox, radio, select, number, url, image, color 175 | 'checkbox_label' => '', //show text after checkbox 176 | 'radio_inline' => false, // show radio inputs inline or on top of each other 177 | 'number_min' => '0', // lowest possible value for number type 178 | 'number_max' => '', // highest possible value for number type 179 | 'options' => array(), // array of keys/values for radio or select 180 | 'upload_button' => '', // for url field; text for button that opens media frame 181 | 'upload_title' => '', // for url field; title appearing at top of media frame 182 | 'upload_type' => '', // for url field; optional type of media to filter by (image, audio, video, application/pdf) 183 | 'default' => '5', // value to pre-populate option with (before first save or on reset) 184 | 'no_empty' => false, // if user empties value, force default to be saved instead 185 | 'allow_html' => false, // allow HTML to be used in the value (text, textarea) 186 | 'attributes' => array(), // attributes to add to input element 187 | 'class' => '', // class(es) to add to input 188 | 'field_attributes' => array(), // attr => value array for field container 189 | 'field_class' => '', // class(es) to add to field container 190 | 'custom_sanitize' => '', // function to do additional sanitization (or array( &$this, 'method' )) 191 | 'custom_field' => '', // function for custom display of field input 192 | 'taxonomies' => array(), // hide field if taxonomies are not supported 193 | ), 194 | 195 | ); 196 | 197 | // Return config 198 | return apply_filters( 'ctfw_galleries_widget_fields', $fields ); 199 | 200 | } 201 | 202 | /** 203 | * Get posts 204 | * 205 | * This can optionally be used by the template. 206 | * $this->instance is sanitized before being made available here. 207 | * 208 | * @since 0.9 209 | * @return array Posts for widget template 210 | */ 211 | function ctfw_get_posts() { 212 | 213 | // Get gallery pages/posts 214 | $posts = ctfw_gallery_posts( array( 215 | 'order' => $this->ctfw_instance['order'], 216 | 'orderby' => $this->ctfw_instance['orderby'], 217 | 'limit' => $this->ctfw_instance['limit'] 218 | ) ); 219 | 220 | // Return filtered 221 | return apply_filters( 'ctfw_galleries_widget_get_posts', $posts ); 222 | 223 | } 224 | 225 | 226 | } --------------------------------------------------------------------------------