diff --git a/README.md b/README.md index 3a0ab27..a1e16f5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Builds protected by CircleCI: [![CircleCI](https://circleci.com/gh/WordPress-Pho ```json { "require": { - "WordPress-Phoenix/wordpress-options-builder-class": "1.*" + "WordPress-Phoenix/wordpress-options-builder-class": "3.*" } } ``` @@ -38,60 +38,4 @@ if( file_exists( dirname( __FILE__ ) . 'vendor/autoload.php' ) ) { # Usage -### WARNING: This documentation isn't yet current to v2 -- coming soon! -## Site Options Page -The following is example code which can be run to create a very basic site options page. You can use standard OOP principals shown in the below code to expand upon the sample. Please read through the class names in the library to find all the different types of fields you can include into your options page, or build your own field types by extending the existing classes. - -```php - $this->site_options_page = new sm_options_page( array ( - 'parent_id' => 'themes.php', - 'id' => 'custom-options', - 'page_title' => 'Custom Settings', - 'menu_title' => 'Settings', - ) ); - $this->site_options_page->add_part($section = new sm_section('branding_options', array('title'=>'Branding')) ); - $section->add_part( $cpt_header_background = new sm_media_upload( 'cpt_header_bg_image', array( - 'label' => 'Header Background Image', - 'description' => 'Background image to be used in the header on the Custom archive page.' - ) ) ); - $section->add_part( $cpt_header_logo = new sm_textfield( 'cpt_header_logo', array( - 'label' => 'Header Logo', - 'description' => 'Logo to be used in the header on the Custom archive page.' - ) ) ); - $section->add_part( $cpt_description = new sm_textarea( 'cpt_description', array( - 'label' => 'Custom Description', - 'value' => 'Custom Description', - 'description' => 'Description to be used in the header on the Custom archive page.', - ) ) ); - $section->add_part( $cpt_color_overlay = new sm_color_picker( 'cpt_color_overlay', array( - 'label' => 'Custom Color Overlay', - 'value' => 'Custom Color Overlay', - 'description' => 'Color overlay to be used over header image only on the Custom archive page.', - ) ) ); - $section->add_part( $cpt_coming_soon_header = new sm_media_upload( 'cpt_coming_soon_header', array( - 'label' => 'Custom Coming Soon Header Image', - 'description' => 'Header image to be used on Custom Coming Soon page.' - ) ) ); - $section->add_part( $cpt_coming_soon_content = new sm_textarea( 'cpt_coming_soon_content', array( - 'label' => 'Custom Coming Soon Content', - 'value' => 'Custom Coming Soon Content', - 'description' => 'Coming Soon content to be used on Custom Coming Soon page.', - ) ) ); - // recommended you move this build line into an init action hook priority 20+ - $this->site_options_page->build(); -``` - -## Network Options Page - -Simply set the `network_page` flag to true, and if you are on a multisite install, your options page will be in the mutlsite network admin navigation. **Note: plugin must be network activated to show network settings.** Here is an example: -```php -// create network-wide settings page - $this->network_options_page = new sm_options_page( array ( - 'parent_id' => 'settings.php', - 'id' => 'network_settings', - 'page_title' => 'Network Options', - 'menu_title' => 'Network Options', - 'network_page' => true - ) ); -``` diff --git a/unminified-assets-v2/unminified.js b/unminified-assets-v2/unminified.js deleted file mode 100755 index 1c03829..0000000 --- a/unminified-assets-v2/unminified.js +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Module: WP-JS-Hooks - * Props: Carl Danley & 10up - */ -!function(t,n){"use strict";t.wp=t.wp||{},t.wp.hooks=t.wp.hooks||new function(){function t(t,n,r,i){var e,o,c;if(f[t][n])if(r)if(e=f[t][n],i)for(c=e.length;c--;)(o=e[c]).callback===r&&o.context===i&&e.splice(c,1);else for(c=e.length;c--;)e[c].callback===r&&e.splice(c,1);else f[t][n]=[]}function n(t,n,i,e,o){var c={callback:i,priority:e,context:o},l=f[t][n];l?(l.push(c),l=r(l)):l=[c],f[t][n]=l}function r(t){for(var n,r,i,e=1,o=t.length;en.priority;)t[r]=t[r-1],--r;t[r]=n}return t}function i(t,n,r){var i,e,o=f[t][n];if(!o)return"filters"===t&&r[0];if(e=o.length,"filters"===t)for(i=0;i n.priority; ) t[r] = t[r - 1], --r; + t[r] = n + } + return t + } + + function i( t, n, r ) { + var i, e, o = f[t][n]; + if ( !o ) return "filters" === t && r[0]; + if ( e = o.length, "filters" === t ) for ( i = 0; i < e; i++ ) r[0] = o[i].callback.apply( o[i].context, r ); else for ( i = 0; i < e; i++ ) o[i].callback.apply( o[i].context, r ); + return "filters" !== t || r[0] + } + + var e = Array.prototype.slice, o = { + removeFilter: function( n, r ) { + return "string" == typeof n && t( "filters", n, r ), o + }, applyFilters: function() { + var t = e.call( arguments ), n = t.shift(); + return "string" == typeof n ? i( "filters", n, t ) : o + }, addFilter: function( t, r, i, e ) { + return "string" == typeof t && "function" == typeof r && (i = parseInt( i || 10, 10 ), n( "filters", t, r, i, e )), o + }, removeAction: function( n, r ) { + return "string" == typeof n && t( "actions", n, r ), o + }, doAction: function() { + var t = e.call( arguments ), n = t.shift(); + return "string" == typeof n && i( "actions", n, t ), o + }, addAction: function( t, r, i, e ) { + return "string" == typeof t && "function" == typeof r && (i = parseInt( i || 10, 10 ), n( "actions", t, r, i, e )), o + } + }, f = { actions: {}, filters: {} }; + return o + } +}( window ); +// BEGIN JS +jQuery( document ).ready( function( $ ) { + var wpModal; + registerAllActions(); + wp.hooks.doAction( 'wpopPreInit' ); + var mediaStats = wp.template( 'wpop-media-stats' ); + + $( '#wpopNav li a' ).click( function( evt ) { + wp.hooks.doAction( 'wpopSectionNav', this, evt ); // reg. here to allow "click" from hash to select a section + } ); + + wp.hooks.doAction( 'wpopInit' ); // main init + + $( 'input[type="submit"]' ).click( function( evt ) { + wp.hooks.doAction( 'wpopSubmit', this, evt ); + } ); + + $( '.pwd-clear' ).click( function( evt ) { + wp.hooks.doAction( 'wpopPwdClear', this, evt ); + } ); + + $( '.img-upload' ).on( 'click', function( event ) { + wp.hooks.doAction( 'wpopImgUpload', this, event ); + } ); + + $( '.img-remove' ).on( 'click', function( event ) { + wp.hooks.doAction( 'wpopImgRemove', this, event ); + } ); + + function registerAllActions() { + wp.hooks.addAction( 'wpopPreInit', nixHashJumpJank ); + wp.hooks.addAction( 'wpopInit', handleInitHashSelection, 5 ); + wp.hooks.addAction( 'wpopFooterScripts', initIrisColorSwatches ); + wp.hooks.addAction( 'wpopInit', initSelectizeInputs ); + wp.hooks.addAction( 'wpopInit', initMediaUploadField ); + + wp.hooks.addAction( 'wpopInit', wpopDisableSpinner, 100 ); + + wp.hooks.addAction( 'wpopSectionNav', handleSectionNavigation ); + + wp.hooks.addAction( 'wpopPwdClear', doPwdFieldClear ); + wp.hooks.addAction( 'wpopImgUpload', doMediaUpload ); + wp.hooks.addAction( 'wpopImgRemove', doMediaRemove ); + + wp.hooks.addAction( 'wpopSubmit', wpopShowSpinner ); + } + + /* CORE */ + function handleSectionNavigation( elem, event ) { + event.preventDefault(); + var page_active = $( ($( elem ).attr( 'href' )) ).addClass( 'active' ); + var menu_active = $( ($( elem ).attr( 'href' ) + '-nav') ).addClass( 'active wp-ui-primary opn' ); + + // add tab's location to URL but stay at the top of the page + window.location.hash = $( elem ).attr( 'href' ); + window.scrollTo( 0, 0 ); + $( page_active ).siblings().removeClass( 'active' ); + $( menu_active ).siblings().removeClass( 'active wp-ui-primary opn' ); + + return false; + } + + function wpopDisableSpinner() { + $( '#panel-loader-positioning-wrap' ).fadeOut( 345 ); + } + + function wpopShowSpinner() { + $( '#panel-loader-positioning-wrap' ).fadeIn( 345 ); + } + + function handleInitHashSelection() { + if ( hash = window.location.hash ) { + $( hash + '-nav a' ).trigger( 'click' ); + } else { + $( '#wpopNav li:first a' ).trigger( 'click' ); + } + } + + function nixHashJumpJank() { + $( 'html, body' ).animate( { scrollTop: 0 } ); + } + + /* FIELDS JS */ + function initIrisColorSwatches() { + var colorPickers = $( '[data-part="color"]' ); + colorPickers.iris( { + width: 215, + hide: false, + border: false, + create: function() { + var currentValue = $( this ).attr( 'value' ); + if ( '' !== currentValue ) { + doColorFieldUpdate( $( this ).attr( 'name' ), $( this ).attr( 'value' ), new Color( $( this ).attr( 'value' ) ).getMaxContrastColor() ); + } + }, + change: function( event, ui ) { + doColorFieldUpdate( $( this ).attr( 'name' ), ui.color.toString(), new Color( ui.color.toString() ).getMaxContrastColor() ); + } + } ); + } + + function initSelectizeInputs() { + $( '[data-select]' ).selectize( { + allowEmptyOption: false, + placeholder: $( this ).attr( 'data-placeholder' ) + } ); + $( '[data-multiselect]' ).selectize( { + plugins: ["restore_on_backspace", "remove_button", "drag_drop", "optgroup_columns"] + } ); + } + + function doColorFieldUpdate( id, color, contrast ) { + $( '#' + id ).css( 'background-color', color ).css( 'color', contrast ); + } + + function doPwdFieldClear( elem, event ) { + event.preventDefault(); + $( elem ).prev().val( null ); + } + + function initMediaUploadField() { + $( '[data-part="media"]' ).each( function() { + if ( '' !== $( this ).attr( 'value' ) ) { + var closest = $( this ).closest( '.wpop-option' ); + wp.media.attachment( $( this ).attr( 'value' ) ).fetch().then( function( data ) { + closest.find( '.img-remove' ).after( mediaStats( data ) ); + } ); + } + } ); + } + + function doMediaUpload( elem, event ) { + event.preventDefault(); + var config = $( elem ).data(); + // Initialize the modal the first time. + if ( !wpModal ) { + wpModal = wp.media.frames.wpModal || wp.media( { + title: config.title, + button: { text: config.button }, + library: { type: 'image' }, // TODO: pass thru other media + multiple: false + } ); + + // Picking an image + wpModal.on( 'select', function() { + // Get the image URL + var image = wpModal.state().get( 'selection' ).first().toJSON(); + if ( 'object' === typeof image ) { + console.log( image ); + var closest = $( elem ).closest( '.wpop-option' ); + closest.find( '[type="hidden"]' ).val( image.id ); + closest.find( 'img' ).attr( 'src', image.sizes.thumbnail.url ).show(); + $( elem ).attr( 'value', 'Replace ' + $( elem ).attr( 'data-media-label' ) ); + closest.find( '.img-remove' ).show().after( mediaStats( image ) ); + } + } ); + } + + // Open the modal + wpModal.open(); + } + + function doMediaRemove( elem, event ) { + event.preventDefault(); + var remove = confirm( 'Remove ' + $( elem ).attr( 'data-media-label' ) + '?' ); + if ( remove ) { + var item = $( elem ).closest( '.wpop-option' ); + var blank = item.find( '.blank-img' ).html(); + item.find( '[type="hidden"]' ).val( null ); + item.find( 'img' ).attr( 'src', blank ); + item.find( '.button-hero' ).val( 'Set Image' ); + item.find( '.media-stats' ).remove(); + $( elem ).hide(); + } + } + +} ); \ No newline at end of file diff --git a/unminified-assets-v2/unminified.css b/unminified-assets-v3/styles.css old mode 100755 new mode 100644 similarity index 52% rename from unminified-assets-v2/unminified.css rename to unminified-assets-v3/styles.css index 5228b38..6de4515 --- a/unminified-assets-v2/unminified.css +++ b/unminified-assets-v3/styles.css @@ -1,3 +1,87 @@ +/*! + * WordPress CSS Spinner + * @license GPL-2.0+ + * @author kuus (http://kunderikuus.net) + */ +@-webkit-keyframes wp-core-spinner { + from { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes wp-core-spinner { + from { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +.wpcore-spin { + position: relative; + width: 20px; + height: 20px; + border-radius: 20px; + background: #A6A6A6; + -webkit-animation: wp-core-spinner 1.04s linear infinite; + animation: wp-core-spinner 1.04s linear infinite; +} + +.wpcore-spin:after { + content: ""; + position: absolute; + top: 2px; + left: 50%; + width: 4px; + height: 4px; + border-radius: 4px; + margin-left: -2px; + background: #fff; +} + +#panel-loader-positioning-wrap { + background: #fff; + display: flex; + align-items: center; + justify-content: center; + height: 100%; + min-height: 10vw; + position: absolute !important; + width: 99%; + max-width: 1600px; + z-index: 50; +} + +#panel-loader-box { + max-width: 50%; +} + +#panel-loader-box .wpcore-spin { + width: 60px; + height: 60px; + border-radius: 60px; +} + +#panel-loader-box .wpcore-spin:after { + top: 6px; + width: 12px; + height: 12px; + border-radius: 12px; + margin-left: -6px; +} + +.onOffSwitch-inner, .onOffSwitch-switch { + transition: all .5s cubic-bezier(1, 0, 0, 1) +} + .onOffSwitch { position: relative; width: 110px; @@ -23,12 +107,10 @@ input[type=checkbox].onOffSwitch-checkbox { .onOffSwitch-inner { display: block; width: 200%; - margin-left: -100%; - transition: all .5s cubic-bezier(1, 0, 0, 1); + margin-left: -100% } -.onOffSwitch-inner:after, -.onOffSwitch-inner:before { +.onOffSwitch-inner:after, .onOffSwitch-inner:before { display: block; float: left; width: 50%; @@ -66,8 +148,7 @@ input[type=checkbox].onOffSwitch-checkbox { bottom: 0; right: 66px; border: 2px solid #EEE; - border-radius: 20px; - transition: all .5s cubic-bezier(1, 0, 0, 1); + border-radius: 20px } .onOffSwitch-checkbox:checked + .onOffSwitch-label .onOffSwitch-inner { @@ -79,64 +160,38 @@ input[type=checkbox].onOffSwitch-checkbox { background-color: #D54E21 } -.wpop-loader-wrapper { - position: fixed; - top: 45%; - right: 45%; - z-index: 99999; - display: none +.radio-wrap { + float: right; + position: relative; + top: -1rem; } -.ball-clip-rotate-multiple { - position: relative +.cb, .save-all, .wpop-option.Color .iris-picker { + float: right; + position: relative; + top: -30px; } -.ball-clip-rotate-multiple > div { - position: absolute; - left: -20px; - top: -20px; - border: 3px solid #cd1713; - border-bottom-color: transparent; - border-top-color: transparent; - border-radius: 100%; - height: 35px; - width: 35px; - -webkit-animation: rotate .99s 0 ease-in-out infinite; - animation: rotate 1s 0 ease-in-out infinite -} - -.cb, -.save-all, -span.spacer { +.wpop-option .selectize-control.multi .selectize-input:after { + content: 'Select one or more options...'; +} + +li.wpop-option.Color input[type="text"] { + height: 50px; +} + +.cb, .cb-wrap, .desc:after, .pwd-clear, .save-all, span.menu-icon, span.spacer { position: relative } -.ball-clip-rotate-multiple > div:last-child { - display: inline-block; - top: -10px; - left: -10px; - width: 15px; - height: 15px; - -webkit-animation-duration: .33s; - animation-duration: .33s; - border-color: #cd1713 transparent; - -webkit-animation-direction: reverse; - animation-direction: reverse +.wpop-form { + margin-bottom: 0; } -@keyframes rotate { - 0% { - -webkit-transform: rotate(0) scale(1); - transform: rotate(0) scale(1) - } - 50% { - -webkit-transform: rotate(180deg) scale(.6); - transform: rotate(180deg) scale(.6) - } - 100% { - -webkit-transform: rotate(360deg) scale(1); - transform: rotate(360deg) scale(1) - } +#wpop { + max-width: 1600px; + margin: 0 auto 0 0 !important; + } #wpopMain { @@ -153,7 +208,7 @@ span.spacer { #wpopContent { background: #F1F1F1; - width: 100%!important; + width: 100% !important; border-top: 1px solid #D8D8D8 } @@ -169,15 +224,11 @@ span.spacer { max-width: 98.5% } -.pure-menu-disabled, -.pure-menu-heading, -.pure-menu-link { +.pure-menu-disabled, .pure-menu-heading, .pure-menu-link { padding: 1.3em 2em } -.pure-menu-active > .pure-menu-link, -.pure-menu-link:focus, -.pure-menu-link:hover { +.pure-menu-active > .pure-menu-link, .pure-menu-link:focus, .pure-menu-link:hover { background: inherit } @@ -186,6 +237,10 @@ span.spacer { max-height: 88px } +#wpopNav li.pure-menu-item { + height: 55px; +} + #wpopNav p.submit input { width: 100% } @@ -196,7 +251,11 @@ span.spacer { } .opn a.pure-menu-link { - color: #fff!important + color: #fff !important +} + +.pure-menu-link .part-count { + float: right; } .opn a.pure-menu-link:focus { @@ -214,11 +273,10 @@ span.spacer { } span.page-icon { - margin: 0 1.5vw 0 0 + margin: 0 1.5rem 0 0 } span.menu-icon { - position: relative; left: -.5rem } @@ -240,7 +298,7 @@ span.page-icon:before { .section h3 { margin: 0 0 10px; - padding: 2vw 1.5vw + padding: 2rem 1.5rem } .section h4.label { @@ -248,11 +306,15 @@ span.page-icon:before { display: table-cell; border: 1px solid #e9e9e9; background: #f1f1f1; - padding: .33vw .66vw .5vw; + padding: .33rem .66rem .5rem; font-weight: 500; font-size: 16px } +.section ul li:nth-child(even) h4.label { + background: #ddd; +} + .section li.wpop-option { margin: 1rem 1rem 1.25rem } @@ -279,7 +341,6 @@ input[disabled=disabled] { } .cb { - float: right; right: 20px } @@ -288,8 +349,8 @@ input[disabled=disabled] { } .fullwidth { - width: 100%!important; - max-width: 100%!important + width: 100% !important; + max-width: 100% !important } .wpop-head { @@ -297,11 +358,10 @@ input[disabled=disabled] { } .wpop-head > .inner { - padding: 1vw 1.5vw 0 + padding: 1rem 1.5rem 0 } .save-all { - float: right; top: -48px } @@ -310,27 +370,69 @@ input[disabled=disabled] { font-weight: 300; font-size: 12px; line-height: 16px; - transition: all 1s ease; color: #888; - -webkit-transition: all 1s ease; - -moz-transition: all 1s ease; - -o-transition: all 1s ease } .desc:after { display: block; - position: relative; width: 98%; border-top: 1px solid rgba(0, 0, 0, .1); border-bottom: 1px solid rgba(255, 255, 255, .3) } -.wpop-option input[type="text"] { +.wpop-option input[type=text], +.wpop-option input[type=url], +.wpop-option input[type=password], +.wpop-option input[type=email], +.wpop-option input[type=number], +.wpop-option input[type=range] { width: 90% } +.wpop-option input[data-part="color"] { + width: 25%; +} + +li[data-part="markdown"] { + padding: 1rem; +} + +li[data-part="markdown"] + span.spacer { + display: none; +} + +li[data-part="markdown"] p { + margin: 0 !important; +} + +li[data-part="markdown"] p, +li[data-part="markdown"] ul, +li[data-part="markdown"] ol { + font-size: 1rem; + line-height 1.05rem; +} + +[data-part="markdown"] h1 { + padding-top: 1.33rem; + padding-bottom: 0.33rem; +} + +[data-part="markdown"] h1:first-of-type { + padding-top: .33rem; + padding-bottom: 0.33rem; +} + +[data-part="markdown"] h1, +[data-part="markdown"] h2, +[data-part="markdown"] h3, +[data-part="markdown"] h4, +[data-part="markdown"] h5, +[data-part="markdown"] h6 { + padding-left: 0 !important; +} + input[data-assigned] { - width: 100%!important + width: 100% !important } .add-button { @@ -340,24 +442,28 @@ input[data-assigned] { text-align: center } +.media-stats { + margin-top: 0.66rem; +} + .img-preview { max-width: 320px; display: block; - margin: 0 0 1rem + margin: 0 0 1rem; + float: right; } .img-remove { - border: 2px solid #cd1713!important; - background: #f1f1f1!important; - color: #cd1713!important; + border: 2px solid #cd1713 !important; + background: #f1f1f1 !important; + color: #cd1713 !important; box-shadow: none; -webkit-box-shadow: none; - margin-left: 1rem!important + margin-left: 1rem !important } .pwd-clear { - margin-left: .5rem!important; - position: relative; + margin-left: .5rem !important; top: 1px } @@ -374,18 +480,13 @@ input[data-assigned] { margin-top: .5rem } -.wpop-option.color_picker input { +.wpop-option.color input { width: 50% } -.wpop-option.color_picker .iris-picker { - float: right -} - .cb-wrap { - position: relative; display: block; - right: 1.33vw; + right: 1.33rem; max-width: 110px; margin-left: auto; top: -1.66rem diff --git a/wordpress-phoenix-options-panel.php b/wordpress-phoenix-options-panel.php index 4a57639..de70a20 100755 --- a/wordpress-phoenix-options-panel.php +++ b/wordpress-phoenix-options-panel.php @@ -1,195 +1,346 @@ id = preg_replace( '/_/', '-', $i ); - $this->container = get_called_class(); - } + /** + * @var null|void - preset with WP Core Object ID from query param + * @see $this->maybe_capture_wp_object_id(); + */ + public $obj_id = null; - public function add_part( $part ) { - $part = $this->insert_parent_vars( $part ); - $this->parts[ $part->id ] = $part; - } + /** + * @var + */ + public $page_title; - public function insert_parent_vars( $part ) { - switch ( $this->get_clean_classname() ) { - case 'section': - case 'tab': - $parent = get_object_vars( $this ); - unset( $parent['parts'], $parent['notifications'], $parent['security_check'] ); - foreach ( $parent as $key => $val ) { - $name = 'section_' . $key; - $part->$name = $val; - } - break; - default: - $parent = get_object_vars( $this ); - if ( isset( $this->network_page ) || $parent->network_page ) { - $part->network_option = true; - } - break; - } + /** + * @var + */ + public $panel_object; - return $part; - } + /** + * @var int + */ + public $part_count = 0; - public function run_legacy_values_wipe() { - if ( ! isset( $_GET['wipe-defaults'] ) || ! isset( $_GET['page'] ) ) { - return false; - } + /** + * @var int + */ + public $section_count = 0; - $deleted_something = false; - foreach ( $this->parts as $section ) { - foreach ( $section->parts as $part ) { - if ( isset( $part->legacy_key ) && ! empty( $part->legacy_key ) ) { - $network = is_multisite() && is_network_admin() && is_super_admin(); - $delete_key = $network ? delete_site_option( $part->legacy_key ) : delete_option( $part->legacy_key ); - $deleted_something = ! $deleted_something && $delete_key ? $delete_key : false; - } - } + /** + * @var int + */ + public $data_count = 0; + + /** + * @var array used to track what happens during save process + */ + public $updated_counts = array( 'created' => 0, 'updated' => 0, 'deleted' => 0 ); + + /** + * Container constructor. + * + * @param array $args + * @param array $sections + */ + public function __construct( $args = [], $sections = [] ) { + global $pagenow; + if ( ! isset( $args['id'] ) ) { + echo "Setting a panel ID is required"; + exit; } - if ( $deleted_something ) { - $this->notifications['update'] = implode( - '', array( - '

', - __( 'Successfully deleted legacy options. Please remove the `legacy_key` parameter and any old field registration code.', 'wpop' ), - '

', - ) - ); + if ( ! defined( 'WPOP_ENCRYPTION_KEY' ) ) { + // IMPORTANT: If you don't define a key, the class hashes the AUTH_KEY found in wp-config.php, + // locking the encrypted value to the current environment. + $trimmed_key = substr( wp_salt(), 0, 15 ); + define( 'WPOP_ENCRYPTION_KEY', Password::pad_key( sha1( $trimmed_key, true ) ) ); } + // establish panel id + $this->id = preg_replace( '/_/', '-', $args['id'] ); - return $deleted_something; - } - - public function run_options_save_process() { - if ( ! isset( $_POST['submit'] ) - || ! is_string( $_POST['submit'] ) - || 'Save All' !== $_POST['submit'] - ) { - return false; // only run logic if submiting + // magic-set class object vars from array + foreach ( $args as $key => $val ) { + $this->$key = $val; } - if ( ! wp_verify_nonce( $_POST['_wpnonce'], $this->id ) ) { - return false; // check for nonce + + // establish data storage api + $this->api = $this->detect_data_api_and_permissions(); + + // maybe establish wordpress object id when api is one of the metadata APIs + $this->obj_id = $this->maybe_capture_wp_object_id(); + + // loop over sections + foreach ( $sections as $section_id => $section ) { + if ( isset( $section['parts'] ) ) { + $this->section_count ++; + // loop over current section's parts + foreach ( $section['parts'] as $part_id => $part_config ) { + if ( isset( $part_config['part'] ) ) { + $current_part_classname = __NAMESPACE__ . '\\' . ucfirst( $part_config['part'] ); + } + $current_part_classname = __NAMESPACE__ . '\\' . $part_config['part']; + $part_config['panel_id'] = $this->id; + $part_config['section_id'] = $section_id; + $part_config['panel_api'] = $this->api; + + // add part to panel/section + $this->add_part( + $section_id, + $section, + $current_part = new $current_part_classname( $part_id, $part_config ) + ); + $this->part_count ++; + if ( is_object( $current_part ) && $current_part->data_store ) { + $this->data_count ++; + if ( $current_part->updated ) { + if ( isset( $this->updated_counts[ $current_part->update_type ] ) ) { + $this->updated_counts[ $current_part->update_type ] ++; + } + } + } + + } + + $update_message = ''; + foreach ( $this->updated_counts as $count_type => $count ) { + $update_message .= $count . ' ' . ucfirst( $count_type ) . '. '; + } + + $this->notifications = [ 'notification' => $update_message ]; + } } + } - $any_updated = false; + public function __toString() { + return $this->id; + } - // note $_POST[ $part->id ] that taps the key's value from the submit array - foreach ( $this->parts as $section ) { - foreach ( $section->parts as $part ) { - // todo: cleanup, but shim to get passwords working - if ( isset( $part->password ) && $part->password ) { - $updated = $part->save_password( $this->network_page ); + /** + * Listen for query parameters denoting Post, User or Term object IDs for metadata api or network/site option apis + */ + public function detect_data_api_and_permissions() { + $error = null; + $api = null; + if ( isset( $_GET['page'] ) ) { + if ( isset( $_GET['post'] ) && is_numeric( $_GET['post'] ) ) { + $api = 'post'; + $this->page_title = $this->page_title . ' for ' . get_the_title( $_GET['post'] ); + $this->panel_object = get_post( $_GET['post'] ); + } elseif ( isset( $_GET['user'] ) && is_numeric( $_GET['user'] ) ) { + if ( is_multisite() && is_network_admin() ) { + $api = 'user-network'; } else { - $updated = $this->do_options_save( $part->id, $_POST[ $part->id ], $this->network_page ); + $api = 'user'; } - $any_updated = ( $updated && ! $any_updated ) ? true : $any_updated; + $this->page_title = esc_attr( $this->page_title ) . ' for ' . esc_attr( get_the_author_meta( 'display_name', absint( $_GET['user'] ) ) ); + $this->panel_object = get_user_by( 'id', absint( $_GET['user'] ) ); + } elseif ( isset( $_GET['term'] ) && is_numeric( $_GET['term'] ) ) { + $api = 'term'; + $term = get_term( $_GET['term'] ); + if ( is_object( $term ) && ! is_wp_error( $term ) && isset( $term->name ) ) { + $this->page_title = esc_attr( $this->page_title ) . ' for ' . esc_attr( $term->name ); + $this->panel_object = $term; + } + } elseif ( is_multisite() && is_network_admin() ) { + $api = 'network'; + } else { + $api = 'site'; } + } else { + $api = ''; } - if ( $any_updated ) { - $this->notifications['update'] = implode( - '', array( - '

', - __( 'Some options were saved!', 'wpop' ), - '

', - ) - ); + // allow api auto detection if 'api' not set in config array, but if its set and doesn't match then ignore and + // use config value for safety + // (tl;dr - will ignore &term=1 param on a site options panel when 'api' is defined to prevent accidental API + // override) + if ( isset( $this->api ) && $api !== $this->api ) { + return $this->api; } - return $any_updated; + return $api; } - public function do_options_save( $key, $value, $network = false, $obj_id = null ) { - if ( ! empty( $obj_id ) && absint( $obj_id ) ) { - return false; // TODO: build term meta API saving for multisite - } - switch ( $network ) { - case true: - return ! empty( $value ) ? update_site_option( $key, $value ) : delete_site_option( $key ); + /** + * @return int|null + */ + public function maybe_capture_wp_object_id() { + switch ( $this->api ) { + case 'post': + return absint( $_GET['post'] ); + break; + case 'user': + return absint( $_GET['user'] ); + break; + case 'term': + return absint( $_GET['term'] ); break; - case false: default: - return ! empty( $value ) ? update_option( $key, $value ) : delete_option( $key ); + return null; break; } } + /** + * Old external developer method used to add parts (sections/fields/markup/etc) to a Panel + * + * Now used internally, but still available public + * + * @param $section_id + * @param $section + * @param $part object - one of the part classes from this file + */ + public function add_part( $section_id, $section, $part ) { + if ( ! isset( $this->parts[ $section_id ] ) ) { + $this->parts[ $section_id ] = $section; + $this->parts[ $section_id ]['parts'] = array(); + } + + array_push( $this->parts[ $section_id ]['parts'], $part ); + } + + /** + * Print WordPress Admin Notifications + * @example $note_data = array( 'notification' => 'My text', 'type' => 'notice-success' ) + */ public function echo_notifications() { - do_action( 'wpop_after_option_save', $this ); - foreach ( $this->notifications as $notify_html ) { - echo $notify_html; + foreach ( $this->notifications as $note_data ) { + $data = is_array( $note_data ) ? $note_data : [ 'notification' => $note_data ]; + $data['type'] = isset( $data['type'] ) ? $data['type'] : 'notice-success'; + echo HTML::tag( + 'div', + [ 'class' => 'notice ' . $data['type'] ], + HTML::tag( 'p', [], $data['notification'] ) + ); } } + /** + * Get class name without versioned namespace. + * + * @return string + */ public function get_clean_classname() { - return explode( '\\', get_called_class() )[2]; // get class name w/o versioned-NS + return strtolower( explode( '\\', get_called_class() )[2] ); } -} +} // END Container -class Page extends Container { +/** + * Class Page + * @package WPOP\V_3_0 + */ +class Page extends Panel { + + /** + * @var string + */ + public $parent_page_id = ''; + /** + * @var string + */ public $page_title = 'Custom Site Options'; + + /** + * @var string + */ public $menu_title = 'Custom Site Options'; - public $capability = 'manage_options'; + + /** + * @var + */ public $dashicon; + + /** + * @var bool + */ public $disable_styles = false; - public $theme_page = false; - public function __construct( $args = [] ) { - parent::__construct( $args['id'] ); - foreach ( $args as $key => $val ) { - $this->$key = $val; - } + + public $initialized = false; + + /** + * Page constructor. + * + * @param array $args + * @param array $fields + */ + public function __construct( $args = [], $fields ) { + parent::__construct( $args, $fields ); } + /** + * !!! USE ME TO RUN THE PANEL !!! + * + * Main method called by extending class to initialize the panel + */ public function initialize_panel() { - $decide_network_or_single_site_admin = $this->network_page ? 'network_admin_menu' : 'admin_menu'; - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_dependencies' ) ); - add_action( $decide_network_or_single_site_admin, array( $this, 'add_settings_submenu_page' ) ); - add_action( 'admin_init', array( $this, 'run_options_save_process' ) ); - add_action( 'admin_init', array( $this, 'run_legacy_values_wipe' ) ); + if ( ! empty( $this->api ) && is_string( $this->api ) ) { + $dashboard = 'admin_menu'; + if ( 'network' === $this->api || 'user-network' === $this->api ) { + $dashboard = 'network_admin_menu'; + } + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_dependencies' ) ); + add_action( $dashboard, array( $this, 'add_settings_submenu_page' ) ); + } } + /** + * Register Submenu Page with WordPress to display the panel on + */ public function add_settings_submenu_page() { add_submenu_page( - $this->theme_page ? 'themes.php' : $this->parent_id, // file.php to hook into + $this->parent_page_id, // file.php to hook into $this->page_title, $this->menu_title, $this->capability, @@ -198,8 +349,22 @@ public function add_settings_submenu_page() { ); } + + /** + * + */ public function build_parts() { - $dashicon = ! empty( $this->dashicon ) ? ' ' : ''; + $page_icon = ! empty( $this->dashicon ) ? HTML::dashicon( $this->dashicon . ' page-icon' ) . ' ' : ''; + $screen = get_current_screen(); + $screen_id = $screen->id; + add_action( 'admin_print_footer_scripts-' . $screen_id, function () { + $this->footer_scripts(); + } ); + if ( 'site' !== $this->api && 'network' !== $this->api && ! is_object( $this->panel_object ) ) { + echo '

Please select a ' . $this->api . '.

'; + echo '?' . $this->api . '=ID'; + exit; + } ob_start(); ?>
inline_styles_and_scripts(); } ?> +
-

+

-
+
+
+
+
+
+
-

page_title; ?>

+ page_title ); ?>
-
-
-
-
-
-
    parts as $key => $part ) { - $dashicon = ! empty( $part->dashicon ) ? ' ' : ''; - echo '
  • ' . $dashicon . $part->title - . '
  • '; + foreach ( $this->parts as $section_id => $section ) { + $section_icon = ! empty( $section['dashicon'] ) ? + HTML::dashicon( $section['dashicon'] . ' menu-icon' ) : ''; + $pcount = count( $section['parts'] ) > 1 ? HTML::tag( 'small', [ 'class' => 'part-count' ], count( $section['parts'] ) ) : ''; + echo HTML::tag( + 'li', + [ + 'id' => $section_id . '-nav', + 'class' => 'pure-menu-item', + ], + HTML::tag( + 'a', + [ + 'href' => '#' . $section_id, + 'class' => 'pure-menu-link', + ], + $section_icon . $section['label'] . $pcount ) + ); } ?>
@@ -252,8 +429,9 @@ class="button button-primary button-hero save-all"
    parts as $key => $part ) { - $part->echo_html(); + foreach ( $this->parts as $section_key => $section ) { + $built_section = new Section( $section_key, $section ); + $built_section->echo_html(); } ?>
@@ -261,7 +439,16 @@ class="button button-primary button-hero save-all"
- Stored in: get_storage_table(); ?> +
    +
  • + Sections: section_count ); ?>
  • +
  • Total Data + Parts: data_count ); ?>
  • +
  • Total + Parts: part_count ); ?>
  • +
  • Stored + in: get_storage_table() ); ?>
  • +
@@ -270,13 +457,7 @@ class="button button-primary button-hero save-all"
- theme_page ? 'themes.php' : $this->parent_id; ?> - +
@@ -288,529 +469,99 @@ class="button button-primary button-hero save-all" echo ob_get_clean(); } + /** + * + */ public function inline_styles_and_scripts() { ob_start(); ?> + + + api ) { + case 'post': + return $wpdb->prefix . 'postmeta'; + break; + case 'term': + return $wpdb->prefix . 'termmeta'; + break; + case 'user': + return is_multisite() ? $wpdb->base_prefix . 'usermeta' : $wpdb->prefix . 'usermeta'; + break; + case 'network': + return $wpdb->prefix . 'sitemeta'; + break; + case 'site': + default: + return $wpdb->prefix . 'options'; + break; + } + } + + /** + * + */ public function enqueue_dependencies() { $unpkg = 'https://unpkg.com/purecss@1.0.0/build/'; wp_register_style( 'wpop-pure-base', $unpkg . 'base-min.css' ); @@ -823,7 +574,8 @@ public function enqueue_dependencies() { // Enqueue media (needed for media modal) wp_enqueue_media(); - wp_enqueue_script( 'iris' ); // core color picker + wp_enqueue_script( array( 'iris', 'wp-util', 'wp-shortcode' ) ); + $selectize_cdn = 'https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.4/'; wp_register_script( 'wpop-selectize', $selectize_cdn . 'js/standalone/selectize.min.js', array( 'jquery-ui-sortable' ) ); wp_enqueue_script( 'wpop-selectize' ); @@ -832,273 +584,350 @@ public function enqueue_dependencies() { wp_register_script( 'clipboard', 'https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.7.1/clipboard.min.js' ); wp_enqueue_script( 'clipboard' ); } +} - public function get_storage_table() { - switch ( is_multisite() ) { - case true: - return $this->network_page ? 'wp_sitemeta' : $this->get_multisite_table( get_current_blog_id() ); - break; - case false: - default: - return 'wp_options'; - break; - } - } +/** + * Class Section + * @package WPOP\V_3_0 + */ +class Section { - public function get_multisite_table( $blog_id ) { - return 1 === intval( $blog_id ) ? 'wp_options' : 'wp_' . $blog_id . '_options'; - } -} + /** + * @var + */ + public $id; -class Section extends Container { + /** + * @var array + */ + public $classes = array( 'section' ); + + /** + * @var string + */ + public $label = 'My Custom Section'; - public $wrapper = array( '
    ', '
' ); - public $classes = array( 'section', 'active' ); - public $title = 'My Custom Section'; + /** + * @var + */ public $dashicon; - public function __construct( $i, $args = [] ) { - parent::__construct( $i ); + /** + * @var + */ + protected $parts; + + /** + * Section constructor. + * + * @param string $id + * @param array $args + */ + public function __construct( $id, $args = [] ) { + $this->id = $id; foreach ( $args as $name => $value ) { $this->$name = $value; } } - private function get_classes() { - return ! empty( $this->classes ) ? 'class="' . implode( ' ', $this->classes ) . '"' : null; - } - + /** + * Print Panel Markup + */ public function echo_html() { ob_start(); - echo '
  • get_classes() . '>' . $this->wrapper[0]; - foreach ( $this->parts as $part ) { - // if ( $this->rollup_options ) { - // $part->field_id = $this->id . '[' . $part->id . ']'; - // } - echo $part->get_html(); + + $section_content = ''; + + foreach ( $this->parts as $part ) { // parts are wrapped in
  • 's + $section_content .= $part->get_html() . HTML::tag( 'span', [ 'class' => 'spacer' ] ); } - echo $this->wrapper[1] . '
  • '; - echo apply_filters( 'echo_html_option', ob_get_clean() ); + + echo HTML::tag( 'li', [ + 'id' => $this->id, + 'class' => implode( ' ', $this->classes ) + ], HTML::tag( 'ul', [], $section_content ) ); + + echo ob_get_clean(); } } -class Option { +/** + * Class Part + * @package WPOP\V_3_0 + */ +class Part { public $id; public $field_id; + public $saved; public $part_type = 'option'; public $label = 'Option'; public $description = ''; public $default_value = ''; - public $classes = array( 'option' ); - public $atts = array( 'disabled' => null ); - public $wrapper; + public $classes = array(); + public $atts = []; + public $data_store = false; public $field_before = null; public $field_after = null; - public $network_option = false; + public $panel_api = false; + public $panel_id = false; + public $update_type = ''; public function __construct( $i, $args = [] ) { $this->id = $i; $this->field_id = $this->id; - $this->wrapper = array( - '
  • ', - '
  • ', - ); + foreach ( $args as $name => $value ) { $this->$name = $value; } - } - public function html_process_atts( $atts ) { - $att_markup = []; - foreach ( $atts as $key => $att ) { - if ( false === empty( $att ) ) { - $att_markup[] = sprintf( '%s="%s"', $key, $att ); + if ( $this->data_store ) { + $old_value = $this->get_saved(); + $this->updated = $this->run_save_process(); + $this->saved = $this->get_saved(); + if ( empty( $old_value ) && $this->updated && ! empty( $this->saved ) ) { + $this->update_type = 'created'; + } elseif ( ! empty( $old_value ) && $this->updated && ! empty( $this->saved ) + && ( $old_value !== $this->saved ) + ) { + $this->update_type = 'updated'; + } elseif ( ! empty( $old_value ) && $this->updated && empty( $this->saved ) ) { + $this->update_type = 'deleted'; } } - - return implode( ' ', $att_markup ); } - public function get_classes( $class_str = '' ) { - $maybe_classes = ! empty( $this->classes ) ? implode( ' ', $this->classes ) : null; - $clean_return = ( ! empty( $maybe_classes ) || ! empty( $passed_str_classes ) ) ? 'class="' . $maybe_classes . $class_str . '"' : null; - - return $clean_return; + public function get_clean_classname() { + return explode( '\\', get_called_class() )[2]; } public function build_base_markup( $field ) { - ob_start(); - echo $this->wrapper[0] . '

    ' . $this->label . '

    '; - echo $this->field_before . $field . $this->field_after; - echo ( $this->description ) ? '
    ' . $this->description . '
    ' : ''; - echo '
    ' . $this->wrapper[1]; + $desc = ( $this->description ) ? HTML::tag( 'div', [ 'class' => 'desc clear' ], $this->description ) : ''; - return ob_get_clean(); + return HTML::tag( + 'li', + [ 'class' => 'wpop-option ' . strtolower( $this->get_clean_classname() ), 'data-part' => $this->id ], + HTML::tag( 'h4', [ 'class' => 'label' ], $this->label ) . $this->field_before . $field . $this->field_after + . $desc . HTML::tag( 'div', [ 'class' => 'clear' ] ) + ); } - public function get_saved() { - $network = is_multisite() && is_network_admin(); - $pre_ = apply_filters( 'wpop_custom_option_enabled', false ) ? SM_SITEOP_PREFIX : ''; + public function run_save_process() { + if ( ! isset( $_POST['submit'] ) + || ! is_string( $_POST['submit'] ) + || 'Save All' !== $_POST['submit'] + ) { + return false; // only run logic if submiting + } + if ( ! wp_verify_nonce( $_POST['_wpnonce'], $this->panel_id ) ) { + return false; // check for nonce + } - if ( $network ) { - return get_site_option( $pre_ . $this->id, $this->get_legacy_value() ); - } else { - return get_option( $pre_ . $this->id, $this->get_legacy_value() ); + $type = ( ! empty( $this->field_type ) ) ? $this->field_type : $this->input_type; + + $field_input = isset( $_POST[ $this->id ] ) ? $_POST[ $this->id ] : false; + + $sanitize_input = $this->sanitize_data_input( $type, $this->id, $field_input ); + + $updated = new Save_Single_Field( + $this->panel_id, // used to check nonce + $this->panel_api, // doing this way to allow multi-api saving from single panel down-the-road + $this->id, // this is the data storage key in the database + $sanitize_input, // sanitized input (maybe empty, triggering delete) + isset( $this->obj_id ) ? $this->obj_id : null // maybe an object ID needed for metadata API + ); + + if ( $updated ) { + return $this->id; } - } - public function get_legacy_value() { - $network = is_multisite() && is_network_admin(); - if ( isset( $this->legacy_key ) - && ! empty( $this->legacy_key ) - && isset( $this->input_type ) - && 'password' === $this->input_type - ) { - $legacy_pwd = isset( $this->legacy_pwd ) ? $this->legacy_pwd : false; - $stored = $network ? get_site_option( $this->legacy_key ) : get_option( $this->legacy_key ); + return false; + } - if ( $legacy_pwd && ! empty( $stored ) ) { - return $stored; + public function get_saved() { + $pre_ = apply_filters( 'wpop_custom_option_enabled', false ) ? SM_SITEOP_PREFIX : ''; - } elseif ( false === $legacy_pwd && ! empty( $stored ) ) { - return base64_encode( mcrypt_encrypt( MCRYPT_RIJNDAEL_256, WPOP_ENCRYPTION_KEY, $stored, MCRYPT_MODE_ECB ) ); - } else { - return false; - } - } elseif ( isset( $this->legacy_key ) - && ! empty( $this->legacy_key ) - ) { - return $network ? get_site_option( $this->legacy_key ) : get_option( $this->legacy_key ); - } else { - return false; + switch ( $this->panel_api ) { + case 'post': + $obj_id = sanitize_text_field( $_GET['post'] ); + break; + case 'term': + $obj_id = sanitize_text_field( $_GET['term'] ); + break; + case 'user': + case 'user-network': + $obj_id = sanitize_text_field( $_GET['user'] ); + break; + case 'network': + case 'site': + default: + $obj_id = null; + break; } - } - public function get_clean_classname() { - return explode( '\\', get_called_class() )[2]; - } + $response = new Get_Single_Field( + $this->panel_id, + $this->panel_api, + $pre_ . $this->id, + $this->default_value, + $obj_id + ); -} + return $response->response; + } -class Section_Desc extends Option { + protected function sanitize_data_input( $input_type, $id, $value ) { + switch ( $input_type ) { + case 'password': + if ( $_POST[ 'stored_' . $id ] === $value && ! empty( $value ) ) { + return '### wpop-encrypted-pwd-field-val-unchanged ###'; + } - public function get_html() { - ob_start(); - echo $this->wrapper[0]; - echo $this->description; - echo $this->wrapper[1]; - echo ''; + return ! empty( $value ) ? Password::encrypt( $value ) : false; + break; + case 'media': + return absint( $value ); + break; + case 'color': + return sanitize_hex_color_no_hash( $value ); + break; + case 'editor': + return wp_filter_post_kses( $value ); + break; + case 'textarea': + return sanitize_textarea_field( $value ); + break; + case 'checkbox': + case 'toggle_switch': + return sanitize_key( $value ); + break; + case 'multiselect': + if ( ! empty( $value ) && is_array( $value ) ) { + return json_encode( array_map( 'sanitize_key', $value ) ); + } - return ob_get_clean(); + return false; + break; + case 'email': + return sanitize_email( $value ); + break; + case 'url': + return esc_url_raw( $value ); + break; + case 'text': + default: + return sanitize_text_field( $value ); + break; + } } } -class Input extends Option { +/** + * Class Input + * @package WPOP\V_3_0 + */ +class Input extends Part { public $input_type; - public $password = false; + public $data_store = true; public function get_html() { - $option_val = ( false === $this->get_saved() || empty( $this->get_saved() ) ) ? $this->default_value : $this->get_saved(); + $option_val = ( false === $this->saved || empty( $this->saved ) ) ? $this->default_value : $this->saved; $type = ! empty( $this->input_type ) ? $this->input_type : 'hidden'; - ob_start(); - echo 'get_classes() . ' ' . $this->html_process_atts( $this->atts ) . ' />'; - return $this->build_base_markup( ob_get_clean() ); + $input = [ + 'id' => $this->field_id, + 'name' => $this->field_id, + 'type' => $type, + 'value' => $option_val, + 'data-part' => strtolower( $this->get_clean_classname() ), + 'autocomplete' => 'false', // prevents pwd field autofilling, among other things + ]; + + if ( ! empty( $this->classes ) ) { + $input['classes'] = implode( ' ', $this->classes ); + } + + if ( ! empty( $this->atts ) ) { + foreach ( $this->atts as $key => $val ) { + $input[ $key ] = $val; + } + } + + return $this->build_base_markup( HTML::tag( 'input', $input ) ); } } +/** + * Class Text + * @package WPOP\V_3_0 + */ class Text extends Input { public $input_type = 'text'; } -class Color_Picker extends Input { +/** + * Class Color + * @package WPOP\V_3_0 + */ +class Color extends Input { public $input_type = 'text'; + public $field_type = 'color'; } +/** + * Class Number + * @package WPOP\V_3_0 + */ class Number extends Input { public $input_type = 'number'; } -class Url extends Input { - public $input_type = 'url'; +/** + * Class Email + * @package WPOP\V_3_0 + */ +class Email extends Input { + public $input_type = 'email'; } -class Hidden extends Input { - public $input_type = 'hidden'; +/** + * Class Url + * @package WPOP\V_3_0 + */ +class Url extends Input { + public $input_type = 'url'; } /** * Class password * @package WPOP\V_2_8 - * @notes how to use: echo $this->decrypt( get_option( $this->id ) ); + * @notes how to use: echo $this->decrypt( get_option( $this->id ) ); */ class Password extends Input { public $input_type = 'password'; public function __construct( $i, $args = [] ) { parent::__construct( $i, $args ); - $this->field_after = $this->pwd_clear_and_hidden_field(); - $this->password = true; - if ( ! defined( 'WPOP_ENCRYPTION_KEY' ) ) { - // IMPORTANT: If you don't define a key, the class hashes the AUTH_KEY found in wp-config.php, - // effectively locking the encrypted value to the current environment. - $trimmed_key = substr( wp_salt(), 0, 15 ); - define( 'WPOP_ENCRYPTION_KEY', static::pad_key( sha1( $trimmed_key, true ) ) ); - } - } - - public function pwd_clear_and_hidden_field() { - if ( isset( $this->legacy_key ) - && ! empty( $this->get_legacy_value() ) - && $this->get_saved() === $this->get_legacy_value() - && ! isset( $this->legacy_pwd ) - ) { - $hidden_val = ''; // when importing legacy - } else { - $hidden_val = $this->get_saved(); - } - ob_start(); - echo 'clear'; - echo ''; - - return ob_get_clean(); - } - public static function decrypt( $encrypted_encoded ) { - // Only call in server actions -- never use to print in markup or risk theft - return trim( mcrypt_decrypt( MCRYPT_RIJNDAEL_256, WPOP_ENCRYPTION_KEY, base64_decode( $encrypted_encoded ), MCRYPT_MODE_ECB ) ); + $this->field_after = $this->pwd_clear_and_hidden_field(); } - public function save_password( $network = false ) { - // overriding default - if ( $_POST[ $this->id ] === $_POST[ 'stored_' . $this->id ] ) { - return false; - } - - $pre_ = apply_filters( 'wpop_custom_option_enabled', false ) ? SM_SITEOP_PREFIX : ''; - - if ( empty( $_POST[ $this->id ] ) ) { - return $network ? delete_site_option( $pre_ . $this->id ) : delete_option( $pre_ . $this->id ); - } elseif ( isset( $this->legacy_key ) && ! empty( $this->get_legacy_value() ) ) { - return $network ? update_site_option( $pre_ . $this->id, $_POST[ $this->id ] ) : update_option( $pre_ . $this->id, $_POST[ $this->id ] ); - } else { - $encrypted = mcrypt_encrypt( MCRYPT_RIJNDAEL_256, WPOP_ENCRYPTION_KEY, $_POST[ $this->id ], MCRYPT_MODE_ECB ); - $base64_encrypted = base64_encode( $encrypted ); // base64 is req'd. Without, storing encryption in option value isn't reliable cross-env. - - return $network ? update_site_option( $pre_ . $this->id, $base64_encrypted ) : update_option( $pre_ . $this->id, $base64_encrypted ); - } - + protected function pwd_clear_and_hidden_field() { + return HTML::tag( 'a', [ 'href' => '#', 'class' => 'button button-secondary pwd-clear' ], 'clear' ) . + HTML::tag( 'input', [ + 'id' => 'stored_' . $this->id, + 'name' => 'stored_' . $this->id, + 'type' => 'hidden', + 'value' => $this->saved, + 'autocomplete' => 'off', + ] ); } /** - * Fixes PHP7 issues where mcrypt_decrypt expects a specific key size. Used on MYSECRETKEY constant. + * Fixes PHP7 issues where mcrypt_decrypt expects a specific key size. Used on WPOP_ENCRYPTION_KEY constant. * You'll still have to run trim on the end result when decrypting,as seen in the "unencrypted_pass" function. * * @see http://stackoverflow.com/questions/27254432/mcrypt-decrypt-error-change-key-size @@ -1126,53 +955,117 @@ static function pad_key( $key ) { return $key; } + + /** + * Field is encrypted using 256-bit encryption using mcrypt and then run through base64 for db env parity/safety + * + * @param $unencrypted_string + * + * @return string + */ + public static function encrypt( $unencrypted_string ) { + return base64_encode( + mcrypt_encrypt( + MCRYPT_RIJNDAEL_256, + WPOP_ENCRYPTION_KEY, + $unencrypted_string, + MCRYPT_MODE_ECB + ) + ); + } + + /** + * 📢 ⚠️ NEVER USE TO PRINT IN MARKUP, IN INPUT VALUES -- ONLY CALL IN SERVER-SIDE ACTIONS OR RISK THEFT ⚠️ 📢 + * + * Field is base64 decoded, then decrypted using mcrypt, then trimmed of any excess characters left from transforms + * + * @param $encrypted_encoded + * + * @return string + */ + public static function decrypt( $encrypted_encoded ) { + return trim( + mcrypt_decrypt( + MCRYPT_RIJNDAEL_256, + WPOP_ENCRYPTION_KEY, + base64_decode( $encrypted_encoded ), + MCRYPT_MODE_ECB + ) + ); + } } -class Textarea extends Option { +/** + * Class Textarea + * @package WPOP\V_3_0 + */ +class Textarea extends Part { public $cols; public $rows; + public $input_type = 'textarea'; + public $data_store = true; + /** + * @return string + */ public function get_html() { - $option_val = $this->get_saved(); - $att_markup = $this->html_process_atts( $this->atts ); $this->cols = ! empty( $this->cols ) ? $this->cols : 80; $this->rows = ! empty( $this->rows ) ? $this->rows : 10; - ob_start(); - echo ''; + $field = [ 'id' => $this->id, 'name' => $this->id, 'cols' => $this->cols, 'rows' => $this->rows ]; - return $this->build_base_markup( ob_get_clean() ); + if ( ! empty( $this->atts ) && is_array( $this->atts ) ) { + foreach ( $this->atts as $key => $val ) { + $field[ $key ] = $val; + } + } + + return $this->build_base_markup( HTML::tag( 'textarea', $field, stripslashes( $this->get_saved() ) ) ); } } -class Editor extends Option { +/** + * Class Editor + * @package WPOP\V_3_0 + */ +class Editor extends Part { + + public $input_type = 'editor'; + public $data_store = true; + public function get_html() { - $option_val = $this->get_saved(); + ob_start(); wp_editor( - stripslashes( $option_val ), + stripslashes( $this->get_saved() ), $this->id . '_editor', array( - 'textarea_name' => $this->id, // used for saving val - 'drag_drop_upload' => false, // no work if multiple - 'tinymce' => array( 'min_height' => 300 ), - 'editor_class' => 'edit', - 'quicktags' => true, + 'textarea_name' => $this->id, // used for saving value + 'tinymce' => array( 'min_height' => 300 ), + 'editor_class' => 'edit', + 'quicktags' => isset( $this->no_quicktags ) ? false : true, + 'teeny' => isset( $this->teeny ) ? true : false, + 'media_buttons' => isset( $this->no_media ) ? false : true ) ); - return $this->build_base_markup( ob_get_clean() ); + return $this->build_base_markup( ob_get_clean() ); // no return param in wp_editor so buffer it is ¯\_//(ツ)_/¯ } } -class Select extends Option { +/** + * Class Select + * @package WPOP\V_3_0 + */ +class Select extends Part { public $values; public $meta; public $empty_default = true; + public $input_type = 'select'; + public $data_store = true; public function __construct( $i, $m ) { parent::__construct( $i, $m ); @@ -1181,32 +1074,51 @@ public function __construct( $i, $m ) { } public function get_html() { - $option_val = $this->get_saved(); $default_option = isset( $this->meta['option_default'] ) ? $this->meta['option_default'] : 'Select an option'; ob_start(); - echo ''; - return $this->build_base_markup( ob_get_clean() ); + return $this->build_base_markup( + HTML::tag( + 'select', + [ + 'id' => $this->id, + 'name' => $this->id, + 'data-select' => true, + 'data-placeholder' => $default_option + ], + ob_get_clean() + ) + ); } } -class Multiselect extends Option { +/** + * Class Multiselect + * @package WPOP\V_3_0 + */ +class Multiselect extends Part { public $values; public $meta; public $allow_reordering = false; public $create_options = false; + public $input_type = 'multiselect'; + public $data_store = true; + public function __construct( $i, $m ) { parent::__construct( $i, $m ); @@ -1215,18 +1127,33 @@ public function __construct( $i, $m ) { } public function get_html() { - $save = $this->get_saved(); - ob_start(); - echo ''; ?> - build_base_markup( ob_get_clean() ); + if ( ! empty( $this->values ) && is_array( $this->values ) ) { + foreach ( $this->values as $key => $value ) { + $opts_markup .= HTML::tag( 'option', [ 'value' => $key ], $value ); + } + } + + return $this->build_base_markup( HTML::tag( 'select', [ + 'id' => $this->id, + 'name' => $this->id . '[]', + 'multiple' => 'multiple', + 'data-multiselect' => '1' + ], $opts_markup + ) ); } function multi_atts( $pairs, $atts ) { @@ -1240,10 +1167,17 @@ function multi_atts( $pairs, $atts ) { } -class Checkbox extends Option { +/** + * Class Checkbox + * @package WPOP\V_3_0 + */ +class Checkbox extends Part { - public $value; + public $value = 'on'; public $label_markup; + public $input_type = 'checkbox'; + public $data_store = true; + public function __construct( $i, $args = [] ) { parent::__construct( $i, $args ); @@ -1253,59 +1187,103 @@ public function __construct( $i, $args = [] ) { } public function get_html() { - $checked = ( $this->get_saved() === $this->value ) ? ' checked="checked"' : ''; $classes = ! empty( $this->label_markup ) ? 'onOffSwitch-checkbox' : 'cb'; - ob_start(); - echo '
    ' . $this->label_markup . '
    '; + $input = [ 'type' => 'checkbox', 'id' => $this->id, 'name' => $this->id, 'class' => $classes ]; + if ( $this->get_saved() === $this->value ) { + $input['checked'] = 'checked'; + } - return $this->build_base_markup( ob_get_clean() ); + return $this->build_base_markup( + HTML::tag( 'div', [ 'class' => 'cb-wrap' ], HTML::tag( 'input', $input ) . $this->label_markup ) + ); } } +/** + * Class Toggle_Switch + * @package WPOP\V_3_0 + */ class Toggle_Switch extends Checkbox { + public $input_type = 'toggle_switch'; + + /** + * Toggle_Switch constructor. + * + * @param string $i + * @param array $args + */ function __construct( $i, array $args = [] ) { parent::__construct( $i, $args ); - $this->label_markup = ''; + $this->label_markup = HTML::tag( + 'label', + [ 'class' => 'onOffSwitch-label', 'for' => $this->id ], + '
    ' + ); } } -class Radio_Buttons extends Option { +/** + * Class Radio_Buttons + * @package WPOP\V_3_0 + */ +class Radio_Buttons extends Part { public $values; - public $default_value; + public $default_value = ''; + public $input_type = 'radio_buttons'; + public $data_store = true; public function __construct( $i, $c ) { parent::__construct( $i, $c ); - $this->values = ( ! empty( $c['values'] ) ) ? $c['values'] : []; - $this->default_value = ! empty( $this->default_value ) ? $this->default_value : ''; + $this->values = ( ! empty( $c['values'] ) ) ? $c['values'] : []; } public function get_html() { - ob_start(); - echo '
    '; + $table_body = ''; foreach ( $this->values as $key => $value ) { $selected_val = $this->get_saved() ? $this->get_saved() : $this->default_value; - $checked = ( $selected_val === $value ) ? ' checked="checked"' : ''; - $echo = ! is_numeric( $key ) ? $key : $value; - echo ''; - echo '
    '; + + $input = [ + 'type' => 'radio', + 'id' => $this->id . '_' . $key, + 'name' => $this->field_id, + 'value' => $value, + 'class' => 'radio-item' + ]; + + if ( $selected_val === $value ) { + $input['checked'] = 'checked'; + } + + $label = HTML::tag( 'td', [], HTML::tag( 'label', [ + 'class' => 'opt-label', + 'for' => $this->id . '_' . $key + ], $value ) + ); + $input_mark = HTML::tag( 'td', [], HTML::tag( 'input', $input ) ); + + $table_body .= HTML::tag( 'tr', [], $label . $input_mark ); } - echo '
    '; - return $this->build_base_markup( ob_get_clean() ); + $table = HTML::tag( 'table', [ 'class' => 'widefat striped' ], $table_body ); + + return $this->build_base_markup( HTML::tag( 'div', [ 'class' => 'radio-wrap' ], $table ) ); } } -class Media extends Option { +/** + * Class Media + * @package WPOP\V_3_0 + */ +class Media extends Part { public $media_label = 'Image'; + public $input_type = 'media'; + public $data_store = true; public function get_html() { - $empty = 'data:image/png;base64,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'; + $empty = ''; // TODO: REPLACE EMPTY IMAGE WITH CSS YO $saved = array( 'url' => $empty, 'id' => '' ); $option_val = $this->get_saved(); $insert_label = 'Insert ' . $this->media_label; @@ -1314,33 +1292,61 @@ public function get_html() { $saved = array( 'url' => is_array( $img ) ? $img[0] : 'err', 'id' => $option_val ); $insert_label = 'Replace ' . $this->media_label; } - $vis = empty( $option_val ) ? ' style="display:none;"' : ''; - $att_markup = $this->html_process_atts( $this->atts ); ob_start(); echo ''; - echo ''; - echo ''; - echo ''; - echo 'Remove ' . $this->media_label . ''; + + $image_btn = [ + 'id' => $this->id . '_button', + 'data-media-label' => $this->media_label, + 'type' => 'button', + 'class' => 'button button-secondary button-hero img-upload', + 'value' => $insert_label, + 'data-id' => $this->id, + 'data-button' => 'Use ' . $this->media_label, + 'data-title' => 'Select or Upload ' . $this->media_label, + ]; + + $hidden = [ + 'id' => $this->id, + 'name' => $this->id, + 'type' => 'hidden', + 'value' => $saved['id'], + 'data-part' => strtolower( $this->get_clean_classname() ) + ]; + + if ( ! empty( $this->atts ) ) { + foreach ( $this->atts as $key => $val ) { + $hidden[ $key ] = $val; + } + } + + echo HTML::tag( 'input', $image_btn ); + echo HTML::tag( 'input', $hidden ); + echo HTML::tag( 'a', [ + 'href' => '#', + 'class' => 'button button-secondary img-remove', + 'data-media-label' => $this->media_label + ], 'Remove ' . $this->media_label + ); return $this->build_base_markup( ob_get_clean() ); } } -class Include_Partial extends Option { +/** + * Class Include_Partial + * @package WPOP\V_3_0 + */ +class Include_Partial extends Part { public $filename; + public $input_type = 'include_partial'; - public function __construct( $i, $config, $f ) { + public function __construct( $i, $config ) { parent::__construct( $i, [] ); - $this->filename = ( ! empty( $f ) ) ? $f : 'set_the_filename.php'; + $this->filename = ( ! empty( $config['filename'] ) ) ? $config['filename'] : 'set_the_filename.php'; } public function get_html() { @@ -1348,27 +1354,247 @@ public function get_html() { } public function echo_html() { - if ( ! empty( $this->filename ) ) { - include_once $this->filename; + if ( ! empty( $this->filename ) && is_file( $this->filename ) ) { + return HTML::tag( 'li', [ 'class' => $this->get_clean_classname() ], file_get_contents( $this->filename ) ); } } } -class Include_Markup extends Option { - public $markup; +/** + * Class Markdown + * @package WPOP\V_3_1 + */ +class Markdown extends Include_Partial { + public $field_type = 'markdown_file'; - public function __construct( $i, $v = [], $m ) { - parent::__construct( $i, $v ); - $this->markup = ( ! empty( $m ) ) ? $m : null; + public function echo_html() { + if ( is_file( $this->filename ) && class_exists( '\\Parsedown' ) ) { + $converter = new \Parsedown(); + $markup = file_get_contents( $this->filename ); + if ( ! empty( $markup ) ) { + return HTML::tag( + 'li', + [ + 'class' => $this->get_clean_classname(), + 'data-part' => strtolower( $this->get_clean_classname() ) + ], + $converter->text( do_shortcode( $markup ) ) + ); + } + } else { + return 'File Status: ' . strval( is_file( $this->filename ) ) . + ' and class exists: ' . strval( class_exists( '\\Parsedown' ) ); + } } +} - public function get_html() { - return $this->echo_html(); +/** + * Class HTML + * @package WPOP\V_2_10 + * @link https://github.com/Automattic/amp-wp/blob/master/includes/utils/class-amp-html-utils.php + */ +class HTML { + /** + * Dashicon Markup Helper + * + * @param $class_str - the dashicons-* class and any addl + * + * @return string + */ + public static function dashicon( $class_str ) { + return self::tag( 'span', [ 'class' => 'dashicons ' . $class_str, 'data-dashicon' ] ); } - public function echo_html() { - if ( is_string( $this->markup ) && ! empty( $this->markup ) ) { - echo $this->markup; + /** + * Create markup for HTML tag from array fully sanitized and prepared + * + * @param $tag_name + * @param array $attributes + * @param string $content + * + * @return string + */ + public static function tag( $tag_name, $attributes = array(), $content = '' ) { + $attr_string = self::build_attributes_string( $attributes ); + + return sprintf( '<%1$s %2$s>%3$s', sanitize_key( $tag_name ), $attr_string, $content ); + } + + /** + * Built Escaped, Sanitized Attribute String for HTML Tag + * + * @param $attributes + * + * @return string + */ + public static function build_attributes_string( $attributes ) { + $string = array(); + foreach ( $attributes as $name => $value ) { + if ( empty( $value ) ) { + $string[] = sprintf( '%s', sanitize_key( $name ) ); + } else { + $string[] = sprintf( '%s="%s"', sanitize_key( $name ), esc_attr( $value ) ); + } + } + + return implode( ' ', $string ); + } + + /** + * WordPress Admin Notification Markup (can be printed anywhere in the DOM and will be relocated to top of page) + * + * @param $class_str - the dashicons-* class and any addl + * + * @return string + */ + public static function notification( $class_str ) { + return self::tag( 'div', [ 'class' => 'dashicons ' . $class_str, 'data-dashicon' ] ); + } +} + +/** + * Helper used by panel for tapping various WordPress APIs + * + * Class Get_Single_Field + * @package WPOP\V_3_0 + */ +class Get_Single_Field { + + public $response; + + protected $type; + protected $key; + protected $obj_id; + protected $single; + + /** + * Get_Single_Field constructor. + * + * @param $panel_id + * @param $type + * @param $key + * @param null $default + * @param null $obj_id + * @param bool $single + */ + function __construct( $panel_id, $type, $key, $default = null, $obj_id = null, $single = true ) { + if ( false !== wp_verify_nonce( $panel_id, $panel_id ) ) { + return false; // check for nonce, only allow panel to use this class + } + $this->type = $type; + $this->key = $key; + $this->obj_id = $obj_id; + $this->single = $single; + + $this->get_data(); + + return $this->response; + } + + function get_data() { + switch ( $this->type ) { + case 'site': + $this->response = get_option( $this->key, '' ); + break; + case 'network': + $this->response = get_site_option( $this->key ); + break; + case 'user': // single-site user option, or per-site user option in multisite + $this->response = is_multisite() ? get_user_option( $this->key, $this->obj_id ) : get_user_meta( $this->obj_id, $this->key, $this->single ); + break; // traditional user meta + case 'user-network': // user network option applied globally across all blogs/sites + $this->response = get_user_meta( $this->obj_id, $this->key, $this->single ); + break; + case 'term': + $this->response = get_metadata( 'term', $this->obj_id, $this->key, $this->single ); + break; + case 'post': + $this->response = get_metadata( 'post', $this->obj_id, $this->key, $this->single ); + break; + default: + $this->response = false; + break; + } + } + +} + +/** + * Class Save_Single_Field + * @package WPOP\V_3_0 + */ +class Save_Single_Field { + /** + * Save_Single_Field constructor. + * + * @param $panel_id + * @param $type + * @param $key + * @param $value + * @param null $obj_id + * @param bool $autoload + */ + function __construct( $panel_id, $type, $key, $value, $obj_id = null, $autoload = true ) { + if ( ! wp_verify_nonce( $_POST['_wpnonce'], $panel_id ) // only allow class to be used by panel + || '### wpop-encrypted-pwd-field-val-unchanged ###' === $value // encrypted pwds never updated after insert + ) { + return false; + } + + return $this->save_data( $panel_id, $type, $key, $value, $obj_id, $autoload ); + } + + private function save_data( $panel_id, $type, $key, $value, $obj_id = null, $autoload = true ) { + switch ( $type ) { + case 'site': + return self::handle_site_option_save( $key, $value, $autoload ); + break; + case 'network': + return self::handle_network_option_save( $key, $value ); + break; + case 'user': + return self::handle_user_site_meta_save( $obj_id, $key, $value ); + break; // traditional user meta + case 'user-network': + return self::handle_user_network_meta_save( $obj_id, $key, $value ); + break; + case 'term': + return self::handle_term_meta_save( $obj_id, $key, $value ); + break; + case 'post': + return self::handle_post_meta_save( $obj_id, $key, $value ); + break; + default: + return new \WP_Error( + '400', + 'WPOP failed to select proper WordPress Data API -- check your config.', + compact( $type, $key, $value, $obj_id, $autoload ) + ); + break; } } + + private static function handle_site_option_save( $key, $value, $autoload ) { + return empty( $value ) ? delete_option( $key ) : update_option( $key, $value, $autoload ); + } + + private static function handle_network_option_save( $key, $value ) { + return empty( $value ) ? delete_site_option( $key ) : update_site_option( $key, $value ); + } + + private static function handle_user_site_meta_save( $user_id, $key, $value ) { + return empty( $value ) ? delete_user_meta( $user_id, $key ) : update_user_meta( $user_id, $key, $value ); + } + + private static function handle_user_network_meta_save( $id, $key, $value ) { + return empty( $value ) ? delete_user_option( $id, $key, true ) : update_user_option( $id, $key, true ); + } + + private static function handle_term_meta_save( $id, $key, $value ) { + return empty( $value ) ? delete_metadata( 'term', $id, $key ) : update_metadata( 'term', $id, $key, $value ); + } + + private static function handle_post_meta_save( $id, $key, $value ) { + return empty( $value ) ? delete_post_meta( $id, $key ) : update_post_meta( $id, $key, $value ); + } }