├── .bowerrc ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── app ├── Core.php ├── Datastores │ └── Serialized_Theme_Options_Datastore.php ├── EnqueueScripts.php ├── Helpers.php ├── Plugin.php ├── PostTypes │ ├── Clients.php │ └── PostTypes_Loader.php ├── Settings │ ├── Customizer_Options.php │ ├── Network_Settings_Page.php │ └── Settings_Page.php ├── Shortcodes │ ├── CurrentYear_Shortcode.php │ ├── Hello_Shortcode.php │ └── Shortcode_Loader.php ├── TGMPA.php └── Widgets │ ├── Example_Widget.php │ └── Widget_Loader.php ├── assets ├── css │ └── .DO_NOT_EDIT.txt ├── images │ └── .gitkeep ├── js │ └── .gitkeep └── screenshot-1.png ├── bower.json ├── composer.json ├── gulpfile.js ├── languages └── .gitkeep ├── package.json ├── plugin.json ├── readme.txt ├── src ├── js │ ├── admin │ │ └── admin.js │ ├── common │ │ └── common.js │ └── frontend │ │ └── plugin.js └── scss │ ├── _carbon-fields.scss │ ├── _core.scss │ ├── _media-queries.scss │ ├── _variables.scss │ ├── admin.scss │ └── plugin.scss └── wordpress-base-plugin.php /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory" : "assets/components", 3 | "ignoredDependencies": [ 4 | "jquery" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | 16 | [*.yml] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | [{*.txt,wp-config-sample.php}] 24 | end_of_line = crlf 25 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Author](https://img.shields.io/badge/author-Daniel%20M.%20Hendricks-lightgrey.svg?colorB=9900cc&style=flat-square)](https://www.danhendricks.com/?utm_source=github.com&utm_medium=campaign&utm_content=button&utm_campaign=wordpress-base-plugin) 2 | [![Latest Version](https://img.shields.io/github/release/dmhendricks/wordpress-base-plugin.svg?style=flat-square)](https://github.com/dmhendricks/wordpress-base-plugin/releases) 3 | [![GitHub License](https://img.shields.io/badge/license-GPLv2-yellow.svg?style=flat-square)](https://raw.githubusercontent.com/dmhendricks/wordpress-base-plugin/master/LICENSE) 4 | [![Flywheel](https://img.shields.io/badge/hosting-Flywheel-green.svg?style=flat-square&label=get%20hosted&colorB=AE2A21)](https://share.getf.ly/e25g6k?utm_source=github.com&utm_medium=campaign&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin) 5 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?style=flat-square)](https://paypal.me/danielhendricks) 6 | [![Analytics](https://ga-beacon.appspot.com/UA-67333102-2/dmhendricks/wordpress-base-plugin?flat)](https://github.com/igrigorik/ga-beacon/?utm_source=github.com&utm_medium=referral&utm_content=button&utm_campaign=dmhendricks%2Fwordpress-base-plugin) 7 | [![Twitter](https://img.shields.io/twitter/url/https/github.com/dmhendricks/wordpress-base-plugin.svg?style=social)](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 | ![Settings Page](https://raw.githubusercontent.com/dmhendricks/wordpress-base-plugin/master/assets/screenshot-1.png "Settings Page") -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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

', $classes, $element_id, $msg ); 42 | }); 43 | } 44 | 45 | // Display message in Network Admin 46 | if( $args['scope'] === true || $args['scope'] == 'network' ) { 47 | add_action( 'network_admin_notices', function() use ( &$classes, &$element_id, &$msg ) { 48 | printf( '

%3$s

', $classes, $element_id, $msg ); 49 | }); 50 | } 51 | 52 | } 53 | 54 | /** 55 | * Merges two arrays, eliminating duplicates 56 | * 57 | * array_merge_recursive_distinct does not change the datatypes of the values in the arrays. 58 | * Matching keys' values in the second array overwrite those in the first array, as is the 59 | * case with array_merge(). 60 | * 61 | * @param array $array1 62 | * @param array $array2 63 | * @return array 64 | * @author Daniel 65 | * @author Gabriel Sobrinho 66 | * @see http://php.net/manual/en/function.array-merge-recursive.php#92195 Source 67 | */ 68 | private function array_merge_recursive_distinct( array &$array1, array &$array2 ) { 69 | 70 | $merged = $array1; 71 | 72 | foreach ( $array2 as $key => &$value ) 73 | { 74 | if ( is_array ( $value ) && isset ( $merged [$key] ) && is_array ( $merged [$key] ) ) { 75 | $merged[$key] = self::array_merge_recursive_distinct ( $merged[$key], $value ); 76 | } else { 77 | $merged[$key] = $value; 78 | } 79 | } 80 | 81 | return $merged; 82 | 83 | } 84 | 85 | /** 86 | * Get the slug of the current page/post 87 | * 88 | * Return the slug of the current page/post. 89 | * Example: http://mysite.com/sample-page/test-page would return: test-page 90 | * 91 | * @param int $post_id (optional) Specify the post ID to retrieve parent slug for, 92 | * else $post global is used instead 93 | * @return string The slug of the specified/current post 94 | */ 95 | public static function get_page_slug( $post_id = null ) { 96 | global $post; 97 | 98 | $_slug = $post_id ? get_post( $post_id )->post_name : $post->post_name; 99 | 100 | if( is_front_page() ) { 101 | $_slug = 'front'; 102 | } else if( is_search() ) { 103 | $_slug = 'search'; 104 | } else if( is_archive() ) { 105 | $_slug = 'archive'; 106 | } else if( is_single() ) { 107 | $_slug = 'single'; 108 | } 109 | 110 | return $_slug; 111 | } 112 | 113 | /** 114 | * Get the slug of the parent post (if any) 115 | * 116 | * Return the slug of the parent page/post. 117 | * Example: http://mysite.com/sample-page/test-page would return: sample-page 118 | * 119 | * @param bool $include_self_as_parent_if_root (optional) Should we return the parent 120 | * slug if the post *is* the parent? This can be useful if you want to apply 121 | * style/logic to child pages as well as the parent 122 | * @param int $post_id (optional) Specify the post ID to retrieve parent slug for, 123 | * else $post global is used instead 124 | * @return string The parent slug of the specified post, if availaable 125 | */ 126 | public static function get_parent_slug( $include_self_as_parent_if_root = false, $post_id = null ) { 127 | global $post; 128 | $post_id = $post_id ? $post_id : @$post->ID; 129 | 130 | if ( is_page() ) { 131 | if( get_post( $post_id )->post_parent ) { 132 | $parent = @end( get_post_ancestors( $post_id ) ) ; 133 | } else { 134 | $parent = $post->ID; 135 | } 136 | $post_data = get_post( $parent, ARRAY_A ); 137 | if( $include_self_as_parent_if_root || $post_data['post_name'] != self::get_page_slug( $post_id ) ) return $post_data['post_name']; 138 | } 139 | return array(); 140 | } 141 | 142 | /** 143 | * Returns the categories of the current post 144 | * 145 | * Returns the categories of the current post, either as labels or as slugs. 146 | * 147 | * @param bool $as_slugs (optional) Returns array of category slugs rather than 148 | * category labels 149 | * @param int $post_id (optional) Specify the post ID to retrieve categories for, 150 | * else $post global is used instead 151 | * @return array 152 | */ 153 | public static function get_post_categories($as_slugs = false, $post_id = null) { 154 | global $post; 155 | $return = array(); 156 | 157 | $post_id = $post_id ? $post_id : @$post->ID; 158 | if( !$post_id ) return $return; 159 | 160 | $categories = get_the_category( $post_id ); 161 | if( !$categories ) return $return; 162 | 163 | foreach( $categories as $cat ) { 164 | $return[] = $as_slugs ? $cat->slug : $cat->name; 165 | } 166 | 167 | return $return; 168 | } 169 | 170 | /** 171 | * Returns script ?ver= version based on environment (WP_ENV) 172 | * 173 | * If WP_ENV is not defined or equals anything other than 'development' or 'staging' 174 | * returns $script_version (if defined) else plugin verson. If WP_ENV is defined 175 | * as 'development' or 'staging', returns string representing file last modification 176 | * date (to discourage browser during development). 177 | * 178 | * @param string $script The filesystem path (relative to the script location of 179 | * calling script) to return the version for. 180 | * @param string $script_version (optional) The version that will be returned if 181 | * WP_ENV is defined as anything other than 'development' or 'staging'. 182 | * 183 | * @return string 184 | * @since 0.1.0 185 | */ 186 | public static function get_script_version( $script, $return_minified = false, $script_version = null ) { 187 | $version = $script_version ?: self::$config->get( 'plugin/meta/Version' ); 188 | if( self::is_production() ) return $version; 189 | 190 | $script = self::get_script_path( $script, $return_minified ); 191 | if( file_exists($script) ) { 192 | $version = date( "ymd-Gis", filemtime( $script ) ); 193 | } 194 | 195 | return $version; 196 | } 197 | 198 | /** 199 | * Returns script path or URL, either regular or minified (if exists). 200 | * 201 | * If in production mode or if @param $force_minify == true, inserts '.min' to the filename 202 | * (if exists), else return script name without (example: style.css vs style.min.css). 203 | * 204 | * @param string $script The relative (to the plugin folder) path to the script. 205 | * @param bool $return_minified If true and is_production() === true then will prefix the 206 | * extension with .min. NB! Due to performance reasons, I did not include logic to check 207 | * to see if the script_name.min.ext exists, so use only when you know it exists. 208 | * @param bool $return_url If true, returns full-qualified URL rather than filesystem path. 209 | * 210 | * @return string The URL or path to minified or regular $script. 211 | * @since 0.1.0 212 | */ 213 | public static function get_script_path( $script, $return_minified = true, $return_url = false ) { 214 | $script = trim( $script, '/' ); 215 | if( $return_minified && strpos( $script, '.' ) && self::is_production() ) { 216 | $script_parts = explode( '.', $script ); 217 | $script_extension = end( $script_parts ); 218 | array_pop( $script_parts ); 219 | $script = implode( '.', $script_parts ) . '.min.' . $script_extension; 220 | } 221 | 222 | return self::$config->get( $return_url ? 'plugin/url' : 'plugin/path' ) . $script; 223 | } 224 | 225 | /** 226 | * Returns absolute URL of $script. 227 | * 228 | * @param string $script The relative (to the plugin folder) path to the script. 229 | * @param bool 230 | * @since 0.1.0 231 | */ 232 | public static function get_script_url( $script, $return_minified = false ) { 233 | return self::get_script_path( $script, $return_minified, true ); 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /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/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/PostTypes/PostTypes_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/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/Settings/Network_Settings_Page.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 | -------------------------------------------------------------------------------- /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/Shortcodes/CurrentYear_Shortcode.php: -------------------------------------------------------------------------------- 1 | 'world' 27 | ), $atts, 'hello' ); 28 | 29 | return sprintf( __( 'Hello %s!', self::$textdomain ), $atts[ 'name' ] ); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /app/Widgets/Widget_Loader.php: -------------------------------------------------------------------------------- 1 | =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 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gulpfile heavily adapted from {@link https://github.com/ahmadawais/WPGulp WPGulp} 3 | * Implements: 4 | * 1. CSS: Sass to CSS conversion, error catching, autoprefixing, sourcemaps, 5 | * CSS minification, and merge media queries. 6 | * 2. JS: Concatenates & uglifies JS files. 7 | * 3. Watches files for changes in CSS or JS. 8 | * 4. Corrects the line endings {@link https://www.npmjs.com/package/gulp-line-ending-corrector}. 9 | * 10 | * @author Daniel M. Hendricks (@danielhendricks) 11 | * @since 0.3.0 12 | */ 13 | 14 | var pkg = require('./package.json'); 15 | 16 | /** 17 | * Configuration 18 | * 19 | * In paths you can add <>. Edit the variables as per your project requirements. 20 | */ 21 | var project = pkg.name; // Project slug 22 | var cssOutputStyle = 'expanded'; // Values: compact, compressed, nested, expanded 23 | var cssOutputComments = false; // Output SASS source/line numbers in compiled CSS files 24 | 25 | var styleSourcePath = './src/scss/'; // Path to source SASS files 26 | var jsSourcePath = './src/js/'; // Path to source JavaScript files 27 | var styleDestination = './assets/css/'; // Path to place the compiled CSS file 28 | var jsDestination = './assets/js/'; // Path to place the compiled CSS file 29 | var styleMapPath = './'; // Path to place the map files 30 | var distZipFile = project + '.zip'; // The destination file for the ZIP task 31 | 32 | /* Define the main CSS files to watch */ 33 | var styleTasks = [ 34 | { 35 | name: 'plugin', 36 | suffix: false, // Can be a string value, false for none, else uses 'name' value 37 | source: 'plugin.scss' // The filename located in the styleSourcePath directory 38 | }, 39 | { 40 | name: 'admin', 41 | source: 'admin.scss' 42 | } 43 | ]; 44 | 45 | /* Define the JavaScript files to watch */ 46 | var jsTasks = [ 47 | { 48 | name: 'frontend', 49 | suffix: false, 50 | include: [ jsSourcePath + 'common/**/*.js' ], 51 | //source: jsSourcePath + 'frontend', // Optional subdirectory to search for *.js. Defaults to 'name' value 52 | //dest: jsDestination // Optionally, specfy alternate destination 53 | }, 54 | { 55 | name: 'admin', 56 | include: [ jsSourcePath + 'common/**/*.js' ], 57 | }, 58 | { 59 | name: 'vendor', 60 | } 61 | ]; 62 | 63 | /* Define strings to replace using 'gulp rename', defined in the config section of package.json */ 64 | var renameStrings = [ 65 | [ 'dmhendricks\/wordpress-base-plugin', pkg.config.username + '/' + pkg.name ], // Git/Composer identifier 66 | [ 'VendorName\\PluginName', pkg.config.php_namespace ], // PHP namespace for your plugin 67 | [ 'VendorName\\\\PluginName', pkg.config.php_namespace.replace( /\\/g, '\\\\' ) ], // Rename Composer namespace 68 | [ 'wordpress-base-plugin', pkg.name ], // Plugin slug 69 | [ 'wordpress_base_plugin', pkg.name.replace( /-/g, '_' ) ], // Plugin underscored slug 70 | [ 'WPBP', pkg.config.prefix.toUpperCase() ], // Unique JavaScript object for your plugin 71 | [ 'wpbp', pkg.config.prefix ], // Replace remaining plugin prefixes 72 | [ 'WordPress Base Plugin', pkg.config.plugin_name ], // Replace plugin long name 73 | [ 'My Plugin', pkg.config.plugin_short_name ] // Replace plugin short name 74 | ]; 75 | 76 | /** 77 | * Browsers for which you want to enable autoprefixing. 78 | * 79 | * @see https://github.com/ai/browserslist 80 | */ 81 | const AUTOPREFIXER_BROWSERS = [ 82 | 'last 2 version', 83 | '> 1%', 84 | 'ie >= 9', 85 | 'ie_mob >= 10', 86 | 'ff >= 30', 87 | 'chrome >= 34', 88 | 'safari >= 7', 89 | 'opera >= 23', 90 | 'ios >= 7', 91 | 'android >= 4', 92 | 'bb >= 10' 93 | ]; 94 | 95 | /** 96 | * Load gulp plugins and pass them semantic names. 97 | */ 98 | var gulp = require('gulp'); 99 | var pkg = require('./package.json'); 100 | 101 | // CSS-related plugins 102 | var sass = require('gulp-sass'); // Gulp pluign for Sass compilation. 103 | var minifycss = require('gulp-uglifycss'); // Minifies CSS files. 104 | var autoprefixer = require('gulp-autoprefixer'); // Autoprefixing magic. 105 | var mmq = require('gulp-merge-media-queries'); // Combine matching media queries into one media query definition. 106 | 107 | // JavaScript-related plugins. 108 | var concat = require('gulp-concat'); // Concatenates JS files 109 | var uglify = require('gulp-uglify'); // Minifies JS files 110 | 111 | // Utility related plugins. 112 | var rename = require('gulp-rename'); // Renames files (ex: style.css -> style.min.css) 113 | var replace = require('gulp-batch-replace'); // Replace strings inside files 114 | var lineec = require('gulp-line-ending-corrector'); // Consistent Line Endings for non UNIX systems. Gulp Plugin for Line Ending Corrector (A utility that makes sure your files have consistent line endings) 115 | var filter = require('gulp-filter'); // Enables you to work on a subset of the original files by filtering them using globbing. 116 | var sourcemaps = require('gulp-sourcemaps'); // Maps code in a compressed file (E.g. style.css) back to it’s original position in a source file (E.g. structure.scss, which was later combined with other css files to generate style.css) 117 | var notify = require('gulp-notify'); // Displays notification message 118 | var batchRename = require('gulp-simple-rename'); // Rename files with wildcard 119 | var vinylPaths = require('vinyl-paths'); // Return each path in a stream 120 | var del = require('del'); // Delete files that are renamed 121 | var plumber = require( 'gulp-plumber' ); // Prevent pipe breaking caused by errors from gulp plugins. 122 | 123 | /** 124 | * Custom Error Handler. 125 | * 126 | * @param Mixed err 127 | */ 128 | const errorHandler = r => { 129 | notify.onError( '\n\n ===> ERROR: <%= error.message %>\n' )( r ); 130 | beep(); 131 | }; 132 | 133 | /* Arrays to hold created task info */ 134 | var tasks_css = []; 135 | var tasks_js = []; 136 | 137 | /** 138 | * Style tasks 139 | * 140 | * Compile SASS, autoprefixes and minifies. 141 | */ 142 | styleTasks.forEach( function( task ) { 143 | 144 | var basename_suffix = ( typeof task.suffix === 'string' ? '-' + task.suffix : '' ); 145 | if( !( typeof task.suffix === 'boolean' && task.suffix === false ) ) basename_suffix = '-' + task.name; 146 | 147 | tasks_css.push( task.name + 'CSS' ); 148 | 149 | gulp.task( task.name + 'CSS', (done) => { 150 | gulp.src( styleSourcePath + task.source ) 151 | .pipe( plumber( errorHandler ) ) 152 | .pipe( sourcemaps.init() ) 153 | .pipe( sass( { 154 | sourceComments: cssOutputComments ? 'map' : null, 155 | errLogToConsole: true, 156 | outputStyle: cssOutputStyle, 157 | precision: 10 158 | } ) ) 159 | .on( 'error', console.error.bind( console ) ) 160 | .pipe( sourcemaps.write( { includeContent: false } ) ) 161 | .pipe( sourcemaps.init( { loadMaps: true } ) ) 162 | .pipe( autoprefixer( AUTOPREFIXER_BROWSERS ) ) 163 | 164 | .pipe( sourcemaps.write ( styleMapPath ) ) 165 | .pipe( rename( { 166 | basename: project + basename_suffix 167 | })) 168 | .pipe( lineec() ) // Consistent Line Endings for non UNIX systems. 169 | .pipe( gulp.dest( styleDestination ) ) 170 | 171 | .pipe( filter( '**/*.css' ) ) // Filtering stream to only css files 172 | .pipe( mmq( { log: true } ) ) // Merge Media Queries only for .min.css version. 173 | 174 | .pipe( rename( { 175 | basename: project + basename_suffix, 176 | suffix: '.min' 177 | })) 178 | .pipe( minifycss() ) 179 | .pipe( lineec() ) // Consistent line endings for non-UNIX systems. 180 | .pipe( gulp.dest( styleDestination ) ) 181 | 182 | .pipe( filter( '**/*.css' ) ) // Filtering stream to only css files 183 | .pipe( notify( { message: 'TASK: "' + task.name + 'CSS" completed.', onLast: true } ) ); 184 | done(); 185 | }); 186 | 187 | }); 188 | 189 | /** 190 | * Style tasks 191 | * 192 | * Concatenate, uglify and rename JavaScripts. 193 | */ 194 | 195 | jsTasks.forEach( function( task ) { 196 | 197 | // Set base filename suffix (example '-admin'), if specified 198 | var basename_suffix = ( typeof task.suffix === 'string' ? '-' + task.suffix : '' ); 199 | if( !( typeof task.suffix === 'boolean' && task.suffix === false ) ) basename_suffix = '-' + task.name; 200 | 201 | // Set base filename suffix (example '-admin'), if specified 202 | var jsSources = [ ( task.source ? task.source : jsSourcePath + task.name ) + '/*.js' ]; 203 | 204 | // Set JavaScript source paths 205 | if( task.include ) { 206 | task.include.forEach( function( item ) { 207 | jsSources.push( item ); 208 | }); 209 | } 210 | 211 | tasks_js.push( { id: task.name + 'JS', name: task.name, watch: jsSources } ); 212 | 213 | gulp.task( task.name + 'JS', (done) => { 214 | 215 | gulp.src( jsSources ) 216 | .pipe( plumber( errorHandler ) ) 217 | .pipe( concat( task.source + '.js' ) ) 218 | .pipe( rename( { 219 | basename: project + basename_suffix, 220 | })) 221 | .pipe( lineec() ) // Consistent Line Endings for non UNIX systems. 222 | .pipe( gulp.dest( jsDestination ) ) 223 | .pipe( rename( { 224 | basename: project + basename_suffix, 225 | suffix: '.min' 226 | })) 227 | .pipe( uglify() ) 228 | .pipe( lineec() ) // Consistent Line Endings for non UNIX systems. 229 | .pipe( gulp.dest( jsDestination ) ) 230 | .pipe( notify( { message: 'TASK: "' + task.name + 'JS" completed.', onLast: true } ) ); 231 | done(); 232 | }); 233 | 234 | 235 | }); 236 | 237 | /** 238 | * Watches for file changes and runs specified tasks. 239 | */ 240 | //gulp.task( 'default', object_property_to_array( tasks_js, 'id', tasks_css ), function () { 241 | 242 | // gulp.watch( styleSourcePath + '**/*.scss', tasks_css ); 243 | 244 | // tasks_js.forEach( function( task ) { 245 | // gulp.watch( task.watch, [ task.id ] ); 246 | // }); 247 | 248 | //}); 249 | 250 | gulp.task( 251 | 'default', 252 | gulp.series( gulp.parallel( object_property_to_array( tasks_js, 'id', tasks_css ) ), () => { 253 | 254 | gulp.watch( styleSourcePath + '**/*.scss', gulp.parallel( tasks_css ) ); 255 | 256 | tasks_js.forEach( function( task ) { 257 | gulp.watch( task.watch, gulp.series( task.id ) ); 258 | }); 259 | 260 | }) 261 | ); 262 | 263 | /** 264 | * Task to rename files and variables 265 | */ 266 | gulp.task( 'rename', () => { 267 | 268 | return gulp.src( [ './**/*.php', './*.json', './**/*.js', './**/*.scss', './*.txt', './*.md', '!./node_modules/**', '!./vendor/**', '!./.git/**', '!./languages/**', '!./*lock*', '!./gulpfile.js' ] ) 269 | .pipe( plumber( errorHandler ) ) 270 | .pipe( replace( renameStrings ) ) 271 | .pipe( vinylPaths( del ) ) 272 | .pipe( batchRename( function (path) { 273 | return path.replace( /wordpress-base-plugin/, pkg.name ); 274 | } ) ) 275 | .pipe( gulp.dest( './' ) ) 276 | .pipe( notify( { message: 'TASK: "rename" completed.', onLast: true } ) ); 277 | 278 | }); 279 | 280 | /** 281 | * Helper functions 282 | */ 283 | function object_property_to_array( obj, id, arr ) { 284 | var result = []; 285 | obj.forEach( function( item ) { 286 | for( key in item ) { 287 | if(item.hasOwnProperty(key)) { 288 | if( key == id ) result.push( item[id] ); 289 | } 290 | } 291 | }); 292 | if( arr ) result = arr.concat( result ); 293 | return result; 294 | } 295 | -------------------------------------------------------------------------------- /languages/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmhendricks/wordpress-base-plugin/3385b986e770ad6b79d86f170bf88b02c4d58ea3/languages/.gitkeep -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefix": "wpbp", 3 | "short_name": "My Plugin", 4 | "object_cache": { 5 | "group": "wordpress_base_plugin_cache", 6 | "expire": 86400 7 | }, 8 | "dependencies": { 9 | "php": "7.0", 10 | "wp": "4.8", 11 | "carbon_fields": "2.2.0", 12 | "wordpress-toolkit": "0.4.0" 13 | }, 14 | "network": { 15 | "default_options_container": "global_settings" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/js/admin/admin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @preserve: Custom JavaScript Logic - WP Admin 3 | */ 4 | 5 | ;var WPBP_NS = WPBP_NS || {}; 6 | 7 | (function($, undefined) { 8 | 9 | WPBP_NS.Admin = { 10 | 11 | exampleFunction: function( name ) { 12 | 13 | name = name || 'world'; 14 | console.log( 'Hello ' + name ); 15 | 16 | } 17 | 18 | } 19 | 20 | // Write a message to the debugger console 21 | WPBP_NS.Admin.exampleFunction( 'James' ); 22 | 23 | })( window.jQuery ); 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/js/frontend/plugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @preserve: Custom JavaScript Logic - Frontend 3 | */ 4 | 5 | ;var WPBP_NS = WPBP_NS || {}; 6 | 7 | (function($, undefined) { 8 | 9 | WPBP_NS.Site = { 10 | 11 | sampleFunction: function( name ) { 12 | 13 | name = name || 'world'; 14 | console.log( 'Hello ' + name + '!' ) 15 | 16 | } 17 | 18 | } 19 | 20 | // Write a message to the debugger console 21 | //WPBP_NS.Site.sampleFunction( 'Darlene' ); 22 | 23 | })( window.jQuery ); 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/_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/_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 | -------------------------------------------------------------------------------- /src/scss/plugin.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * @preserve: Frontend style 3 | */ 4 | 5 | @import "variables"; 6 | @import "core"; 7 | @import "media-queries"; 8 | -------------------------------------------------------------------------------- /wordpress-base-plugin.php: -------------------------------------------------------------------------------- 1 |