├── assets ├── js │ └── .gitkeep ├── images │ └── .gitkeep ├── screenshot-1.png └── css │ └── .DO_NOT_EDIT.txt ├── languages └── .gitkeep ├── .bowerrc ├── src ├── scss │ ├── plugin.scss │ ├── _media-queries.scss │ ├── _core.scss │ ├── _variables.scss │ ├── admin.scss │ └── _carbon-fields.scss └── js │ ├── admin │ └── admin.js │ ├── frontend │ └── plugin.js │ └── common │ └── common.js ├── bower.json ├── app ├── Widgets │ ├── Widget_Loader.php │ └── Example_Widget.php ├── PostTypes │ ├── PostTypes_Loader.php │ └── Clients.php ├── Shortcodes │ ├── Shortcode_Loader.php │ ├── CurrentYear_Shortcode.php │ └── Hello_Shortcode.php ├── Settings │ ├── Network_Settings_Page.php │ ├── Customizer_Options.php │ └── Settings_Page.php ├── Datastores │ └── Serialized_Theme_Options_Datastore.php ├── TGMPA.php ├── Core.php ├── EnqueueScripts.php ├── Plugin.php └── Helpers.php ├── plugin.json ├── .editorconfig ├── .gitignore ├── composer.json ├── package.json ├── wordpress-base-plugin.php ├── readme.txt ├── README.md ├── gulpfile.js └── LICENSE /assets/js/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /languages/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory" : "assets/components", 3 | "ignoredDependencies": [ 4 | "jquery" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmhendricks/wordpress-base-plugin/HEAD/assets/screenshot-1.png -------------------------------------------------------------------------------- /src/scss/plugin.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @preserve: Frontend style 3 | */ 4 | 5 | @import "variables"; 6 | @import "core"; 7 | @import "media-queries"; 8 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wordpress-base-plugin", 3 | "dependencies": { 4 | "jq.waituntilexists": "*" 5 | }, 6 | "private": true 7 | } 8 | -------------------------------------------------------------------------------- /src/scss/_media-queries.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Media Queries - Example 3 | */ 4 | 5 | @media (max-width: 575px) { 6 | body { 7 | font-size: 115% 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/scss/_core.scss: -------------------------------------------------------------------------------- 1 | a { 2 | color: $link-color; 3 | 4 | &:hover { 5 | color: $link-color-hover; 6 | } 7 | } 8 | 9 | h1 { 10 | 11 | &.site-title { 12 | font-family: $font-family-base; 13 | 14 | a { 15 | text-decoration: underline; 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | // Typography 2 | 3 | $font-family-base: Arial, Helvetica, sans-serif; 4 | $font-family-headers: 'Palatino Linotype', 'Book Antiqua', Palatino, serif; 5 | 6 | // Colors 7 | 8 | $link-color: #663399; 9 | $link-color-hover: #9966CC; 10 | $admin-bar-color: #336699; 11 | -------------------------------------------------------------------------------- /src/scss/admin.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @preserve: WP Admin style 3 | */ 4 | 5 | @import "variables"; 6 | @import "carbon-fields"; 7 | 8 | /* 9 | #wpadminbar { 10 | background-color: $admin-bar-color; 11 | } 12 | */ 13 | 14 | #wp-admin-bar-my-account { 15 | > .ab-item { 16 | color: #FFFF99 !important; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Widgets/Widget_Loader.php: -------------------------------------------------------------------------------- 1 | posttypes = array( 16 | Clients::class 17 | ); 18 | 19 | foreach( $this->posttypes as $postTypesClass ) { 20 | if( class_exists( $postTypesClass ) ) new $postTypesClass(); 21 | } 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/Shortcodes/Shortcode_Loader.php: -------------------------------------------------------------------------------- 1 | shortcodes = array( 16 | Hello_Shortcode::class, 17 | CurrentYear_Shortcode::class 18 | ); 19 | 20 | foreach( $this->shortcodes as $shortcodeClass ) { 21 | if( class_exists( $shortcodeClass ) ) new $shortcodeClass(); 22 | } 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /assets/css/.DO_NOT_EDIT.txt: -------------------------------------------------------------------------------- 1 | Do not edit the CSS files in the directory - they are automatically 2 | generated/compiled with SASS. Doing so may result in your changes being 3 | overwritten. 4 | 5 | To make changes to style, modify the appropriate file in the src/scss directory 6 | and Gulp (recommended) or other SASS compiler to generate the CSS file(s). 7 | 8 | If you don't want to use Gulp, Koala (http://koala-app.com/) is a good, 9 | cross-platform compiler. Just point it to the src/scss folder, make your 10 | changes to the SCSS file(s), and click compile. It will automatically generate 11 | the CSS file(s) here. 12 | 13 | (This only applies if SASS is being used to generate style.) 14 | -------------------------------------------------------------------------------- /app/Shortcodes/CurrentYear_Shortcode.php: -------------------------------------------------------------------------------- 1 | 'world' 27 | ), $atts, 'hello' ); 28 | 29 | return sprintf( __( 'Hello %s!', self::$textdomain ), $atts[ 'name' ] ); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Packages # 2 | ############ 3 | *.7z 4 | *.dmg 5 | *.gz 6 | *.bz2 7 | *.iso 8 | *.jar 9 | *.rar 10 | *.tar 11 | *.zip 12 | *.tgz 13 | *.map 14 | 15 | # Logs and databases # 16 | ###################### 17 | *.log 18 | *.sql 19 | 20 | # OS generated files # 21 | ###################### 22 | **.DS_Store* 23 | ehthumbs.db 24 | Icon? 25 | Thumbs.db 26 | ._* 27 | 28 | # Vim generated files # 29 | ####################### 30 | *.un~ 31 | 32 | # SASS # 33 | ######## 34 | **/.sass-cache 35 | **/.sass-cache/* 36 | **/.map 37 | *.scssc 38 | config.rb 39 | 40 | # Composer # 41 | ############ 42 | !src/js/vendor/ 43 | yarn.lock 44 | composer.lock 45 | 46 | # Gulp # 47 | ######## 48 | package-lock.json 49 | 50 | # Bower # 51 | ######### 52 | assets/components/ 53 | 54 | # Codekit # 55 | ########### 56 | /codekit-config.json 57 | *.codekit 58 | **.codekit-cache/* 59 | 60 | # NPM # 61 | ####### 62 | node_modules 63 | 64 | # Development - Remove if you want them included in your own plugin repo # 65 | ########################################################################## 66 | .env 67 | languages/*.pot 68 | languages/*.po 69 | languages/*.mo 70 | vendor/ 71 | assets/css/**/*.css 72 | assets/css/**/*.map 73 | assets/js/**/*.js 74 | -------------------------------------------------------------------------------- /src/js/common/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @preserve: Custom JavaScript Logic - Frontend & Backend 3 | */ 4 | 5 | ;var WPBP_NS = WPBP_NS || {}; 6 | 7 | (function($, undefined) { 8 | 9 | WPBP_NS.Common = { 10 | 11 | clearObjectCache: function() { 12 | 13 | $.ajax({ 14 | type: 'GET', 15 | url: wpbp_ajax_filter_params.ajax_url, 16 | dataType: 'json', 17 | data: { 18 | action: 'clear_object_cache_ajax' 19 | }, 20 | success: function(result) 21 | { 22 | alert( result.success ? _wpbp_plugin_settings['admin_bar_add_clear_cache_success'] : 'Error: ' + result.message ); 23 | } 24 | }); 25 | 26 | } 27 | 28 | } 29 | 30 | // Bind event to clear theme cache Admin Bar link 31 | if( typeof _wpbp_plugin_settings !== 'undefined' && _wpbp_plugin_settings['show_clear_cache_link'] && _wpbp_plugin_settings['admin_bar_add_clear_cache'] ) { 32 | $( '#wpadminbar' ).waitUntilExists(function() { 33 | $('#wp-admin-bar-clear_object_cache').on( 'click', function( event ) { 34 | event.preventDefault(); 35 | WPBP_NS.Common.clearObjectCache(); 36 | return false; 37 | }); 38 | }); 39 | } 40 | 41 | })( window.jQuery ); 42 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dmhendricks/wordpress-base-plugin", 3 | "type": "wordpress-plugin", 4 | "description": "A bloilerplate for WordPress plugins", 5 | "keywords": ["wordpress", "plugin", "boilerplate", "carbonfields"], 6 | "homepage": "https://2lab.net", 7 | "license": "GPL-2.0-or-later", 8 | "authors": [ 9 | { 10 | "name": "Daniel M. Hendricks", 11 | "homepage": "https://danhendricks.com", 12 | "role": "Developer" 13 | } 14 | ], 15 | "support": { 16 | "issues": "https://github.com/dmhendricks/wordpress-base-plugin/issues/", 17 | "wiki": "https://github.com/dmhendricks/wordpress-base-plugin/wiki/", 18 | "source": "https://github.com/dmhendricks/wordpress-base-plugin/" 19 | }, 20 | "require": { 21 | "php": ">=7.0", 22 | "jjgrainger/posttypes": "^2.0.1", 23 | "tgmpa/tgm-plugin-activation": "^2.6.1", 24 | "dmhendricks/wordpress-toolkit": "^0.4.1", 25 | "underdev/requirements": "^1.3", 26 | "inc2734/wp-customizer-framework": "3.2.3", 27 | "composer/installers": "^1.0.6" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "VendorName\\PluginName\\": "app/", 32 | "Carbon_Fields\\Datastore\\Datastore\\": "app/Datastores/" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Widgets/Example_Widget.php: -------------------------------------------------------------------------------- 1 | setup( Plugin::prefix( 'example_widget' ), __( 'Plugin Widget - Example', Plugin::$textdomain ), 16 | __( 'Displays a block with title/text', Plugin::$textdomain ), array( 17 | Field::make( 'text', Plugin::prefix( 'widget_title' ), __( 'Title', Plugin::$textdomain ) ) 18 | ->set_default_value( 'Hello World!' ), 19 | Field::make( 'textarea', Plugin::prefix( 'widget_content' ), __( 'Content', Plugin::$textdomain ) ) 20 | ->set_default_value( 'Lorem Ipsum dolor sit amet' ) 21 | ) 22 | ); 23 | 24 | } 25 | 26 | /** 27 | * A simple widget that displays a text field (title) and textarea. 28 | * @since 0.3.0 29 | */ 30 | public function front_end( $args, $instance ) { 31 | 32 | echo $args[ 'before_title' ] . $instance[ Plugin::prefix( 'widget_title' ) ] . $args[ 'after_title' ]; 33 | echo '
' . $instance[ Plugin::prefix( 'widget_content') ] . '
'; 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wordpress-base-plugin", 3 | "author": "Daniel M. Hendricks", 4 | "license": "GPL-2.0-or-later", 5 | "private": true, 6 | "config": { 7 | "plugin_name": "WordPress Base Plugin", 8 | "plugin_short_name": "Base Plugin", 9 | "username": "dmhendricks", 10 | "php_namespace": "VendorName\\PluginName", 11 | "prefix": "wpbp" 12 | }, 13 | "scripts": { 14 | "translate": "wp-pot --src 'app/**/*.php' --dest-file ./languages/$npm_package_name.pot --package $npm_package_name", 15 | "zip": "zip -q -r ../$npm_package_name.zip * -x 'node_modules/*' '.git*' './src/*' './.*' './package*.*' './composer.*' './*.md' './*.bak' './bower.json' './gulpfile.js' ", 16 | "zip-dev": "zip -q -r ../$npm_package_name.zip * -x 'node_modules/*' '.git*' '.DS_Store' './*.bak' " 17 | }, 18 | "devDependencies": { 19 | "del": "^3.0", 20 | "gulp": "^4.0", 21 | "gulp-autoprefixer": "^3.1.0", 22 | "gulp-batch-replace": "*", 23 | "gulp-concat": "^2.6.1", 24 | "gulp-filter": "^5.1.0", 25 | "gulp-line-ending-corrector": "^1.0.3", 26 | "gulp-merge-media-queries": "^0.2.1", 27 | "gulp-notify": "^3.1.0", 28 | "gulp-plumber": "^1.2", 29 | "gulp-rename": "^1.4.0", 30 | "gulp-sass": "^3.2.1", 31 | "gulp-simple-rename": "^0.1.3", 32 | "gulp-sourcemaps": "^1.5.2", 33 | "gulp-uglify": "^3.0.1", 34 | "gulp-uglifycss": "^1.1.0", 35 | "vinyl-paths": "^2.1.0", 36 | "wp-pot-cli": "^1.0" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git://github.com/dmhendricks/wordpress-base-plugin.git" 41 | }, 42 | "dependencies": {} 43 | } 44 | -------------------------------------------------------------------------------- /wordpress-base-plugin.php: -------------------------------------------------------------------------------- 1 | Plugin Name) 22 | $this->create_network_options_page(); 23 | 24 | } 25 | 26 | /** 27 | * Create network options/settings page in WP Network Admin > Settings > Global Settings 28 | * 29 | * @since 0.5.0 30 | */ 31 | public function create_network_options_page() { 32 | 33 | $container_name = $this->prefix( self::$config->get( 'network/default_options_container' ) ); 34 | Container::make( 'network', $container_name, __( 'Global Settings', self::$textdomain ) ) 35 | ->set_page_parent( 'settings.php' ) 36 | ->add_tab( __( 'General', self::$textdomain ), array( 37 | Field::make( 'textarea', $this->prefix( 'network_site_footer' ), __( 'WP Admin Site Footer', self::$textdomain ) ) 38 | ->help_text( __( 'Replaces the WP Admin footer text. Leave blank for default.', self::$textdomain ) ) 39 | ->set_rows( 2 ) 40 | ) 41 | ); 42 | 43 | } 44 | 45 | /** 46 | * Callback when settings are saved 47 | */ 48 | public function options_saved_hook() { 49 | 50 | // Flush the plugin group cache 51 | self::$cache->flush_group(); 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === WordPress Base Plugin === 2 | Contributors: hendridm 3 | Tags: wordpress,base,plugin,boilerplate,composer,carbonfields 4 | Donate link: https://paypal.me/danielhendricks 5 | Requires at least: 4.6 6 | Requires PHP: 5.6 7 | Tested up to: 5.0 8 | Stable tag: 0.4.0 9 | License: GPL-2.0 10 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 11 | 12 | This plugin is intended to be used as a boilerplate for creating WordPress plugins. 13 | 14 | == Description == 15 | This is a boilerplate WordPress plugin featuring namespace autoloading and integration with [Carbon Fields](https://github.com/htmlburger/carbon-fields). 16 | 17 | It is intended to be used as a starting point for creating WordPress plugins. 18 | 19 | = Requirements = 20 | 21 | * WordPress 4.6 or higher 22 | * PHP 5.6 or higher 23 | * [Carbon Fields](https://github.com/htmlburger/carbon-fields) 2.0 or higher (see the wiki section on [Carbon Fields](https://github.com/dmhendricks/wordpress-base-plugin/wiki#carbon-fields) for more info) 24 | 25 | == Installation == 26 | If you need tips on installing Node.js, Composer, Gulp & Bower, see [Installing Dependencies](https://github.com/dmhendricks/wordpress-base-plugin/wiki/Installing-Dependencies). 27 | 28 | = Clone Repository = 29 | 30 | 1. At command prompt, change to your `wp-content/plugins` directory. 31 | 2. Clone the repository: `git clone https://github.com/dmhendricks/wordpress-base-plugin.git` 32 | 3. Renamed the newly created `wordpress-base-plugin` directory to your own plugin slug. 33 | 34 | = Next Steps = 35 | 36 | See the [Getting Started](https://github.com/dmhendricks/wordpress-base-plugin/wiki#getting-started) documentation for further steps. 37 | 38 | == Frequently Asked Questions == 39 | = Q. Why do I get the error "Warning: require( ... /autoload.php): failed to open stream: No such file or directory" when I try to activate it? 40 | A. You need to use the command prompt and [run Composer](https://github.com/dmhendricks/wordpress-base-plugin#composer) before this plugin will work. 41 | 42 | = Q. What is Composer? = 43 | A. Composer is an application-level package manager for the PHP programming language that provides a standard format for managing dependencies of PHP software and required libraries. 44 | 45 | == Screenshots == 46 | 1. Settings Page 47 | -------------------------------------------------------------------------------- /app/Datastores/Serialized_Theme_Options_Datastore.php: -------------------------------------------------------------------------------- 1 | get_base_name(); 20 | return $key; 21 | } 22 | 23 | /** 24 | * Save a single key-value pair to the database with autoload 25 | * 26 | * @param string $key 27 | * @param string $value 28 | * @param bool $autoload 29 | */ 30 | protected function save_key_value_pair_with_autoload( $key, $value, $autoload ) { 31 | $notoptions = wp_cache_get( 'notoptions', 'options' ); 32 | $notoptions[ $key ] = ''; 33 | wp_cache_set( 'notoptions', $notoptions, 'options' ); 34 | $autoload = $autoload ? 'yes' : 'no'; 35 | 36 | if ( ! add_option( $key, $value, null, $autoload ) ) { 37 | update_option( $key, $value, $autoload ); 38 | } 39 | } 40 | 41 | /** 42 | * Load the field value(s) 43 | * 44 | * @param Field $field The field to load value(s) in. 45 | * @return array 46 | */ 47 | public function load( Field $field ) { 48 | $key = $this->get_key_for_field( $field ); 49 | $value = get_option( $key, null ); 50 | return $value; 51 | } 52 | 53 | /** 54 | * Save the field value(s) 55 | * 56 | * @param Field $field The field to save. 57 | */ 58 | public function save( Field $field ) { 59 | if ( ! empty( $field->get_hierarchy() ) ) { 60 | return; // only applicable to root fields 61 | } 62 | $key = $this->get_key_for_field( $field ); 63 | $value = $field->get_full_value(); 64 | if ( is_a( $field, '\\Carbon_Fields\\Field\\Complex_Field' ) ) { 65 | $value = $field->get_value_tree(); 66 | } 67 | $this->save_key_value_pair_with_autoload( $key, $value, $field->get_autoload() ); 68 | } 69 | 70 | /** 71 | * Delete the field value(s) 72 | * 73 | * @param Field $field The field to delete. 74 | */ 75 | public function delete( Field $field ) { 76 | if ( ! empty( $field->get_hierarchy() ) ) { 77 | return; // only applicable to root fields 78 | } 79 | $key = $this->get_key_for_field( $field ); 80 | delete_option( $key ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/scss/_carbon-fields.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Carbon Fields style 3 | */ 4 | 5 | div[id^="carbon_fields_container_"] { 6 | 7 | // Change the default Carbon Fields H3 header font size 8 | div.carbon-separator { 9 | h3 { 10 | font-size: 1.6em !important; 11 | } 12 | } 13 | 14 | // Add a background globe icon to URL input fields 15 | .carbon-fields-custom-field-url, .carbon-fields-custom-field-email, .carbon-fields-custom-field-tel { 16 | 17 | input { 18 | padding-left: 29px; 19 | } 20 | 21 | .field-holder, .cf-field__body { 22 | 23 | &:before { 24 | position: absolute; 25 | font-family: 'dashicons'; 26 | color: #D4D4D4; 27 | font-size: 1.2em; 28 | padding: 5px 8px; 29 | } 30 | 31 | } 32 | 33 | } 34 | 35 | // Input field background dashicons 36 | .carbon-fields-custom-field-url { 37 | .field-holder:before, .cf-field__body:before { 38 | content: '\f319'; 39 | } 40 | } 41 | 42 | .carbon-fields-custom-field-email { 43 | .field-holder:before, .cf-field__body:before { 44 | content: '\f466'; 45 | } 46 | } 47 | 48 | .carbon-fields-custom-field-tel { 49 | .field-holder:before, .cf-field__body:before { 50 | content: '\f525'; 51 | } 52 | } 53 | 54 | } 55 | 56 | // Custom Carbon Custom Field Classes 57 | @media (min-width: 783px) { 58 | 59 | div[id^='carbon_fields_container_'] { 60 | 61 | // Radio buttons - horizontal alignment 62 | .carbon-fields-custom-radio-horizontal { 63 | li { 64 | float: left; 65 | padding-right: .75rem; 66 | } 67 | div { 68 | clear: left; 69 | } 70 | &:after { 71 | clear: both; 72 | } 73 | 74 | .carbon-help-text { 75 | clear: left; 76 | } 77 | } 78 | 79 | // Text input field width 80 | .carbon-fields-custom-input-tiny { 81 | input, select { 82 | width: 55px; 83 | } 84 | } 85 | 86 | .carbon-fields-custom-input-smaller { 87 | input, select { 88 | width: 180px; 89 | max-width: 100%; 90 | } 91 | } 92 | 93 | .carbon-fields-custom-input-small { 94 | input, select { 95 | width: 225px; 96 | max-width: 100%; 97 | } 98 | } 99 | 100 | .carbon-fields-custom-input-medium { 101 | input, select { 102 | width: 355px; 103 | max-width: 100%; 104 | } 105 | } 106 | 107 | .carbon-fields-custom-input-large { 108 | input, select { 109 | width: 575px; 110 | max-width: 100%; 111 | } 112 | } 113 | 114 | .carbon-fields-custom-input--1-2 { 115 | input, select { 116 | width: 50%; 117 | } 118 | } 119 | 120 | .carbon-fields-custom-input--1-3 { 121 | input, select { 122 | width: 33%; 123 | } 124 | } 125 | 126 | .carbon-fields-custom-input--2-3 { 127 | input, select { 128 | width: 66%; 129 | } 130 | } 131 | 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /app/TGMPA.php: -------------------------------------------------------------------------------- 1 | 'Carbon Fields', // The plugin name. 29 | 'slug' => 'carbon-fields', // The plugin slug (typically the folder name). 30 | 'source' => 'https://carbonfields.net/zip/latest/', // The plugin source. 31 | 'required' => true, // If false, the plugin is only 'recommended' instead of required. 32 | 'version' => self::$config->get( 'dependencies/carbon_fields' ), // E.g. 1.0.0. If set, the active plugin must be this version or higher. If the plugin version is higher than the plugin version installed, the user will be notified to update the plugin. 33 | 'force_activation' => true // If true, plugin is activated upon theme activation and cannot be deactivated until theme switch. 34 | ) 35 | 36 | ); 37 | 38 | /* 39 | * Array of configuration settings. Amend each line as needed. 40 | * 41 | * TGMPA will start providing localized text strings soon. If you already have translations of our standard 42 | * strings available, please help us make TGMPA even better by giving us access to these translations or by 43 | * sending in a pull-request with .po file(s) with the translations. 44 | * 45 | * Only uncomment the strings in the config array if you want to customize the strings. 46 | */ 47 | $config = array( 48 | 'id' => $this->prefix( 'tgmpa' ), // Unique ID for hashing notices for multiple instances of TGMPA. 49 | 'menu' => 'tgmpa-install-plugins', // Menu slug. 50 | 'parent_slug' => 'themes.php', // Parent menu slug. 51 | 'capability' => 'edit_theme_options', // Capability needed to view plugin install page, should be a capability associated with the parent menu used. 52 | 'has_notices' => true, // Show admin notices or not. 53 | 'dismissable' => true, // If false, a user cannot dismiss the nag message. 54 | 'is_automatic' => true, // Automatically activate plugins after installation or not. 55 | 'strings' => array( 56 | 'notice_can_install_required' => _n_noop( 57 | 'This plugin has the following dependency: %1$s.', 58 | 'This plugin has the following dependencies: %1$s.', 59 | self::$textdomain 60 | ), 61 | ) 62 | ); 63 | 64 | tgmpa( $plugins, $config ); 65 | 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/PostTypes/Clients.php: -------------------------------------------------------------------------------- 1 | add_post_type_client(); 14 | 15 | // Hide unnecessary publishing options like Draft, visibility, etc. 16 | add_action( 'admin_head-post.php', array( $this, 'hide_publishing_actions' ) ); 17 | add_action( 'admin_head-post-new.php', array( $this, 'hide_publishing_actions' ) ); 18 | 19 | } 20 | 21 | /** 22 | * Creates Client List (client) custom post type. 23 | * 24 | * @see https://github.com/jjgrainger/PostTypes PostTypes Reference 25 | * @since 0.1.0 26 | */ 27 | public function add_post_type_client() { 28 | 29 | $labels = [ 'menu_name' => 'Client List' ]; 30 | 31 | $options = array( 32 | 'supports' => array( 'title' ), 33 | 'exclude_from_search' => true, 34 | 'publicly_queryable' => true, 35 | 'show_ui' => true, 36 | 'show_in_nav_menus' => false, 37 | 'rewrite' => false, 38 | 'has_archive' => false 39 | ); 40 | 41 | // Create new post type: client 42 | $cpt = new \PostTypes\PostType( 43 | array( 44 | 'name' => 'client', 45 | 'singular' => 'Client', 46 | 'plural' => 'Clients', 47 | 'slug' => 'client' 48 | ), $options, $labels 49 | ); 50 | 51 | $cpt->icon( 'dashicons-star-filled' ); 52 | $cpt->register(); 53 | 54 | // Add fields 55 | Container::make( 'post_meta', __( 'Client Details', self::$textdomain ) ) 56 | ->show_on_post_type( $cpt->name ) 57 | ->add_fields( array( 58 | Field::make( 'text', $this->prefix( 'name' ), __( 'Name', self::$textdomain ) ), 59 | Field::make( 'text', $this->prefix( 'company' ), __( 'Company', self::$textdomain ) ), 60 | ) 61 | ); 62 | Container::make( 'post_meta', 'Contact Info' ) 63 | ->show_on_post_type( $cpt->name ) 64 | ->add_fields( array( 65 | Field::make( 'text', $this->prefix( 'url' ), __( 'Web Site', self::$textdomain ) ) 66 | ->set_classes( 'carbon-fields-custom-field-url' ), 67 | Field::make( 'text', $this->prefix( 'phone' ), __( 'Phone Number', self::$textdomain ) ) 68 | ->set_classes( 'carbon-fields-custom-field-tel' ), 69 | Field::make( 'textarea', $this->prefix( 'address' ), __( 'Address', self::$textdomain ) ) 70 | ->set_rows( 4 ) 71 | ) 72 | ); 73 | 74 | } 75 | 76 | /** 77 | * Remove unnecessary publishing actions as well as some some annoying 78 | * third-party metaboxes (like Yoast SEO and the Visual Composer buttons, 79 | * since we're not using the editor in this example) 80 | * 81 | * @since 0.1.0 82 | */ 83 | public function hide_publishing_actions() { 84 | global $post; 85 | if( in_array($post->post_type, array('client')) ) { 86 | echo ''; 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /app/Settings/Customizer_Options.php: -------------------------------------------------------------------------------- 1 | get( 'plugin/slug' ), '_' ); 21 | 22 | // Define Customizer sections and fields 23 | $this->customizer = Customizer_Framework::init(); 24 | 25 | // Add new panel 26 | $panel = $this->customizer->panel( $this->prefix( $slug ) . '_panel', [ 27 | 'title' => self::$config->get( 'plugin/meta/Name' ) 28 | ]); 29 | 30 | // Add new section 31 | $section = $this->customizer->section( $this->prefix( 'my-plugin-section' ), [ 32 | 'title' => __( 'My Plugin Section', self::$textdomain ), 33 | 'description' => __( 'Example Customizer Section', self::$textdomain ) 34 | ]); 35 | 36 | // Define fields 37 | $controls = [ 38 | $this->customizer->control( 'file', $this->prefix( 'favicon' ), [ 39 | 'label' => __( 'Favicon', self::$textdomain ), 40 | //'default' => get_home_url( null, 'favicon.ico' ) 41 | ]), 42 | $this->customizer->control( 'color', $this->prefix( 'hyperlink-color' ), [ 43 | 'label' => __( 'Hyperlink Color', self::$textdomain ), 44 | 'default' => '#007acc' 45 | ]), 46 | $this->customizer->control( 'checkbox', $this->prefix( 'hyperlink-underline' ), [ 47 | 'label' => __( 'Underline Hyperlinks', self::$textdomain ), 48 | 'default' => false, 49 | 'description' => __( 'Add underline to hyperlinks.', self::$textdomain ) 50 | ]), 51 | $this->customizer->control( 'color', $this->prefix( 'h2-color' ), [ 52 | 'label' => __( 'H2 Header Color', self::$textdomain ), 53 | 'default' => '#1A1A1A' 54 | ]) 55 | ]; 56 | 57 | // Add panel with section and fields 58 | foreach( $controls as $control ) { 59 | $control->join( $section )->join( $panel ); 60 | } 61 | 62 | // Initialize Customizer options 63 | add_action( 'wp_loaded', array( $this, 'init_customizer_options' ) ); 64 | 65 | // Inject favicon link into page head 66 | add_action( 'wp_head', array( $this, 'inject_customizer_options'), 1 ); 67 | 68 | } 69 | 70 | /** 71 | * Initialize Customizer option sections and fields 72 | * @since 0.4.0 73 | */ 74 | public function init_customizer_options() { 75 | 76 | $cfs = $this->customizer->styles(); 77 | 78 | // Inject hyperlink style 79 | $hyperlink_color = get_theme_mod( $this->prefix( 'hyperlink-color' ) ); 80 | $hyperlink_underline = get_theme_mod( $this->prefix( 'hyperlink-underline' ) ) ? 'underline' : 'none'; 81 | 82 | $cfs->register([ 83 | 'a' // CSS selector 84 | ], 85 | [ 86 | "color: {$hyperlink_color}", 87 | "text-decoration: {$hyperlink_underline}", 88 | ] 89 | //,'@media (min-width: 768px)' // Optional 90 | ); 91 | 92 | // Inject H2 color style 93 | $h2_color = get_theme_mod( $this->prefix( 'h2-color' ) ); 94 | 95 | $cfs->register([ 96 | 'h2' // CSS selector 97 | ], 98 | [ 99 | "color: {$h2_color}" 100 | ] 101 | ); 102 | 103 | } 104 | 105 | /** 106 | * Add relevant Customizer options to page head 107 | * @since 0.4.0 108 | */ 109 | public function inject_customizer_options() { 110 | 111 | $favicon = get_theme_mod( $this->prefix( 'favicon' ) ); 112 | if( !$favicon ) return; 113 | $file_type = wp_check_filetype( $favicon ); 114 | 115 | echo sprintf('', $favicon, $file_type['type'] ); 116 | 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /app/Core.php: -------------------------------------------------------------------------------- 1 | tag for selector targeting 9 | add_filter( 'body_class', array( $this, 'add_body_classes' ) ); 10 | 11 | // Example - Remove Emoji code from header 12 | if( $this->get_carbon_plugin_option( 'remove_header_emojicons' ) ) { 13 | add_filter( 'init', array( $this, 'disable_wp_emojicons' ) ); 14 | } 15 | 16 | // Multisite Example - Change WP Admin footer text 17 | if( is_multisite() && trim( $this->get_carbon_network_option( 'network_site_footer' ) ) ) { 18 | add_filter( 'admin_footer_text', array( $this, 'set_admin_footer_text' ) ); 19 | } 20 | 21 | /** 22 | * Example - Ajax call for when "Clear Cache" is clicked from the admin bar dropdown. 23 | * 24 | * Note: If this Ajax call was intended to be available to those who are not 25 | * logged in, you would need to uncommend the 'wp_ajax_nopriv_clear_object_cache_ajax' 26 | * hook. 27 | */ 28 | if( current_user_can( 'manage_options' ) && $this->get_carbon_plugin_option( 'admin_bar_add_clear_cache' ) ) { 29 | add_action( 'admin_bar_menu', array( $this, 'admin_bar_add_clear_cache' ), 900 ); 30 | //add_action( 'wp_ajax_nopriv_clear_object_cache_ajax', array( $this, 'clear_object_cache_ajax' ) ); 31 | add_action( 'wp_ajax_clear_object_cache_ajax', array( $this, 'clear_object_cache_ajax' ) ); 32 | } 33 | 34 | } 35 | 36 | /** 37 | * Returns string of addition CSS classes based on post type 38 | * 39 | * Returns CSS classes such as page-{slug}, parent-{slug}, post-type-{type} and 40 | * category-{slug} for easier selector targeting 41 | * 42 | * @param array $classes An array of *current* body_class classes 43 | * @return array Modified array of body classes including new ones 44 | * @since 0.1.0 45 | */ 46 | public function add_body_classes($classes) { 47 | $parent_slug = Helpers::get_parent_slug(true); 48 | $categories = is_single() ? Helpers::get_post_categories(true) : array(); 49 | 50 | // Add page, parent and post-type classes, if available 51 | $classes[] = 'page-' . Helpers::get_page_slug(); 52 | if( $parent_slug ) $classes[] = 'parent-' . $parent_slug; 53 | $classes[] = 'post-type-' . get_post_type(); 54 | 55 | // Add category slugs 56 | foreach( $categories as $cat ) { 57 | $classes[] = 'category-' . $cat; 58 | } 59 | 60 | return $classes; 61 | } 62 | 63 | /** 64 | * Disabled Emojicons in page headers 65 | */ 66 | public function disable_wp_emojicons() { 67 | 68 | remove_action( 'admin_print_styles', 'print_emoji_styles' ); 69 | remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); 70 | remove_action( 'admin_print_scripts', 'print_emoji_detection_script' ); 71 | remove_action( 'wp_print_styles', 'print_emoji_styles' ); 72 | remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' ); 73 | remove_filter( 'the_content_feed', 'wp_staticize_emoji' ); 74 | remove_filter( 'comment_text_rss', 'wp_staticize_emoji' ); 75 | add_filter( 'tiny_mce_plugins', function( $plugins) { 76 | return is_array($plugins) ? array_diff($plugins, array('wpemoji')) : $plugins; 77 | }); 78 | 79 | } 80 | 81 | /** 82 | * Set WP Admin footer text (multisite only) 83 | * 84 | * @param string Default footer text 85 | * @return string Modified footer text 86 | * @since 0.5.0 87 | */ 88 | public function set_admin_footer_text( $footer_text ) { 89 | return trim( $this->get_carbon_network_option( 'network_site_footer' ) ) ?: $footer_text; 90 | } 91 | 92 | /** 93 | * Add "Clear Cache" link to admin bar dropdown 94 | * @since 0.3.0 95 | */ 96 | public function admin_bar_add_clear_cache( $wp_admin_bar ) { 97 | 98 | $args = array( 99 | 'id' => 'clear_object_cache', 100 | 'title' => __( 'Clear Cache', self::$textdomain ), 101 | 'parent' => 'site-name', 102 | 'href' => '#' 103 | ); 104 | $wp_admin_bar->add_node( $args ); 105 | 106 | } 107 | 108 | /** 109 | * Ajax handler for 'clear_object_cache_ajax' call. 110 | * @since 0.3.0 111 | */ 112 | public function clear_object_cache_ajax() { 113 | 114 | $result = [ 'success' => true ]; 115 | 116 | try { 117 | self::$cache->flush(); 118 | } catch ( Exception $e ) { 119 | $result = [ 'success' => false, 'message' => $e->getMessage() ]; 120 | } 121 | 122 | echo json_encode( $result ); 123 | wp_die(); 124 | 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /app/EnqueueScripts.php: -------------------------------------------------------------------------------- 1 | inject_javascript_settings(); 15 | 16 | // Example - Load Font Awesome from CDN, if enabled in Settings Page 17 | $enqueue_font_awesome = $this->get_carbon_plugin_option( 'enqueue_font_awesome' ); 18 | if( $enqueue_font_awesome ) { 19 | if( in_array( 'frontend', $enqueue_font_awesome) ) 20 | add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_font_awesome' ) ); 21 | if( in_array( 'backend', $enqueue_font_awesome) ) 22 | add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_font_awesome' ) ); 23 | } 24 | 25 | } 26 | 27 | /** 28 | * Enqueue scripts used on frontend of site 29 | * @since 0.1.0 30 | */ 31 | public function enqueue_frontend_scripts() { 32 | 33 | // Enqueue script dependencies 34 | $this->enqueue_common_scripts(); 35 | 36 | // Enqueuing custom CSS for child theme (Twentysixteen was used for testing) 37 | wp_enqueue_style( 'wordpress-base-plugin', Helpers::get_script_url( 'assets/css/wordpress-base-plugin.css' ), null, Helpers::get_script_version( 'assets/css/wordpress-base-plugin.css' ) ); 38 | 39 | // Enqueue frontend JavaScript 40 | wp_enqueue_script( 'wordpress-base-plugin', Helpers::get_script_url( 'assets/js/wordpress-base-plugin.js' ), array( 'jquery', 'jquery-waituntilexists' ), Helpers::get_script_version( 'assets/js/wordpress-base-plugin.js' ), true ); 41 | wp_localize_script( 'wordpress-base-plugin', $this->prefix( 'ajax_filter_params' ), array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) ); 42 | 43 | } 44 | 45 | /** 46 | * Enqueue scripts used in WP admin interface 47 | * @since 0.1.0 48 | */ 49 | public function enqueue_admin_scripts() { 50 | 51 | // Enqueue script dependencies 52 | $this->enqueue_common_scripts(); 53 | 54 | // Enqueuing custom CSS for child theme (Twentysixteen was used for testing) 55 | wp_enqueue_style( 'wordpress-base-plugin', Helpers::get_script_url( 'assets/css/wordpress-base-plugin-admin.css' ), null, Helpers::get_script_version( 'assets/css/wordpress-base-plugin-admin.css' ) ); 56 | 57 | // Enqueue WP Admin JavaScript 58 | wp_enqueue_script( 'wordpress-base-plugin-admin', Helpers::get_script_url( 'assets/js/wordpress-base-plugin-admin.js' ), array('jquery', 'jquery-waituntilexists'), Helpers::get_script_version( 'assets/js/wordpress-base-plugin-admin.js' ), true ); 59 | wp_localize_script( 'wordpress-base-plugin-admin', $this->prefix( 'ajax_filter_params' ), array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) ); 60 | 61 | } 62 | 63 | /** 64 | * Enqueue scripts common to the public site and WP Admin 65 | * @since 0.3.0 66 | */ 67 | private function enqueue_common_scripts() { 68 | 69 | // Enqueue common (frontend/backend) JavaScript 70 | wp_enqueue_script( 'jquery-waituntilexists', Helpers::get_script_url( 'assets/components/jq.waituntilexists/jquery.waitUntilExists.min.js', false ), array( 'jquery' ), '0.1.0' ); 71 | 72 | } 73 | 74 | /** 75 | * Enqueue Font Awesome 76 | * @since 0.1.0 77 | */ 78 | public function enqueue_font_awesome() { 79 | 80 | wp_enqueue_style( 'font-awesome', 'https://use.fontawesome.com/releases/v5.6.1/css/all.css', null, null ); 81 | wp_enqueue_style( 'font-awesome-shims', 'https://use.fontawesome.com/releases/v5.6.1/css/v4-shims.css', [ 'font-awesome' ], null ); 82 | 83 | } 84 | 85 | /** 86 | * Inject JavaScript settings into header. You can add any variables/settings 87 | * that you want to make available to your JavaScripts. 88 | * @since 0.3.0 89 | */ 90 | private function inject_javascript_settings() { 91 | 92 | $args = array( 93 | 'variable_name' => $this->prefix( 'plugin_settings', '_' ), 94 | 'target' => [ 'wp', 'admin' ] 95 | ); 96 | 97 | $values = array( 98 | 'admin_bar_add_clear_cache' => $this->get_carbon_plugin_option( 'admin_bar_add_clear_cache' ), 99 | 'admin_bar_add_clear_cache_success' => __( 'WordPress cache has been cleared.', self::$textdomain ), 100 | 'show_clear_cache_link' => current_user_can( 'manage_options' ), 101 | ); 102 | 103 | $js = new \WordPress_ToolKit\ScriptObject( $values ); 104 | $js->injectJS( $args ); 105 | 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://www.danhendricks.com/?utm_source=github.com&utm_medium=campaign&utm_content=button&utm_campaign=wordpress-base-plugin) 2 | [](https://github.com/dmhendricks/wordpress-base-plugin/releases) 3 | [](https://raw.githubusercontent.com/dmhendricks/wordpress-base-plugin/master/LICENSE) 4 | [](https://share.getf.ly/e25g6k?utm_source=github.com&utm_medium=campaign&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin) 5 | [](https://paypal.me/danielhendricks) 6 | [](https://github.com/igrigorik/ga-beacon/?utm_source=github.com&utm_medium=referral&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin) 7 | [](https://twitter.com/danielhendricks) 8 | 9 | # WordPress Base Plugin 10 | 11 | - [Documentation](https://github.com/dmhendricks/wordpress-base-plugin/wiki/) 12 | - [Features](#features) 13 | - [Requirements](#requirements) 14 | - [Installation](#installation) 15 | - [Future Goals](#future-goals) 16 | - [Change Log](#change-log) 17 | 18 | ## Description 19 | 20 | This is a boilerplate WordPress plugin featuring namespace autoloading and [Carbon Fields](https://carbonfields.net/?utm_source=github.com&utm_medium=referral&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin) examples. It is intended to be used as a starting point for creating WordPress plugins. It contains several examples and dependencies to get you started. 21 | 22 | It may also be used as the means of [separating custom code](http://www.billerickson.net/core-functionality-plugin/?utm_source=github.com&utm_medium=referral&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin) from the theme or [extending a child theme](https://www.wp-code.com/wordpress-snippets/wordpress-grandchildren-themes/?utm_source=github.com&utm_medium=referral&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin). 23 | 24 | ### Contributing 25 | 26 | Here are some ways that you can contribute: 27 | 28 | * Suggest improvements and/or code them. 29 | * [Report bugs](https://github.com/dmhendricks/wordpress-base-plugin/issues) and/or incompatibilities. 30 | * Host your sites with [Flywheel](https://share.getf.ly/e25g6k?utm_source=github.com&utm_medium=campaign&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin), use [KeyCDN](https://www.keycdn.com/?a=42672&utm_source=github.com&utm_medium=campaign&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin 31 | ) for speedy delivery of assets. 32 | 33 | ## Features 34 | 35 | * Namespaces & dependency autoloading 36 | * Dependency checking via [Requirements](https://github.com/Kubitomakita/Requirements) 37 | * Powered by [Composer](https://getcomposer.org/?utm_source=github.com&utm_medium=referral&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin), [Gulp](https://gulpjs.com/?utm_source=github.com&utm_medium=referral&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin) and [Bower](https://bower.io/?utm_source=github.com&utm_medium=referral&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin) 38 | * Object caching (where available; [usage examples](https://github.com/dmhendricks/wordpress-toolkit/wiki/ObjectCache)) 39 | * Easy installable ZIP file generation: `npm run zip` 40 | * Automatic translation file (`.pot`) creation. See [Translation](https://github.com/dmhendricks/wordpress-base-plugin/wiki/Translation). 41 | * Network Admin (multisite) options, shortcodes, widgets (via [Carbon Fields](https://carbonfields.net?utm_source=github.com&utm_medium=referral&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin)) and custom post types (via [PostTypes](https://github.com/jjgrainger/PostTypes/)) examples 42 | * Configuration registry ([docs](https://github.com/dmhendricks/wordpress-toolkit/wiki/ConfigRegistry)) and optional `wp-config.php` [constants](https://github.com/dmhendricks/wordpress-base-plugin/wiki/Configuration-&-Constants) 43 | * Customizer examples using [WP Customizer Framework](https://github.com/inc2734/wp-customizer-framework/) 44 | * Define environmental variables via `.env` files ([reference](https://github.com/dmhendricks/wordpress-toolkit/wiki/ToolKit#environment)) 45 | * [More to come...](#future-plans) 46 | 47 | ## Requirements 48 | 49 | * WordPress 4.8 or higher 50 | * PHP 7 or higher 51 | * [Carbon Fields](https://github.com/htmlburger/carbon-fields) 2.2 or higher. See the wiki section [Carbon Fields](https://github.com/dmhendricks/wordpress-base-plugin/wiki#carbon-fields) for more info. 52 | * Carbon Fields is only required for the demo. You're welcome to strip out references if you do not wish to use it. 53 | 54 | ## Installation 55 | 56 | If you need tips on installing Node.js, Composer, Gulp & Bower, see [Installing Dependencies](https://github.com/dmhendricks/wordpress-base-plugin/wiki/Installing-Dependencies). 57 | 58 | #### The short version: 59 | 60 | 1. Clone repository to your `plugins` directory 61 | 1. Change the four variables in [package.json](https://github.com/dmhendricks/wordpress-base-plugin/wiki#setting-initial-variables). Modify [plugin.json](https://github.com/dmhendricks/wordpress-base-plugin/blob/master/plugin.json) as necessary. 62 | 1. Run `npm install; gulp rename; composer install` 63 | 1. (optional) For some of the included examples to work, you'll also want to run: `bower install; gulp;` 64 | 65 | You'll want to delete features that you don't like (such as references to [TGMPA](http://tgmpluginactivation.com/) if you don't need it). 66 | 67 | ### Clone Repository 68 | 69 | 1. At command prompt, change to your `wp-content/plugins` directory. 70 | 1. Clone the repository: `git clone https://github.com/dmhendricks/wordpress-base-plugin.git` 71 | 1. Renamed the newly created `wordpress-base-plugin` directory to your own plugin slug. 72 | 73 | ### Next Steps 74 | 75 | See the [Getting Started](https://github.com/dmhendricks/wordpress-base-plugin/wiki#getting-started) documentation for further steps. 76 | 77 | ## Future Goals 78 | 79 | * Add plugin `uninstall.php` 80 | * Switch to [webpack](https://webpack.js.org/) for frontend dependency management 81 | * ~~Remove or replace [wordpress-settings-api-class](https://github.com/tareq1988/wordpress-settings-api-class/) example with something actively developed~~ 82 | * Clean up Carbon Fields _custom_ CSS classes 83 | * Add automatic GitHub update example 84 | * Add Gutenberg block examples 85 | 86 | ## Screenshot 87 | 88 |  -------------------------------------------------------------------------------- /app/Settings/Settings_Page.php: -------------------------------------------------------------------------------- 1 | settings_containers = array(); 22 | 23 | // Flush the cache when settings are saved 24 | add_action( 'carbon_fields_theme_options_container_saved', array( $this, 'options_saved_hook' ) ); 25 | 26 | // Create tabbed plugin options page (Settings > Plugin Name) 27 | $this->create_tabbed_options_page(); 28 | 29 | // Register uninstall hook to delete settings 30 | if( $this->get_carbon_plugin_option( 'uninstall_remove_settings' ) ) { 31 | register_uninstall_hook( self::$config->get( 'plugin/identifier' ), array( __CLASS__, 'plugin_settings_uninstall' ) ); 32 | } 33 | 34 | } 35 | 36 | /** 37 | * Create a tabbed options/settings page in WP Admin 38 | * 39 | * @since 0.1.0 40 | */ 41 | private function create_tabbed_options_page() { 42 | 43 | // Carbon Fields Docs: https://carbonfields.net/docs/containers-theme-options/ 44 | $container = Container::make( 'theme_options', self::$config->get( 'plugin/meta/Name' ) ) 45 | ->set_page_parent( 'options-general.php' ) 46 | ->add_tab( __( 'General', self::$textdomain ), array( 47 | Field::make( 'checkbox', $this->prefix( 'uninstall_remove_settings' ), __( 'Delete Plugin Settings On Uninstall', self::$textdomain ) ), 48 | Field::make( 'checkbox', $this->prefix( 'admin_bar_add_clear_cache' ), __( 'Add "Clear Cache" Link to Admin Bar Dropdown Menu', self::$textdomain ) )->set_default_value( true ), 49 | Field::make( 'checkbox', $this->prefix( 'remove_header_emojicons' ), __( 'Remove Emoji Code From Page Headers', self::$textdomain ) ) 50 | ->help_text( __( 'Checking this box will remove the default Emoji code from page headers.', self::$textdomain ) ), 51 | Field::make( 'set', $this->prefix( 'enqueue_font_awesome' ), __( 'Load Font Awesome from CDN', self::$textdomain ) ) 52 | ->help_text( __( 'Load Font Awesome from CDN.', self::$textdomain ) ) 53 | ->set_default_value( 'backend' ) 54 | ->add_options( array( 55 | 'frontend' => __( 'Frontend', self::$textdomain ), 56 | 'backend' => __( 'Backend', self::$textdomain ) 57 | ) 58 | ), 59 | Field::make( 'separator', $this->prefix( 'general_separator_examples' ), __( 'Example Fields', self::$textdomain ) ) 60 | ->help_text( __( 'These fields are just provided as examples and are not used by any logic in the plugin.', self::$textdomain ) ), 61 | Field::make( 'text', $this->prefix( 'blog_title' ), __( 'Blog Title', self::$textdomain ) ) 62 | ->set_classes( 'carbon-fields-custom-input--1-2' ), 63 | Field::make( 'text', $this->prefix( 'email' ), __( 'Your E-mail Address', self::$textdomain ) ) 64 | ->set_attribute( 'type', 'email' ) 65 | ->set_classes( 'carbon-fields-custom-field-email carbon-fields-custom-input--1-3' ) 66 | ->help_text( __( 'This input field is an HTML5 email type.', self::$textdomain ) ), 67 | Field::make( 'text', $this->prefix( 'web_site_url' ), __( 'Web Site Address', self::$textdomain ) ) 68 | ->set_attribute( 'type', 'url' ) 69 | ->set_attribute( 'placeholder', site_url() ) 70 | ->set_classes( 'carbon-fields-custom-field-url' ) 71 | ->help_text( __( 'This input field is an HTML5 url type. It is also wrapped in a .carbon-fields-custom-field-url CSS class to add a background globe dashicon.', self::$textdomain ) ), 72 | Field::make( 'text', $this->prefix( 'phone' ), __( 'Phone Number', self::$textdomain ) ) 73 | ->set_classes( 'carbon-fields-custom-field-tel carbon-fields-custom-input-smaller' ) 74 | ->set_attribute( 'type', 'tel' ), 75 | Field::make( 'date_time', $this->prefix( 'date_time' ), __( 'Date & Time', self::$textdomain ) ), 76 | Field::make( 'radio', $this->prefix( 'menu_position' ), __( 'Menu Position', self::$textdomain ) ) 77 | ->add_options(array( 78 | 'none' => __( 'Disabled', self::$textdomain ), 79 | 'top' => __( 'Top (Default)', self::$textdomain ), 80 | 'left' => __( 'Left', self::$textdomain ) 81 | )) 82 | ->set_default_value( 'top' ), 83 | Field::make( 'radio', $this->prefix( 'search_position' ), __( 'Search Box Location', self::$textdomain ) ) 84 | ->add_options(array( 85 | 'header' => __( 'Header', self::$textdomain ), 86 | 'side' => __( 'Sidebar', self::$textdomain ), 87 | 'flyout' => __( 'Flyout Menu', self::$textdomain ) 88 | )) 89 | ->set_classes( 'carbon-fields-custom-radio-horizontal' ) 90 | ->help_text( __( 'Example of horizontally-aligned radio buttons.', self::$textdomain ) ), 91 | Field::make( 'complex', $this->prefix( 'slides' ), self::$config->get( 'plugin/meta/Name' ) . ' ' . __( 'Slides', self::$textdomain ) ) 92 | ->set_datastore( new Serialized_Theme_Options_Datastore() ) 93 | ->add_fields( array( 94 | Field::make( 'text', 'title' ), 95 | Field::make( 'image', 'photo' ), 96 | ) 97 | ), 98 | Field::make( 'select', $this->prefix( 'select_dropdown' ), __( 'Favorite Continent', self::$textdomain ) ) 99 | ->set_classes( 'carbon-fields-custom-input-small' ) 100 | ->add_options(array( 101 | 'aria' => __( 'Asia', self::$textdomain ), 102 | 'africa' => __( 'Africa', self::$textdomain ), 103 | 'europe' => __( 'Europe', self::$textdomain ), 104 | 'north-america' => __( 'North America', self::$textdomain ), 105 | 'south-america' => __( 'South America', self::$textdomain ), 106 | 'australia' => __( 'Australia', self::$textdomain ), 107 | 'antarctica' => __( 'Antarctica', self::$textdomain ) 108 | )) 109 | ) 110 | ) 111 | ->add_tab( __( 'Miscellaneous', self::$textdomain ), array( 112 | Field::make( 'color', $this->prefix( 'font_color' ), __( 'Foreground Color', self::$textdomain ) ), 113 | Field::make( 'image', $this->prefix( 'default_image' ), __( 'Default Image', self::$textdomain ) ), 114 | Field::make( 'file', $this->prefix( 'file' ), __( 'File Upload', self::$textdomain ) ) 115 | ) 116 | ); 117 | 118 | // Store container and fields for register_uninstall_hook 119 | $this->settings_containers[] = $container; 120 | 121 | } 122 | 123 | /** 124 | * Create a single options/settings page in WP Admin 125 | * 126 | * @since 0.1.0 127 | */ 128 | private function create_single_options_page() { 129 | 130 | $container = Container::make( 'theme_options', self::$config->get( 'plugin/meta/Name' ) ) 131 | ->set_page_parent( 'options-general.php' ) 132 | ->add_fields( array( 133 | Field::make( 'text', $this->prefix( 'your_name' ), __( 'Your Name', self::$textdomain ) ), 134 | Field::make( 'image', $this->prefix( 'profile_pic' ), __( 'Profile Pic', self::$textdomain ) ) 135 | ) 136 | ); 137 | 138 | // Store container and fields for register_uninstall_hook 139 | $this->settings_containers[] = $container; 140 | 141 | } 142 | 143 | /** 144 | * Plugin uninstall hook callback. Removes Carbon Fields settings on 145 | * plugin removal 146 | * 147 | * @since 0.3.0 148 | */ 149 | public static function plugin_settings_uninstall() { 150 | 151 | foreach( $this->settings_containers as $container ) { 152 | 153 | foreach ( $container->get_fields() as $field ) { 154 | $field->delete(); 155 | } 156 | 157 | } 158 | 159 | } 160 | 161 | /** 162 | * Callback when settings are saved 163 | */ 164 | public function options_saved_hook() { 165 | 166 | // Flush the plugin group cache 167 | self::$cache->flush_group(); 168 | 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /app/Plugin.php: -------------------------------------------------------------------------------- 1 | init( dirname( __DIR__ ), trailingslashit( dirname( __DIR__ ) ) . 'plugin.json' ); 23 | self::$config->merge( new ConfigRegistry( [ 'plugin' => self::$instance->get_current_plugin_meta( ARRAY_A ) ] ) ); 24 | 25 | // Set Text Domain 26 | self::$textdomain = self::$config->get( 'plugin/meta/TextDomain' ) ?: self::$config->get( 'plugin/slug' ); 27 | 28 | // Define plugin version 29 | if ( !defined( __NAMESPACE__ . '\VERSION' ) ) define( __NAMESPACE__ . '\VERSION', self::$config->get( 'plugin/meta/Version' ) ); 30 | 31 | // Load dependecies and load plugin logic 32 | register_activation_hook( self::$config->get( 'plugin/identifier' ), array( self::$instance, 'activate' ) ); 33 | add_action( 'plugins_loaded', array( self::$instance, 'load_dependencies' ) ); 34 | 35 | } 36 | 37 | return self::$instance; 38 | 39 | } 40 | 41 | /** 42 | * Load plugin classes - Modify as needed, remove features that you don't need. 43 | * 44 | * @since 0.2.0 45 | */ 46 | public function load_plugin() { 47 | 48 | if( !$this->verify_dependencies() ) { 49 | deactivate_plugins( self::$config->get( 'plugin/identifier' ) ); 50 | return; 51 | } 52 | 53 | // Add TGM Plugin Activation notices for required/recommended plugins 54 | new TGMPA(); 55 | 56 | // Add admin settings page using Carbon Fields framework 57 | new Settings\Settings_Page(); 58 | 59 | // Add a settings page to the Network Admin (requires multisite) 60 | if ( is_multisite() ) new Settings\Network_Settings_Page(); 61 | 62 | // Add Customizer panels and options 63 | new Settings\Customizer_Options(); 64 | 65 | // Enqueue scripts and stylesheets 66 | new EnqueueScripts(); 67 | 68 | // Create custom post types 69 | new PostTypes\PostTypes_Loader(); 70 | 71 | // Load custom widgets 72 | new Widgets\Widget_Loader(); 73 | 74 | // Load shortcodes 75 | new Shortcodes\Shortcode_Loader(); 76 | 77 | // Perform core plugin logic 78 | new Core(); 79 | 80 | } 81 | 82 | /** 83 | * Check plugin dependencies on activation. 84 | * 85 | * @since 0.2.0 86 | */ 87 | public function activate() { 88 | 89 | $this->verify_dependencies( true, true ); 90 | 91 | } 92 | 93 | /** 94 | * Initialize Carbon Fields and load plugin logic 95 | * 96 | * @since 0.2.0 97 | */ 98 | public function load_dependencies() { 99 | 100 | if( class_exists( 'Carbon_Fields\\Carbon_Fields' ) ) { 101 | add_action( 'after_setup_theme', array( 'Carbon_Fields\\Carbon_Fields', 'boot' ) ); 102 | } 103 | 104 | add_action( 'carbon_fields_fields_registered', array( $this, 'load_plugin' )); 105 | 106 | } 107 | 108 | /** 109 | * Function to verify dependencies, such as if an outdated version of Carbon 110 | * Fields is detected. 111 | * 112 | * @param bool $die If true, plugin execution is halted with die(), useful for 113 | * outputting error(s) in during activate() 114 | * @return bool 115 | * @since 0.2.0 116 | */ 117 | private function verify_dependencies( $die = false, $activate = false ) { 118 | 119 | // Check if underDEV_Requirements class is loaded 120 | if( !class_exists( 'underDEV_Requirements' ) ) { 121 | if( $die ) { 122 | die( sprintf( __( '%s: One or more dependencies failed to load', self::$textdomain ), __( self::$config->get( 'plugin/meta/Name' ) ) ) ); 123 | } else { 124 | return false; 125 | } 126 | } 127 | 128 | $requirements = new \underDEV_Requirements( __( self::$config->get( 'plugin/meta/Name' ), self::$textdomain ), self::$config->get( 'dependencies' ) ); 129 | 130 | // Check for WordPress Toolkit 131 | $requirements->add_check( 'wordpress-toolkit', function( $val, $res ) { 132 | $wordpress_toolkit_version = defined( '\WordPress_ToolKit\VERSION' ) ? \WordPress_ToolKit\VERSION : null; 133 | if( !$wordpress_toolkit_version ) { 134 | $res->add_error( __( 'WordPress ToolKit not loaded.', self::$textdomain ) ); 135 | } else if( version_compare( $wordpress_toolkit_version, self::$config->get( 'dependencies/wordpress-toolkit' ), '<' ) ) { 136 | $res->add_error( sprintf( __( 'An outdated version of WordPress ToolKit has been detected: %s (>= %s required).', self::$textdomain ), $wordpress_toolkit_version, self::$config->get( 'dependencies/wordpress-toolkit' ) ) ); 137 | } 138 | }); 139 | 140 | // Check for Carbon Fields 141 | $requirements->add_check( 'carbon_fields', function( $val, $res ) use ( &$activate ) { 142 | if( $activate ) return; 143 | $cf_version = defined('\\Carbon_Fields\\VERSION') ? current( explode( '-', \Carbon_Fields\VERSION ) ) : null; 144 | if( !$cf_version ) { 145 | $res->add_error( sprintf( __( 'The Carbon Fields framework is not loaded.', self::$textdomain ), 'https://carbonfields.net/release-archive/' ) ); 146 | } else if( version_compare( $cf_version, self::$config->get( 'dependencies/carbon_fields' ), '<' ) ) { 147 | $res->add_error( sprintf( __( 'An outdated version of Carbon Fields has been detected: %s (>= %s required).', self::$textdomain ), $cf_version, self::$config->get( 'dependencies/carbon_fields' ) ) ); 148 | } 149 | }); 150 | 151 | // Display errors if requirements not met 152 | if( !$requirements->satisfied() ) { 153 | if( $die ) { 154 | die( $requirements->notice() ); 155 | } else { 156 | add_action( 'admin_notices', array( $requirements, 'notice' ) ); 157 | return false; 158 | } 159 | } 160 | 161 | return true; 162 | 163 | } 164 | 165 | /** 166 | * Append a field prefix as defined in $config 167 | * 168 | * @param string $field_name The string/field to prefix 169 | * @param string $before String to add before the prefix 170 | * @param string $after String to add after the prefix 171 | * @return string Prefixed string/field value 172 | * @since 0.1.0 173 | */ 174 | public static function prefix( $field_name = null, $before = '', $after = '_' ) { 175 | 176 | $prefix = $before . self::$config->get( 'prefix' ) . $after; 177 | return $field_name !== null ? $prefix . $field_name : $prefix; 178 | 179 | } 180 | 181 | /** 182 | * Get Carbon Fields option, with object caching (if available). Currently 183 | * only supports plugin options because meta fields would need to have the 184 | * cache flushed appropriately. 185 | * 186 | * @param string $key The name of the option key 187 | * @param bool $cache Whether or not to attempt to get cached value 188 | * @return mixed The value of specified Carbon Fields option key 189 | * @link https://carbonfields.net/docs/containers-usage/ Carbon Fields containers 190 | * @since 0.2.0 191 | * 192 | */ 193 | public static function get_carbon_plugin_option( $key, $cache = true ) { 194 | 195 | $key = self::prefix( $key ); 196 | 197 | if( $cache ) { 198 | // Attempt to get value from cache, else fetch value from database 199 | return self::$cache->get_object( $key, function() use ( &$key ) { 200 | return carbon_get_theme_option( $key ); 201 | }); 202 | } else { 203 | // Return uncached value 204 | return carbon_get_theme_option( $key ); 205 | } 206 | 207 | } 208 | 209 | /** 210 | * Get Carbon Fields network container option (if multisite enabled) 211 | * 212 | * @param string $key The name of the option key 213 | * @param string $container The name of the Carbon Fields network container 214 | * @param bool $cache Whether or not to attempt to get cached value 215 | * @param int $site_id The network site ID to use - default: SITE_ID_CURRENT_SITE 216 | * @return mixed The value of specified Carbon Fields option key 217 | * @link https://carbonfields.net/docs/containers-usage/ Carbon Fields containers 218 | * @since 0.5.0 219 | * 220 | */ 221 | public static function get_carbon_network_option( $key, $cache = true, $site_id = null ) { 222 | 223 | if( !$site_id ) { 224 | if( !defined( 'SITE_ID_CURRENT_SITE' ) ) return null; 225 | $site_id = SITE_ID_CURRENT_SITE; 226 | } 227 | 228 | $key = self::prefix( $key ); 229 | 230 | if( $cache ) { 231 | // Attempt to get value from cache, else fetch value from database 232 | return self::$cache->get_object( $key, function() use ( &$site_id, &$key ) { 233 | return carbon_get_network_option( $site_id, $key ); 234 | }, null, [ 'network_global' => true ] ); 235 | } else { 236 | // Return uncached value 237 | return carbon_get_network_option( $site_id, $key ); 238 | } 239 | 240 | } 241 | 242 | } 243 | -------------------------------------------------------------------------------- /app/Helpers.php: -------------------------------------------------------------------------------- 1 | 'success', // 'error', 'warning', 'success', 'info' 20 | 'dismissible' => true, // notice is dismissible 21 | 'scope' => true, // 'admin', 'network', true (both) 22 | 'class' => null, // additional CSS classes to add to notice 23 | 'id' => null // container element ID 24 | ), $args); 25 | 26 | // Merge CSS classes 27 | if( !is_array( $args['class'] ) ) $args['class'] = explode( ' ', $args['class'] ); 28 | $classes = array_merge([ 29 | 'notice', 30 | 'notice-' . $args['type'] 31 | ], $args['class'] ); 32 | if( $args['dismissible'] ) $classes[] = 'is-dismissible'; 33 | $classes = implode( ' ', array_filter( $classes ) ); 34 | 35 | // Set ID string, if specified 36 | $element_id = $args['id'] ? ' id="' . $args['id'] . '"' : ''; 37 | 38 | // Display message in WP Admin 39 | if( $args['scope'] === true || $args['scope'] == 'admin' ) { 40 | add_action( 'admin_notices', function() use ( &$classes, &$element_id, &$msg ) { 41 | printf( '%3$s
%3$s