diff --git a/.travis.yml b/.travis.yml index 9a7f2395c8..c1dc43638a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,8 @@ php: - 5.6 env: - - WP_VERSION=latest WP_MULTISITE=0 + - WP_VERSION=nightly WP_MULTISITE=0 + - WP_VERSION=4.3 WP_MULTISITE=0 - WP_VERSION=4.2 WP_MULTISITE=0 - WP_VERSION=4.1 WP_MULTISITE=0 - WP_VERSION=4.0 WP_MULTISITE=0 @@ -17,7 +18,7 @@ env: matrix: include: - php: 5.3 - env: WP_VERSION=latest WP_MULTISITE=1 + env: WP_VERSION=nightly WP_MULTISITE=1 before_script: - bash tests/bin/install.sh gravityview_test root '' localhost $WP_VERSION diff --git a/Gruntfile.js b/Gruntfile.js index f27a765f52..fbe849c56b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,7 +16,7 @@ module.exports = function(grunt) { files: [{ expand: true, cwd: 'assets/css/scss', - src: ['*.scss','!admin-merge-tags.scss','!admin-tooltips.scss','!admin-metabox-panel.scss','!admin-metabox.scss'], + src: ['*.scss','!admin-merge-tags.scss','!admin-tooltips.scss','!admin-metabox-panel.scss','!admin-metabox.scss','!admin-members-plugin.scss'], dest: 'assets/css', ext: '.css' }] @@ -137,7 +137,7 @@ module.exports = function(grunt) { transifex: 'tx pull -a', // Create a ZIP file - zip: 'python /usr/bin/git-archive-all ../gravityview.zip' + zip: 'git-archive-all ../gravityview.zip' }, // Build translations without POEdit diff --git a/assets/css/admin-global.css b/assets/css/admin-global.css index 20dd7b9136..fe3de0d4ad 100644 --- a/assets/css/admin-global.css +++ b/assets/css/admin-global.css @@ -1 +1 @@ -@font-face{font-family:"gravityview";src:url("../fonts/gravityview.eot");src:url("../fonts/gravityview.eot?#iefix") format("embedded-opentype"),url("../fonts/gravityview.woff") format("woff"),url("../fonts/gravityview.ttf") format("truetype"),url("../fonts/gravityview.svg#gravityview") format("svg");font-weight:normal;font-style:normal;}a.icon{text-decoration:none;}[data-gv-icon]:before{font-family:"gravityview" !important;content:attr(data-gv-icon);font-style:normal !important;font-weight:normal !important;font-variant:normal !important;text-transform:none !important;speak:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}[class^="gv-icon-"]:before,#toplevel_page_gravityview_settings .wp-menu-image:before,#adminmenu .menu-icon-gravityview div.wp-menu-image:before,#available-widgets [class*=gv_recent_entries] .widget-title:before,#available-widgets [class*=gravityview_search] .widget-title:before,[class*=" gv-icon-"]:before{font-family:"gravityview" !important;font-style:normal !important;font-weight:normal !important;font-variant:normal !important;text-transform:none !important;speak:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}.gv-icon-astronaut-head:before,#toplevel_page_gravityview_settings .wp-menu-image:before,#adminmenu .menu-icon-gravityview div.wp-menu-image:before,#available-widgets [class*=gv_recent_entries] .widget-title:before,#available-widgets [class*=gravityview_search] .widget-title:before{content:"a";}.gv-icon-astronaut:before{content:"b";}.gv-icon-datatables-icon:before{content:"c";}.gv-icon-caret-up-down:before{content:"d";}.gv-icon-minus-square:before{content:"f";}.gv-icon-plus-square:before{content:"g";}.gv-icon-level-down:before{content:"e";}.gv-icon-sort-asc:before{content:"h";}.gv-icon-sort-desc:before{content:"i";}.wp-editor-tools #add_gravityview{width:auto;}#gf_form_toolbar .gv_connected_forms .gv-icon{font-size:19px;margin-top:-0.5em;line-height:0.5em;display:inline-block;}#gf_form_toolbar .gv_connected_forms .hidden{display:none !important;}#gf_form_toolbar .gv_connected_forms .gf_submenu{min-width:150px;max-width:100%;}#gf_form_toolbar .gv_connected_forms li a{padding:0.5em 0.75em !important;display:block;width:auto;}.post-type-gravityview .changelog ul{list-style-type:square;}.post-type-gravityview .changelog ul ul{list-style:circle;margin:0.5em 0 0.5em 1.3em;} \ No newline at end of file +@font-face{font-family:"gravityview";src:url("../fonts/gravityview.eot");src:url("../fonts/gravityview.eot?#iefix") format("embedded-opentype"),url("../fonts/gravityview.woff") format("woff"),url("../fonts/gravityview.ttf") format("truetype"),url("../fonts/gravityview.svg#gravityview") format("svg");font-weight:normal;font-style:normal;}a.icon{text-decoration:none;}[data-gv-icon]:before{font-family:"gravityview" !important;content:attr(data-gv-icon);font-style:normal !important;font-weight:normal !important;font-variant:normal !important;text-transform:none !important;speak:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}[class^="gv-icon-"]:before,#toplevel_page_gravityview_settings .wp-menu-image:before,#adminmenu .menu-icon-gravityview div.wp-menu-image:before,#available-widgets [class*=gv_recent_entries] .widget-title:before,#available-widgets [class*=gravityview_search] .widget-title:before,[class*=" gv-icon-"]:before{font-family:"gravityview" !important;font-style:normal !important;font-weight:normal !important;font-variant:normal !important;text-transform:none !important;speak:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}.gv-icon-astronaut-head:before,#toplevel_page_gravityview_settings .wp-menu-image:before,#adminmenu .menu-icon-gravityview div.wp-menu-image:before,#available-widgets [class*=gv_recent_entries] .widget-title:before,#available-widgets [class*=gravityview_search] .widget-title:before{content:"a";}.gv-icon-astronaut:before{content:"b";}.gv-icon-datatables-icon:before{content:"c";}.gv-icon-caret-up-down:before{content:"d";}.gv-icon-minus-square:before{content:"f";}.gv-icon-plus-square:before{content:"g";}.gv-icon-level-down:before{content:"e";}.gv-icon-sort-asc:before{content:"h";}.gv-icon-sort-desc:before{content:"i";}.members-tab-title a[href$="type-gravityview"]{display:none !important;}.members-tab-title .gv-icon-astronaut-head{font-size:20px;width:20px;line-height:20px;margin-right:6.25px;display:block;float:left;}#hs-beacon iframe{z-index:9991 !important;}#hs-beacon iframe[data-reactid-hs=".0.0"]{right:-5px !important;bottom:-5px !important;}.rtl #hs-beacon iframe[data-reactid-hs=".0.0"]{right:auto !important;left:-5px !important;}.wp-editor-tools #add_gravityview{width:auto;}#gf_form_toolbar .gv_connected_forms .gv-icon{font-size:19px;margin-top:-0.5em;line-height:0.5em;display:inline-block;}#gf_form_toolbar .gv_connected_forms .hidden{display:none !important;}#gf_form_toolbar .gv_connected_forms .gf_submenu{min-width:150px;max-width:100%;}#gf_form_toolbar .gv_connected_forms li a{padding:0.5em 0.75em !important;display:block;width:auto;}.post-type-gravityview .changelog ul{list-style-type:square;}.post-type-gravityview .changelog ul ul{list-style:circle;margin:0.5em 0 0.5em 1.3em;} \ No newline at end of file diff --git a/assets/css/admin-settings.css b/assets/css/admin-settings.css index 73cfc1672e..7ae5f7d0c1 100644 --- a/assets/css/admin-settings.css +++ b/assets/css/admin-settings.css @@ -1 +1 @@ -#gform-settings .gforms_form_settings .edd-license-key{float:left;margin:0 10px 10px 0;}#gform-settings .gforms_form_settings .gv-edd-button-wrapper{float:left;padding-left:0;}#gform-settings .gforms_form_settings .gv-edd-button-wrapper .button,#gform-settings .gforms_form_settings .gv-edd-button-wrapper .button-primary{margin:0 10px 10px 0 !important;}.gravityview_page_gravityview_settings .gform_tab_content>h3,.toplevel_page_gravityview_settings .gform_tab_content>h3{position:absolute;top:0.5em;right:10px;}.gravityview_page_gravityview_settings .version-info,.toplevel_page_gravityview_settings .version-info{position:absolute;right:10px;top:0.5em;}.gravityview_page_gravityview_settings .gv-edd-message,.toplevel_page_gravityview_settings .gv-edd-message{min-height:20px;padding:8px 19px;margin:10px 0 !important;border:1px solid;border-radius:4px;position:relative;display:block !important;background-color:#eeeeee;border-color:#cccccc;color:#666666;}.gravityview_page_gravityview_settings .gv-edd-message.valid,.toplevel_page_gravityview_settings .gv-edd-message.valid{background-color:#c4ee91;border-color:#71af5d;color:#4d7615;}.gravityview_page_gravityview_settings .gv-edd-message.error,.gravityview_page_gravityview_settings .gv-edd-message.invalid,.gravityview_page_gravityview_settings .gv-edd-message.failed,.toplevel_page_gravityview_settings .gv-edd-message.error,.toplevel_page_gravityview_settings .gv-edd-message.invalid,.toplevel_page_gravityview_settings .gv-edd-message.failed{background-color:#fba1a3;border-color:#b84f5b;color:#981225;}.gravityview_page_gravityview_settings .gv-edd-message.site_inactive,.gravityview_page_gravityview_settings .gv-edd-message.deactivated,.toplevel_page_gravityview_settings .gv-edd-message.site_inactive,.toplevel_page_gravityview_settings .gv-edd-message.deactivated{background-color:#fbeba4;border-color:#d7c281;color:#958234;}.gravityview_page_gravityview_settings .gv-edd-message.pending,.toplevel_page_gravityview_settings .gv-edd-message.pending{background-color:#d3e4f4;border-color:#a9b6c2;color:#5c80a1;}.gravityview_page_gravityview_settings .gv-edd-message p:first-child,.toplevel_page_gravityview_settings .gv-edd-message p:first-child{margin:0;padding:2px;}.gravityview_page_gravityview_settings .inline.hide:empty,.toplevel_page_gravityview_settings .inline.hide:empty{display:none !important;}.gravityview_page_gravityview_settings .hide,.toplevel_page_gravityview_settings .hide{display:none;}.gravityview_page_gravityview_settings #gform_tab_group,.toplevel_page_gravityview_settings #gform_tab_group{padding:0;background:url(../images/stars.jpg) left top repeat-x;}.gravityview_page_gravityview_settings #gform_tab_group:before,.toplevel_page_gravityview_settings #gform_tab_group:before{content:'';display:block;position:relative;margin-left:0;background-color:#fff;min-height:110px;background:url(../images/astronaut-200x263.png) -30px top no-repeat;margin:0;padding:10px 0 0 160px;}.gravityview_page_gravityview_settings .gform_tab_group,.toplevel_page_gravityview_settings .gform_tab_group{position:relative;}.gravityview_page_gravityview_settings .gform_tab_group #message,.toplevel_page_gravityview_settings .gform_tab_group #message{position:absolute;width:auto;white-space:nowrap;top:35px;left:150px;margin-bottom:10px;padding-top:5px;padding-bottom:5px;z-index:1000;opacity:0.95;}.gravityview_page_gravityview_settings .gform_tab_group #message p,.toplevel_page_gravityview_settings .gform_tab_group #message p{margin:0;padding:0.25em 5px;font-size:16px;}.gravityview_page_gravityview_settings .gform_tab_group #message p strong,.toplevel_page_gravityview_settings .gform_tab_group #message p strong{font-weight:400;}.gravityview_page_gravityview_settings #gform_tab_container,.toplevel_page_gravityview_settings #gform_tab_container{margin-left:0;background-color:#fff;padding-bottom:0;}.gravityview_page_gravityview_settings #gform_tabs,.toplevel_page_gravityview_settings #gform_tabs{margin-top:0;display:none;}.gravityview_page_gravityview_settings #gform-settings,.toplevel_page_gravityview_settings #gform-settings{clear:both;}.gravityview_page_gravityview_settings #gform-settings tr,.toplevel_page_gravityview_settings #gform-settings tr{border-top:1px solid #E7E7E7;}.gravityview_page_gravityview_settings #gform-settings tr:first-child,.gravityview_page_gravityview_settings #gform-settings tr:last-child,.toplevel_page_gravityview_settings #gform-settings tr:first-child,.toplevel_page_gravityview_settings #gform-settings tr:last-child{border-top:none;}.gravityview_page_gravityview_settings #gform-settings tr:first-child td,.gravityview_page_gravityview_settings #gform-settings tr:first-child th,.gravityview_page_gravityview_settings #gform-settings tr:last-child td,.gravityview_page_gravityview_settings #gform-settings tr:last-child th,.toplevel_page_gravityview_settings #gform-settings tr:first-child td,.toplevel_page_gravityview_settings #gform-settings tr:first-child th,.toplevel_page_gravityview_settings #gform-settings tr:last-child td,.toplevel_page_gravityview_settings #gform-settings tr:last-child th{padding-top:10px !important;}.gravityview_page_gravityview_settings #gform-settings tr:last-child td,.gravityview_page_gravityview_settings #gform-settings tr:last-child th,.toplevel_page_gravityview_settings #gform-settings tr:last-child td,.toplevel_page_gravityview_settings #gform-settings tr:last-child th{padding-bottom:10px !important;}.gravityview_page_gravityview_settings #gform-settings th,.gravityview_page_gravityview_settings #gform-settings td,.toplevel_page_gravityview_settings #gform-settings th,.toplevel_page_gravityview_settings #gform-settings td{margin:0;padding:20px 10px 20px 0px;width:auto;}.gravityview_page_gravityview_settings #gform-settings td,.toplevel_page_gravityview_settings #gform-settings td{width:70%;}.gravityview_page_gravityview_settings #gform-settings td div,.toplevel_page_gravityview_settings #gform-settings td div{padding-left:10px;}.gravityview_page_gravityview_settings #gform-settings th,.toplevel_page_gravityview_settings #gform-settings th{font-weight:bold;display:block;}.gravityview_page_gravityview_settings #gform-settings span.description,.toplevel_page_gravityview_settings #gform-settings span.description{display:block;font-style:normal;font-weight:400;color:#666;} \ No newline at end of file +#gform-settings .gforms_form_settings .edd-license-key{float:left;margin:0 10px 10px 0;}#gform-settings .gforms_form_settings .gv-edd-button-wrapper{float:left;padding-left:0;}#gform-settings .gforms_form_settings .gv-edd-button-wrapper .button,#gform-settings .gforms_form_settings .gv-edd-button-wrapper .button-primary{margin:0 10px 10px 0 !important;}.gravityview_page_gravityview_settings .gform_tab_content>h3,.toplevel_page_gravityview_settings .gform_tab_content>h3{position:absolute;top:0.5em;right:10px;}.gravityview_page_gravityview_settings .version-info,.toplevel_page_gravityview_settings .version-info{position:absolute;right:10px;top:0.5em;}.gravityview_page_gravityview_settings .gv-edd-message,.toplevel_page_gravityview_settings .gv-edd-message{min-height:20px;padding:8px 19px;margin:10px 0 !important;border:1px solid;border-radius:4px;position:relative;display:block !important;background-color:#eeeeee;border-color:#cccccc;color:#666666;}.gravityview_page_gravityview_settings .gv-edd-message.valid,.toplevel_page_gravityview_settings .gv-edd-message.valid{background-color:#c4ee91;border-color:#71af5d;color:#4d7615;}.gravityview_page_gravityview_settings .gv-edd-message.error,.gravityview_page_gravityview_settings .gv-edd-message.invalid,.gravityview_page_gravityview_settings .gv-edd-message.failed,.toplevel_page_gravityview_settings .gv-edd-message.error,.toplevel_page_gravityview_settings .gv-edd-message.invalid,.toplevel_page_gravityview_settings .gv-edd-message.failed{background-color:#fba1a3;border-color:#b84f5b;color:#981225;}.gravityview_page_gravityview_settings .gv-edd-message.site_inactive,.gravityview_page_gravityview_settings .gv-edd-message.deactivated,.toplevel_page_gravityview_settings .gv-edd-message.site_inactive,.toplevel_page_gravityview_settings .gv-edd-message.deactivated{background-color:#fbeba4;border-color:#d7c281;color:#958234;}.gravityview_page_gravityview_settings .gv-edd-message.pending,.toplevel_page_gravityview_settings .gv-edd-message.pending{background-color:#d3e4f4;border-color:#a9b6c2;color:#5c80a1;}.gravityview_page_gravityview_settings .gv-edd-message p:first-child,.toplevel_page_gravityview_settings .gv-edd-message p:first-child{margin:0;padding:2px;}.gravityview_page_gravityview_settings .inline.hide:empty,.toplevel_page_gravityview_settings .inline.hide:empty{display:none !important;}.gravityview_page_gravityview_settings .hide,.toplevel_page_gravityview_settings .hide{display:none;}.gravityview_page_gravityview_settings #gform_tab_group,.toplevel_page_gravityview_settings #gform_tab_group{padding:0;background:url(../images/stars.jpg) left top repeat-x;}.gravityview_page_gravityview_settings #gform_tab_group:before,.toplevel_page_gravityview_settings #gform_tab_group:before{content:'';display:block;position:relative;margin-left:0;background-color:#fff;min-height:110px;background:url(../images/astronaut-200x263.png) -30px top no-repeat;margin:0;padding:10px 0 0 160px;}.gravityview_page_gravityview_settings .gform_tab_group,.toplevel_page_gravityview_settings .gform_tab_group{position:relative;}.gravityview_page_gravityview_settings .gform_tab_group #message,.toplevel_page_gravityview_settings .gform_tab_group #message{position:absolute;width:auto;white-space:nowrap;top:35px;left:150px;margin-bottom:10px;padding-top:5px;padding-bottom:5px;z-index:1000;opacity:0.95;}.gravityview_page_gravityview_settings .gform_tab_group #message p,.toplevel_page_gravityview_settings .gform_tab_group #message p{margin:0;padding:0.25em 5px;font-size:16px;}.gravityview_page_gravityview_settings .gform_tab_group #message p strong,.toplevel_page_gravityview_settings .gform_tab_group #message p strong{font-weight:400;}.gravityview_page_gravityview_settings #gform_tab_container,.toplevel_page_gravityview_settings #gform_tab_container{margin-left:0;background-color:#fff;padding-bottom:0;}.gravityview_page_gravityview_settings #gform_tabs,.toplevel_page_gravityview_settings #gform_tabs{margin-top:0;display:none;}.gravityview_page_gravityview_settings #gform-settings,.toplevel_page_gravityview_settings #gform-settings{clear:both;}.gravityview_page_gravityview_settings #gform-settings tr,.toplevel_page_gravityview_settings #gform-settings tr{border-top:1px solid #E7E7E7;}.gravityview_page_gravityview_settings #gform-settings tr:first-child,.toplevel_page_gravityview_settings #gform-settings tr:first-child{border-top:none;}.gravityview_page_gravityview_settings #gform-settings tr:first-child td,.gravityview_page_gravityview_settings #gform-settings tr:first-child th,.toplevel_page_gravityview_settings #gform-settings tr:first-child td,.toplevel_page_gravityview_settings #gform-settings tr:first-child th{padding-top:10px !important;}.gravityview_page_gravityview_settings #gform-settings .gaddon-section,.toplevel_page_gravityview_settings #gform-settings .gaddon-section{padding-top:0;}.gravityview_page_gravityview_settings #gform-settings .gaddon-section>h4,.toplevel_page_gravityview_settings #gform-settings .gaddon-section>h4{color:#777;font-size:16px;font-weight:400;line-height:22px;}.gravityview_page_gravityview_settings #gform-settings table:last-child>tr:first-child td,.gravityview_page_gravityview_settings #gform-settings table:last-child>tr:first-child th,.gravityview_page_gravityview_settings #gform-settings table:last-child>tr:last-child td,.gravityview_page_gravityview_settings #gform-settings table:last-child>tr:last-child th,.toplevel_page_gravityview_settings #gform-settings table:last-child>tr:first-child td,.toplevel_page_gravityview_settings #gform-settings table:last-child>tr:first-child th,.toplevel_page_gravityview_settings #gform-settings table:last-child>tr:last-child td,.toplevel_page_gravityview_settings #gform-settings table:last-child>tr:last-child th{padding-bottom:10px !important;}.gravityview_page_gravityview_settings #gform-settings th,.gravityview_page_gravityview_settings #gform-settings td,.toplevel_page_gravityview_settings #gform-settings th,.toplevel_page_gravityview_settings #gform-settings td{margin:0;padding:20px 10px 20px 0px;width:auto;}.gravityview_page_gravityview_settings #gform-settings td,.toplevel_page_gravityview_settings #gform-settings td{width:60%;}.gravityview_page_gravityview_settings #gform-settings td div,.toplevel_page_gravityview_settings #gform-settings td div{padding-left:10px;}.gravityview_page_gravityview_settings #gform-settings th,.toplevel_page_gravityview_settings #gform-settings th{font-weight:bold;display:block;}.gravityview_page_gravityview_settings #gform-settings label,.toplevel_page_gravityview_settings #gform-settings label{display:inline-block;}.gravityview_page_gravityview_settings #gform-settings span.description,.toplevel_page_gravityview_settings #gform-settings span.description{display:block;font-style:normal;font-weight:400;color:#666;} \ No newline at end of file diff --git a/assets/css/beacon.css b/assets/css/beacon.css new file mode 100644 index 0000000000..5b9f79c8b4 --- /dev/null +++ b/assets/css/beacon.css @@ -0,0 +1 @@ +#hs-beacon iframe{z-index:9991 !important;}#hs-beacon iframe[data-reactid-hs=".0.0"]{right:-5px !important;bottom:-5px !important;}.rtl #hs-beacon iframe[data-reactid-hs=".0.0"]{right:auto !important;left:-5px !important;} \ No newline at end of file diff --git a/assets/css/scss/admin-global.scss b/assets/css/scss/admin-global.scss index a5ab0a3bba..f5cb5314ac 100644 --- a/assets/css/scss/admin-global.scss +++ b/assets/css/scss/admin-global.scss @@ -11,6 +11,8 @@ */ @import "font"; +@import "admin-members-plugin"; +@import "beacon"; /* add gravityview menu icon */ #toplevel_page_gravityview_settings .wp-menu-image:before, diff --git a/assets/css/scss/admin-members-plugin.scss b/assets/css/scss/admin-members-plugin.scss new file mode 100644 index 0000000000..389308316a --- /dev/null +++ b/assets/css/scss/admin-members-plugin.scss @@ -0,0 +1,21 @@ +/** + * Styles for Members plugin 1.x + * @since 1.15 + * @see https://wordpress.org/plugins/members/ + */ + +// Hide the Views CPT label, since we have our own. +.members-tab-title a[href$="type-gravityview"] { + display:none!important; +} + +// Add Floaty's head +.members-tab-title .gv-icon-astronaut-head { + font-size: 20px; + width: 20px; + line-height: 20px; + // Our icon is narrower than dashicons; we add 3.25px to match expected position + margin-right: 3 + 3.25px; + display: block; + float:left; +} \ No newline at end of file diff --git a/assets/css/scss/admin-settings.scss b/assets/css/scss/admin-settings.scss index 183ccd0df6..e37003d267 100644 --- a/assets/css/scss/admin-settings.scss +++ b/assets/css/scss/admin-settings.scss @@ -147,18 +147,29 @@ clear: both; tr { - border-top:1px solid #E7E7E7 + border-top:1px solid #E7E7E7; } - tr:first-child, - tr:last-child { + tr:first-child { border-top: none; td, th { padding-top: 10px!important; } } - tr:last-child { + .gaddon-section { + padding-top: 0; + & > h4 { + color: #777; + font-size: 16px; + font-weight: 400; + line-height: 22px; + } + } + + table:last-child > tr:first-child, + table:last-child > tr:last-child { + td, th { padding-bottom: 10px!important; } @@ -171,7 +182,7 @@ } td { - width: 70%; + width: 60%; div { padding-left: 10px; @@ -180,7 +191,15 @@ th { font-weight: bold; - display: block + display: block; + } + + /** + * On smaller screens, the radio inputs were displaying on different lines than the labels. + * This keeps the s and the s together. + */ + label { + display: inline-block; } span.description { diff --git a/assets/css/scss/beacon.scss b/assets/css/scss/beacon.scss new file mode 100644 index 0000000000..f68abbf3ba --- /dev/null +++ b/assets/css/scss/beacon.scss @@ -0,0 +1,24 @@ +#hs-beacon { + + iframe { + + z-index: 9991!important; // Above #adminmenuwrap, which is 9990 + + // The Button iframe + &[data-reactid-hs=".0.0"] { + right: -5px!important; + bottom: -5px!important; + + .rtl & { + right: auto!important; + left: -5px!important; + } + } + // End button iframe + + // The Button content iframe + &[data-reactid-hs=".0.1"] {} + // End Button content iframe + + } +} \ No newline at end of file diff --git a/assets/css/scss/feedback.scss b/assets/css/scss/feedback.scss deleted file mode 100644 index 736e785557..0000000000 --- a/assets/css/scss/feedback.scss +++ /dev/null @@ -1,363 +0,0 @@ -html body .chatlio-widget.closed { - height: 30px; - text-align: center; -} -html body .chatlio-widget.closed .title-text { - margin-left: -5px; -} -.chatlio-widget.hide { - display: none !important; -} -#chatlio-widget { - position: relative; - overflow: hidden; -} -#chatlio-widget.embed-inline-container { - height: 100%; -} -.chatlio-widget.embed-inline { - position: relative !important; - width: 100% !important; - height: 100% !important; - left: 0; - right: 0; -} -.chatlio-widget.embed-inline .message-container { - height: 480px !important; -} -.chatlio-widget.embed-inline .new-message-container { - border: 1px solid #B8B8B8; -} -.chatlio-widget.embed-inline .controls { - display: none; -} -.chatlio-widget { - color: black; - line-height: normal; - font-weight: normal; - text-align: left; - box-shadow: 0px 1px 7px rgba(0, 50, 100, 0.25); - -webkit-border-top-left-radius: 5px; - -webkit-border-top-right-radius: 5px; - -moz-border-radius-topleft: 5px; - -moz-border-radius-topright: 5px; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - border-left: 1px solid #B8B8B8; - border-right: 1px solid #B8B8B8; - opacity: 1 !important; - //font-family: "Helvetica Neue", Arial, sans-serif; - position: fixed; - bottom: 0; - - - left: auto; - right: 30px; - - height: 330px; - width: 260px; - z-index: 9999; - background-color: #fff; - box-sizing: border-box; - border-top: 0; - border-bottom: 0; - -webkit-animation: fadein 2s; - /* Safari, Chrome and Opera > 12.1 */ - - -moz-animation: fadein 2s; - /* Firefox < 16 */ - - -ms-animation: fadein 2s; - /* Internet Explorer */ - - -o-animation: fadein 2s; - /* Opera < 12.1 */ - - animation: fadein 2s; - - - textarea { - min-height: 0; - } - img { - margin: 0; - vertical-align: baseline; - } - .btn { - // This is all copied from .button-secondary - color: #555; - border-color: #ccc; - background: #f7f7f7; - -webkit-box-shadow: inset 0 1px 0 #fff,0 1px 0 rgba(0,0,0,.08); - box-shadow: inset 0 1px 0 #fff,0 1px 0 rgba(0,0,0,.08); - vertical-align: top; - - display: inline-block; - text-decoration: none; - font-size: 13px; - line-height: 26px; - height: 28px; - margin: 0; - padding: 0 10px 1px; - cursor: pointer; - border-width: 1px; - border-style: solid; - -webkit-appearance: none; - -webkit-border-radius: 3px; - border-radius: 3px; - white-space: nowrap; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - - &:hover { - background: #fafafa; - border-color: #999; - color: #23282d; - } - &:active { - outline: 0 none; - background: #eee; - border-color: #999; - color: #32373c; - -webkit-box-shadow: inset 0 2px 5px -3px rgba(0,0,0,.5); - box-shadow: inset 0 2px 5px -3px rgba(0,0,0,.5); - } - } - .btn-primary { - - background: #00a0d2; - border-color: #0073aa; - -webkit-box-shadow: inset 0 1px 0 rgba(120,200,230,.5),0 1px 0 rgba(0,0,0,.15); - box-shadow: inset 0 1px 0 rgba(120,200,230,.5),0 1px 0 rgba(0,0,0,.15); - color: #fff; - text-decoration: none; - - &:hover { - background: #0091cd; - border-color: #0073aa; - -webkit-box-shadow: inset 0 1px 0 rgba(120,200,230,.6); - box-shadow: inset 0 1px 0 rgba(120,200,230,.6); - color: #fff; - } - - &:active { - background: #0073aa; - border-color: #005082; - color: rgba(255, 255, 255, .95); - -webkit-box-shadow: inset 0 1px 0 rgba(0, 0, 0, .1); - box-shadow: inset 0 1px 0 rgba(0, 0, 0, .1); - vertical-align: top; - } - } - p { - padding: 0 - } - .hide { - display: none !important; - } - - *, - *:before, - *:after { - box-sizing: inherit; - } - .clear { - clear: both; - } - .chatlio-widget-body.closed { - display: none; - } - .title-bar { - -webkit-border-top-left-radius: 3px; - -webkit-border-top-right-radius: 3px; - -moz-border-radius-topleft: 3px; - -moz-border-radius-topright: 3px; - border-top-left-radius: 3px; - border-top-right-radius: 3px; - line-height: normal; - position: relative; - font-weight: 300; - font-size: 14px; - padding: 6px 10px 6px 10px; - color: #F0F0F0; - background-color: #3f51b5; - cursor: pointer; - height: 30px; - text-align: center; - - a { - color: white; - } - } - - .title-text { - padding-left: 5px; - font-weight: 400; - - a:link { - color: white; - text-decoration: none; - } - a:visited { - color: white; - text-decoration: none; - } - a:hover { - color: white; - text-decoration: underline; - } - a:active { - color: white; - text-decoration: none; - } - } - - .title-bar .controls:before { - content: "\00a0\00a0"; - } - .online-dot { - margin-top: 3px; - float: left; - } - .title-bar .controls { - background: url("https://w.chatlio.com/images/Arrowhead-Down-16.d59887e8.png") right no-repeat; - background-size: 10px 10px; - min-width: 20px; - float: right; - } - .title-bar .controls.closed { - background: url("https://w.chatlio.com/images/Arrowhead-Up-16.aa2df86e.png") right no-repeat; - background-size: 10px 10px; - } - .message-container { - overflow: auto !important; - height: 199px !important; - font-size: 14px; - } - .message { - padding: 5px !important; - font-size: 12px; - border-bottom: 1px solid #d8d8d8; - } - .message-author { - float: left; - font-weight: bold; - color: #3E3D41; - padding-bottom: 5px; - } - .message-timestamp { - float: right; - color: #ccc; - font-size: 0.8em; - padding-right: 5px; - } - .message-body { - color: #3E3D41; - - pre { - //font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif; - overflow: auto; - line-height: 0.7; - width: auto; - max-height: 200px; - background-color: #F4F4F6; - padding: 6px; - margin: 2px; - border: 1px solid #dddde4; - border-radius: 3px; - } - code { - border-radius: 3px; - padding: 2px 4px 0px 4px; - background-color: #F4F4F6; - color: #c92657; - border: 1px solid #dddde4; - //font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif; - } - } - - .new-message-container { - background-color: #F0F2F7; - padding: 5px; - padding-bottom: 3px; - height: 71px; - border-top: 1px solid #d8d8d8; - - .connecting { - border: 1px solid orange; - } - } - - .new-message { - overflow: auto; - font-size: 12px; - width: 100%; - border: 1px solid #d8d8d8; - padding: 5px; - margin-bottom: 3px; - //font-family: "Helvetica Neue", Arial, sans-serif; - height: 59px; - box-sizing: inherit; - } - .offline-message-container { - background-color: #f0f2f7; - height: 270px; - padding: 5px; - font-size: 13px; - width: 100%; - - input { - font-size: 12px; - width: 100%; - border: 1px solid #d8d8d8; - padding: 5px; - margin-bottom: 3px; - height: 35px; - box-sizing: inherit; - } - - textarea { - font-size: 12px; - padding: 5px; - border: 1px solid #d8d8d8; - margin-bottom: 3px; - //font-family: "Helvetica Neue", Arial, sans-serif; - width: 100%; - } - - .missed-you-message { - padding: 2px; - margin: 0 2px 4px 0; - } - } - - - .offline-message-container-sent { - padding-top: 100px; - text-align: center; - vertical-align: middle; - } - input.error, - textarea.error { - border: 2px solid red; - } - img.file-attachment { - padding-bottom: 3px; - max-width: 240px; - max-height: 128px; - } - .file-attachment-title { - font-size: 0.9em; - padding-bottom: 5px; - } - .powered-by { - background-color: #f0f2f7; - text-align: left; - text-indent: -9999px; - overflow: hidden; - padding: 1px 5px 5px 5px; - height: 30px; - width: 100%; - } -} diff --git a/assets/images/screenshots/beacon.png b/assets/images/screenshots/beacon.png new file mode 100644 index 0000000000..d56c9ca3e8 Binary files /dev/null and b/assets/images/screenshots/beacon.png differ diff --git a/assets/images/screenshots/caps.png b/assets/images/screenshots/caps.png new file mode 100644 index 0000000000..8d9886709c Binary files /dev/null and b/assets/images/screenshots/caps.png differ diff --git a/assets/images/screenshots/get.png b/assets/images/screenshots/get.png new file mode 100644 index 0000000000..a44860215a Binary files /dev/null and b/assets/images/screenshots/get.png differ diff --git a/assets/images/screenshots/support-port.png b/assets/images/screenshots/support-port.png new file mode 100644 index 0000000000..45f9b6fbbc Binary files /dev/null and b/assets/images/screenshots/support-port.png differ diff --git a/assets/js/admin-entries-list.js b/assets/js/admin-entries-list.js index bb8d0b3ae3..b9a736977a 100644 --- a/assets/js/admin-entries-list.js +++ b/assets/js/admin-entries-list.js @@ -26,10 +26,13 @@ self.maybeDisplayMessages(); - self.addBulkAction(); + // Only add Bulk Actions if user has the capability + if( gvGlobals.add_bulk_action * 1 ) { + self.addBulkAction(); + } // Only support approve/reject if the column is visible - if( 1 === parseInt( gvGlobals.show_column, 10 ) ) { + if( gvGlobals.show_column * 1 ) { self.addApprovedColumn(); diff --git a/assets/js/admin-entries-list.min.js b/assets/js/admin-entries-list.min.js index 13d8c9aadc..aa1c888bf7 100644 --- a/assets/js/admin-entries-list.min.js +++ b/assets/js/admin-entries-list.min.js @@ -1 +1 @@ -!function($){"use strict";var self={};self.init=function(){self.maybeDisplayMessages(),self.addBulkAction(),1===parseInt(gvGlobals.show_column,10)&&(self.addApprovedColumn(),self.setInitialApprovedEntries(),$(".toggleApproved").click(self.toggleApproved))},self.maybeDisplayMessages=function(){gvGlobals.bulk_message.length>0&&self.displayMessage(gvGlobals.bulk_message,"updated","#lead_form")},self.setInitialApprovedEntries=function(){$("tr:has(input.entry_approved)").find("a.toggleApproved").addClass("entry_approved").prop("title",gvGlobals.unapprove_title)},self.addBulkAction=function(){$("#bulk_action, #bulk_action2").append('")},self.addApprovedColumn=function(){$("thead th.check-column:eq(1), tfoot th.check-column:eq(1)").after(''),$('td:has(img[src*="star"])').after('')},self.toggleApproved=function(e){e.preventDefault();var entryID=$(this).parent().parent().find('th input[type="checkbox"]').val();return $(this).addClass("loading"),$(this).hasClass("entry_approved")?($(this).prop("title",gvGlobals.approve_title),self.updateApproved(entryID,0,$(this))):($(this).prop("title",gvGlobals.unapprove_title),self.updateApproved(entryID,"Approved",$(this))),!1},self.displayMessage=function(message,messageClass,container){self.hideMessage(container,!0);var messageBox=$('");$(messageBox).prependTo(container).slideDown(),"updated"===messageClass&&window.setTimeout(function(){self.hideMessage(container,!1)},1e4)},self.hideMessage=function(container,messageQueued){var messageBox=$(container).find(".message");messageQueued?$(messageBox).remove():$(messageBox).slideUp(function(){$(this).remove()})},self.updateApproved=function(entryID,approved,$target){var data={action:"gv_update_approved",entry_id:entryID,form_id:gvGlobals.form_id,approved:approved,nonce:gvGlobals.nonce};return $.post(ajaxurl,data,function(response){response&&$target.removeClass("loading").toggleClass("entry_approved","Approved"===approved)}),!0},$(self.init)}(jQuery); \ No newline at end of file +!function($){"use strict";var self={};self.init=function(){self.maybeDisplayMessages(),1*gvGlobals.add_bulk_action&&self.addBulkAction(),1*gvGlobals.show_column&&(self.addApprovedColumn(),self.setInitialApprovedEntries(),$(".toggleApproved").click(self.toggleApproved))},self.maybeDisplayMessages=function(){gvGlobals.bulk_message.length>0&&self.displayMessage(gvGlobals.bulk_message,"updated","#lead_form")},self.setInitialApprovedEntries=function(){$("tr:has(input.entry_approved)").find("a.toggleApproved").addClass("entry_approved").prop("title",gvGlobals.unapprove_title)},self.addBulkAction=function(){$("#bulk_action, #bulk_action2").append('")},self.addApprovedColumn=function(){$("thead th.check-column:eq(1), tfoot th.check-column:eq(1)").after(''),$('td:has(img[src*="star"])').after('')},self.toggleApproved=function(e){e.preventDefault();var entryID=$(this).parent().parent().find('th input[type="checkbox"]').val();return $(this).addClass("loading"),$(this).hasClass("entry_approved")?($(this).prop("title",gvGlobals.approve_title),self.updateApproved(entryID,0,$(this))):($(this).prop("title",gvGlobals.unapprove_title),self.updateApproved(entryID,"Approved",$(this))),!1},self.displayMessage=function(message,messageClass,container){self.hideMessage(container,!0);var messageBox=$('");$(messageBox).prependTo(container).slideDown(),"updated"===messageClass&&window.setTimeout(function(){self.hideMessage(container,!1)},1e4)},self.hideMessage=function(container,messageQueued){var messageBox=$(container).find(".message");messageQueued?$(messageBox).remove():$(messageBox).slideUp(function(){$(this).remove()})},self.updateApproved=function(entryID,approved,$target){var data={action:"gv_update_approved",entry_id:entryID,form_id:gvGlobals.form_id,approved:approved,nonce:gvGlobals.nonce};return $.post(ajaxurl,data,function(response){response&&$target.removeClass("loading").toggleClass("entry_approved","Approved"===approved)}),!0},$(self.init)}(jQuery); \ No newline at end of file diff --git a/assets/js/feedback.js b/assets/js/feedback.js deleted file mode 100644 index 9f70956216..0000000000 --- a/assets/js/feedback.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @global jQuery - */ -var _chatlio=_chatlio||[]; -!function(){var t=document.getElementById("chatlio-widget-embed");if(t&&window.React&&_chatlio.init)return void _chatlio.init(t,React);for(var e=function(t){return function(){_chatlio.push([t].concat(arguments))}},i=["identify","track","show","hide","isShown","isOnline"],a=0;aadd_hooks(); + } + + /** + * @since 1.15 + */ + function add_hooks() { + add_action( 'personal_options', array( $this, 'user_field' ) ); + add_action( 'personal_options_update', array( $this, 'update_user_meta_value' ) ); + add_action( 'edit_user_profile_update', array( $this, 'update_user_meta_value' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'maybe_enqueue_script' ), 1000 ); + add_action( 'update_option_active_plugins', array( $this, 'flush_related_plugins_transient' ) ); + add_action( 'update_option_active_sitewide_plugins', array( $this, 'flush_related_plugins_transient' ) ); + } + + /** + * Enqueue Support Port script if user has it enabled and we're on a GravityView plugin page + * + * @uses gravityview_is_admin_page() + * @uses wp_enqueue_script() + * @since 1.15 + * + * @return void + */ + static function maybe_enqueue_script( $hook ) { + global $pagenow; + + // Don't show if not GravityView page, or if we're on the Widgets page + if ( ! gravityview_is_admin_page( $hook ) || $pagenow === 'widgets.php' ) { + return; + } + + /** + * @filter `gravityview/support_port/display` Whether to display Support Port + * @since 1.15 + * + * @param boolean $display_beacon Default: `true` + */ + $display_support_port = apply_filters( 'gravityview/support_port/display', self::show_for_user() ); + + if ( empty( $display_support_port ) ) { + do_action( 'gravityview_log_debug', __METHOD__ . ' - Not showing Support Port' ); + + return; + } + + $script_debug = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; + + wp_enqueue_script( 'gravityview-support', plugins_url( 'assets/js/support' . $script_debug . '.js', GRAVITYVIEW_FILE ), array(), GravityView_Plugin::version, true ); + + self::_localize_script(); + } + + /** + * Localize the Support Port script + * + * @uses wp_localize_script() + * @since 1.15 + * @return void + */ + private static function _localize_script() { + + $translation = array( + 'agentLabel' => __( 'GravityView Support', 'gravityview' ), + 'searchLabel' => __( 'Search GravityView Docs', 'gravityview' ), + 'searchErrorLabel' => __( 'Your search timed out. Please double-check your internet connection and try again.', 'gravityview' ), + 'noResultsLabel' => _x( 'No results found for', 'a support form search has returned empty for the following word', 'gravityview' ), + 'contactLabel' => __( 'Contact Support', 'gravityview' ), + 'attachFileLabel' => __( 'Attach a screenshot or file', 'gravityview' ), + 'attachFileError' => __( 'The maximum file size is 10 MB', 'gravityview' ), + 'nameLabel' => __( 'Your Name', 'gravityview' ), + 'nameError' => __( 'Please enter your name', 'gravityview' ), + 'emailLabel' => __( 'Email address', 'gravityview' ), + 'emailError' => __( 'Please enter a valid email address', 'gravityview' ), + 'subjectLabel' => __( 'Subject', 'gravityview' ), + 'subjectError' => _x( 'Please enter a subject', 'Error shown when submitting support request and there is no subject provided', 'gravityview' ), + 'messageLabel' => __( 'How can we help you?', 'gravityview' ), + 'messageError' => _x( 'Please enter a message', 'Error shown when submitting support request and there is no message provided', 'gravityview' ), + 'contactSuccessLabel' => __( 'Message sent!', 'gravityview' ), + 'contactSuccessDescription' => __( 'Thanks for reaching out! Someone from the GravityView team will get back to you soon.', 'gravityview' ), + #'topicLabel' => __('Select a topic', 'gravityview' ), // Not yet implemented + #'topicError' => __('Please select a topic from the list', 'gravityview' ), // Not yet implemented + ); + + $response = GravityView_Settings::getSetting( 'license_key_response' ); + + $response = wp_parse_args( $response, array( + 'license' => '', + 'message' => '', + 'license_key' => '', + 'license_limit' => '', + 'expires' => '', + 'activations_left' => '', + 'site_count' => '', + 'payment_id' => '', + 'customer_name' => '', + 'customer_email' => '', + ) ); + + // This is just HTML we don't need. + unset( $response['message'] ); + + switch ( intval( $response['license_limit'] ) ) { + case 1: + $package = 'Sol'; + break; + case 100: + $package = 'Galactic'; + break; + case 3: + $package = 'Interstellar'; + break; + default: + $package = sprintf( '%d-Site License', $response['license_limit'] ); + } + + $data = array( + 'email' => GravityView_Settings::getSetting( 'support-email' ), + 'name' => $response['customer_name'], + 'Valid License?' => ucwords( $response['license'] ), + 'License Key' => $response['license_key'], + 'License Level' => $package, + 'Site Admin Email' => get_bloginfo( 'admin_email' ), + 'Support Email' => GravityView_Settings::getSetting( 'support-email' ), + 'License Limit' => $response['license_limit'], + 'Site Count' => $response['site_count'], + 'License Expires' => $response['expires'], + 'Activations Left' => $response['activations_left'], + 'Payment ID' => $response['payment_id'], + 'Payment Name' => $response['customer_name'], + 'Payment Email' => $response['customer_email'], + 'WordPress Version' => get_bloginfo( 'version', 'display' ), + 'PHP Version' => phpversion(), + 'GravityView Version' => GravityView_Plugin::version, + 'Gravity Forms Version' => GFForms::$version, + 'Plugins & Extensions' => self::get_related_plugins_and_extensions(), + ); + + $localization_data = array( + 'contactEnabled' => (int)current_user_can( 'gravityview_contact_support' ), // @todo use GVCommon::has_cap() after merge + 'data' => $data, + 'translation' => $translation, + ); + + wp_localize_script( 'gravityview-support', 'gvSupport', $localization_data ); + + unset( $localization_data, $data, $translation, $response, $package ); + } + + /** + * Get active GravityView Extensions and Gravity Forms Add-ons to help debug issues. + * + * @since 1.15 + * @return string List of active extensions related to GravityView or Gravity Forms, separated by HTML line breaks + */ + static private function get_related_plugins_and_extensions() { + + if ( ! function_exists( 'wp_get_active_and_valid_plugins' ) ) { + return 'Running < WP 3.0'; + } + + $extensions = (array)get_site_transient( self::related_plugins_key ); + + if ( empty( $extensions ) ) { + + $active_plugins = wp_get_active_and_valid_plugins(); + + foreach ( $active_plugins as $active_plugin ) { + + // Match gravityview, gravity-forms, gravityforms, gravitate + if ( ! preg_match( '/(gravityview|gravity-?forms|gravitate)/ism', $active_plugin ) ) { + continue; + } + + $plugin_data = get_plugin_data( $active_plugin ); + + $extensions[] = sprintf( '%s %s', $plugin_data['Name'], $plugin_data['Version'] ); + } + + if( $extensions ) { + set_site_transient( self::related_plugins_key, $extensions, HOUR_IN_SECONDS ); + } else { + return 'There was an error fetching related plugins.'; + } + } + + return implode( '
', $extensions ); + } + + /** + * When a plugin is activated or deactivated, delete the cached extensions/plugins used by get_related_plugins_and_extensions() + * + * @see get_related_plugins_and_extensions() + * @since 1.15 + */ + public function flush_related_plugins_transient() { + if ( function_exists( 'delete_site_transient' ) ) { + delete_site_transient( self::related_plugins_key ); + } + } + + /** + * Check whether to show Support for a user + * + * If the user doesn't have the `gravityview_support_port` capability, returns false. + * If there is no preference set for the user, use the global plugin setting. + * + * @since 1.15 + * + * @param int $user Optional. ID of the user to check, defaults to 0 for current user. + * + * @return bool Whether to show GravityView support + */ + static public function show_for_user( $user = 0 ) { + + if ( ! GVCommon::has_cap( 'gravityview_support_port' ) ) { + return false; + } + + $pref = get_user_option( self::user_pref_name, $user ); + + // Not set; default to plugin setting + if ( false === $pref ) { + return GravityView_Settings::getSetting( 'support_port' ); + } + + return ! empty( $pref ); + } + + + /** + * Update User Profile preferences for GravityView Support + * + * @since 1.5 + * + * @param int $user_id + * + * @return void + */ + public function update_user_meta_value( $user_id ) { + if ( current_user_can( 'edit_user', $user_id ) && isset( $_POST[ self::user_pref_name ] ) ) { + update_user_meta( $user_id, self::user_pref_name, intval( $_POST[ self::user_pref_name ] ) ); + } + } + + /** + * Modify User Profile + * + * Modifies the output of profile.php to add GravityView Support preference + * + * @since 1.15 + * + * @param WP_User $user Current user info + * + * @return void + */ + function user_field( $user ) { + + /** + * @filter `gravityview/support_port/show_profile_setting` Should the "GravityView Support Port" setting be shown on user profiles? + * @todo use GVCommon::has_cap() after merge + * @since 1.15 + * + * @param boolean $allow_profile_setting Default: `true`, if the user has the `gravityview_support_port` capability, which defaults to true for Contributors and higher + * @param WP_User $user Current user object + */ + $allow_profile_setting = apply_filters( 'gravityview/support_port/show_profile_setting', current_user_can( 'gravityview_support_port' ), $user ); + + if ( $allow_profile_setting && current_user_can( 'edit_user', $user->ID ) ) { + ?> + + + + + + + +
+
+ + +
+
+ @@ -34,9 +34,9 @@ if( !empty( $forms ) ) { ?> diff --git a/includes/class-admin-add-shortcode.php b/includes/class-admin-add-shortcode.php index c89982fca7..a948daa9e8 100644 --- a/includes/class-admin-add-shortcode.php +++ b/includes/class-admin-add-shortcode.php @@ -55,7 +55,7 @@ function add_shortcode_button() { return; } ?> - "> + "> get_error_message() ); + $result = false; + } + + } exit( $result ); } @@ -481,6 +507,11 @@ static public function get_approved_column( $form ) { static public function add_entry_approved_hidden_input( $form_id, $field_id, $value, $entry, $query_string ) { + + if( ! GVCommon::has_cap( 'gravityview_moderate_entries', $entry['id'] ) ) { + return; + } + if( empty( $entry['id'] ) ) { return; } @@ -529,7 +560,8 @@ function add_scripts_and_styles( $hook ) { wp_localize_script( 'gravityview_gf_entries_scripts', 'gvGlobals', array( 'nonce' => wp_create_nonce( 'gravityview_ajaxgfentries'), 'form_id' => $form_id, - 'show_column' => $this->show_approve_entry_column( $form_id ), + 'show_column' => (int)$this->show_approve_entry_column( $form_id ), + 'add_bulk_action' => (int)GVCommon::has_cap( 'gravityview_moderate_entries' ), 'label_approve' => __( 'Approve', 'gravityview' ) , 'label_disapprove' => __( 'Disapprove', 'gravityview' ), 'bulk_message' => $this->bulk_update_message, @@ -554,7 +586,7 @@ function add_scripts_and_styles( $hook ) { */ private function show_approve_entry_column( $form_id ) { - $show_approve_column = true; + $show_approve_column = GVCommon::has_cap( 'gravityview_moderate_entries' ); /** * @filter `gravityview/approve_entries/hide-if-no-connections` Return true to hide reject/approve if there are no connected Views diff --git a/includes/class-admin-views.php b/includes/class-admin-views.php index bbc98aceb7..d2f37b90d3 100644 --- a/includes/class-admin-views.php +++ b/includes/class-admin-views.php @@ -53,6 +53,55 @@ function __construct() { add_action( 'manage_gravityview_posts_custom_column', array( $this, 'add_custom_column_content'), 10, 2 ); + add_action( 'restrict_manage_posts', array( $this, 'add_view_dropdown' ) ); + + add_action( 'pre_get_posts', array( $this, 'filter_pre_get_posts_by_gravityview_form_id' ) ); + + } + + /** + * @since 1.15 + * @param WP_Query $query + */ + public function filter_pre_get_posts_by_gravityview_form_id( &$query ) { + global $pagenow; + + if ( !is_admin() ) { + return; + } + + if( 'edit.php' !== $pagenow || ! rgget( 'gravityview_form_id' ) || ! isset( $query->query_vars[ 'post_type' ] ) ) { + return; + } + + if ( $query->query_vars[ 'post_type' ] == 'gravityview' ) { + $query->set( 'meta_query', array( + array( + 'key' => '_gravityview_form_id', + 'value' => rgget( 'gravityview_form_id' ), + ) + ) ); + } + } + + function add_view_dropdown() { + $current_screen = get_current_screen(); + + if( 'gravityview' !== $current_screen->post_type ) { + return; + } + + $forms = gravityview_get_forms(); + $current_form = rgget( 'gravityview_form_id' ); + // If there are no forms to select, show no forms. + if( !empty( $forms ) ) { ?> + + '#', - 'label' => '', - 'menu_class' => 'hidden', - 'capabilities' => '', - ) - ); - + $sub_menu_items = array(); foreach ( (array)$connected_views as $view ) { + + if( ! GVCommon::has_cap( 'edit_gravityview', $view->ID ) ) { + continue; + } + $label = empty( $view->post_title ) ? sprintf( __('No Title (View #%d)', 'gravityview' ), $view->ID ) : $view->post_title; + $sub_menu_items[] = array( - 'url' => admin_url( 'post.php?action=edit&post='.$view->ID ), 'label' => esc_attr( $label ), - 'capabilities' => current_user_can( 'edit_post', $view->ID ), + 'url' => admin_url( 'post.php?action=edit&post='.$view->ID ), ); } - $menu_items['gravityview'] = array( - 'label' => __( 'Connected Views', 'gravityview' ), - 'icon' => '', - 'title' => __('GravityView Views using this form as a data source', 'gravityview'), - 'url' => '#', - 'onclick' => 'return false;', - 'menu_class' => 'gv_connected_forms gf_form_toolbar_settings', - 'link_class' => ( 1 === 1 ? '' : 'gf_toolbar_disabled' ), - 'sub_menu_items' => $sub_menu_items, - 'capabilities' => array(), - 'priority' => 0 - ); + // If there were no items added, then let's create the parent menu + if( $sub_menu_items ) { + + // Make sure Gravity Forms uses the submenu; if there's only one item, it uses a link instead of a dropdown + $sub_menu_items[] = array( + 'url' => '#', + 'label' => '', + 'menu_class' => 'hidden', + 'capabilities' => '', + ); + + $menu_items['gravityview'] = array( + 'label' => __( 'Connected Views', 'gravityview' ), + 'icon' => '', + 'title' => __( 'GravityView Views using this form as a data source', 'gravityview' ), + 'url' => '#', + 'onclick' => 'return false;', + 'menu_class' => 'gv_connected_forms gf_form_toolbar_settings', + 'link_class' => ( 1 === 1 ? '' : 'gf_toolbar_disabled' ), + 'sub_menu_items' => $sub_menu_items, + 'priority' => 0, + 'capabilities' => array( 'edit_gravityviews' ), + ); + } return $menu_items; } @@ -287,26 +343,27 @@ static public function get_connected_form_links( $form, $include_form_link = tru } $form_id = $form['id']; - $form_link = ''; $links = array(); - if( GFCommon::current_user_can_any('gravityforms_edit_forms') ) { + if( GVCommon::has_cap( 'gravityforms_edit_forms' ) ) { $form_url = admin_url( sprintf( 'admin.php?page=gf_edit_forms&id=%d', $form_id ) ); $form_link = ''.gravityview_get_link( $form_url, $form['title'], 'class=row-title' ).''; $links[] = ''.gravityview_get_link( $form_url, __('Edit Form', 'gravityview') ).''; + } else { + $form_link = ''. esc_html( $form['title'] ). ''; } - if( GFCommon::current_user_can_any('gravityforms_view_entries') ) { + if( GVCommon::has_cap( 'gravityforms_view_entries' ) ) { $entries_url = admin_url( sprintf( 'admin.php?page=gf_entries&id=%d', $form_id ) ); $links[] = ''.gravityview_get_link( $entries_url, __('Entries', 'gravityview') ).''; } - if( GFCommon::current_user_can_any('gravityforms_edit_settings') ) { + if( GVCommon::has_cap( array( 'gravityforms_edit_settings', 'gravityview_view_settings' ) ) ) { $settings_url = admin_url( sprintf( 'admin.php?page=gf_edit_forms&view=settings&id=%d', $form_id ) ); $links[] = ''.gravityview_get_link( $settings_url, __('Settings', 'gravityview'), 'title='.__('Edit settings for this form', 'gravityview') ).''; } - if( GFCommon::current_user_can_any( array("gravityforms_edit_forms", "gravityforms_create_form", "gravityforms_preview_forms") ) ) { + if( GVCommon::has_cap( array("gravityforms_edit_forms", "gravityforms_create_form", "gravityforms_preview_forms") ) ) { $preview_url = site_url( sprintf( '?gf_page=preview&id=%d', $form_id ) ); $links[] = ''.gravityview_get_link( $preview_url, __('Preview Form', 'gravityview'), 'title='.__('Preview this form', 'gravityview') ).''; } @@ -342,9 +399,20 @@ public function add_post_type_columns( $columns ) { $date = $columns['date']; unset( $columns['date'] ); - $columns['gv_connected_form'] = __('Data Source', 'gravityview'); + $data_source_required_caps = array( + 'gravityforms_edit_forms', + 'gravityforms_view_entries', + 'gravityforms_edit_settings', + 'gravityview_view_settings', + 'gravityforms_create_form', + 'gravityforms_preview_forms', + ); + + if( GVCommon::has_cap( $data_source_required_caps ) ) { + $columns['gv_connected_form'] = __( 'Data Source', 'gravityview' ); + } - $columns['gv_template'] = __('Template', 'gravityview'); + $columns['gv_template'] = _x( 'Template', 'Column title that shows what template is being used for Views', 'gravityview' ); // Add the date back in. $columns['date'] = $date; @@ -356,7 +424,7 @@ public function add_post_type_columns( $columns ) { * Save View configuration * * @access public - * @param mixed $post_id + * @param int $post_id Currently saved Post ID * @return void */ function save_postdata( $post_id ) { @@ -369,13 +437,11 @@ function save_postdata( $post_id ) { if ( ! isset( $_POST['post_type'] ) || 'gravityview' != $_POST['post_type'] ) { return; } - // validate user can edit and save post/page - if ( 'page' == $_POST['post_type'] ) { - if ( ! current_user_can( 'edit_page', $post_id ) ) - return; - } else { - if ( ! current_user_can( 'edit_post', $post_id ) ) - return; + + // validate user can edit and save View + if ( ! GVCommon::has_cap( 'edit_gravityview', $post_id ) ) { + do_action( 'gravityview_log_error', __METHOD__ . ' - Current user does not have the capability to edit View #' . $post_id, wp_get_current_user() ); + return; } do_action( 'gravityview_log_debug', '[save_postdata] Saving View post type.', $_POST ); @@ -391,6 +457,11 @@ function save_postdata( $post_id ) { } + if( false === GVCommon::has_cap( 'gravityforms_create_form' ) && empty( $statii['form_id'] ) ) { + do_action( 'gravityview_log_error', __METHOD__ . ' - Current user does not have the capability to create a new Form.', wp_get_current_user() ); + return; + } + // Was this a start fresh? if ( ! empty( $_POST['gravityview_form_id_start_fresh'] ) ) { $statii['start_fresh'] = add_post_meta( $post_id, '_gravityview_start_fresh', 1 ); @@ -900,84 +971,6 @@ function render_directory_active_areas( $template_id = '', $context = 'single', return $output; } - /** - * Chatlio.com customer support widget - */ - static function enqueue_feedback_widget() { - - /** - * @filter `gravityview/admin/display_live_chat` Whether to display live chat support widget when operators are available - * @since 1.13.1 - * @param boolean $display_live_chat Default: `true` - */ - $display_live_chat = apply_filters( 'gravityview/admin/display_live_chat', true ); - - if( ! $display_live_chat ) { - return; - } - - $script_debug = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? '' : '.min'; - - $response = GravityView_Settings::getSetting( 'license_key_response' ); - - $response = wp_parse_args( $response, array( - 'license' => '', - 'message' => '', - 'license_key' => '', - 'license_limit' => '', - 'expires' => '', - 'activations_left' => '', - 'site_count' => '', - 'payment_id' => '', - 'customer_name' => '', - 'customer_email' => '', - ) ); - - // This is just HTML we don't need. - unset( $response['message'] ); - - switch( intval( $response['license_limit'] ) ) { - case 1: - $package = 'Sol'; - break; - case 100: - $package = 'Galactic'; - break; - default: - case 3: - $package = 'Interstellar'; - break; - } - - $chat_settings = array( - 'onlineTitle' => __('Ask GravityView Support', 'gravityview'), - 'offlineTitle' => __('GravityView Support', 'gravityview'), - 'offlineGreeting' => __('If you have any questions, send us an email and we will get respond to you as soon as possible.', 'gravityview'), - 'offlineNamePlaceholder' => __('Your Name', 'gravityview'), - "autoResponseMessage" => sprintf( __('Question or comment? We are online and ready to answer! If you don\'t hear back from us, you can send your question to %s', 'gravityview'), 'support@gravityview.co' ), - "agentLabel" => __('GravityView Support', 'gravityview'), - 'css' => plugins_url( 'assets/css/feedback.css', GRAVITYVIEW_FILE ), - ); - - wp_enqueue_script( 'gravityview-feedback-widget', plugins_url('assets/js/feedback'.$script_debug.'.js', GRAVITYVIEW_FILE), array('jquery'), GravityView_Plugin::version, true); - - wp_localize_script( 'gravityview-feedback-widget', 'gvFeedback', array( - 'Valid License?' => ucwords( $response['license'] ), - 'License Key' => $response['license_key'], - 'License Level' => $package, - 'Site Admin Email' => get_bloginfo( 'admin_email' ), - 'Support Email' => GravityView_Settings::getSetting( 'support-email' ), - 'License Limit' => $response['license_limit'], - 'Site Count' => $response['site_count'], - 'License Expires' => $response['expires'], - 'License Activations Left' => $response['activations_left'], - 'Payment ID' => $response['payment_id'], - 'Payment Name' => $response['customer_name'], - 'Payment Email' => $response['customer_email'], - 'chat_settings' => json_encode( $chat_settings ), - )); - } - /** * Enqueue scripts and styles at Views editor * @@ -998,14 +991,6 @@ static function add_scripts_and_styles( $hook ) { // Don't process any scripts below here if it's not a GravityView page. if( !gravityview_is_admin_page($hook) && !$is_widgets_page ) { return; } - - if( !$is_widgets_page ) { - - // Add the Chatlio widget on all GV pages - self::enqueue_feedback_widget(); - - } - // Only enqueue the following on single pages if( gravityview_is_admin_page($hook, 'single') || $is_widgets_page ) { @@ -1063,7 +1048,7 @@ function register_no_conflict( $registered ) { $filter = current_filter(); if( preg_match('/script/ism', $filter ) ) { - $allow_scripts = array( 'jquery-ui-core', 'jquery-ui-dialog', 'jquery-ui-tabs', 'jquery-ui-draggable', 'jquery-ui-droppable', 'jquery-ui-sortable', 'jquery-ui-tooltip', 'gravityview_views_scripts', 'gravityview-feedback-widget', 'gravityview-jquery-cookie', 'gravityview_views_datepicker', + $allow_scripts = array( 'jquery-ui-core', 'jquery-ui-dialog', 'jquery-ui-tabs', 'jquery-ui-draggable', 'jquery-ui-droppable', 'jquery-ui-sortable', 'jquery-ui-tooltip', 'gravityview_views_scripts', 'gravityview-support', 'gravityview-jquery-cookie', 'gravityview_views_datepicker', 'sack', 'gform_gravityforms', 'gform_forms', 'gform_form_admin', 'jquery-ui-autocomplete' ); $registered = array_merge( $registered, $allow_scripts ); } elseif( preg_match('/style/ism', $filter ) ) { diff --git a/includes/class-admin-welcome.php b/includes/class-admin-welcome.php index 1bc6d7320c..e1d52d1f28 100644 --- a/includes/class-admin-welcome.php +++ b/includes/class-admin-welcome.php @@ -26,7 +26,7 @@ class GravityView_Welcome { /** * @var string The capability users should have to view the page */ - public $minimum_capability = 'manage_options'; + public $minimum_capability = 'gravityview_getting_started'; /** * Get things started @@ -256,26 +256,75 @@ public function changelog_screen() {

-
+
-
Search bar labels
-

Custom Search Labels & Search Mode

-

You can now modify the search labels from the Search Bar configuration, and you can now choose whether you want your search to match all of the search field filters, or any.

+
Icons representing capabilities
+

Capability Management

+

Manage what users and roles have access to GravityView functionality. See what capabilities are available.

+ +
+

The {get} Merge Tag

+

Pass data using URLs and create even more powerful integrations with GravityView. Learn how to use {get}.

-
Clocks
-

Sort by Time

-

Now you can sort time fields! Why is that so exciting? Because Gravity Forms doesn't natively support sorting by time!

- -
Insert single list column
-

Display a single column from a Multiple-Column List field

-

Why is sorting by time being featured? Because Gravity Forms doesn't natively support sorting by time, but you can with GravityView!

+
The Support Port
+

Support Port

+

Users can easily search GravityView help docs. Administrators can use it to contact support. Just click the The Support Port icon looks like a question mark icon on GravityView screens to activate.


+

1.15 on October 15

+ +
    +
  • Added: {get} Merge Tag that allows passing data via URL to be safely displayed in Merge Tags. Learn how this works. + +
      +
    • Example: When adding ?first-name=Floaty to a URL, the Custom Content My name is {get:first-name} would be replaced with My name is Floaty
    • +
    +
  • +
  • Added: GravityView Capabilities: restrict access to GravityView functionality to certain users and roles. Learn more. + +
      +
    • Fixed: Users without the ability to create Gravity Forms forms are able to create a new form via "Start Fresh"
    • +
    • Only add the Approve Entries column if user has the gravityview_moderate_entries capability (defaults to Editor role or higher)
    • +
    • Fixed: Contributors now have access to the GravityView "Getting Started" screen
    • +
    +
  • +
  • Added: [gv_entry_link] shortcode to link directly to an entry. Learn more. + +
      +
    • Existing [gv_delete_entry_link] and [gv_edit_entry_link] shortcodes will continue to work
    • +
    +
  • +
  • Added: Ability to filter View by form in the Admin. Learn more.
  • +
  • Added: Option to delete GravityView data when the plugin is uninstalled, then deleted. Learn more.
  • +
  • Added: New support "Beacon" to easily search documentation and ask support questions
  • +
  • Added: Clear search button to the Search Widget (WP widget)
  • +
  • Fixed: number_format() PHP warning on blank Number fields
  • +
  • Fixed: {created_by} merge tags weren't being escaped using esc_html()
  • +
  • Fixed: Checkmark icons weren't always available when displaying checkbox input field
  • +
  • Fixed: When "Shorten Link Display" was enabled for Website fields, "Link Text" wasn't respected
  • +
  • Fixed: Only process "Create" Gravity Forms User Registration Addon feeds, by default the user role and the user display name format persist
  • +
  • Fixed: Error with List field Call to undefined method GF_Field::get_input_type()
  • +
  • Fixed: BuddyPress/bbPress bbp_setup_current_user() warning
  • +
  • Fixed: gravityview_is_admin_page() wasn't recognizing the Settings page as a GravityView admin page
  • +
  • Fixed: Custom Content Widgets didn't replace Merge Tags
  • +
  • Fixed: PHP Warnings
  • +
  • Fixed: WordPress Multisite fatal error when Gravity Forms not Network Activated
  • +
  • Tweak: Don't show Data Source column in Views screen to users who don't have permissions to see any of the data anyway
  • +
  • Tweak: Entry notes are now created using GravityView_Entry_Notes class
  • +
  • Tweak: Improved automated code testing
  • +
  • Tweak: Added gravityview/support_port/display filter to enable/disable displaying Support Port
  • +
  • Tweak: Added gravityview/support_port/show_profile_setting filter to disable adding the Support Port setting on User Profile pages
  • +
  • Tweak: Removed gravityview/admin/display_live_chat filter
  • +
  • Tweak: Removed gravityview_settings_capability filter
  • +
  • Tweak: Escape form name in dropdowns
  • +
+ +

1.14.2 & 1.14.3 on September 17

  • Fixed: Issue affecting Gravity Forms User Registration Addon. Passwords were being reset when an user edited their own entry.
  • @@ -499,7 +548,7 @@ public function credits_screen() { ?>

    -
    +

    Zack Katz

    diff --git a/includes/class-admin.php b/includes/class-admin.php index c79a050e2d..0081185ca0 100644 --- a/includes/class-admin.php +++ b/includes/class-admin.php @@ -4,26 +4,28 @@ class GravityView_Admin { function __construct() { - if( !is_admin() ) { return; } + if( ! is_admin() ) { return; } + // If Gravity Forms isn't active or compatibile, stop loading + if( false === GravityView_Compatibility::is_valid() ) { + return; + } + + $this->include_required_files(); $this->add_hooks(); } /** - * @since 1.7.5 + * @since 1.15 + * @return void */ - function add_hooks() { - - // If Gravity Forms isn't active or compatibile, stop loading - if( false === GravityView_Compatibility::is_valid() ) { - return; - } + private function include_required_files() { // Migrate Class require_once( GRAVITYVIEW_DIR . 'includes/class-migrate.php' ); // Don't load tooltips if on Gravity Forms, otherwise it overrides translations - if( !GFForms::is_gravity_page() ) { + if( class_exists( 'GFCommon' ) && class_exists( 'GFForms' ) && !GFForms::is_gravity_page() ) { require_once( GFCommon::get_base_path() . '/tooltips.php' ); } @@ -31,8 +33,18 @@ function add_hooks() { require_once( GRAVITYVIEW_DIR . 'includes/admin/entry-list.php' ); require_once( GRAVITYVIEW_DIR . 'includes/class-change-entry-creator.php' ); + /** @since 1.15 **/ + require_once( GRAVITYVIEW_DIR . 'includes/admin/class-gravityview-support-port.php' ); + /** @since 1.6 */ require_once( GRAVITYVIEW_DIR . 'includes/class-gravityview-admin-duplicate-view.php' ); + } + + /** + * @since 1.7.5 + * @return void + */ + private function add_hooks() { // Filter Admin messages add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) ); @@ -81,19 +93,25 @@ public function backend_actions() { /** * Modify plugin action links at plugins screen * + * @since 1.15 Added check for `gravityview_view_settings` and `gravityview_support_port` capabilities * @access public * @static - * @param mixed $links - * @return array Action links with Support included + * @param array $links Array of action links under GravityView on the plugin page + * @return array Action links with Settings and Support included, if the user has the appropriate caps */ public static function plugin_action_links( $links ) { - $action = array( - sprintf( '%s', admin_url( 'edit.php?post_type=gravityview&page=gravityview_settings' ), esc_html__( 'Settings', 'gravityview' ) ), - '' . esc_html__( 'Support', 'gravityview' ) . '' - ); + $actions = array(); + + if( GVCommon::has_cap( 'gravityview_view_settings' ) ) { + $actions[] = sprintf( '%s', admin_url( 'edit.php?post_type=gravityview&page=gravityview_settings' ), esc_html__( 'Settings', 'gravityview' ) ); + } - return array_merge( $action, $links ); + if( GVCommon::has_cap( 'gravityview_support_port' ) ) { + $actions[] = '' . esc_html__( 'Support', 'gravityview' ) . ''; + } + + return array_merge( $actions, $links ); } /** @@ -455,6 +473,8 @@ static function is_admin_page($hook = '', $page = NULL) { $is_gv_post_type_get = (isset($_GET['post_type']) && $_GET['post_type'] === 'gravityview'); + $is_gv_settings_get = isset( $_GET['page'] ) && $_GET['page'] === 'gravityview_settings'; + if( empty( $post ) && $pagenow === 'post.php' && !empty( $_GET['post'] ) ) { $gv_post = get_post( intval( $_GET['post'] ) ); $is_gv_post_type = (!empty($gv_post) && !empty($gv_post->post_type) && $gv_post->post_type === 'gravityview'); @@ -462,12 +482,12 @@ static function is_admin_page($hook = '', $page = NULL) { $is_gv_post_type = (!empty($post) && !empty($post->post_type) && $post->post_type === 'gravityview'); } - if( $is_gv_screen || $is_gv_post_type || $is_gv_post_type || $is_gv_post_type_get ) { + if( $is_gv_screen || $is_gv_post_type || $is_gv_post_type || $is_gv_post_type_get || $is_gv_settings_get ) { // $_GET `post_type` variable if(in_array($pagenow, array( 'post.php' , 'post-new.php' )) ) { $is_page = 'single'; - } elseif ( $plugin_page === 'gravityview_settings' || ( !empty( $_GET['page'] ) && $_GET['page'] === 'gravityview_settings' ) ) { + } else if ( in_array( $plugin_page, array( 'gravityview_settings', 'gravityview_page_gravityview_settings' ) ) || ( !empty( $_GET['page'] ) && $_GET['page'] === 'gravityview_settings' ) ) { $is_page = 'settings'; } else { $is_page = 'views'; diff --git a/includes/class-ajax.php b/includes/class-ajax.php index f5b0e9b3d7..7757ea32ec 100644 --- a/includes/class-ajax.php +++ b/includes/class-ajax.php @@ -22,6 +22,27 @@ function __construct() { add_action( 'wp_ajax_gv_sortable_fields_form', array( $this, 'get_sortable_fields' ) ); } + /** + * Handle exiting the script (for unit testing) + * + * @since 1.15 + * @param bool|false $mixed + * + * @return bool + */ + private function _exit( $mixed = NULL ) { + + /** + * Don't exit if we're running test suite. + * @since 1.15 + */ + if( defined( 'DOING_GRAVITYVIEW_TESTS' ) && DOING_GRAVITYVIEW_TESTS ) { + return $mixed; + } + + exit( $mixed ); + } + /** -------- AJAX ---------- */ /** @@ -30,7 +51,7 @@ function __construct() { */ function check_ajax_nonce() { if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'gravityview_ajaxviews' ) ) { - exit( false ); + $this->_exit( false ); } } @@ -51,15 +72,15 @@ function get_available_fields_html() { // If Form was changed, JS sends form ID, if start fresh, JS sends template_id if( !empty( $_POST['form_id'] ) ) { do_action( 'gravityview_render_available_fields', (int) $_POST['form_id'], $context ); - exit(); + $this->_exit(); } elseif( !empty( $_POST['template_id'] ) ) { $form = GravityView_Ajax::pre_get_form_fields( $_POST['template_id'] ); do_action( 'gravityview_render_available_fields', $form, $context ); - exit(); + $this->_exit(); } //if everything fails.. - exit( false ); + $this->_exit( false ); } @@ -74,7 +95,7 @@ function get_active_areas() { $this->check_ajax_nonce(); if( empty( $_POST['template_id'] ) ) { - exit( false ); + $this->_exit( false ); } ob_start(); @@ -87,7 +108,7 @@ function get_active_areas() { $response = array_map( 'gravityview_strip_whitespace', $response ); - exit( json_encode( $response ) ); + $this->_exit( json_encode( $response ) ); } /** @@ -99,7 +120,7 @@ function get_preset_fields_config() { $this->check_ajax_nonce(); if( empty( $_POST['template_id'] ) ) { - exit( false ); + $this->_exit( false ); } // get the fields xml config file for this specific preset @@ -140,7 +161,7 @@ function get_preset_fields_config() { do_action( 'gravityview_log_debug', '[get_preset_fields_config] AJAX Response', $response ); - exit( json_encode( $response ) ); + $this->_exit( json_encode( $response ) ); } /** @@ -154,7 +175,7 @@ function create_preset_form() { if( empty( $_POST['template_id'] ) ) { do_action( 'gravityview_log_error', '[create_preset_form] Cannot create preset form; the template_id is empty.' ); - exit( false ); + $this->_exit( false ); } // get the xml for this specific template_id @@ -164,34 +185,35 @@ function create_preset_form() { $form = $this->import_form( $preset_form_xml_path ); // get the form ID - if( $form === false ) { + if( false === $form ) { // send error to user do_action( 'gravityview_log_error', '[create_preset_form] Error importing form for template id: ' . (int) $_POST['template_id'] ); - exit( false ); + $this->_exit( false ); } - exit( '' ); + $this->_exit( '' ); } /** - * Import Gravity Form XML - * @param string $xml_path Path to form xml file - * @return int | bool Imported form ID or false + * Import Gravity Form XML or JSON + * + * @param string $xml_or_json_path Path to form XML or JSON file + * @return int|bool Imported form ID or false */ - function import_form( $xml_path = '' ) { + function import_form( $xml_or_json_path = '' ) { - do_action( 'gravityview_log_debug', '[import_form] Import Preset Form. (File)', $xml_path ); + do_action( 'gravityview_log_debug', '[import_form] Import Preset Form. (File)', $xml_or_json_path ); - if( empty( $xml_path ) || !class_exists('GFExport') || !file_exists( $xml_path ) ) { - do_action( 'gravityview_log_error', '[import_form] Class GFExport or file not found. file: ' , $xml_path ); + if( empty( $xml_or_json_path ) || !class_exists('GFExport') || !file_exists( $xml_or_json_path ) ) { + do_action( 'gravityview_log_error', '[import_form] Class GFExport or file not found. file: ', $xml_or_json_path ); return false; } // import form $forms = ''; - $count = GFExport::import_file( $xml_path, $forms ); + $count = GFExport::import_file( $xml_or_json_path, $forms ); do_action( 'gravityview_log_debug', '[import_form] Importing form (Result)', $count ); do_action( 'gravityview_log_debug', '[import_form] Importing form (Form) ', $forms ); @@ -218,24 +240,24 @@ function get_field_options() { if( empty( $_POST['template'] ) || empty( $_POST['area'] ) || empty( $_POST['field_id'] ) || empty( $_POST['field_type'] ) ) { do_action( 'gravityview_log_error', '[get_field_options] Required fields were not set in the $_POST request. ' ); - exit( false ); + $this->_exit( false ); } // Fix apostrophes added by JSON response - $post = array_map( 'stripslashes_deep', $_POST ); + $_post = array_map( 'stripslashes_deep', $_POST ); // Sanitize - $post = array_map( 'esc_attr', $post ); + $_post = array_map( 'esc_attr', $_post ); // The GF type of field: `product`, `name`, `creditcard`, `id`, `text` - $input_type = isset($post['input_type']) ? esc_attr( $post['input_type'] ) : NULL; - $context = isset($post['context']) ? esc_attr( $post['context'] ) : NULL; + $input_type = isset($_post['input_type']) ? esc_attr( $_post['input_type'] ) : NULL; + $context = isset($_post['context']) ? esc_attr( $_post['context'] ) : NULL; - $response = GravityView_Render_Settings::render_field_options( $post['field_type'], $post['template'], $post['field_id'], $post['field_label'], $post['area'], $input_type, '', '', $context ); + $response = GravityView_Render_Settings::render_field_options( $_post['field_type'], $_post['template'], $_post['field_id'], $_post['field_label'], $_post['area'], $input_type, '', '', $context ); $response = gravityview_strip_whitespace( $response ); - exit( $response ); + $this->_exit( $response ); } /** @@ -256,7 +278,9 @@ function get_sortable_fields() { $form = (int) $_POST['form_id']; - } elseif( !empty( $_POST['template_id'] ) ) { + } + // get form from preset + elseif( !empty( $_POST['template_id'] ) ) { $form = GravityView_Ajax::pre_get_form_fields( $_POST['template_id'] ); @@ -266,7 +290,7 @@ function get_sortable_fields() { $response = gravityview_strip_whitespace( $response ); - exit( $response ); + $this->_exit( $response ); } /** @@ -277,19 +301,28 @@ function get_sortable_fields() { static function pre_get_form_fields( $template_id = '') { if( empty( $template_id ) ) { + do_action( 'gravityview_log_error', __METHOD__ . ' - Template ID not set.' ); return false; } else { $form_file = apply_filters( 'gravityview_template_formxml', '', $template_id ); if( !file_exists( $form_file ) ) { - do_action( 'gravityview_log_error', '[pre_get_available_fields] Importing Form Fields for preset ['. $template_id .']. File not found. file: ' . $form_file ); + do_action( 'gravityview_log_error', __METHOD__ . ' - Importing Form Fields for preset ['. $template_id .']. File not found. file: ' . $form_file ); return false; } } // Load xml parser (from GravityForms) - $xml_parser = trailingslashit( WP_PLUGIN_DIR ) . 'gravityforms/xml.php'; + if( class_exists( 'GFCommon' ) ) { + $xml_parser = GFCommon::get_base_path() . '/xml.php'; + } else { + $xml_parser = trailingslashit( WP_PLUGIN_DIR ) . 'gravityforms/xml.php'; + } + if( file_exists( $xml_parser ) ) { require_once( $xml_parser ); + } else { + do_action( 'gravityview_log_debug', __METHOD__ . ' - Gravity Forms XML Parser not found.', $xml_parser ); + return false; } // load file diff --git a/includes/class-api.php b/includes/class-api.php index a685e7a0c7..5d864044b7 100644 --- a/includes/class-api.php +++ b/includes/class-api.php @@ -484,7 +484,7 @@ public static function directory_link( $post_id = NULL, $add_query_args = true ) } // Deal with returning to proper pagination for embedded views - if( $add_query_args ) { + if( $link && $add_query_args ) { $args = array(); diff --git a/includes/class-cache.php b/includes/class-cache.php index 141323deab..fac58e7407 100644 --- a/includes/class-cache.php +++ b/includes/class-cache.php @@ -495,7 +495,7 @@ public function use_cache() { $use_cache = true; - if ( current_user_can( 'edit_posts' ) ) { + if ( GVCommon::has_cap( 'edit_gravityviews' ) ) { if ( isset( $_GET['cache'] ) || isset( $_GET['nocache'] ) ) { diff --git a/includes/class-change-entry-creator.php b/includes/class-change-entry-creator.php index 58da112c03..93351704e5 100644 --- a/includes/class-change-entry-creator.php +++ b/includes/class-change-entry-creator.php @@ -16,14 +16,19 @@ function __construct() { if( !is_admin() ) { return; } /** + * @filter `gravityview_disable_change_entry_creator` Disable the Change Entry Creator functionality * @since 1.7.4 - * @param boolean $disable Disable the Change Entry Creator functionality + * @param boolean $disable Disable the Change Entry Creator functionality. Default: false. */ if( apply_filters('gravityview_disable_change_entry_creator', false ) ) { return; } - add_action('plugins_loaded', array( $this, 'load'), 100 ); + /** + * Use `init` to fix bbPress warning + * @see https://bbpress.trac.wordpress.org/ticket/2309 + */ + add_action('init', array( $this, 'load'), 100 ); add_action('plugins_loaded', array( $this, 'prevent_conflicts') ); @@ -110,7 +115,7 @@ function load() { } // Can the user edit entries? - if( !GFCommon::current_user_can_any("gravityforms_edit_entries") ) { + if( ! GVCommon::has_cap( array( 'gravityforms_edit_entries', 'gravityview_edit_entries' ) ) ) { return; } diff --git a/includes/class-common.php b/includes/class-common.php index f39eb10bce..01109b3020 100644 --- a/includes/class-common.php +++ b/includes/class-common.php @@ -41,6 +41,23 @@ public static function get_form( $form_id ) { return false; } + /** + * Alias of GravityView_Roles_Capabilities::has_cap() + * + * @since 1.15 + * + * @see GravityView_Roles_Capabilities::has_cap() + * + * @param string|array $caps Single capability or array of capabilities + * @param int $object_id (optional) Parameter can be used to check for capabilities against a specific object, such as a post or user + * @param int|null $user_id (optional) Check the capabilities for a user who is not necessarily the currently logged-in user + * + * @return bool True: user has at least one passed capability; False: user does not have any defined capabilities + */ + public static function has_cap( $caps = '', $object_id = null, $user_id = null ) { + return GravityView_Roles_Capabilities::has_cap( $caps, $object_id, $user_id ); + } + /** * Return a Gravity Forms field array, whether using GF 1.9 or not * @@ -1181,12 +1198,14 @@ public static function gv_parse_str( $string, &$result ) { * Generate an HTML anchor tag with a list of supported attributes * * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a Supported attributes defined here + * @uses esc_url_raw() to sanitize $href + * @uses esc_attr() to sanitize $atts * * @since 1.6 * - * @param string $href URL of the link. + * @param string $href URL of the link. Sanitized using `esc_url_raw()` * @param string $anchor_text The text or HTML inside the anchor. This is not sanitized in the function. - * @param array $atts Attributes to be added to the anchor tag + * @param array $atts Attributes to be added to the anchor tag. They are sanitized using `esc_attr()` * * @return string HTML output of anchor link. If empty $href, returns NULL */ diff --git a/includes/class-data.php b/includes/class-data.php index 707a98e236..8acc3f4111 100644 --- a/includes/class-data.php +++ b/includes/class-data.php @@ -289,7 +289,7 @@ private function filter_fields( $dir_fields ) { /** - * Check wether a certain field should not be presented based on its own properties. + * Check whether a certain field should not be presented based on its own properties. * * @access public * @param array $properties @@ -298,7 +298,7 @@ private function filter_fields( $dir_fields ) { private function hide_field_check_conditions( $properties ) { // logged-in visibility - if( ! empty( $properties['only_loggedin'] ) && ! current_user_can( $properties['only_loggedin_cap'] ) ) { + if( ! empty( $properties['only_loggedin'] ) && ! GVCommon::has_cap( $properties['only_loggedin_cap'] ) ) { return true; } diff --git a/includes/class-frontend-views.php b/includes/class-frontend-views.php index 303d829c81..7935fb23ae 100644 --- a/includes/class-frontend-views.php +++ b/includes/class-frontend-views.php @@ -20,7 +20,7 @@ class GravityView_frontend { * @since 1.7.4.1 * @var array */ - private static $search_parameters = array( 'gv_search', 'gv_start', 'gv_end', 'gv_id', 'filter_*' ); + private static $search_parameters = array( 'gv_search', 'gv_start', 'gv_end', 'gv_id', 'gv_by', 'filter_*' ); /** * Is the currently viewed post a `gravityview` post type? @@ -554,6 +554,14 @@ public function render_view( $passed_args ) { return null; } + /** + * Don't render View if user isn't allowed to see it + * @since 1.15 + */ + if( is_user_logged_in() && false === GVCommon::has_cap( 'read_gravityview', $view_id ) ) { + return null; + } + ob_start(); /** @@ -878,7 +886,15 @@ public static function get_search_criteria( $args, $form_id ) { * * @uses gravityview_get_entries() * @access public - * @param mixed $args + * @param array $args\n + * - $id - View id + * - $page_size - Page + * - $sort_field - form field id to sort + * - $sort_direction - ASC / DESC + * - $start_date - Ymd + * - $end_date - Ymd + * - $class - assign a html class to the view + * - $offset (optional) - This is the start point in the current data set (0 index based). * @param int $form_id Gravity Forms Form ID * @return array Associative array with `count`, `entries`, and `paging` keys. `count` has the total entries count, `entries` is an array with Gravity Forms full entry data, `paging` is an array with `offset` and `page_size` keys */ @@ -925,13 +941,28 @@ public static function get_view_entries( $args, $form_id ) { ); /** - * Filter get entries criteria - * - * Passes and returns array with `search_criteria`, `sorting` and `paging` keys. - * - * @var array + * @filter `gravityview_get_entries` Filter get entries criteria + * @param array $parameters Array with `search_criteria`, `sorting` and `paging` keys. + * @param array $args View configuration args. { + * @type int $id View id + * @type int $page_size Number of entries to show per page + * @type string $sort_field Form field id to sort + * @type string $sort_direction Sorting direction ('ASC' or 'DESC') + * @type string $start_date - Ymd + * @type string $end_date - Ymd + * @type string $class - assign a html class to the view + * @type string $offset (optional) - This is the start point in the current data set (0 index based). + * } + * @param int $form_id ID of Gravity Forms form + */ + $parameters = apply_filters( 'gravityview_get_entries', $parameters, $args, $form_id ); + + /** + * @filter `gravityview_get_entries_{View ID}` Filter get entries criteria + * @param array $parameters Array with `search_criteria`, `sorting` and `paging` keys. + * @param array $args View configuration args. */ - $parameters = apply_filters( 'gravityview_get_entries', apply_filters( 'gravityview_get_entries_'.$args['id'], $parameters, $args, $form_id ), $args, $form_id ); + $parameters = apply_filters( 'gravityview_get_entries_'.$args['id'], $parameters, $args, $form_id ); do_action( 'gravityview_log_debug', '[get_view_entries] $parameters passed to gravityview_get_entries(): ', $parameters ); @@ -942,7 +973,7 @@ public static function get_view_entries( $args, $form_id ) { do_action( 'gravityview_log_debug', sprintf( '[get_view_entries] Get Entries. Found: %s entries', $count ), $entries ); /** - * Filter the entries output to the View + * @filter `gravityview_view_entries` Filter the entries output to the View * @deprecated since 1.5.2 * @param array $args View settings associative array * @var array @@ -950,11 +981,9 @@ public static function get_view_entries( $args, $form_id ) { $entries = apply_filters( 'gravityview_view_entries', $entries, $args ); /** - * Filter the entries output to the View - * - * @param array associative array containing count, entries & paging + * @filter `gravityview/view/entries` Filter the entries output to the View + * @param array $criteria associative array containing count, entries & paging * @param array $args View settings associative array - * * @since 1.5.2 */ return apply_filters( 'gravityview/view/entries', compact( 'count', 'entries', 'paging' ), $args ); @@ -1131,14 +1160,16 @@ public function add_scripts_and_styles() { $views = $this->getGvOutputData()->get_views(); - $js_localization = array( - 'cookiepath' => COOKIEPATH, - 'clear' => _x( 'Clear', 'Clear all data from the form', 'gravityview' ), - 'reset' => _x( 'Reset', 'Reset the search form to the state that existed on page load', 'gravityview' ), - ); - foreach ( $views as $view_id => $data ) { + /** + * Don't enqueue the scripts or styles if it's not going to be displayed. + * @since 1.15 + */ + if( is_user_logged_in() && false === GVCommon::has_cap( 'read_gravityview', $view_id ) ) { + continue; + } + // By default, no thickbox $js_dependencies = array( 'jquery', 'gravityview-jquery-cookie' ); $css_dependencies = array(); @@ -1159,6 +1190,15 @@ public function add_scripts_and_styles() { $css_dependencies[] = apply_filters( 'gravity_view_lightbox_style', 'thickbox' ); } + /** + * If the form has checkbox fields, enqueue dashicons + * @see https://github.com/katzwebservices/GravityView/issues/536 + * @since 1.15 + */ + if( gravityview_view_has_single_checkbox_or_radio( $data['form'], $data['fields'] ) ) { + $css_dependencies[] = 'dashicons'; + } + wp_register_script( 'gravityview-jquery-cookie', plugins_url( 'includes/lib/jquery-cookie/jquery_cookie.js', GRAVITYVIEW_FILE ), array( 'jquery' ), GravityView_Plugin::version, true ); $script_debug = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; @@ -1167,13 +1207,6 @@ public function add_scripts_and_styles() { wp_enqueue_script( 'gravityview-fe-view' ); - /** - * @filter `gravityview_js_localization` Modify the array passed to wp_localize_script() - * @param array $js_localization The data padded to the Javascript file - * @param array $data View data array with View settings - */ - $js_localization = apply_filters( 'gravityview_js_localization', $js_localization, $data ); - if ( ! empty( $data['atts']['sort_columns'] ) ) { wp_enqueue_style( 'gravityview_font', plugins_url( 'assets/css/font.css', GRAVITYVIEW_FILE ), $css_dependencies, GravityView_Plugin::version, 'all' ); } @@ -1185,6 +1218,20 @@ public function add_scripts_and_styles() { } if ( 'wp_print_footer_scripts' === current_filter() ) { + + $js_localization = array( + 'cookiepath' => COOKIEPATH, + 'clear' => _x( 'Clear', 'Clear all data from the form', 'gravityview' ), + 'reset' => _x( 'Reset', 'Reset the search form to the state that existed on page load', 'gravityview' ), + ); + + /** + * @filter `gravityview_js_localization` Modify the array passed to wp_localize_script() + * @param array $js_localization The data padded to the Javascript file + * @param array $data View data array with View settings + */ + $js_localization = apply_filters( 'gravityview_js_localization', $js_localization, $data ); + wp_localize_script( 'gravityview-fe-view', 'gvGlobals', $js_localization ); } } diff --git a/includes/class-gravityview-admin-bar.php b/includes/class-gravityview-admin-bar.php index 79898abbf1..fcde8e0cc4 100644 --- a/includes/class-gravityview-admin-bar.php +++ b/includes/class-gravityview-admin-bar.php @@ -53,7 +53,9 @@ function add_edit_entry_link() { /** @var WP_Admin_Bar $wp_admin_bar */ global $wp_admin_bar; - if ( GFCommon::current_user_can_any( 'gravityforms_edit_entries' ) && $this->gravityview_view->getSingleEntry() ) { + $entry_id = $this->gravityview_view->getSingleEntry(); + + if ( $entry_id && GVCommon::has_cap( array( 'gravityforms_edit_entries', 'gravityview_edit_entries' ), $entry_id ) ) { $entry = $this->gravityview_view->getEntry(); @@ -76,7 +78,7 @@ function add_edit_view_link() { /** @var WP_Admin_Bar $wp_admin_bar */ global $wp_admin_bar; - if( GFCommon::current_user_can_any('edit_pages') ) { + if( GVCommon::has_cap('edit_gravityviews') ) { $view_data = GravityView_View_Data::getInstance(); @@ -86,11 +88,14 @@ function add_edit_view_link() { // todo: Support multiple View embeds with a drop-down menu if ( ! $this->gravityview_view->isGravityviewPostType() && ! empty( $views ) && ! $view_data->has_multiple_views() ) { $view = reset( $views ); - $wp_admin_bar->add_menu( array( - 'id' => 'edit-view', - 'title' => __( 'Edit View', 'gravityview' ), - 'href' => esc_url_raw( admin_url( sprintf( 'post.php?post=%d&action=edit', $view['id'] ) ) ), - ) ); + + if( GVCommon::has_cap( 'edit_gravityview', $view['id'] ) ) { + $wp_admin_bar->add_menu( array( + 'id' => 'edit-view', + 'title' => __( 'Edit View', 'gravityview' ), + 'href' => esc_url_raw( admin_url( sprintf( 'post.php?post=%d&action=edit', $view['id'] ) ) ), + ) ); + } } } } diff --git a/includes/class-gravityview-admin-duplicate-view.php b/includes/class-gravityview-admin-duplicate-view.php index d56bc054c5..4c931ac3a2 100644 --- a/includes/class-gravityview-admin-duplicate-view.php +++ b/includes/class-gravityview-admin-duplicate-view.php @@ -24,6 +24,15 @@ function __construct() { return; } + $this->add_hooks(); + } + + /** + * Add actions & filters + * @since 1.15 + * @return void + */ + private function add_hooks() { add_filter( 'post_row_actions', array( $this, 'make_duplicate_link_row' ), 10, 2 ); /** @@ -36,7 +45,6 @@ function __construct() { add_action( 'gv_duplicate_view', array( $this, 'copy_view_meta_info' ), 10, 2 ); add_filter( 'gravityview_connected_form_links', array( $this, 'connected_form_links' ), 10, 2 ); - } /** @@ -48,14 +56,15 @@ function __construct() { * @return array If it's the All Views page, return unedited. Otherwise, add a link to create cloned draft of View */ function connected_form_links( $links = array(), $form = array() ) { + /** @global WP_Post $post */ global $post; // We only want to add Clone links to the Edit View metabox if( !$this->is_all_views_page() ) { - $duplicate_links = $this->make_duplicate_link_row( array(), $post ); - - $links[] = ''.$duplicate_links['edit_as_new_draft'].''; + if( $duplicate_links = $this->make_duplicate_link_row( array(), $post ) ) { + $links[] = '' . $duplicate_links['edit_as_new_draft'] . ''; + } } @@ -68,7 +77,7 @@ function connected_form_links( $links = array(), $form = array() ) { * * @return bool */ - function is_all_views_page() { + private function is_all_views_page() { global $pagenow; return $pagenow === 'edit.php'; @@ -79,25 +88,23 @@ function is_all_views_page() { * * @since 1.6 */ - function current_user_can_copy( $post ) { + private function current_user_can_copy( $post ) { - if( is_object( $post ) ) { - $id = $post->ID; - } else { - $id = $post; - } + $id = is_object( $post ) ? $post->ID : $post; // Can't edit this current View - return current_user_can( 'edit_posts', $id ); + return GVCommon::has_cap( 'copy_gravityviews', $id ); } /** * Create a duplicate from a View $post * + * @param WP_Post $post + * @param string $status The post status * @since 1.6 */ - function create_duplicate( $post, $status = '' ) { + private function create_duplicate( $post, $status = '' ) { // We only want to clone Views if ( $post->post_type !== 'gravityview' ) { @@ -107,8 +114,8 @@ function create_duplicate( $post, $status = '' ) { $new_view_author = wp_get_current_user(); /** - * The default status for a new View. Return empty for the new View to inherit existing View status - * + * @filter `gravityview/duplicate-view/status` Modify the default status for a new View. Return empty for the new View to inherit existing View status + * @since 1.6 * @param string|null If string, the status to set for the new View. If empty, use existing View status. * @param WP_Post $post View being cloned */ @@ -130,9 +137,9 @@ function create_duplicate( $post, $status = '' ) { ); /** - * When copying the View, should the date also be copied? - * - * @param boolean + * @filter `gravityview/duplicate-view/copy-date` When copying a View, should the date also be copied? + * @since 1.6 + * @param boolean $copy_date Whether the copy the date from the existing View. Default: `false` * @param WP_Post $post View being cloned */ $copy_date = apply_filters('gravityview/duplicate-view/copy-date', false, $post ); @@ -143,8 +150,8 @@ function create_duplicate( $post, $status = '' ) { } /** - * Modify View configuration before creating the duplicated View. - * + * @filter `gravityview/duplicate-view/new-view` Modify View configuration before creating the duplicated View. + * @since 1.6 * @param array $new_view Array of settings to be passed to wp_insert_post() * @param WP_Post $post View being cloned */ @@ -167,6 +174,13 @@ function create_duplicate( $post, $status = '' ) { wp_update_post( $new_view_name_array ); } + /** + * @action `gv_duplicate_view` After the View is duplicated, perform an action + * @since 1.6 + * @see GravityView_Admin_Duplicate_View::copy_view_meta_info + * @param int $new_view_id The ID of the newly created View + * @param WP_Post The View that was just cloned + */ do_action( 'gv_duplicate_view', $new_view_id, $post ); delete_post_meta( $new_view_id, '_dp_original' ); @@ -180,8 +194,13 @@ function create_duplicate( $post, $status = '' ) { * Copy the meta information of a post to another View * * @since 1.6 + * + * @param int $new_view_id The ID of the newly created View + * @param WP_Post $post The View that was just cloned + * + * @return void */ - function copy_view_meta_info( $new_id, $post ) { + public function copy_view_meta_info( $new_id, $post ) { $post_meta_keys = get_post_custom_keys( $post->ID ); @@ -206,16 +225,13 @@ function copy_view_meta_info( $new_id, $post ) { * Add the link to action list for post_row_actions * * @since 1.6 + * @param array $actions Row action links. Defaults are 'Edit', 'Quick Edit', 'Restore, 'Trash', 'Delete Permanently', 'Preview', and 'View' + * @param WP_Post $post */ - function make_duplicate_link_row( $actions, $post ) { - global $pagenow; + public function make_duplicate_link_row( $actions, $post ) { // Only process on GravityView Views - if( get_post_type( $post ) !== 'gravityview' ) { - return $actions; - } - - if ( $this->current_user_can_copy( $post ) ) { + if ( get_post_type( $post ) === 'gravityview' && $this->current_user_can_copy( $post ) ) { $clone_link = $this->get_clone_view_link( $post->ID, 'display', false ); $clone_text = __( 'Clone', 'gravityview' ); @@ -227,7 +243,7 @@ function make_duplicate_link_row( $actions, $post ) { $clone_draft_text = $this->is_all_views_page() ? __( 'New Draft', 'gravityview' ) : __( 'Clone View', 'gravityview' ); $clone_draft_title = __( 'Copy as a new draft View', 'gravityview' ); - $actions['edit_as_new_draft'] = gravityview_get_link( $clone_draft_link, $clone_draft_text, 'title='.$clone_draft_title ); + $actions['edit_as_new_draft'] = gravityview_get_link( $clone_draft_link, esc_html( $clone_draft_text ), 'title='.$clone_draft_title ); } return $actions; @@ -243,34 +259,44 @@ function make_duplicate_link_row( $actions, $post ) { * @param string $draft Optional, default to true * @return string */ - function get_clone_view_link( $id = 0, $context = 'display', $draft = true ) { + private function get_clone_view_link( $id = 0, $context = 'display', $draft = true ) { // Make sure they have permission if ( false === $this->current_user_can_copy( $id ) ) { - return; + return ''; } // Verify the View exists if ( !$view = get_post( $id ) ) { - return; + return ''; } $action_name = $draft ? "duplicate_view_as_draft" : "duplicate_view"; + $action = '?action=' . $action_name . '&post=' . $view->ID; + if ( 'display' == $context ) { - $action = '?action=' . $action_name . '&post=' . $view->ID; - } else { - $action = '?action='.$action_name.'&post='.$view->ID; + $action = esc_html( $action ); } - // $post_type_object = get_post_type_object( $view->post_type ); + /** If there's no gravityview post type for some reason, abort! */ if ( !$post_type_object ) { - return; + do_action( 'gravityview_log_error', __METHOD__ . ' No gravityview post type exists when trying to clone the View.', $view ); + return ''; } - return apply_filters( 'gravityview/duplicate-view/get_clone_view_link', admin_url( "admin.php". $action ), $view->ID, $context ); + /** + * @filter `gravityview/duplicate-view/get_clone_view_link` Modify the Clone View URL that is generated + * @since 1.6 + * @param string $clone_view_link Link with `admin_url("admin.php")`, plus the action query string + * @param int $view_id View ID + * @param string $context How to display the link. If "display", the URL is run through esc_html(). Default: `display` + */ + $clone_view_link = apply_filters( 'gravityview/duplicate-view/get_clone_view_link', admin_url( "admin.php". $action ), $view->ID, $context ); + + return $clone_view_link; } @@ -279,8 +305,9 @@ function get_clone_view_link( $id = 0, $context = 'display', $draft = true ) { * then redirects to the edit post screen * * @since 1.6 + * @return void */ - function save_as_new_view_draft() { + public function save_as_new_view_draft() { $this->save_as_new_view( 'draft' ); } @@ -289,8 +316,10 @@ function save_as_new_view_draft() { * then redirects to the post list * * @since 1.6 + * @param string $status The status to set for the new View + * @return void */ - function save_as_new_view( $status = '' ) { + public function save_as_new_view( $status = '' ) { if ( ! ( isset( $_GET['post'] ) || isset( $_POST['post'] ) ) ) { wp_die( __( 'No post to duplicate has been supplied!', 'gravityview' ) ); @@ -298,6 +327,11 @@ function save_as_new_view( $status = '' ) { // Get the original post $id = ( isset( $_GET['post'] ) ? $_GET['post'] : $_POST['post'] ); + + if( ! $this->current_user_can_copy( $id ) ) { + wp_die( __( 'You don\'t have permission to copy this View.', 'gravityview' ) ); + } + $post = get_post( $id ); // Copy the post and insert it diff --git a/includes/class-gravityview-compatibility.php b/includes/class-gravityview-compatibility.php index 5ae22e885d..622c9ec86f 100644 --- a/includes/class-gravityview-compatibility.php +++ b/includes/class-gravityview-compatibility.php @@ -152,7 +152,7 @@ public static function get_notices() { */ public function _shortcode_gf_notice( $atts = array(), $content = null, $shortcode = 'gravityview' ) { - if( ! current_user_can('manage_options') ) { + if( ! GVCommon::has_cap('manage_options') ) { return null; } diff --git a/includes/class-gravityview-entry-link-shortcode.php b/includes/class-gravityview-entry-link-shortcode.php new file mode 100644 index 0000000000..b849c82de7 --- /dev/null +++ b/includes/class-gravityview-entry-link-shortcode.php @@ -0,0 +1,334 @@ + 'read', + 'view_id' => 0, + 'entry_id' => 0, + 'post_id' => 0, + 'link_atts' => '', + 'return' => 'html', + 'field_values' => '', + ); + + /** + * @type array The final settings for the shortcode, after merging passed $atts with self::$defaults + * @since 1.15 + */ + private $settings = array(); + + function __construct() { + $this->add_hooks(); + } + + /** + * Add shortcodes + * + * @since 1.15 + */ + private function add_hooks() { + add_shortcode( 'gv_entry_link', array( $this, 'read_shortcode' ) ); + add_shortcode( 'gv_edit_entry_link', array( $this, 'edit_shortcode' ) ); + add_shortcode( 'gv_delete_entry_link', array( $this, 'delete_shortcode' ) ); + } + + /** + * @since 1.15 + * @copydoc GravityView_Entry_Link_Shortcode::shortcode + */ + public function read_shortcode( $atts, $content = null, $context = 'gv_entry_link' ) { + return $this->shortcode( $atts, $content, $context ); + } + + /** + * Backward compatibility for existing `gv_edit_entry_link` shortcode + * Forces $atts['action'] to "edit" + * + * @since 1.15 + * @copydoc GravityView_Entry_Link_Shortcode::shortcode + */ + public function edit_shortcode( $atts, $content = null, $context = 'gv_edit_entry_link' ) { + $atts['action'] = 'edit'; + + return $this->shortcode( $atts, $content, $context ); + } + + /** + * Backward compatibility for existing `gv_delete_entry_link` shortcodes + * Forces $atts['action'] to "delete" + * + * @since 1.15 + * @copydoc GravityView_Entry_Link_Shortcode::shortcode + */ + public function delete_shortcode( $atts, $content = null, $context = 'gv_delete_entry_link' ) { + $atts['action'] = 'delete'; + + return $this->shortcode( $atts, $content, $context ); + } + + /** + * Generate a link to an entry. The link can be an edit, delete, or standard link. + * + * @since 1.15 + * + * @param array $atts { + * @type string $action What type of link to generate. Options: `read`, `edit`, and `delete`. Default: `read` + * @type string $view_id Define the ID for the View. If not set, use current View ID, if exists. + * @type string $entry_id ID of the entry to edit. If undefined, uses the current entry ID, if exists. + * @type string $post_id ID of the base post or page to use for an embedded View + * @type string $link_atts Pass anchor tag attributes (`target=_blank` to open Edit Entry link in a new window, for example) + * @type string $return What should the shortcode return: link HTML (`html`) or the URL (`url`). Default: `html` + * @type string $field_values Only used for `action="edit"`. Parameters to pass in to the prefill data in Edit Entry form. Uses the same format as Gravity Forms "Allow field to be populated dynamically" {@see https://www.gravityhelp.com/documentation/article/allow-field-to-be-populated-dynamically/ } + * } + * + * @param string|null $content Used as link anchor text, if specified. + * @param string $context Current shortcode being called. Not used. + * + * @return null|string If admin or an error occurred, returns null. Otherwise, returns entry link output. If `$atts['return']` is 'url', the entry link URL. Otherwise, entry link `` HTML tag. + */ + private function shortcode( $atts, $content = null, $context = 'gv_entry_link' ) { + + // Don't process when saving post. + if ( is_admin() ) { + return null; + } + + // Make sure GV is loaded + if ( ! class_exists( 'GravityView_frontend' ) || ! class_exists( 'GravityView_View' ) ) { + do_action( 'gravityview_log_error', __METHOD__ . ' GravityView_frontend or GravityView_View do not exist.' ); + + return null; + } + + $this->settings = shortcode_atts( self::$defaults, $atts, $context ); + + $this->view_id = empty( $this->settings['view_id'] ) ? GravityView_View::getInstance()->getViewId() : absint( $this->settings['view_id'] ); + + if ( empty( $this->view_id ) ) { + do_action( 'gravityview_log_error', __METHOD__ . ' A View ID was not defined and we are not inside a View' ); + + return null; + } + + $this->entry = $this->get_entry( $this->settings['entry_id'] ); + + do_action( 'gravityview_log_debug', __METHOD__ . ' ' . $context . ' $atts: ', $atts ); + + if ( ! $this->has_cap() ) { + do_action( 'gravityview_log_error', __METHOD__ . ' User does not have the capability to ' . esc_attr( $this->settings['action'] ) . ' this entry: ' . $this->entry['id'] ); + + return null; + } + + $url = $this->get_url(); + + if ( ! $url ) { + do_action( 'gravityview_log_error', __METHOD__ . ' Link returned false; View or Post may not exist.' ); + + return false; + } + + // Get just the URL, not the tag + if ( 'url' === $this->settings['return'] ) { + return $url; + } + + $link_atts = $this->get_link_atts(); + + $link_text = $this->get_anchor_text( $content ); + + return gravityview_get_link( $url, $link_text, $link_atts ); + } + + /** + * Parse shortcode atts to fetch `link_atts`, which will be added to the output of the HTML anchor tag generated by shortcode + * Only used when `return` value of shortcode is not "url" + * + * @since 1.15 + * @see gravityview_get_link() See acceptable attributes here + * @return array Array of attributes to be added + */ + private function get_link_atts() { + + wp_parse_str( $this->settings['link_atts'], $link_atts ); + + if ( 'delete' === $this->settings['action'] ) { + $link_atts['onclick'] = isset( $link_atts['onclick'] ) ? $link_atts['onclick'] : GravityView_Delete_Entry::get_confirm_dialog(); + } + + return (array) $link_atts; + } + + /** + * Get the anchor text for the link. If content inside shortcode is defined, use that as the text. Otherwise, use default values. + * + * Only used when `return` value of shortcode is not "url" + * + * @since 1.15 + * + * @param string|null $content Content inside shortcode, if defined + * + * @return string Text to use for HTML anchor + */ + private function get_anchor_text( $content = null ) { + + if ( $content ) { + return $content; + } + + switch ( $this->settings['action'] ) { + case 'edit': + $anchor_text = __( 'Edit Entry', 'gravityview' ); + break; + case 'delete': + $anchor_text = __( 'Delete Entry', 'gravityview' ); + break; + default: + $anchor_text = __( 'View Details', 'gravityview' ); + } + + return $anchor_text; + } + + /** + * Get the URL for the entry. + * + * Uses the `post_id`, `view_id` params as defined in the shortcode attributes. + * + * @since 1.15 + * + * @param string|null $content Content inside shortcode, if defined + * + * @return string|boolean If URL is fetched, the URL to the entry link. If not found, returns false. + */ + private function get_url() { + + // if post_id is not defined, default to view_id + $post_id = empty( $this->settings['post_id'] ) ? $this->view_id : absint( $this->settings['post_id'] ); + + switch ( $this->settings['action'] ) { + case 'edit': + $url = GravityView_Edit_Entry::get_edit_link( $this->entry, $this->view_id, $post_id ); + break; + case 'delete': + $url = GravityView_Delete_Entry::get_delete_link( $this->entry, $this->view_id, $post_id ); + break; + case 'read': + default: + $url = GravityView_API::entry_link( $this->entry, $post_id ); + } + + $url = $this->maybe_add_field_values_query_args( $url ); + + return $url; + } + + /** + * Check whether the user has the capability to see the shortcode output, depending on the action ('read', 'edit', 'delete') + * + * @since 1.15 + * @return bool True: has cap. + */ + private function has_cap() { + + switch ( $this->settings['action'] ) { + case 'edit': + $has_cap = GravityView_Edit_Entry::check_user_cap_edit_entry( $this->entry, $this->view_id ); + break; + case 'delete': + $has_cap = GravityView_Delete_Entry::check_user_cap_delete_entry( $this->entry, array(), $this->view_id ); + break; + case 'read': + default: + $has_cap = true; // TODO: add cap check for read_gravityview + } + + return $has_cap; + } + + /** + * Get entry array from `entry_id` parameter. If no $entry_id + * + * @since 1.15 + * @uses GVCommon::get_entry + * @uses GravityView_frontend::getSingleEntry + * + * @param int $entry_id Gravity Forms Entry ID. If not passed, current View's current entry ID will be used, if found. + * + * @return array|bool Gravity Forms array, if found. Otherwise, false. + */ + private function get_entry( $entry_id = 0 ) { + + $backup_entry = GravityView_frontend::getInstance()->getSingleEntry() ? GravityView_frontend::getInstance()->getEntry() : GravityView_View::getInstance()->getCurrentEntry(); + + if ( empty( $entry_id ) ) { + if ( ! $backup_entry ) { + do_action( 'gravityview_log_error', __METHOD__ . ' No entry defined (or entry id not valid number)', $this->settings ); + + return false; + } + $entry = $backup_entry; + } else { + $entry = wp_cache_get( 'gv_entry_link_entry_' . $entry_id, 'gravityview_entry_link_shortcode' ); + if ( false === $entry ) { + $entry = GVCommon::get_entry( $entry_id, true, false ); + wp_cache_add( 'gv_entry_link_entry_' . $entry_id, $entry, 'gravityview_entry_link_shortcode' ); + } + } + + // No search results + if ( false === $entry ) { + do_action( 'gravityview_log_error', __METHOD__ . ' No entries match the entry ID defined', $entry_id ); + + return false; + } + + return $entry; + } + + /** + * Allow passing URL params to dynamically populate the Edit Entry form + * If `field_values` key is set, run it through `parse_str()` and add the values to $url + * + * @since 1.15 + * + * @param string $href URL + */ + private function maybe_add_field_values_query_args( $url ) { + + if ( $url && ! empty( $this->settings['field_values'] ) ) { + + wp_parse_str( $this->settings['field_values'], $field_values ); + + $url = add_query_arg( $field_values, $url ); + } + + return $url; + } +} + +new GravityView_Entry_Link_Shortcode; \ No newline at end of file diff --git a/includes/class-gravityview-entry-notes.php b/includes/class-gravityview-entry-notes.php index 0634b66f0f..22933b1de1 100644 --- a/includes/class-gravityview-entry-notes.php +++ b/includes/class-gravityview-entry-notes.php @@ -2,7 +2,7 @@ /** * Class GravityView_Entry_Notes - * @since TODO + * @since 1.15 */ class GravityView_Entry_Notes { @@ -14,7 +14,7 @@ public function __construct() { } /** - * @since TODO + * @since 1.15 */ private function add_hooks() { add_filter( 'gform_notes_avatar', array( 'GravityView_Entry_Notes', 'filter_avatar' ), 10, 2 ); @@ -24,7 +24,7 @@ private function add_hooks() { /** * Alias for GFFormsModel::add_note() with default note_type of 'gravityview' * @see GFFormsModel::add_note() - * @since TODO + * @since 1.15 * @param int $lead_id ID of the Entry * @param int $user_id ID of the user creating the note * @param string $user_name User name of the user creating the note @@ -72,7 +72,7 @@ public static function get_notes( $entry_id ) { /** * @filter `gravityview/entry_notes/get_notes` Modify the notes array for an entry - * @since TODO + * @since 1.15 * @param stdClass[] $notes Integer-keyed array of note objects * @param int $entry_id Entry to get notes for */ @@ -84,7 +84,7 @@ public static function get_notes( $entry_id ) { /** * Use the GravityView avatar for notes created by GravityView * Note: The function is static so that it's easier to remove the filter: `remove_filter( 'gform_notes_avatar', array( 'GravityView_Entry_Notes', 'filter_avatar' ) );` - * @since TODO + * @since 1.15 * @param string $avatar Avatar image, if available. 48px x 48px by default. * @param object $note Note object with id, user_id, date_created, value, note_type, user_name, user_email vars * @return string Possibly-modified avatar diff --git a/includes/class-gravityview-merge-tags.php b/includes/class-gravityview-merge-tags.php index dc54379577..ae73312a18 100644 --- a/includes/class-gravityview-merge-tags.php +++ b/includes/class-gravityview-merge-tags.php @@ -21,7 +21,7 @@ private function add_hooks() { add_filter( 'gform_custom_merge_tags', array( $this, '_gform_custom_merge_tags' ), 10, 4 ); - add_filter( 'gform_replace_merge_tags', array( $this, '_gform_replace_merge_tags' ), 10, 7 ); + add_filter( 'gform_replace_merge_tags', array( 'GravityView_Merge_Tags', '_gform_replace_merge_tags' ), 10, 7 ); } @@ -67,6 +67,10 @@ public static function replace_variables($text, $form = array(), $entry = array( } } + if ( empty( $form ) || empty( $entry ) ) { + return self::_gform_replace_merge_tags( $text ); + } + return GFCommon::replace_variables( $text, $form, $entry, false, false, false, "html"); } @@ -120,7 +124,7 @@ public function _gform_custom_merge_tags( $existing_merge_tags = array(), $form_ * * @return mixed */ - public function _gform_replace_merge_tags( $text, $form = array(), $entry = array(), $url_encode = false, $esc_html = false ) { + public static function _gform_replace_merge_tags( $text, $form = array(), $entry = array(), $url_encode = false, $esc_html = false ) { /** * This prevents the gform_replace_merge_tags filter from being called twice, as defined in: @@ -131,10 +135,10 @@ public function _gform_replace_merge_tags( $text, $form = array(), $entry = arr return $text; } - $text = $this->replace_get_variables( $text, $form, $entry, $url_encode ); + $text = self::replace_get_variables( $text, $form, $entry, $url_encode ); // Process the merge vars here - $text = $this->replace_user_variables_created_by( $text, $form, $entry, $url_encode, $esc_html ); + $text = self::replace_user_variables_created_by( $text, $form, $entry, $url_encode, $esc_html ); return $text; @@ -161,10 +165,10 @@ public function _gform_replace_merge_tags( $text, $form = array(), $entry = arr * @param array $entry Entry array * @param bool $url_encode Whether to URL-encode output */ - private function replace_get_variables( $text, $form = array(), $entry = array(), $url_encode = false ) { + private static function replace_get_variables( $text, $form = array(), $entry = array(), $url_encode = false ) { - // Is there is {created_by:[xyz]} merge tag? - preg_match_all( "/\{get:(.*?)\}/", $text, $matches, PREG_SET_ORDER ); + // Is there is {get:[xyz]} merge tag? + preg_match_all( "/{get:(.*?)}/ism", $text, $matches, PREG_SET_ORDER ); // If there are no matches OR the Entry `created_by` isn't set or is 0 (no user) if( empty( $matches ) ) { @@ -204,10 +208,10 @@ private function replace_get_variables( $text, $form = array(), $entry = array() /** * @filter `gravityview/merge_tags/get/esc_html/{url parameter name}` Modify the value of the `{get}` replacement before being used - * @param string $value Text to replace - * @param string $text Text to replace - * @param array $form Gravity Forms form array - * @param array $entry Entry array + * @param[in,out] string $value Value that will replace `{get}` + * @param[in] string $text Text that contains `{get}` (before replacement) + * @param[in] array $form Gravity Forms form array + * @param[in] array $entry Entry array */ $value = apply_filters('gravityview/merge_tags/get/value/' . $property, $value, $text, $form, $entry ); @@ -235,7 +239,7 @@ private function replace_get_variables( $text, $form = array(), $entry = array() * * @return string Text, with user variables replaced, if they existed */ - private function replace_user_variables_created_by( $text, $form = array(), $entry = array(), $url_encode = false, $esc_html = false ) { + private static function replace_user_variables_created_by( $text, $form = array(), $entry = array(), $url_encode = false, $esc_html = false ) { // Is there is {created_by:[xyz]} merge tag? preg_match_all( "/\{created_by:(.*?)\}/", $text, $matches, PREG_SET_ORDER ); diff --git a/includes/class-gravityview-roles-capabilities.php b/includes/class-gravityview-roles-capabilities.php index bdc59a2a84..c1625181cc 100644 --- a/includes/class-gravityview-roles-capabilities.php +++ b/includes/class-gravityview-roles-capabilities.php @@ -18,7 +18,7 @@ * * This class handles the role creation and assignment of capabilities for those roles. * - * @since 1.14 + * @since 1.15 */ class GravityView_Roles_Capabilities { @@ -28,7 +28,7 @@ class GravityView_Roles_Capabilities { static $instance = null; /** - * @since 1.14 + * @since 1.15 * @return GravityView_Roles_Capabilities */ public static function get_instance() { @@ -43,29 +43,126 @@ public static function get_instance() { /** * Get things going * - * @since 1.14 + * @since 1.15 */ public function __construct() { $this->add_hooks(); } /** - * Add Members plugin hook - * @since 1.14 + * @since 1.15 */ private function add_hooks() { - add_filter( 'members_get_capabilities', array( $this, 'members_get_capabilities' ) ); + add_filter( 'members_get_capabilities', array( 'GravityView_Roles_Capabilities', 'merge_with_all_caps' ) ); + add_action( 'members_register_cap_groups', array( $this, 'members_register_cap_group' ), 20 ); + add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 10, 4 ); + } + + + /** + * Add support for `gravityview_full_access` capability, and + * + * @see map_meta_cap() + * + * @since 1.15 + * + * @param array $allcaps An array of all the user's capabilities. + * @param array $caps Actual capabilities for meta capability. + * @param array $args Optional parameters passed to has_cap(), typically object ID. + * @param WP_User|null $user The user object, in WordPress 3.7.0 or higher + * + * @return mixed + */ + public function filter_user_has_cap( $usercaps = array(), $caps = array(), $args = array(), $user = NULL ) { + + // Empty caps_to_check array + if( ! $usercaps || ! $caps ) { + return $usercaps; + } + + /** + * Enable all GravityView caps_to_check if `gravityview_full_access` is enabled + */ + if( ! empty( $usercaps['gravityview_full_access'] ) ) { + + $all_gravityview_caps = self::all_caps(); + + foreach( $all_gravityview_caps as $gv_cap ) { + $usercaps[ $gv_cap ] = true; + } + + unset( $all_gravityview_caps ); + } + + $usercaps = $this->add_gravity_forms_usercaps_to_gravityview_caps( $usercaps ); + + return $usercaps; + } + + /** + * If a user has been assigned custom capabilities for Gravity Forms, but they haven't been assigned similar abilities + * in GravityView yet, we give temporary access to the permissions, until they're set. + * + * This is for custom roles that GravityView_Roles_Capabilities::add_caps() doesn't modify. If you have a + * custom role with the ability to edit any Gravity Forms entries (`gravityforms_edit_entries`), you would + * expect GravityView to match that capability, until the role has been updated with GravityView caps. + * + * @since 1.15 + * + * @param array $usercaps User's allcaps array + * + * @return array + */ + private function add_gravity_forms_usercaps_to_gravityview_caps( $usercaps ) { + + $gf_to_gv_caps = array( + 'gravityforms_edit_entries' => 'gravityview_edit_others_entries', + 'gravityforms_delete_entries' => 'gravityview_delete_others_entries', + 'gravityforms_edit_entry_notes' => 'gravityview_edit_others_entry_notes', + ); + + foreach ( $gf_to_gv_caps as $gf_cap => $gv_cap ) { + if ( isset( $usercaps[ $gf_cap ] ) && ! isset( $usercaps[ $gv_cap ] ) ) { + $usercaps[ $gv_cap ] = $usercaps[ $gf_cap ]; + } + } + + return $usercaps; + } + + /** + * Add GravityView group to Members 1.x plugin management screen + * @see members_register_cap_group() + * @since 1.15 + * @return void + */ + function members_register_cap_group() { + if ( function_exists( 'members_register_cap_group' ) ) { + + $args = array( + 'label' => __( 'GravityView' ), + 'icon' => 'gv-icon-astronaut-head', + 'caps' => self::all_caps(), + 'merge_added' => true, + 'diff_added' => false, + ); + + members_register_cap_group( 'gravityview', $args ); + } } /** - * Add GravityView capabilities to the Members plugin + * Merge capabilities array with GravityView capabilities * - * @since 1.14 - * @param array $caps Existing capabilities registered with Members + * @since 1.15 Used to add GravityView caps to the Members plugin + * @param array $caps Existing capabilities * @return array Modified capabilities array */ - public function members_get_capabilities( $caps ) { - return array_merge( $caps, $this->all_caps('all') ); + public static function merge_with_all_caps( $caps ) { + + $return_caps = array_merge( $caps, self::all_caps() ); + + return array_unique( $return_caps ); } /** @@ -77,19 +174,21 @@ public function members_get_capabilities( $caps ) { * * @return WP_Roles WP_Roles global instance if not already instantiated. */ - function wp_roles() { + private function wp_roles() { global $wp_roles; if ( ! isset( $wp_roles ) ) { $wp_roles = new WP_Roles(); } + return $wp_roles; } /** - * Add capabilities to their respective roles + * Add capabilities to their respective roles if they don't already exist + * This could be simpler, but the goal is speed. * - * @since 1.14 + * @since 1.15 * @return void */ public function add_caps() { @@ -98,14 +197,33 @@ public function add_caps() { if ( is_object( $wp_roles ) ) { - foreach( $wp_roles->get_names() as $role_slug => $role_label ) { + $_use_db_backup = $wp_roles->use_db; - $capabilities = $this->all_caps( $role_slug ); + /** + * When $use_db is true, add_cap() performs update_option() every time. + * We disable updating the database here, then re-enable it below. + */ + $wp_roles->use_db = false; - foreach( $capabilities as $cap ) { + $capabilities = self::all_caps( false, false ); + + foreach ( $capabilities as $role_slug => $role_caps ) { + foreach ( $role_caps as $cap ) { $wp_roles->add_cap( $role_slug, $cap ); } } + + /** + * Update the option, as it does in add_cap when $use_db is true + * + * @see WP_Roles::add_cap() Original code + */ + update_option( $wp_roles->role_key, $wp_roles->roles ); + + /** + * Restore previous $use_db setting + */ + $wp_roles->use_db = $_use_db_backup; } } @@ -114,22 +232,26 @@ public function add_caps() { * * @see get_post_type_capabilities() * - * @since 1.14 + * @since 1.15 * - * @param string $role If set, get the caps for a specific role. Pass 'all' to get all caps in a flat array. Default: '' + * @param string $single_role If set, get the caps_to_check for a specific role. Pass 'all' to get all caps_to_check in a flat array. Default: `all` + * @param boolean $flat_array True: return all caps in a one-dimensional array. False: a multi-dimensional array with `$single_role` as keys and the caps as the values * - * @return array If $role is set, flat array of caps. Otherwise, a multi-dimensional array of roles and their caps with the following keys: 'administrator', 'editor', 'author', 'contributor', 'subscriber' + * @return array If $role is set, flat array of caps_to_check. Otherwise, a multi-dimensional array of roles and their caps_to_check with the following keys: 'administrator', 'editor', 'author', 'contributor', 'subscriber' */ - public function all_caps( $role = '' ) { + public static function all_caps( $single_role = false, $flat_array = true ) { - $administrator = array( - // Settings + // Change settings + $administrator_caps = array( + 'gravityview_full_access', // Grant access to all caps_to_check 'gravityview_view_settings', 'gravityview_edit_settings', + 'gravityview_uninstall', // Ability to trigger the Uninstall @todo + 'gravityview_contact_support', // Whether able to send a message to support via the Support Port ); // Edit, publish, delete own and others' stuff - $editor = array( + $editor_caps = array( 'edit_others_gravityviews', 'read_private_gravityviews', 'delete_private_gravityviews', @@ -138,74 +260,135 @@ public function all_caps( $role = '' ) { 'publish_gravityviews', 'delete_published_gravityviews', 'edit_published_gravityviews', + 'copy_gravityviews', // For duplicate/clone View functionality - // GF caps + // GF caps_to_check 'gravityview_edit_others_entries', - - // GF caps 'gravityview_view_others_entry_notes', - 'gravityview_moderate_entries', + 'gravityview_edit_others_entry_notes', + 'gravityview_moderate_entries', // Approve or reject entries from the Admin; show/hide approval column in Entries screen 'gravityview_delete_others_entries', ); - // Edit, publish and delete own stuff - $author = array( - - // GF caps + // Edit, delete own stuff + $author_caps = array( + // GF caps_to_check 'gravityview_edit_entries', + 'gravityview_edit_entry', + 'gravityview_edit_form_entries', // This is similar to `gravityview_edit_entries`, but checks against a Form ID $object_id 'gravityview_view_entry_notes', 'gravityview_delete_entries', - + 'gravityview_delete_entry', ); // Edit and delete drafts but not publish - $contributor = array( - 'edit_gravityview', - 'edit_gravityviews', - 'delete_gravityview', + $contributor_caps = array( + 'edit_gravityviews', // Affects if you're able to see the Views menu in the Admin 'delete_gravityviews', + 'gravityview_getting_started', // Getting Started page access + 'gravityview_support_port', // Display GravityView Support Port ); // Read only - $subscriber = array( - 'read_gravityview', + $subscriber_caps = array( + 'gravityview_view_entries', + 'gravityview_view_others_entries', ); - $capabilities = array(); + $subscriber = $subscriber_caps; + $contributor = array_merge( $contributor_caps, $subscriber_caps ); + $author = array_merge( $author_caps, $contributor_caps, $subscriber_caps ); + $editor = array_merge( $editor_caps, $author_caps, $contributor_caps, $subscriber_caps ); + $administrator = array_merge( $administrator_caps, $editor_caps, $author_caps, $contributor_caps, $subscriber_caps ); + $all = $administrator; + + // If role is set, return caps_to_check for just that role. + if( $single_role ) { + $caps = isset( ${$single_role} ) ? ${$single_role} : false; + return $flat_array ? $caps : array( $single_role => $caps ); + } - switch( $role ) { - case 'subscriber': - $capabilities = $subscriber; - break; - case 'contributor': - $capabilities = array_merge( $contributor, $subscriber ); - break; - case 'author': - $capabilities = array_merge( $author, $contributor, $subscriber ); - break; - case 'editor': - $capabilities = array_merge( $editor, $author, $contributor, $subscriber ); - break; - case 'administrator': - case 'all': - $capabilities = array_merge( $administrator, $editor, $author, $contributor, $subscriber ); - break; + // Otherwise, return multi-dimensional array of all caps_to_check + return $flat_array ? $all : compact( 'administrator', 'editor', 'author', 'contributor', 'subscriber' ); + } + + /** + * Check whether the current user has a capability + * + * @since 1.15 + * + * @see WP_User::user_has_cap() + * @see https://codex.wordpress.org/Plugin_API/Filter_Reference/user_has_cap You can filter permissions based on entry/View/form ID using `user_has_cap` filter + * + * @see GFCommon::current_user_can_any + * @uses GFCommon::current_user_can_any + * + * @param string|array $caps_to_check Single capability or array of capabilities + * @param int|null $object_id (optional) Parameter can be used to check for capabilities against a specific object, such as a post or us + * @param int|null $user_id (optional) Check the capabilities for a user who is not necessarily the currently logged-in user + * + * @return bool True: user has at least one passed capability; False: user does not have any defined capabilities + */ + public static function has_cap( $caps_to_check = '', $object_id = null, $user_id = null ) { + + $has_cap = false; + + if( empty( $caps_to_check ) ) { + return $has_cap; } - // If role is set, return empty array if not exists - if( $role ) { - return isset( $capabilities[ $role ] ) ? $capabilities[ $role ] : array(); + // Add full access caps for GV & GF + $caps_to_check = self::maybe_add_full_access_caps( $caps_to_check ); + + foreach ( $caps_to_check as $cap ) { + if( ! is_null( $object_id ) ) { + $has_cap = $user_id ? user_can( $user_id, $cap, $object_id ) : current_user_can( $cap, $object_id ); + } else { + $has_cap = $user_id ? user_can( $user_id, $cap ) : current_user_can( $cap ); + } + // At the first successful response, stop checking + if( $has_cap ) { + break; + } } - // By default, return multi-dimensional array of all caps - return compact( 'administrator', 'editor', 'author', 'contributor', 'subscriber' ); + return $has_cap; } + /** + * Add Gravity Forms and GravityView's "full access" caps when any other caps are checked against. + * + * @since 1.15 + + * @param array $caps_to_check + * + * @return array + */ + public static function maybe_add_full_access_caps( $caps_to_check = array() ) { + + $caps_to_check = (array)$caps_to_check; + + $all_gravityview_caps = self::all_caps(); + + // Are there any $caps_to_check that are from GravityView? + if( $has_gravityview_caps = array_intersect( $caps_to_check, $all_gravityview_caps ) ) { + $caps_to_check[] = 'gravityview_full_access'; + } + + $all_gravity_forms_caps = class_exists( 'GFCommon' ) ? GFCommon::all_caps() : array(); + + // Are there any $caps_to_check that are from Gravity Forms? + if( $all_gravity_forms_caps = array_intersect( $caps_to_check, $all_gravity_forms_caps ) ) { + $caps_to_check[] = 'gform_full_access'; + } + + return array_unique( $caps_to_check ); + } /** - * Remove all GravityView caps from all roles + * Remove all GravityView caps_to_check from all roles * - * @since 1.14 + * @since 1.15 * @return void */ public function remove_caps() { @@ -214,10 +397,10 @@ public function remove_caps() { if ( is_object( $wp_roles ) ) { - /** Remove all GravityView caps from all roles */ - $capabilities = $this->all_caps('all'); + /** Remove all GravityView caps_to_check from all roles */ + $capabilities = self::all_caps(); - // Loop through each role and remove GV caps + // Loop through each role and remove GV caps_to_check foreach( $wp_roles->get_names() as $role_slug => $role_name ) { foreach ( $capabilities as $cap ) { $wp_roles->remove_cap( $role_slug, $cap ); @@ -226,3 +409,5 @@ public function remove_caps() { } } } + +add_action( 'init', array( 'GravityView_Roles_Capabilities', 'get_instance' ), 1 ); \ No newline at end of file diff --git a/includes/class-gravityview-template.php b/includes/class-gravityview-template.php new file mode 100644 index 0000000000..51e18deb42 --- /dev/null +++ b/includes/class-gravityview-template.php @@ -0,0 +1,252 @@ +template_id = $id; + + $this->merge_defaults( $settings ); + + $this->field_options = $field_options; + $this->active_areas = $areas; + + $this->add_hooks(); + } + + /** + * Add filters and actions for the templates + * + * @since 1.15 + */ + private function add_hooks() { + + add_filter( 'gravityview_register_directory_template', array( $this, 'register_template' ) ); + + // presets hooks: + // form xml + add_filter( 'gravityview_template_formxml', array( $this, 'assign_form_xml' ), 10, 2 ); + // fields config xml + add_filter( 'gravityview_template_fieldsxml', array( $this, 'assign_fields_xml' ), 10, 2 ); + + // assign active areas + add_filter( 'gravityview_template_active_areas', array( $this, 'assign_active_areas' ), 10, 3 ); + + // field options + add_filter( 'gravityview_template_field_options', array( $this, 'assign_field_options' ), 10, 4 ); + + // template slug + add_filter( "gravityview_template_slug_{$this->template_id}", array( $this, 'assign_view_slug' ), 10, 2 ); + + // register template CSS + add_action( 'wp_enqueue_scripts', array( $this, 'register_styles' ) ); + } + + /** + * Merge the template settings with the default settings + * + * Sets the `settings` object var. + * + * @param array $settings Defined template settings + * + * @return array Merged template settings. + */ + private function merge_defaults( $settings = array() ) { + + $defaults = array( + 'slug' => '', + 'css_source' => '', + 'type' => '', + 'label' => '', + 'description' => '', + 'logo' => '', + 'preview' => '', + 'buy_source' => '', + 'preset_form' => '', + 'preset_fields' => '' + ); + + $this->settings = wp_parse_args( $settings, $defaults ); + + return $this->settings; + } + + /** + * Register the template to display in the admin + * + * @access private + * + * @param mixed $templates + * + * @return array Array of templates available for GV + */ + public function register_template( $templates ) { + $templates[ $this->template_id ] = $this->settings; + + return $templates; + } + + + /** + * Assign active areas (for admin configuration) + * + * @access protected + * + * @param array $areas + * @param string $template (default: '') + * + * @return array Array of active areas + */ + public function assign_active_areas( $areas, $template = '', $context = 'directory' ) { + if ( $this->template_id === $template ) { + $areas = $this->get_active_areas( $context ); + } + + return $areas; + } + + public function get_active_areas( $context ) { + if ( isset( $this->active_areas[ $context ] ) ) { + return $this->active_areas[ $context ]; + } else { + return $this->active_areas; + } + } + + + /** + * Assign template specific field options + * + * @param array $options (default: array()) + * @param string $template (default: '') + * @param string $field_id key for the field + * @param string|array $context Context for the field; `directory` or `single` for example. + * + * @return array Array of field options + */ + public function assign_field_options( $field_options, $template_id, $field_id = NULL, $context = 'directory' ) { + + if ( $this->template_id === $template_id ) { + + foreach ( $this->field_options as $key => $field_option ) { + + $field_context = rgar( $field_option, 'context' ); + + // Does the field option only apply to a certain context? + // You can define multiple contexts as an array: `context => array("directory", "single")` + $context_matches = is_array( $field_context ) ? in_array( $context, $field_context ) : $context === $field_context; + + // If the context matches (or isn't defined), add the field options. + if ( $context_matches ) { + $field_options[ $key ] = $field_option; + } + } + } + + return $field_options; + } + + /** + * Set the Gravity Forms import form information by using the `preset_form` field defined in the template. + * + * @see GravityView_Admin_Views::pre_get_form_fields() + * @see GravityView_Admin_Views::create_preset_form() + * @return string Path to XML file + */ + public function assign_form_xml( $xml = '', $template = '' ) { + if ( $this->settings['type'] === 'preset' && ! empty( $this->settings['preset_form'] ) && $this->template_id === $template ) { + return $this->settings['preset_form']; + } + + return $xml; + } + + /** + * Set the Gravity Forms import form by using the `preset_fields` field defined in the template. + * + * @see GravityView_Admin_Views::pre_get_form_fields() + * @return string Path to XML file + */ + public function assign_fields_xml( $xml = '', $template = '' ) { + if ( $this->settings['type'] === 'preset' && ! empty( $this->settings['preset_fields'] ) && $this->template_id === $template ) { + return $this->settings['preset_fields']; + } + + return $xml; + } + + + /** + * Assign the template slug when loading the presentation template (frontend) + * + * @access protected + * + * @param mixed $default + * + * @return string + */ + public function assign_view_slug( $default, $context ) { + + if ( ! empty( $this->settings['slug'] ) ) { + return $this->settings['slug']; + } + if ( ! empty( $default ) ) { + return $default; + } + + // last resort, template_id + return $this->template_id; + } + + /** + * If the template has a CSS file defined in the `css_source` setting, register it + * It will be registered using `gravityview_style_{template_id}` format + * + * @return void + */ + public function register_styles() { + if ( ! empty( $this->settings['css_source'] ) ) { + wp_register_style( 'gravityview_style_' . $this->template_id, $this->settings['css_source'], array(), GravityView_Plugin::version, 'all' ); + } + } + +} \ No newline at end of file diff --git a/includes/class-gv-license-handler.php b/includes/class-gv-license-handler.php index eaeea7282c..caebf881f3 100644 --- a/includes/class-gv-license-handler.php +++ b/includes/class-gv-license-handler.php @@ -17,7 +17,7 @@ class GV_License_Handler { /** * Post ID on gravityview.co - * @since 1.14.4 + * @since 1.15 */ const item_id = 17; @@ -71,6 +71,7 @@ function settings_edd_license_activation( $field, $echo ) { 'license_box' => $this->get_license_message( $response ) )); + $fields = array( array( 'name' => 'edd-activate', @@ -96,15 +97,21 @@ function settings_edd_license_activation( $field, $echo ) { ), ); + $class = 'button gv-edd-action'; $class .= ( !empty( $key ) && $status !== 'valid' ) ? '' : ' hide'; + $disabled_attribute = GVCommon::has_cap( 'gravityview_edit_settings' ) ? false : 'disabled'; + $submit = '
    '; foreach ( $fields as $field ) { $field['type'] = 'button'; $field['class'] = isset( $field['class'] ) ? $field['class'] . ' '. $class : $class; $field['style'] = 'margin-left: 10px;'; + if( $disabled_attribute ) { + $field['disabled'] = $disabled_attribute; + } $submit .= $this->Addon->settings_submit( $field, $echo ); } $submit .= '
    '; @@ -267,44 +274,54 @@ public function license_call( $array = array() ) { $is_ajax = ( defined('DOING_AJAX') && DOING_AJAX ); $data = empty( $array ) ? $_POST['data'] : $array; + $has_cap = GVCommon::has_cap( 'gravityview_edit_settings' ); if ( $is_ajax && empty( $data['license'] ) ) { die( - 1 ); } - $license = esc_attr( rgget( 'license', $data ) ); - $license_data = $this->_license_get_remote_response( $data, $license ); + // If the user isn't allowed to edit settings, show an error message + if( ! $has_cap ) { + $license_data = new stdClass(); + $license_data->error = 'capability'; + $license_data->message = $this->get_license_message( $license_data ); + $json = json_encode( $license_data ); + } else { - // Empty is returned when there's an error. - if ( empty( $license_data ) ) { - if ( $is_ajax ) { - exit( json_encode( array() ) ); - } else { // Non-ajax call - return json_encode( array() ); + $license = esc_attr( rgget( 'license', $data ) ); + $license_data = $this->_license_get_remote_response( $data, $license ); + + // Empty is returned when there's an error. + if ( empty( $license_data ) ) { + if ( $is_ajax ) { + exit( json_encode( array() ) ); + } else { // Non-ajax call + return json_encode( array() ); + } } - } - $license_data->message = $this->get_license_message( $license_data ); + $license_data->message = $this->get_license_message( $license_data ); - $json = json_encode( $license_data ); + $json = json_encode( $license_data ); - $update_license = ( !isset( $data['update'] ) || !empty( $data['update'] ) ); + $update_license = ( ! isset( $data['update'] ) || ! empty( $data['update'] ) ); - $is_check_action_button = ( 'check_license' === $data['edd_action'] && defined('DOING_AJAX') && DOING_AJAX ); + $is_check_action_button = ( 'check_license' === $data['edd_action'] && defined( 'DOING_AJAX' ) && DOING_AJAX ); - // Failed is the response from trying to de-activate a license and it didn't work. - // This likely happened because people entered in a different key and clicked "Deactivate", - // meaning to deactivate the original key. We don't want to save this response, since it is - // most likely a mistake. - if ( $license_data->license !== 'failed' && !$is_check_action_button && $update_license ) { + // Failed is the response from trying to de-activate a license and it didn't work. + // This likely happened because people entered in a different key and clicked "Deactivate", + // meaning to deactivate the original key. We don't want to save this response, since it is + // most likely a mistake. + if ( $license_data->license !== 'failed' && ! $is_check_action_button && $update_license ) { - if( !empty( $data['field_id'] ) ) { - set_transient( 'gravityview_' . esc_attr( $data['field_id'] ) . '_valid', $license_data, DAY_IN_SECONDS ); - } + if ( ! empty( $data['field_id'] ) ) { + set_transient( 'gravityview_' . esc_attr( $data['field_id'] ) . '_valid', $license_data, DAY_IN_SECONDS ); + } - $this->license_call_update_settings( $license_data, $data ); + $this->license_call_update_settings( $license_data, $data ); - } + } + } // End $has_cap if ( $is_ajax ) { exit( $json ); @@ -362,6 +379,7 @@ public function strings( $status = NULL, $license_data = null ) { 'missing' => esc_html__('The license key was not defined.', 'gravityview'), 'revoked' => esc_html__('This license key has been revoked.', 'gravityview'), 'expired' => sprintf( esc_html__('This license key has expired. %sRenew your license on the GravityView website%s to receive updates and support.', 'gravityview'), '
    ', '' ), + 'capability' => esc_html__( 'You don\'t have the ability to edit plugin settings.', 'gravityview' ), 'verifying_license' => esc_html__('Verifying license…', 'gravityview'), 'activate_license' => esc_html__('Activate License', 'gravityview'), diff --git a/includes/class-gvlogic-shortcode.php b/includes/class-gvlogic-shortcode.php index 4f606796e0..5d0a3b87e5 100644 --- a/includes/class-gvlogic-shortcode.php +++ b/includes/class-gvlogic-shortcode.php @@ -222,7 +222,7 @@ public function shortcode( $atts = array(), $content = NULL, $shortcode_tag = '' } /** - * Does the if and the comparson match? + * Does the if and the comparison match? * @uses GVCommon::matches_operation * * @return boolean True: yep; false: nope diff --git a/includes/class-logging.php b/includes/class-logging.php index a49bfc8771..f8ee0d1beb 100644 --- a/includes/class-logging.php +++ b/includes/class-logging.php @@ -24,11 +24,13 @@ function __construct() { * * @see http://wordpress.org/plugins/debug-bar/ */ - function add_debug_bar( $panels ) { + public function add_debug_bar( $panels ) { - if(!class_exists('Debug_Bar_Panel')) { return; } + if ( ! class_exists( 'Debug_Bar_Panel' ) ) { + return; + } - if(!class_exists('GravityView_Debug_Bar')) { + if ( ! class_exists( 'GravityView_Debug_Bar' ) ) { include_once( GRAVITYVIEW_DIR . 'includes/class-debug-bar.php' ); } @@ -46,11 +48,19 @@ public function enable_gform_logging( $supported_plugins ) { return $supported_plugins; } - static function get_notices() { + /** + * @static + * @return array Array of notices (with `message`, `data`, and `backtrace` keys), if any + */ + public static function get_notices() { return self::$notices; } - static function get_errors() { + /** + * @static + * @return array Array of errors (with `message`, `data`, and `backtrace` keys), if any + */ + public static function get_errors() { return self::$errors; } diff --git a/includes/class-post-types.php b/includes/class-post-types.php index 0a06f9e92b..eb611017d2 100644 --- a/includes/class-post-types.php +++ b/includes/class-post-types.php @@ -34,11 +34,10 @@ function __construct() { public static function init_post_types() { /** - * Make GravityView Views hierarchical by returning TRUE - * + * @filter `gravityview_is_hierarchical` Make GravityView Views hierarchical by returning TRUE * This will allow for Views to be nested with Parents and also allows for menu order to be set in the Page Attributes metabox - * * @since 1.13 + * @param boolean $is_hierarchical Default: false */ $is_hierarchical = (bool)apply_filters( 'gravityview_is_hierarchical', false ); @@ -79,9 +78,9 @@ public static function init_post_types() { 'menu_icon' => '', 'can_export' => true, /** - * Enable Custom Post Type archive + * @filter `gravityview_has_archive` Enable Custom Post Type archive? * @since 1.7.3 - * @param boolean False: don't have frontend archive; True: yes, have archive + * @param boolean False: don't have frontend archive; True: yes, have archive. Default: false */ 'has_archive' => apply_filters( 'gravityview_has_archive', false ), 'exclude_from_search' => true, @@ -93,7 +92,7 @@ public static function init_post_types() { */ 'slug' => apply_filters( 'gravityview_slug', 'view' ) ), - 'capability_type' => 'page', + 'capability_type' => 'gravityview', 'map_meta_cap' => true, ); @@ -148,9 +147,13 @@ static function no_views_text() { // Floaty the astronaut $image = GravityView_Admin::get_floaty(); - $not_found = sprintf( esc_attr__("%sYou don't have any active views. Let’s go %screate one%s!%s\n\nIf you feel like you're lost in space and need help getting started, check out the %sGetting Started%s page.", 'gravityview' ), '

    ', '', '', '

    ', '', '' ); + if( GVCommon::has_cap( 'edit_gravityviews' ) ) { + $output = sprintf( esc_attr__( "%sYou don't have any active views. Let’s go %screate one%s!%s\n\nIf you feel like you're lost in space and need help getting started, check out the %sGetting Started%s page.", 'gravityview' ), '

    ', '', '', '

    ', '', '' ); + } else { + $output = esc_attr__( 'There are no active Views', 'gravityview' ); + } - return $image.wpautop( $not_found ); + return $image . wpautop( $output ); } diff --git a/includes/class-settings.php b/includes/class-settings.php index afd1365610..4572b24120 100644 --- a/includes/class-settings.php +++ b/includes/class-settings.php @@ -33,7 +33,17 @@ class GravityView_Settings extends GFAddOn { /** * @var string|array A string or an array of capabilities or roles that can uninstall the plugin */ - protected $_capabilities_uninstall = 'gravityview_gfaddon_uninstall'; + protected $_capabilities_uninstall = 'gravityview_uninstall'; + + /** + * @var string|array A string or an array of capabilities or roles that have access to the settings page + */ + protected $_capabilities_app_settings = 'gravityview_view_settings'; + + /** + * @var string|array A string or an array of capabilities or roles that have access to the settings page + */ + protected $_capabilities_app_menu = 'gravityview_view_settings'; /** * @var string The hook suffix for the app menu @@ -93,14 +103,18 @@ public static function get_instance() { public function current_user_can_any( $caps ) { /** - * Don't show uninstall tab + * Prevent Gravity Forms from showing the uninstall tab on the settings page * @hack */ if( $caps === $this->_capabilities_uninstall ) { return false; } - return parent::current_user_can_any( $caps ); + if( empty( $caps ) ) { + $caps = array( 'gravityview_full_access' ); + } + + return GVCommon::has_cap( $caps ); } /** @@ -114,8 +128,6 @@ function init_admin() { $this->_load_license_handler(); - $this->_capabilities_app_settings = apply_filters( 'gravityview_settings_capability' , 'manage_options' ); - $this->license_key_notice(); add_filter( 'gform_addon_app_settings_menu_gravityview', array( $this, 'modify_app_settings_menu_title' ) ); @@ -477,12 +489,33 @@ private function get_default_settings() { 'license_key_status' => '', 'support-email' => get_bloginfo( 'admin_email' ), 'no-conflict-mode' => '0', + 'support_port' => '1', 'delete-on-uninstall' => '0', ); return $defaults; } + /** + * Check for the `gravityview_edit_settings` capability before saving plugin settings. + * Gravity Forms says you're able to edit if you're able to view settings. GravityView allows two different permissions. + * + * @since 1.15 + * @return void + */ + public function maybe_save_app_settings() { + + if ( $this->is_save_postback() ) { + if ( ! GVCommon::has_cap( 'gravityview_edit_settings' ) ) { + $_POST = array(); // If you don't reset the $_POST array, it *looks* like the settings were changed, but they weren't + GFCommon::add_error_message( __( 'You don\'t have the ability to edit plugin settings.', 'gravityview' ) ); + return; + } + } + + parent::maybe_save_app_settings(); + } + /** * When the settings are saved, make sure the license key matches the previously activated key * @@ -523,6 +556,8 @@ public function app_settings_fields() { $default_settings = $this->get_default_settings(); + $disabled_attribute = GVCommon::has_cap( 'gravityview_edit_settings' ) ? false : 'disabled'; + $fields = apply_filters( 'gravityview_settings_fields', array( array( 'name' => 'license_key', @@ -553,6 +588,28 @@ public function app_settings_fields() { 'description' => __( 'In order to provide responses to your support requests, please provide your email address.', 'gravityview' ), 'class' => 'code regular-text', ), + /** + * @since 1.15 Added Support Port support + */ + array( + 'name' => 'support_port', + 'type' => 'radio', + 'label' => __( 'Show Support Port?', 'gravityview' ), + 'default_value' => $default_settings['support_port'], + 'horizontal' => 1, + 'choices' => array( + array( + 'label' => _x('Show', 'Setting: Show or Hide', 'gravityview'), + 'value' => '1', + ), + array( + 'label' => _x('Hide', 'Setting: Show or Hide', 'gravityview'), + 'value' => '0', + ), + ), + 'tooltip' => '

    ' . esc_attr__( 'The Support Port looks like this.', 'gravityview' ) . '' . esc_html__('The Support Port provides quick access to how-to articles and tutorials. For administrators, it also makes it easy to contact support.', 'gravityview') . '

    ', + 'description' => __( 'Show the Support Port on GravityView pages?', 'gravityview' ), + ), array( 'name' => 'no-conflict-mode', 'type' => 'radio', @@ -562,39 +619,40 @@ public function app_settings_fields() { 'choices' => array( array( 'label' => _x('On', 'Setting: On or off', 'gravityview'), - 'value' => '1' + 'value' => '1', ), array( 'label' => _x('Off', 'Setting: On or off', 'gravityview'), 'value' => '0', ), ), - 'description' => __( 'Set this to ON to prevent extraneous scripts and styles from being printed on GravityView admin pages, reducing conflicts with other plugins and themes.', 'gravityview' ) . ' ' . __('If your Edit View tabs are ugly, enable this setting.'), + 'description' => __( 'Set this to ON to prevent extraneous scripts and styles from being printed on GravityView admin pages, reducing conflicts with other plugins and themes.', 'gravityview' ) . ' ' . __('If your Edit View tabs are ugly, enable this setting.', 'gravityview'), ), array( 'name' => 'delete-on-uninstall', 'type' => 'radio', - 'label' => __( 'Remove Data on Uninstall?', 'gravityview' ), + 'label' => __( 'Remove Data on Delete?', 'gravityview' ), 'default_value' => $default_settings['delete-on-uninstall'], 'horizontal' => 1, 'choices' => array( array( - 'label' => _x('Permanently Delete', 'Setting: what to do when uninstalling plugin', 'gravityview'), - 'value' => 'delete', - 'tooltip' => sprintf( '
    %s

    %s

    %s

    ', __('Delete all GravityView content and settings'), __('If you delete then re-install GravityView, it will be like installing GravityView for the first time.'), __('When GravityView is uninstalled and deleted, delete all Views, GravityView entry approvals, GravityView-generated entry notes (including approval and entry creator changes), and GravityView plugin settings. No Gravity Forms data will be touched.') ), + 'label' => _x( 'Keep GravityView Data', 'Setting: what to do when uninstalling plugin', 'gravityview' ), + 'value' => '0', + 'tooltip' => sprintf( '
    %s

    %s

    ', __( 'Keep GravityView content and settings', 'gravityview' ), __( 'If you delete then re-install the plugin, all GravityView data will be kept. Views, settings, etc. will be untouched.', 'gravityview' ) ), ), array( - 'label' => _x('Keep GravityView Data', 'Setting: what to do when uninstalling plugin', 'gravityview'), - 'value' => '0', - 'tooltip' => sprintf( '
    %s

    %s

    ', __('Keep GravityView content and settings'), __('If you delete then re-install the plugin, all Views, plugin settings, entry notes, and entry approvals will still be here.') ), + 'label' => _x( 'Permanently Delete', 'Setting: what to do when uninstalling plugin', 'gravityview' ), + 'value' => 'delete', + 'tooltip' => sprintf( '
    %s

    %s

    %s

    ', __( 'Delete all GravityView content and settings', 'gravityview' ), __( 'If you delete then re-install GravityView, it will be like installing GravityView for the first time.', 'gravityview' ), __( 'When GravityView is uninstalled and deleted, delete all Views, GravityView entry approvals, GravityView-generated entry notes (including approval and entry creator changes), and GravityView plugin settings. No Gravity Forms data will be touched.', 'gravityview' ) ), ), ), - 'description' => sprintf( __( 'Should GravityView content be removed from the site when the GravityView plugin is deleted?', 'gravityview' ), __('Permanently Delete', 'gravityview') ), + 'description' => sprintf( __( 'Should GravityView content and entry approval status be removed from the site when the GravityView plugin is deleted?', 'gravityview' ), __( 'Permanently Delete', 'gravityview' ) ), ), ) ); + /** * Redux backward compatibility * @since 1.7.4 @@ -604,6 +662,10 @@ public function app_settings_fields() { $field['label'] = isset( $field['label'] ) ? $field['label'] : rgget('title', $field ); $field['default_value'] = isset( $field['default_value'] ) ? $field['default_value'] : rgget('default', $field ); $field['description'] = isset( $field['description'] ) ? $field['description'] : rgget('subtitle', $field ); + + if( $disabled_attribute ) { + $field['disabled'] = $disabled_attribute; + } } @@ -620,20 +682,35 @@ public function app_settings_fields() { 'type' => 'save', ); + if( $disabled_attribute ) { + $button['disabled'] = $disabled_attribute; + } + /** + * @filter `gravityview/settings/extension/sections` Modify the GravityView settings page * Extensions can tap in here to insert their own section and settings. - * + * * $sections[] = array( * 'title' => __( 'GravityView My Extension Settings', 'gravityview' ), * 'fields' => $settings, * ); - * + * + * @param array $extension_settings Empty array, ready for extension settings! */ $extension_sections = apply_filters( 'gravityview/settings/extension/sections', array() ); // If there are extensions, add a section for them if ( ! empty( $extension_sections ) ) { + + if( $disabled_attribute ) { + foreach ( $extension_sections as &$section ) { + foreach ( $section['fields'] as &$field ) { + $field['disabled'] = $disabled_attribute; + } + } + } + $k = count( $extension_sections ) - 1 ; $extension_sections[ $k ]['fields'][] = $button; $sections = array_merge( $sections, $extension_sections ); diff --git a/includes/connector-functions.php b/includes/connector-functions.php index 0cf75dd060..b92a6ba95c 100644 --- a/includes/connector-functions.php +++ b/includes/connector-functions.php @@ -259,6 +259,7 @@ function gravityview_get_directory_fields( $post_id ) { * * @access public * @param int $formid Form ID + * @param string $current Field ID of field used to sort * @return string html */ function gravityview_get_sortable_fields( $formid, $current = '' ) { @@ -320,3 +321,31 @@ function the_gravityview( $view_id = '', $atts = array() ) { function gravityview_is_single_entry() { return GravityView_frontend::is_single_entry(); } + +/** + * Determine whether a View has a single checkbox or single radio input + * @see GravityView_frontend::add_scripts_and_styles() + * @since 1.15 + * @param array $form Gravity Forms form + * @param array $view_fields GravityView fields array + */ +function gravityview_view_has_single_checkbox_or_radio( $form, $view_fields ) { + + if( $form_fields = GFFormsModel::get_fields_by_type( $form, array( 'checkbox', 'radio' ) ) ) { + + /** @var GF_Field_Radio|GF_Field_Checkbox $form_field */ + foreach( $form_fields as $form_field ) { + $field_id = $form_field->id; + foreach( $view_fields as $zone ) { + foreach( $zone as $field ) { + // If it's an input, not the parent and the parent ID matches a checkbox or radio + if( ( strpos( $field['id'], '.' ) > 0 ) && floor( $field['id'] ) === floor( $field_id ) ) { + return true; + } + } + } + } + } + + return false; +} \ No newline at end of file diff --git a/includes/default-templates.php b/includes/default-templates.php deleted file mode 100644 index 8b0dbbc000..0000000000 --- a/includes/default-templates.php +++ /dev/null @@ -1,631 +0,0 @@ - 'table', - 'type' => 'custom', - 'label' => __( 'Table (default)', 'gravityview' ), - 'description' => __('Display items in a table view.', 'gravityview'), - 'logo' => plugins_url('includes/presets/default-table/logo-default-table.png', GRAVITYVIEW_FILE), - 'css_source' => plugins_url('templates/css/table-view.css', GRAVITYVIEW_FILE), - ); - - $settings = wp_parse_args( $settings, $table_settings ); - - /** - * @see GravityView_Admin_Views::get_default_field_options() for Generic Field Options - * @var array - */ - $field_options = array( - 'show_as_link' => array( - 'type' => 'checkbox', - 'label' => __( 'Link to single entry', 'gravityview' ), - 'value' => false, - 'context' => 'directory' - ), - ); - - $areas = array( - array( - '1-1' => array( - array( - 'areaid' => 'table-columns', - 'title' => __('Visible Table Columns', 'gravityview' ) , - 'subtitle' => __('Each field will be displayed as a column in the table.', 'gravityview'), - ) - ) - ) - ); - - - parent::__construct( $id, $settings, $field_options, $areas ); - - } - -} - -/** - * GravityView_Default_Template_Edit class. - * Defines Edit Table(default) template (Edit Entry) - */ -class GravityView_Default_Template_Edit extends GravityView_Template { - - function __construct( $id = 'default_table_edit', $settings = array(), $field_options = array(), $areas = array() ) { - - $edit_settings = array( - 'slug' => 'edit', - 'type' => 'internal', - 'label' => __( 'Edit Table', 'gravityview' ), - 'description' => __('Display items in a table view.', 'gravityview'), - 'logo' => plugins_url('includes/presets/default-table/logo-default-table.png', GRAVITYVIEW_FILE), - 'css_source' => plugins_url('templates/css/table-view.css', GRAVITYVIEW_FILE), - ); - - $settings = wp_parse_args( $settings, $edit_settings ); - - /** - * @see GravityView_Admin_Views::get_default_field_options() for Generic Field Options - * @var array - */ - $field_options = array(); - - $areas = array( - array( - '1-1' => array( - array( - 'areaid' => 'edit-fields', - 'title' => __('Visible Edit Fields', 'gravityview' ) - ) - ) - ) - ); - - - parent::__construct( $id, $settings, $field_options, $areas ); - - } - -} - - - -/** - * GravityView_Default_Template_List class. - * Defines List (default) template - */ -class GravityView_Default_Template_List extends GravityView_Template { - - function __construct( $id = 'default_list', $settings = array(), $field_options = array(), $areas = array() ) { - - $list_settings = array( - 'slug' => 'list', - 'type' => 'custom', - 'label' => __( 'List (default)', 'gravityview' ), - 'description' => __('Display items in a listing view.', 'gravityview'), - 'logo' => plugins_url('includes/presets/default-list/logo-default-list.png', GRAVITYVIEW_FILE), - 'css_source' => plugins_url('templates/css/list-view.css', GRAVITYVIEW_FILE), - ); - - $settings = wp_parse_args( $settings, $list_settings ); - - $field_options = array( - 'show_as_link' => array( - 'type' => 'checkbox', - 'label' => __( 'Link to single entry', 'gravityview' ), - 'value' => false, - 'context' => 'directory' - ), - ); - - $areas = array( - array( - '1-1' => array( - array( 'areaid' => 'list-title', 'title' => __('Listing Title', 'gravityview' ) , 'subtitle' => '' ), - array( 'areaid' => 'list-subtitle', 'title' => __('Subheading', 'gravityview' ) , 'subtitle' => 'Data placed here will be bold.' ), - ), - '1-3' => array( - array( 'areaid' => 'list-image', 'title' => __( 'Image', 'gravityview' ) , 'subtitle' => 'Leave empty to remove.' ) - ), - '2-3' => array( - array( 'areaid' => 'list-description', 'title' => __('Other Fields', 'gravityview' ) , 'subtitle' => 'Below the subheading, a good place for description and other data.' ) ) - ), - array( - '1-2' => array( - array( 'areaid' => 'list-footer-left', 'title' => __('Footer Left', 'gravityview' ) , 'subtitle' => '' ) - ), - '2-2' => array( - array( 'areaid' => 'list-footer-right', 'title' => __('Footer Right', 'gravityview' ) , 'subtitle' => '' ) - ) - ) - ); - - parent::__construct( $id, $settings, $field_options, $areas ); - - } -} - - -abstract class GravityView_Template { - - // template unique id - public $template_id; - - // define template settings - public $settings; - /** - * $settings: - * slug - template slug (frontend) - * css_source - url path to CSS file, to be enqueued (frontend) - * type - 'custom' or 'preset' (admin) - * label - template nicename (admin) - * description - short about text (admin) - * logo - template icon (admin) - * preview - template image for previewing (admin) - * buy_source - url source for buying this template - * preset_form - path to Gravity Form form XML file - * preset_config - path to View config (XML) - * - */ - - // form fields extra options - public $field_options; - - // define the active areas - public $active_areas; - - - function __construct( $id, $settings = array(), $field_options = array(), $areas = array() ) { - - if( empty( $id ) ) { - return; - } - - $this->template_id = $id; - - $this->merge_defaults( $settings ); - - $this->field_options = $field_options; - $this->active_areas = $areas; - - add_filter( 'gravityview_register_directory_template', array( $this, 'register_template' ) ); - - // presets hooks: - // form xml - add_filter( 'gravityview_template_formxml', array( $this, 'assign_form_xml' ), 10 , 2); - // fields config xml - add_filter( 'gravityview_template_fieldsxml', array( $this, 'assign_fields_xml' ), 10 , 2); - - // assign active areas - add_filter( 'gravityview_template_active_areas', array( $this, 'assign_active_areas' ), 10, 3 ); - - // field options - add_filter( 'gravityview_template_field_options', array( $this, 'assign_field_options' ), 10, 4 ); - - // template slug - add_filter( "gravityview_template_slug_{$id}", array( $this, 'assign_view_slug' ), 10, 2 ); - - // register template CSS - add_action( 'wp_enqueue_scripts', array( $this, 'register_styles' ) ); - } - - /** - * Merge the template settings with the default settings - * - * Sets the `settings` object var. - * - * @param array $settings Defined template settings - * @return array Merged template settings. - */ - function merge_defaults( $settings = array() ) { - - $defaults = array( - 'slug' => '', - 'css_source' => '', - 'type' => '', - 'label' => '', - 'description' => '', - 'logo' => '', - 'preview' => '', - 'buy_source' => '', - 'preset_form' => '', - 'preset_fields' => '' - ); - - $this->settings = wp_parse_args( $settings, $defaults); - - return $this->settings; - } - - /** - * Register the template to display in the admin - * - * @access private - * @param mixed $templates - * @return array Array of templates available for GV - */ - public function register_template( $templates ) { - $templates[ $this->template_id ] = $this->settings; - return $templates; - } - - - /** - * Assign active areas (for admin configuration) - * - * @access protected - * @param array $areas - * @param string $template (default: '') - * @return array Array of active areas - */ - public function assign_active_areas( $areas, $template = '', $context = 'directory' ) { - if( $this->template_id === $template ) { - $areas = $this->get_active_areas( $context ); - } - return $areas; - } - - public function get_active_areas( $context ) { - if( isset( $this->active_areas[ $context ] ) ) { - return $this->active_areas[ $context ]; - } else { - return $this->active_areas; - } - } - - - /** - * Assign template specific field options - * - * @access protected - * @param array $options (default: array()) - * @param string $template (default: '') - * @param string $field_id key for the field - * @param string|array $context Context for the field; `directory` or `single` for example. - * @return array Array of field options - */ - public function assign_field_options( $field_options, $template_id, $field_id = NULL, $context = 'directory' ) { - - if( $this->template_id === $template_id ) { - - foreach ($this->field_options as $key => $field_option) { - - $field_context = rgar($field_option, 'context'); - - // Does the field option only apply to a certain context? - // You can define multiple contexts as an array: `context => array("directory", "single")` - $context_matches = is_array($field_context) ? in_array($context, $field_context) : $context === $field_context; - - // If the context matches (or isn't defined), add the field options. - if($context_matches) { - $field_options[$key] = $field_option; - } - } - } - - return $field_options; - } - - /** - * Set the Gravity Forms import form information by using the `preset_form` field defined in the template. - * @see GravityView_Admin_Views::pre_get_form_fields() - * @see GravityView_Admin_Views::create_preset_form() - * @return string Path to XML file - */ - public function assign_form_xml( $xml = '' , $template = '' ) { - if( $this->settings['type'] === 'preset' && !empty( $this->settings['preset_form'] ) && $this->template_id === $template ) { - return $this->settings['preset_form']; - } - - return $xml; - } - - /** - * Set the Gravity Forms import form by using the `preset_fields` field defined in the template. - * @see GravityView_Admin_Views::pre_get_form_fields() - * @return string Path to XML file - */ - public function assign_fields_xml( $xml = '' , $template = '' ) { - if( $this->settings['type'] === 'preset' && !empty( $this->settings['preset_fields'] ) && $this->template_id === $template ) { - return $this->settings['preset_fields']; - } - - return $xml; - } - - - /** - * Assign the template slug when loading the presentation template (frontend) - * - * @access protected - * @param mixed $default - * @return void - */ - public function assign_view_slug( $default, $context ) { - - if( !empty( $this->settings['slug'] ) ) { - return $this->settings['slug']; - } - if( !empty( $default ) ) { - return $default; - } - // last resort, template_id - return $this->template_id; - } - - /** - * Register styles - * @return void - */ - public function register_styles() { - if( !empty( $this->settings['css_source'] ) ) { - wp_register_style( 'gravityview_style_' . $this->template_id, $this->settings['css_source'], array(), GravityView_Plugin::version, 'all' ); - } - } - - - -} - -/** Preset templates */ - -class GravityView_Preset_Business_Data extends GravityView_Default_Template_Table { - - function __construct() { - - $id = 'preset_business_data'; - - $settings = array( - 'slug' => 'table', - 'type' => 'preset', - 'label' => __( 'Business Data', 'gravityview' ), - 'description' => __( 'Display business information in a table.', 'gravityview'), - 'logo' => plugins_url('includes/presets/business-data/logo-business-data.png', GRAVITYVIEW_FILE), - 'preview' => 'http://demo.gravityview.co/blog/view/business-table/', - 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/business-data/form-business-data.xml', - 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/business-data/fields-business-data.xml' - ); - - parent::__construct( $id, $settings ); - } -} - - -class GravityView_Preset_Resume_Board extends GravityView_Default_Template_Table { - - function __construct() { - - $id = 'preset_resume_board'; - - $settings = array( - 'slug' => 'table', - 'type' => 'preset', - 'label' => __( 'Resume Board', 'gravityview' ), - 'description' => __( 'Allow job-seekers to post their resumes.', 'gravityview'), - 'logo' => plugins_url('includes/presets/resume-board/logo-resume-board.png', GRAVITYVIEW_FILE), - 'preview' => 'http://demo.gravityview.co/blog/view/resume-board/', - 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/resume-board/form-resume-board.xml', - 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/resume-board/fields-resume-board.xml' - ); - - parent::__construct( $id, $settings ); - - } -} - -class GravityView_Preset_Job_Board extends GravityView_Default_Template_List { - - function __construct() { - - $id = 'preset_job_board'; - - $settings = array( - 'slug' => 'list', - 'type' => 'preset', - 'label' => __( 'Job Board', 'gravityview' ), - 'description' => __( 'Post available jobs in a simple job board.', 'gravityview'), - 'logo' => plugins_url('includes/presets/job-board/logo-job-board.png', GRAVITYVIEW_FILE), - 'preview' => 'http://demo.gravityview.co/blog/view/job-board/', - 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/job-board/form-job-board.xml', - 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/job-board/fields-job-board.xml' - - ); - - parent::__construct( $id, $settings ); - } -} - -class GravityView_Preset_People_Table extends GravityView_Default_Template_Table { - - function __construct() { - - $id = 'preset_people_table'; - - $settings = array( - 'slug' => 'table', - 'type' => 'preset', - 'label' => __( 'People Table', 'gravityview' ), - 'description' => __( 'Display information about people in a table.', 'gravityview'), - 'logo' => plugins_url('includes/presets/people-table/logo-people-table.png', GRAVITYVIEW_FILE), - 'preview' => '', - 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/people-table/form-people-table.xml', - #'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/people-table/fields-people-table.xml' - - ); - - parent::__construct( $id, $settings ); - - } -} - -class GravityView_Preset_Issue_Tracker extends GravityView_Default_Template_Table { - - function __construct() { - - $id = 'preset_issue_tracker'; - - $settings = array( - 'slug' => 'table', - 'type' => 'preset', - 'label' => __( 'Issue Tracker', 'gravityview' ), - 'description' => __( 'Manage issues and their statuses.', 'gravityview'), - 'logo' => plugins_url('includes/presets/issue-tracker/logo-issue-tracker.png', GRAVITYVIEW_FILE), - 'preview' => 'http://demo.gravityview.co/blog/view/issue-tracker/', - 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/issue-tracker/form-issue-tracker.xml', - 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/issue-tracker/fields-issue-tracker.xml' - - ); - - parent::__construct( $id, $settings ); - - } -} - -class GravityView_Preset_Business_Listings extends GravityView_Default_Template_List { - - function __construct() { - - $id = 'preset_business_listings'; - - $settings = array( - 'slug' => 'list', - 'type' => 'preset', - 'label' => __( 'Business Listings', 'gravityview' ), - 'description' => __( 'Display business profiles.', 'gravityview'), - 'logo' => plugins_url('includes/presets/business-listings/logo-business-listings.png', GRAVITYVIEW_FILE), - 'preview' => 'http://demo.gravityview.co/blog/view/business-listings/', - 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/business-listings/form-business-listings.xml', - 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/business-listings/fields-business-listings.xml' - ); - - parent::__construct( $id, $settings ); - - } -} - -class GravityView_Preset_Event_Listings extends GravityView_Default_Template_List { - - function __construct() { - - $id = 'preset_event_listings'; - - $settings = array( - 'slug' => 'list', - 'type' => 'preset', - 'label' => __( 'Event Listings', 'gravityview' ), - 'description' => __( 'Present a list of your events.', 'gravityview'), - 'logo' => plugins_url('includes/presets/event-listings/logo-event-listings.png', GRAVITYVIEW_FILE), - 'preview' => 'http://demo.gravityview.co/blog/view/event-listings/', - 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/event-listings/form-event-listings.xml', - 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/event-listings/fields-event-listings.xml' - ); - - parent::__construct( $id, $settings ); - - } -} - -class GravityView_Preset_Profiles extends GravityView_Default_Template_List { - - function __construct() { - - $id = 'preset_profiles'; - - $settings = array( - 'slug' => 'list', - 'type' => 'preset', - 'label' => __( 'People Profiles', 'gravityview' ), - 'description' => __( 'List people with individual profiles.', 'gravityview'), - 'logo' => plugins_url('includes/presets/profiles/logo-profiles.png', GRAVITYVIEW_FILE), - 'preview' => 'http://demo.gravityview.co/blog/view/people-profiles/', - 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/profiles/form-profiles.xml', - 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/profiles/fields-profiles.xml' - ); - - parent::__construct( $id, $settings ); - - } -} - -class GravityView_Preset_Staff_Profiles extends GravityView_Default_Template_List { - - function __construct() { - - $id = 'preset_staff_profiles'; - - $settings = array( - 'slug' => 'list', - 'type' => 'preset', - 'label' => __( 'Staff Profiles', 'gravityview' ), - 'description' => __( 'List members of your team.', 'gravityview'), - 'logo' => plugins_url('includes/presets/staff-profiles/logo-staff-profiles.png', GRAVITYVIEW_FILE), - 'preview' => 'http://demo.gravityview.co/blog/view/staff-profiles/', - 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/staff-profiles/form-staff-profiles.xml', - 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/staff-profiles/fields-staff-profiles.xml', - ); - - parent::__construct( $id, $settings ); - - } -} - -class GravityView_Preset_Website_Showcase extends GravityView_Default_Template_List { - - function __construct() { - - $id = 'preset_website_showcase'; - - $settings = array( - 'slug' => 'list', - 'type' => 'preset', - 'label' => __( 'Website Showcase', 'gravityview' ), - 'description' => __( 'Feature submitted websites with screenshots.', 'gravityview'), - 'logo' => plugins_url('includes/presets/website-showcase/logo-website-showcase.png', GRAVITYVIEW_FILE), - 'preview' => 'http://demo.gravityview.co/blog/view/website-showcase/', - 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/website-showcase/form-website-showcase.xml', - 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/website-showcase/fields-website-showcase.xml' - ); - - parent::__construct( $id, $settings ); - - } -} - -new GravityView_Default_Template_Table; -new GravityView_Default_Template_List; -new GravityView_Default_Template_Edit; - -//presets -new GravityView_Preset_Business_Listings; -new GravityView_Preset_Business_Data; -new GravityView_Preset_Profiles; -new GravityView_Preset_Staff_Profiles; -new GravityView_Preset_Website_Showcase; -new GravityView_Preset_Issue_Tracker; -new GravityView_Preset_Resume_Board; -new GravityView_Preset_Job_Board; - -new GravityView_Preset_Event_Listings; -#new GravityView_Preset_People_Table; diff --git a/includes/extensions/delete-entry/class-delete-entry-shortcode.php b/includes/extensions/delete-entry/class-delete-entry-shortcode.php deleted file mode 100644 index a9e34c0eb3..0000000000 --- a/includes/extensions/delete-entry/class-delete-entry-shortcode.php +++ /dev/null @@ -1,157 +0,0 @@ -add_hooks(); - } - - function add_hooks() { - add_shortcode( 'gv_delete_entry_link', array( $this, 'shortcode' ) ); - } - - /** - * Fetch the entry for the View - * - * We need to use this instead of GFAPI::get_entry() because we want to also pass the Form ID to the - * get_entries() method. - * - * @param int $entry_id - * @param int $form_id - * - * @return array|bool False if no entry exists; Entry array if exists. - */ - function get_entry( $entry_id = 0, $form_id = 0 ) { - - $search_criteria = array( - 'field_filters' => array( - array( - 'key' => 'id', - 'value' => $entry_id - ) - ), - ); - - $paging = array( - 'offset' => 0, - 'page_size' => 1 - ); - - $entries = GFAPI::get_entries( $form_id, $search_criteria, null, $paging ); - - $entry = ( ! is_wp_error( $entries ) && ! empty( $entries[0] ) ) ? $entries[0] : false; - - return $entry; - } - - /** - * @param array $atts { - * @type string $view_id Define the ID for the View where the entry will - * @type string $entry_id ID of the entry to edit. If undefined, uses the current entry ID - * @type string $post_id ID of the base post or page to use for an embedded View - * @type string $link_atts Whether to open Edit Entry link in a new window or the same window - * @type string $return What should the shortcode return: link HTML (`html`) or the URL (`url`). Default: `html` - * @type string $field_values Parameters to pass in to the Edit Entry form to prefill data. Uses the same format as Gravity Forms "Allow field to be populated dynamically" {@see https://www.gravityhelp.com/documentation/article/allow-field-to-be-populated-dynamically/ } - * } - * @param string $content - * @param string $context - * - * @return string|void - */ - public function shortcode( $atts = array(), $content = '', $context = 'gv_edit_entry' ) { - - // Make sure GV is loaded - if( !class_exists('GravityView_frontend') || !class_exists('GravityView_View') ) { - return null; - } - - $defaults = array( - 'view_id' => 0, - 'entry_id' => 0, - 'post_id' => 0, - 'link_atts' => '', - 'return' => 'html', - 'field_values' => '', - ); - - $settings = shortcode_atts( $defaults, $atts, $context ); - - if( empty( $settings['view_id'] ) ) { - $view_id = GravityView_View::getInstance()->getViewId(); - } else { - $view_id = absint( $settings['view_id'] ); - } - - if( empty( $view_id ) ) { - do_action( 'gravityview_log_debug', __METHOD__ . ' A View ID was not defined' ); - return null; - } - - $post_id = empty( $settings['post_id'] ) ? $view_id : absint( $settings['post_id'] ); - - $form_id = gravityview_get_form_id( $view_id ); - - $backup_entry_id = GravityView_frontend::getInstance()->getSingleEntry() ? GravityView_frontend::getInstance()->getSingleEntry() : GravityView_View::getInstance()->getCurrentEntry(); - - $entry_id = empty( $settings['entry_id'] ) ? $backup_entry_id : absint( $settings['entry_id'] ); - - if( empty( $entry_id ) ) { - do_action( 'gravityview_log_debug', __METHOD__ . ' No entry defined' ); - return null; - } - - // By default, show only current user - $user = wp_get_current_user(); - - if( ! $user ) { - do_action( 'gravityview_log_debug', __METHOD__ . ' No user defined; edit entry requires logged in user' ); - return null; - } - - $entry = $this->get_entry( $entry_id, $form_id ); - - // No search results - if( false === $entry ) { - do_action( 'gravityview_log_debug', __METHOD__ . ' No entries match the entry ID defined', $entry_id ); - return null; - } - - // Check permissions - if( false === GravityView_Edit_Entry::check_user_cap_edit_entry( $entry, $view_id ) ) { - do_action( 'gravityview_log_debug', __METHOD__ . ' User does not have the capability to edit this entry: ' . $entry_id ); - return null; - } - - $href = GravityView_Delete_Entry::get_delete_link( $entry, $view_id, $post_id, $settings ); - - // Get just the URL, not the tag - if( 'url' === $settings['return'] ) { - return $href; - } - - $link_text = empty( $content ) ? __('Delete Entry', 'gravityview') : $content; - - return gravityview_get_link( $href, $link_text, $settings['link_atts'] ); - - } - -} //end class - -new GravityView_Delete_Entry_Shortcode; diff --git a/includes/extensions/delete-entry/class-delete-entry.php b/includes/extensions/delete-entry/class-delete-entry.php index 7147b76be0..af17b3d817 100644 --- a/includes/extensions/delete-entry/class-delete-entry.php +++ b/includes/extensions/delete-entry/class-delete-entry.php @@ -32,18 +32,9 @@ function __construct() { self::$file = plugin_dir_path( __FILE__ ); - $this->include_files(); - $this->add_hooks(); } - /** - * @since 1.9.2 - */ - private function include_files() { - require_once( self::$file . 'class-delete-entry-shortcode.php' ); - } - /** * @since 1.9.2 */ @@ -240,6 +231,7 @@ public static function get_delete_link( $entry, $view_id = 0, $post_id = null ) $base = GravityView_API::directory_link( $post_id, true ); if( empty( $base ) ) { + do_action( 'gravityview_log_error', __METHOD__ . ' - Post ID does not exist: '.$post_id ); return NULL; } @@ -522,22 +514,28 @@ function user_can_delete_entry( $entry = array() ) { * checks if user has permissions to view the link or delete a specific entry * * @since 1.5.1 + * @since 1.15 Added `$view_id` param + * * @param array $entry Gravity Forms entry array * @param array $field Field settings (optional) + * @param int $view_id Pass a View ID to check caps against. If not set, check against current View (optional) * @return bool */ - public static function check_user_cap_delete_entry( $entry, $field = array() ) { + public static function check_user_cap_delete_entry( $entry, $field = array(), $view_id = 0 ) { $gravityview_view = GravityView_View::getInstance(); + $current_user = wp_get_current_user(); + + $entry_id = isset( $entry['id'] ) ? $entry['id'] : NULL; + // Or if they can delete any entries (as defined in Gravity Forms), we're good. - if( GFCommon::current_user_can_any( 'gravityforms_delete_entries' ) ) { + if( GVCommon::has_cap( array( 'gravityforms_delete_entries', 'gravityview_delete_others_entries' ), $entry_id ) ) { - do_action('gravityview_log_debug', 'GravityView_Delete_Entry[check_user_cap_delete_entry] Current user has `gravityforms_delete_entries` capability.' ); + do_action('gravityview_log_debug', 'GravityView_Delete_Entry[check_user_cap_delete_entry] Current user has `gravityforms_delete_entries` or `gravityview_delete_others_entries` capability.' ); return true; } - $current_user = wp_get_current_user(); // If field options are passed, check if current user can view the link if( !empty( $field ) ) { @@ -550,7 +548,7 @@ public static function check_user_cap_delete_entry( $entry, $field = array() ) { return false; } - if( GFCommon::current_user_can_any( $field['allow_edit_cap'] ) ) { + if( GVCommon::has_cap( $field['allow_edit_cap'] ) ) { // Do not return true if cap is read, as we need to check if the current user created the entry if( $field['allow_edit_cap'] !== 'read' ) { @@ -573,10 +571,12 @@ public static function check_user_cap_delete_entry( $entry, $field = array() ) { return false; } + $view_id = empty( $view_id ) ? $gravityview_view->getViewId() : $view_id; + // Only checks user_delete view option if view is already set - if( $gravityview_view->getViewId() ) { + if( $view_id ) { - $current_view = gravityview_get_current_view_data(); + $current_view = gravityview_get_current_view_data( $view_id ); $user_delete = isset( $current_view['atts']['user_delete'] ) ? $current_view['atts']['user_delete'] : false; diff --git a/includes/extensions/edit-entry/class-edit-entry-render.php b/includes/extensions/edit-entry/class-edit-entry-render.php index 2245e82cae..f977074c0e 100644 --- a/includes/extensions/edit-entry/class-edit-entry-render.php +++ b/includes/extensions/edit-entry/class-edit-entry-render.php @@ -351,7 +351,7 @@ function maybe_update_post_fields( $form ) { $post_id = $this->entry['post_id']; // Security check - if( false === current_user_can( 'edit_post', $post_id ) ) { + if( false === GVCommon::has_cap( 'edit_post', $post_id ) ) { do_action( 'gravityview_log_error', 'The current user does not have the ability to edit Post #'.$post_id ); return; } @@ -509,12 +509,12 @@ public function edit_entry_form() {

    @@ -642,9 +642,7 @@ private function render_edit_form() { * @return string */ public function render_form_buttons() { - ob_start(); - include( GravityView_Edit_Entry::$file .'/partials/form-buttons.php'); - return ob_get_clean(); + return gravityview_ob_include( GravityView_Edit_Entry::$file .'/partials/form-buttons.php', $this ); } @@ -718,12 +716,9 @@ function modify_edit_field_input( $field_content = '', $field, $value, $lead_id } /** - * Allow the pre-populated value to override saved value - * By default, pre-populate mechanism only kicks on empty fields - * + * @filter `gravityview/edit_entry/pre_populate/override` Allow the pre-populated value to override saved value in Edit Entry form. By default, pre-populate mechanism only kicks on empty fields. * @param boolean True: override saved values; False: don't override (default) * @param $field GF_Field object Gravity Forms field object - * * @since 1.13 */ $override_saved_value = apply_filters( 'gravityview/edit_entry/pre_populate/override', false, $field ); @@ -777,9 +772,8 @@ function modify_edit_field_input( $field_content = '', $field, $value, $lead_id $field_value = $field->get_value_default_if_empty( $field_value ); /** - * change the field value if needed + * @filter `gravityview/edit_entry/field_value` Change the value of an Edit Entry field, if needed * @since 1.11 - * * @param mixed $field_value field value used to populate the input * @param object $field Gravity Forms field object ( Class GF_Field ) */ @@ -794,7 +788,7 @@ function modify_edit_field_input( $field_content = '', $field, $value, $lead_id $warnings = ob_get_clean(); if( !empty( $warnings ) ) { - do_action( 'gravityview_log_error', __METHOD__ . $warnings ); + do_action( 'gravityview_log_error', __METHOD__ . $warnings, $field_value ); } /** @@ -1206,10 +1200,11 @@ private function filter_fields( $fields, $configured_fields ) { ); /** - * Hide product fields from being editable. Default: false (set using self::$supports_product_fields) + * @filter `gravityview/edit_entry/hide-product-fields` Hide product fields from being editable. * @since 1.9.1 + * @param boolean $hide_product_fields Whether to hide product fields in the editor. Default: false */ - $hide_product_fields = apply_filters( 'gravityview/edit_entry/hide-product-fields', empty( $supports_product_fields ) ); + $hide_product_fields = apply_filters( 'gravityview/edit_entry/hide-product-fields', empty( self::$supports_product_fields ) ); if( $hide_product_fields ) { $field_type_blacklist[] = 'option'; @@ -1297,18 +1292,17 @@ private function merge_field_properties( $field, $field_setting ) { function filter_admin_only_fields( $fields = array(), $edit_fields = null, $form = array(), $view_id = 0 ) { /** + * @filter `gravityview/edit_entry/use_gf_admin_only_setting` When Edit tab isn't configured, should the Gravity Forms "Admin Only" field settings be used to control field display to non-admins? Default: true * If the Edit Entry tab is not configured, adminOnly fields will not be shown to non-administrators. * If the Edit Entry tab *is* configured, adminOnly fields will be shown to non-administrators, using the configured GV permissions - * * @since 1.9.1 - * * @param boolean $use_gf_adminonly_setting True: Hide field if set to Admin Only in GF and the user is not an admin. False: show field based on GV permissions, ignoring GF permissions. * @param array $form GF Form array * @param int $view_id View ID */ $use_gf_adminonly_setting = apply_filters( 'gravityview/edit_entry/use_gf_admin_only_setting', empty( $edit_fields ), $form, $view_id ); - if( $use_gf_adminonly_setting && false === GFCommon::current_user_can_any( 'gravityforms_edit_entries' ) ) { + if( $use_gf_adminonly_setting && false === GVCommon::has_cap( 'gravityforms_edit_entries', $this->entry['id'] ) ) { return $fields; } @@ -1326,12 +1320,20 @@ function filter_admin_only_fields( $fields = array(), $edit_fields = null, $form * * @since 1.9 * - * @param $form - * @return mixed + * @param array $form Gravity Forms form + * @return array Modified form, if not using Conditional Logic */ function filter_conditional_logic( $form ) { - if( apply_filters( 'gravityview/edit_entry/conditional_logic', true, $form ) ) { + /** + * @filter `gravityview/edit_entry/conditional_logic` Should the Edit Entry form use Gravity Forms conditional logic showing/hiding of fields? + * @since 1.9 + * @param bool $use_conditional_logic True: Gravity Forms will show/hide fields just like in the original form; False: conditional logic will be disabled and fields will be shown based on configuration. Default: true + * @param array $form Gravity Forms form + */ + $use_conditional_logic = apply_filters( 'gravityview/edit_entry/conditional_logic', true, $form ); + + if( $use_conditional_logic ) { return $form; } @@ -1488,7 +1490,7 @@ private function user_can_edit_field( $field, $echo = false ) { private function check_user_cap_edit_field( $field ) { // If they can edit any entries (as defined in Gravity Forms), we're good. - if( GFCommon::current_user_can_any( 'gravityforms_edit_entries' ) ) { + if( GVCommon::has_cap( array( 'gravityforms_edit_entries', 'gravityview_edit_others_entries' ) ) ) { return true; } @@ -1496,7 +1498,7 @@ private function check_user_cap_edit_field( $field ) { // If the field has custom editing capaibilities set, check those if( $field_cap ) { - return GFCommon::current_user_can_any( $field['allow_edit_cap'] ); + return GVCommon::has_cap( $field['allow_edit_cap'] ); } return false; @@ -1524,9 +1526,8 @@ public function verify_nonce() { } /** - * Override nonce validation + * @filter `gravityview/edit_entry/verify_nonce` Override Edit Entry nonce validation. Return true to declare nonce valid. * @since 1.13 - * * @param int|boolean $valid False if invalid; 1 or 2 when nonce was generated * @param string $nonce_field Key used when validating submissions. Default: is_gv_edit_entry */ diff --git a/includes/extensions/edit-entry/class-edit-entry-shortcode.php b/includes/extensions/edit-entry/class-edit-entry-shortcode.php deleted file mode 100644 index 516224557b..0000000000 --- a/includes/extensions/edit-entry/class-edit-entry-shortcode.php +++ /dev/null @@ -1,192 +0,0 @@ -loader = $loader; - } - - function load() { - - add_shortcode( 'gv_edit_entry_link', array( $this, 'shortcode' ) ); - - } - - /** - * Fetch the entry for the View - * - * We need to use this instead of GFAPI::get_entry() because we want to also pass the Form ID to the - * get_entries() method. - * - * @param int $entry_id - * @param int $form_id - * - * @return array|bool False if no entry exists; Entry array if exists. - */ - function get_entry( $entry_id = 0, $form_id = 0 ) { - - $search_criteria = array( - 'field_filters' => array( - array( - 'key' => 'id', - 'value' => $entry_id - ) - ), - ); - - $paging = array( - 'offset' => 0, - 'page_size' => 1 - ); - - $entries = GFAPI::get_entries( $form_id, $search_criteria, null, $paging ); - - $entry = ( ! is_wp_error( $entries ) && ! empty( $entries[0] ) ) ? $entries[0] : false; - - return $entry; - } - - /** - * Get the URL for the Edit Entry link - * - * @param array $entry GF Entry array - * @param int $view_id View ID - * @param int $post_id Optional: alternative base URL for embedded Views - */ - private function get_edit_url( $entry, $view_id, $post_id, $settings = array() ) { - - $href = GravityView_Edit_Entry::get_edit_link( $entry, $view_id, $post_id ); - - // Allow passing params to dynamically populate - if( !empty( $settings['field_values'] ) ) { - - parse_str( $settings['field_values'], $field_values ); - - $href = add_query_arg( $field_values, $href ); - } - - return $href; - } - - /** - * @param array $atts { - * @type string $view_id Define the ID for the View where the entry will - * @type string $entry_id ID of the entry to edit. If undefined, uses the current entry ID - * @type string $post_id ID of the base post or page to use for an embedded View - * @type string $link_atts Whether to open Edit Entry link in a new window or the same window - * @type string $return What should the shortcode return: link HTML (`html`) or the URL (`url`). Default: `html` - * @type string $field_values Parameters to pass in to the Edit Entry form to prefill data. Uses the same format as Gravity Forms "Allow field to be populated dynamically" {@see https://www.gravityhelp.com/documentation/article/allow-field-to-be-populated-dynamically/ } - * } - * @param string $content - * @param string $context - * - * @return string|void - */ - public function shortcode( $atts = array(), $content = '', $context = 'gv_edit_entry' ) { - - // Make sure GV is loaded - if( !class_exists('GravityView_frontend') || !class_exists('GravityView_View') ) { - return null; - } - - $defaults = array( - 'view_id' => 0, - 'entry_id' => 0, - 'post_id' => 0, - 'link_atts' => '', - 'return' => 'html', - 'field_values' => '', - ); - - $settings = shortcode_atts( $defaults, $atts, $context ); - - if( empty( $settings['view_id'] ) ) { - $view_id = GravityView_View::getInstance()->getViewId(); - } else { - $view_id = absint( $settings['view_id'] ); - } - - if( empty( $view_id ) ) { - do_action( 'gravityview_log_debug', __METHOD__ . ' A View ID was not defined' ); - return null; - } - - // if post_id is not defined, default to view_id - $post_id = empty( $settings['post_id'] ) ? $view_id : absint( $settings['post_id'] ); - - $form_id = gravityview_get_form_id( $view_id ); - - $backup_entry_id = GravityView_frontend::getInstance()->getSingleEntry() ? GravityView_frontend::getInstance()->getSingleEntry() : GravityView_View::getInstance()->getCurrentEntry(); - - $entry_id = empty( $settings['entry_id'] ) ? $backup_entry_id : absint( $settings['entry_id'] ); - - if( empty( $entry_id ) ) { - do_action( 'gravityview_log_debug', __METHOD__ . ' No entry defined' ); - return null; - } - - // By default, show only current user - $user = wp_get_current_user(); - - if( ! $user ) { - do_action( 'gravityview_log_debug', __METHOD__ . ' No user defined; edit entry requires logged in user' ); - return null; - } - - $entry = $this->get_entry( $entry_id, $form_id ); - - // No search results - if( false === $entry ) { - do_action( 'gravityview_log_debug', __METHOD__ . ' No entries match the entry ID defined', $entry_id ); - return null; - } - - // Check permissions - if( false === GravityView_Edit_Entry::check_user_cap_edit_entry( $entry, $view_id ) ) { - do_action( 'gravityview_log_debug', __METHOD__ . ' User does not have the capability to edit this entry: ' . $entry_id ); - return null; - } - - $href = $this->get_edit_url( $entry, $view_id, $post_id, $settings ); - - // Get just the URL, not the tag - if( 'url' === $settings['return'] ) { - return $href; - } - - $link_text = empty( $content ) ? __('Edit Entry', 'gravityview') : $content; - - return gravityview_get_link( $href, $link_text, $settings['link_atts'] ); - - } - -} //end class diff --git a/includes/extensions/edit-entry/class-edit-entry-user-registration.php b/includes/extensions/edit-entry/class-edit-entry-user-registration.php index 2048025a4e..17a11e64c1 100644 --- a/includes/extensions/edit-entry/class-edit-entry-user-registration.php +++ b/includes/extensions/edit-entry/class-edit-entry-user-registration.php @@ -46,9 +46,7 @@ public function load() { if( apply_filters( 'gravityview/edit_entry/user_registration/trigger_update', true ) ) { add_action( 'gravityview/edit_entry/after_update' , array( $this, 'update_user' ), 10, 2 ); - /** - * TODO: Should be removed once Gravity Forms allows for defining "Preserve current Display Name" option - */ + // last resort in case the current user display name don't match any of the defaults add_action( 'gform_user_updated', array( $this, 'restore_display_name' ), 10, 4 ); } } @@ -83,6 +81,27 @@ public function update_user( $form = array(), $entry_id = 0 ) { */ $config = GFUser::get_active_config( $form, $entry ); + /** + * @filter `gravityview/edit_entry/user_registration/preserve_role` Keep the current user role or override with the role defined in the Create feed + * @since 1.15 + * @param[in,out] boolean $preserve_role Preserve current user role Default: true + * @param[in] array $config Gravity Forms User Registration feed configuration for the form + * @param[in] array $form Gravity Forms form array + * @param[in] array $entry Gravity Forms entry being edited + */ + $preserve_role = apply_filters( 'gravityview/edit_entry/user_registration/preserve_role', true, $config, $form, $entry ); + + if( $preserve_role ) { + $config['meta']['role'] = 'gfur_preserve_role'; + } + + /** + * Make sure the current display name is not changed with the update user method. + * @since 1.15 + */ + $config['meta']['displayname'] = $this->match_current_display_name( $entry['created_by'] ); + + /** * @filter `gravityview/edit_entry/user_registration/config` Modify the User Registration Addon feed configuration * @since 1.14 @@ -92,25 +111,84 @@ public function update_user( $form = array(), $entry_id = 0 ) { */ $config = apply_filters( 'gravityview/edit_entry/user_registration/config', $config, $form, $entry ); - $is_update_feed = ( $config && rgars( $config, 'meta/feed_type') === 'update' ); + $is_create_feed = ( $config && rgars( $config, 'meta/feed_type') === 'create' ); + + // Only update if it's a create feed + if( ! $is_create_feed ) { + return; + } + + // The priority is set to 3 so that default priority (10) will still override it + add_filter( 'send_password_change_email', '__return_false', 3 ); + add_filter( 'send_email_change_email', '__return_false', 3 ); + + // Trigger the User Registration update user method + GFUser::update_user( $entry, $form, $config ); + + remove_filter( 'send_password_change_email', '__return_false', 3 ); + remove_filter( 'send_email_change_email', '__return_false', 3 ); + + } - // Only update if it's an update feed (not a create feed) - if( $is_update_feed ) { + /** + * Calculate the user display name format + * + * @since 1.15 + * + * @param int $user_id WP User ID + * @return string Display name format as used inside Gravity Forms User Registration + */ + public function match_current_display_name( $user_id ) { + + $user = get_userdata( $user_id ); + + $names = $this->generate_display_names( $user ); + + $format = array_search( $user->display_name, $names, true ); + + // In case we can't find the current display name format, or it is the 'nickname' format (which Gravity Forms doesn't support) + // trigger last resort method at the 'gform_user_updated' hook + if( false === $format || 'nickname' === $format ) { + $this->_user_before_update = $user; + $format = 'nickname'; + } + + return $format; - $this->_user_before_update = get_userdata( $entry['created_by'] ); + } + + /** + * Generate an array of all the user display names possibilities + * + * @since 1.15 + * + * @param object $profileuser WP_User object + * @return array List all the possible display names for a certain User object + */ + public function generate_display_names( $profileuser ) { + + $public_display = array(); + $public_display['nickname'] = $profileuser->nickname; + $public_display['username'] = $profileuser->user_login; - // The priority is set to 3 so that default priority (10) will still override it - add_filter( 'send_password_change_email', '__return_false', 3 ); - add_filter( 'send_email_change_email', '__return_false', 3 ); + if ( !empty($profileuser->first_name) ) + $public_display['firstname'] = $profileuser->first_name; - // Trigger the User Registration update user method - GFUser::update_user( $entry, $form, $config ); + if ( !empty($profileuser->last_name) ) + $public_display['lastname'] = $profileuser->last_name; - remove_filter( 'send_password_change_email', '__return_false', 3 ); - remove_filter( 'send_email_change_email', '__return_false', 3 ); + if ( !empty($profileuser->first_name) && !empty($profileuser->last_name) ) { + $public_display['firstlast'] = $profileuser->first_name . ' ' . $profileuser->last_name; + $public_display['lastfirst'] = $profileuser->last_name . ' ' . $profileuser->first_name; } + + $public_display = array_map( 'trim', $public_display ); + $public_display = array_unique( $public_display ); + + return $public_display; } + /** * Restore the Display Name and roles of a user after being updated by Gravity Forms User Registration Addon * @@ -134,10 +212,13 @@ public function restore_display_name( $user_id = 0, $config = array(), $entry = $is_update_feed = ( $config && rgars( $config, 'meta/feed_type') === 'update' ); /** - * Don't restore display name: either disabled, or not an Update feed (it's a Create feed) + * Don't restore display name: + * - either disabled, + * - or it is an Update feed (we only care about Create feed) + * - or we don't need as we found the correct format before updating user. * @since 1.14.4 */ - if( ! $restore_display_name || ! $is_update_feed ) { + if( ! $restore_display_name || $is_update_feed || is_null( $this->_user_before_update ) ) { return; } diff --git a/includes/extensions/edit-entry/class-edit-entry.php b/includes/extensions/edit-entry/class-edit-entry.php index 9db40f3f58..ec5798bbbf 100644 --- a/includes/extensions/edit-entry/class-edit-entry.php +++ b/includes/extensions/edit-entry/class-edit-entry.php @@ -18,6 +18,9 @@ class GravityView_Edit_Entry { + /** + * @var string + */ static $file; static $instance; @@ -31,7 +34,7 @@ class GravityView_Edit_Entry { function __construct() { - self::$file = plugin_dir_path( __FILE__ ); + self::$file = plugin_dir_path( __FILE__ ); if( is_admin() ) { $this->load_components( 'admin' ); @@ -39,7 +42,6 @@ function __construct() { $this->load_components( 'render' ); - $this->load_components( 'shortcode' ); // If GF User Registration Add-on exists if( class_exists( 'GFUser' ) ) { @@ -223,8 +225,12 @@ public static function check_user_cap_edit_entry( $entry, $view_id = 0 ) { // No permission by default $user_can_edit = false; - // Or if they can edit any entries (as defined in Gravity Forms), we're good. - if( GFCommon::current_user_can_any( 'gravityforms_edit_entries' ) ) { + // If they can edit any entries (as defined in Gravity Forms) + // Or if they can edit other people's entries + // Then we're good. + if( GVCommon::has_cap( array( 'gravityforms_edit_entries', 'gravityview_edit_others_entries' ), $entry['id'] ) ) { + + do_action('gravityview_log_debug', __METHOD__ . ' - User has ability to edit all entries.'); $user_can_edit = true; @@ -261,15 +267,20 @@ public static function check_user_cap_edit_entry( $entry, $view_id = 0 ) { do_action('gravityview_log_debug', sprintf( 'GravityView_Edit_Entry[check_user_cap_edit_entry] User %s created the entry.', $current_user->ID ) ); $user_can_edit = true; + + } else if( ! is_user_logged_in() ) { + + do_action( 'gravityview_log_debug', __METHOD__ . ' No user defined; edit entry requires logged in user' ); } } /** * @filter `gravityview/edit_entry/user_can_edit_entry` Modify whether user can edit an entry. + * @since 1.15 Added `$entry` and `$view_id` parameters * @param[in,out] boolean $user_can_edit Can the current user edit the current entry? (Default: false) - * @param[in] array $entry Gravity Forms entry array {@since 1.14.4} - * @param[in] int $view_id ID of the view you want to check visibility against {@since 1.14.4} + * @param[in] array $entry Gravity Forms entry array {@since 1.15} + * @param[in] int $view_id ID of the view you want to check visibility against {@since 1.15} */ $user_can_edit = apply_filters( 'gravityview/edit_entry/user_can_edit_entry', $user_can_edit, $entry, $view_id ); diff --git a/includes/extensions/edit-entry/partials/form-buttons.php b/includes/extensions/edit-entry/partials/form-buttons.php index 3a4d44bcd5..df54a10d64 100644 --- a/includes/extensions/edit-entry/partials/form-buttons.php +++ b/includes/extensions/edit-entry/partials/form-buttons.php @@ -1,3 +1,9 @@ +
    form, $this->entry, $this->view_id ); + $back_link = apply_filters( 'gravityview/edit_entry/cancel_link', remove_query_arg( array( 'page', 'view', 'edit' ) ), $object->form, $object->entry, $object->view_id ); /** * @action `gravityview/edit-entry/publishing-action/before` Triggered before the submit buttons in the Edit Entry screen, inside the `
    ` container. @@ -18,10 +24,10 @@ * @param array $entry The Gravity Forms entry * @param int $view_id The current View ID */ - do_action( 'gravityview/edit-entry/publishing-action/before', $this->form, $this->entry, $this->view_id ); + do_action( 'gravityview/edit-entry/publishing-action/before', $object->form, $object->entry, $object->view_id ); ?> - + form, $this->entry, $this->view_id ); + do_action( 'gravityview/edit-entry/publishing-action/after', $object->form, $object->entry, $object->view_id ); ?> - +
    diff --git a/includes/extensions/edit-entry/partials/inline-javascript.php b/includes/extensions/edit-entry/partials/inline-javascript.php index 27f8a120a4..b4e5582d08 100644 --- a/includes/extensions/edit-entry/partials/inline-javascript.php +++ b/includes/extensions/edit-entry/partials/inline-javascript.php @@ -1,4 +1,9 @@ - + \ No newline at end of file diff --git a/includes/fields/list.php b/includes/fields/list.php index cc1eeef7b3..ed10ad3ad8 100644 --- a/includes/fields/list.php +++ b/includes/fields/list.php @@ -81,7 +81,7 @@ public function _filter_field_label( $label, $field, $form, $entry ) { $field_object = RGFormsModel::get_field( $form, $field['id'] ); // Not a list field - if( ( ! $field_object || 'list' !== $field_object->get_input_type() ) ) { + if( ! $field_object || 'list' !== $field_object->type ) { return $label; } diff --git a/includes/helper-functions.php b/includes/helper-functions.php index ff6e95ccea..328ded2c00 100644 --- a/includes/helper-functions.php +++ b/includes/helper-functions.php @@ -70,6 +70,11 @@ function gravityview_sanitize_html_class( $classes ) { /** * Replace multiple newlines, tabs, and spaces with a single space * + * First, runs normalize_whitespace() on a string. This replaces multiple lines with a single line, and tabs with spaces. + * We then strip any tabs or newlines and replace *those* with a single space. + * + * @see normalize_whitespace() + * @see GravityView_Helper_Functions_Test::test_gravityview_strip_whitespace * @since 1.13 * * @param string $string String to strip whitespace from @@ -85,12 +90,13 @@ function gravityview_strip_whitespace( $string ) { * Get the contents of a file using `include()` and `ob_start()` * * @since 1.13 + * @since 1.15 Added $object param * * @param string $file_path Full path to a file - * + * @param mixed $object Pass pseudo-global to the included file * @return string Included file contents */ -function gravityview_ob_include( $file_path ) { +function gravityview_ob_include( $file_path, $object = NULL ) { if( ! file_exists( $file_path ) ) { do_action( 'gravityview_log_error', __FUNCTION__ . ': File path does not exist. ', $file_path ); return ''; diff --git a/includes/presets/business-data/class-gravityview-preset-business-data.php b/includes/presets/business-data/class-gravityview-preset-business-data.php new file mode 100644 index 0000000000..89bbca3c82 --- /dev/null +++ b/includes/presets/business-data/class-gravityview-preset-business-data.php @@ -0,0 +1,36 @@ + 'table', + 'type' => 'preset', + 'label' => __( 'Business Data', 'gravityview' ), + 'description' => __( 'Display business information in a table.', 'gravityview' ), + 'logo' => plugins_url( 'includes/presets/business-data/logo-business-data.png', GRAVITYVIEW_FILE ), + 'preview' => 'http://demo.gravityview.co/blog/view/business-table/', + 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/business-data/form-business-data.xml', + 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/business-data/fields-business-data.xml' + ); + + parent::__construct( $id, $settings ); + } +} + +new GravityView_Preset_Business_Data; diff --git a/includes/presets/business-listings/class-gravityview-preset-business-listings.php b/includes/presets/business-listings/class-gravityview-preset-business-listings.php new file mode 100644 index 0000000000..e56772e83f --- /dev/null +++ b/includes/presets/business-listings/class-gravityview-preset-business-listings.php @@ -0,0 +1,37 @@ + 'list', + 'type' => 'preset', + 'label' => __( 'Business Listings', 'gravityview' ), + 'description' => __( 'Display business profiles.', 'gravityview' ), + 'logo' => plugins_url( 'includes/presets/business-listings/logo-business-listings.png', GRAVITYVIEW_FILE ), + 'preview' => 'http://demo.gravityview.co/blog/view/business-listings/', + 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/business-listings/form-business-listings.xml', + 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/business-listings/fields-business-listings.xml' + ); + + parent::__construct( $id, $settings ); + + } +} + +new GravityView_Preset_Business_Listings; diff --git a/includes/presets/default-edit/class-gravityview-default-template-edit.php b/includes/presets/default-edit/class-gravityview-default-template-edit.php new file mode 100644 index 0000000000..bef91b4cf7 --- /dev/null +++ b/includes/presets/default-edit/class-gravityview-default-template-edit.php @@ -0,0 +1,58 @@ + 'edit', + 'type' => 'internal', + 'label' => __( 'Edit Table', 'gravityview' ), + 'description' => __('Display items in a table view.', 'gravityview'), + 'logo' => plugins_url('includes/presets/default-table/logo-default-table.png', GRAVITYVIEW_FILE), + 'css_source' => plugins_url('templates/css/table-view.css', GRAVITYVIEW_FILE), + ); + + $settings = wp_parse_args( $settings, $edit_settings ); + + /** + * @see GravityView_Admin_Views::get_default_field_options() for Generic Field Options + * @var array + */ + $field_options = array(); + + $areas = array( + array( + '1-1' => array( + array( + 'areaid' => 'edit-fields', + 'title' => __('Visible Edit Fields', 'gravityview' ) + ) + ) + ) + ); + + + parent::__construct( $id, $settings, $field_options, $areas ); + + } + +} + +new GravityView_Default_Template_Edit; diff --git a/includes/presets/default-list/class-gravityview-default-template-list.php b/includes/presets/default-list/class-gravityview-default-template-list.php new file mode 100644 index 0000000000..fa66814556 --- /dev/null +++ b/includes/presets/default-list/class-gravityview-default-template-list.php @@ -0,0 +1,83 @@ + 'list', + 'type' => 'custom', + 'label' => __( 'List (default)', 'gravityview' ), + 'description' => __( 'Display items in a listing view.', 'gravityview' ), + 'logo' => plugins_url( 'includes/presets/default-list/logo-default-list.png', GRAVITYVIEW_FILE ), + 'css_source' => plugins_url( 'templates/css/list-view.css', GRAVITYVIEW_FILE ), + ); + + $settings = wp_parse_args( $settings, $list_settings ); + + $field_options = array( + 'show_as_link' => array( + 'type' => 'checkbox', + 'label' => __( 'Link to single entry', 'gravityview' ), + 'value' => false, + 'context' => 'directory' + ), + ); + + $areas = array( + array( + '1-1' => array( + array( + 'areaid' => 'list-title', + 'title' => __( 'Listing Title', 'gravityview' ), + 'subtitle' => '' + ), + array( + 'areaid' => 'list-subtitle', + 'title' => __( 'Subheading', 'gravityview' ), + 'subtitle' => __( 'Data placed here will be bold.', 'gravityview' ), + ), + ), + '1-3' => array( + array( + 'areaid' => 'list-image', + 'title' => __( 'Image', 'gravityview' ), + 'subtitle' => __( 'Leave empty to remove.', 'gravityview' ), + ) + ), + '2-3' => array( + array( + 'areaid' => 'list-description', + 'title' => __( 'Other Fields', 'gravityview' ), + 'subtitle' => __( 'Below the subheading, a good place for description and other data.', 'gravityview' ), + ) + ) + ), + array( + '1-2' => array( + array( + 'areaid' => 'list-footer-left', + 'title' => __( 'Footer Left', 'gravityview' ), + 'subtitle' => '' + ) + ), + '2-2' => array( + array( + 'areaid' => 'list-footer-right', + 'title' => __( 'Footer Right', 'gravityview' ), + 'subtitle' => '' + ) + ) + ) + ); + + parent::__construct( $id, $settings, $field_options, $areas ); + + } +} + +new GravityView_Default_Template_List; diff --git a/includes/presets/default-table/class-gravityview-default-template-table.php b/includes/presets/default-table/class-gravityview-default-template-table.php new file mode 100644 index 0000000000..3d6f5f17a5 --- /dev/null +++ b/includes/presets/default-table/class-gravityview-default-template-table.php @@ -0,0 +1,53 @@ + 'table', + 'type' => 'custom', + 'label' => __( 'Table (default)', 'gravityview' ), + 'description' => __( 'Display items in a table view.', 'gravityview' ), + 'logo' => plugins_url( 'includes/presets/default-table/logo-default-table.png', GRAVITYVIEW_FILE ), + 'css_source' => plugins_url( 'templates/css/table-view.css', GRAVITYVIEW_FILE ), + ); + + $settings = wp_parse_args( $settings, $table_settings ); + + /** + * @see GravityView_Admin_Views::get_default_field_options() for Generic Field Options + * @var array + */ + $field_options = array( + 'show_as_link' => array( + 'type' => 'checkbox', + 'label' => __( 'Link to single entry', 'gravityview' ), + 'value' => false, + 'context' => 'directory' + ), + ); + + $areas = array( + array( + '1-1' => array( + array( + 'areaid' => 'table-columns', + 'title' => __( 'Visible Table Columns', 'gravityview' ), + 'subtitle' => __( 'Each field will be displayed as a column in the table.', 'gravityview' ), + ) + ) + ) + ); + + + parent::__construct( $id, $settings, $field_options, $areas ); + + } +} + +new GravityView_Default_Template_Table; \ No newline at end of file diff --git a/includes/presets/event-listings/class-gravityview-preset-event-listings.php b/includes/presets/event-listings/class-gravityview-preset-event-listings.php new file mode 100644 index 0000000000..02c9d0401a --- /dev/null +++ b/includes/presets/event-listings/class-gravityview-preset-event-listings.php @@ -0,0 +1,37 @@ + 'list', + 'type' => 'preset', + 'label' => __( 'Event Listings', 'gravityview' ), + 'description' => __( 'Present a list of your events.', 'gravityview' ), + 'logo' => plugins_url( 'includes/presets/event-listings/logo-event-listings.png', GRAVITYVIEW_FILE ), + 'preview' => 'http://demo.gravityview.co/blog/view/event-listings/', + 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/event-listings/form-event-listings.xml', + 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/event-listings/fields-event-listings.xml' + ); + + parent::__construct( $id, $settings ); + + } +} + +new GravityView_Preset_Event_Listings; diff --git a/includes/presets/issue-tracker/class-gravityview-preset-issue-tracker.php b/includes/presets/issue-tracker/class-gravityview-preset-issue-tracker.php new file mode 100644 index 0000000000..601ab33005 --- /dev/null +++ b/includes/presets/issue-tracker/class-gravityview-preset-issue-tracker.php @@ -0,0 +1,38 @@ + 'table', + 'type' => 'preset', + 'label' => __( 'Issue Tracker', 'gravityview' ), + 'description' => __( 'Manage issues and their statuses.', 'gravityview' ), + 'logo' => plugins_url( 'includes/presets/issue-tracker/logo-issue-tracker.png', GRAVITYVIEW_FILE ), + 'preview' => 'http://demo.gravityview.co/blog/view/issue-tracker/', + 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/issue-tracker/form-issue-tracker.xml', + 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/issue-tracker/fields-issue-tracker.xml' + + ); + + parent::__construct( $id, $settings ); + + } +} + +new GravityView_Preset_Issue_Tracker; diff --git a/includes/presets/job-board/class-gravityview-preset-job-board.php b/includes/presets/job-board/class-gravityview-preset-job-board.php new file mode 100644 index 0000000000..4b4c54cab7 --- /dev/null +++ b/includes/presets/job-board/class-gravityview-preset-job-board.php @@ -0,0 +1,37 @@ + 'list', + 'type' => 'preset', + 'label' => __( 'Job Board', 'gravityview' ), + 'description' => __( 'Post available jobs in a simple job board.', 'gravityview' ), + 'logo' => plugins_url( 'includes/presets/job-board/logo-job-board.png', GRAVITYVIEW_FILE ), + 'preview' => 'http://demo.gravityview.co/blog/view/job-board/', + 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/job-board/form-job-board.xml', + 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/job-board/fields-job-board.xml' + + ); + + parent::__construct( $id, $settings ); + } +} + +new GravityView_Preset_Job_Board; diff --git a/includes/presets/people-table/form-people-table.xml b/includes/presets/people-table/form-people-table.xml deleted file mode 100644 index 8c5ef17ec7..0000000000 --- a/includes/presets/people-table/form-people-table.xml +++ /dev/null @@ -1,95 +0,0 @@ - - -
    - <![CDATA[GV Template - Profiles]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    \ No newline at end of file diff --git a/includes/presets/people-table/index.php b/includes/presets/people-table/index.php deleted file mode 100644 index e71af0ef21..0000000000 --- a/includes/presets/people-table/index.php +++ /dev/null @@ -1 +0,0 @@ - 'list', + 'type' => 'preset', + 'label' => __( 'People Profiles', 'gravityview' ), + 'description' => __( 'List people with individual profiles.', 'gravityview' ), + 'logo' => plugins_url( 'includes/presets/profiles/logo-profiles.png', GRAVITYVIEW_FILE ), + 'preview' => 'http://demo.gravityview.co/blog/view/people-profiles/', + 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/profiles/form-profiles.xml', + 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/profiles/fields-profiles.xml' + ); + + parent::__construct( $id, $settings ); + + } +} + +new GravityView_Preset_Profiles; diff --git a/includes/presets/register-default-templates.php b/includes/presets/register-default-templates.php new file mode 100644 index 0000000000..a3d2d6a9f6 --- /dev/null +++ b/includes/presets/register-default-templates.php @@ -0,0 +1,41 @@ + 'table', + 'type' => 'preset', + 'label' => __( 'Resume Board', 'gravityview' ), + 'description' => __( 'Allow job-seekers to post their resumes.', 'gravityview' ), + 'logo' => plugins_url( 'includes/presets/resume-board/logo-resume-board.png', GRAVITYVIEW_FILE ), + 'preview' => 'http://demo.gravityview.co/blog/view/resume-board/', + 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/resume-board/form-resume-board.xml', + 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/resume-board/fields-resume-board.xml' + ); + + parent::__construct( $id, $settings ); + + } +} + +new GravityView_Preset_Resume_Board; diff --git a/includes/presets/staff-profiles/class-gravityview-preset-staff-profiles.php b/includes/presets/staff-profiles/class-gravityview-preset-staff-profiles.php new file mode 100644 index 0000000000..9261ee9f5c --- /dev/null +++ b/includes/presets/staff-profiles/class-gravityview-preset-staff-profiles.php @@ -0,0 +1,37 @@ + 'list', + 'type' => 'preset', + 'label' => __( 'Staff Profiles', 'gravityview' ), + 'description' => __( 'List members of your team.', 'gravityview' ), + 'logo' => plugins_url( 'includes/presets/staff-profiles/logo-staff-profiles.png', GRAVITYVIEW_FILE ), + 'preview' => 'http://demo.gravityview.co/blog/view/staff-profiles/', + 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/staff-profiles/form-staff-profiles.xml', + 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/staff-profiles/fields-staff-profiles.xml', + ); + + parent::__construct( $id, $settings ); + + } +} + +new GravityView_Preset_Staff_Profiles; diff --git a/includes/presets/website-showcase/class-gravityview-preset-website-showcase.php b/includes/presets/website-showcase/class-gravityview-preset-website-showcase.php new file mode 100644 index 0000000000..7e4a6ce3d7 --- /dev/null +++ b/includes/presets/website-showcase/class-gravityview-preset-website-showcase.php @@ -0,0 +1,37 @@ + 'list', + 'type' => 'preset', + 'label' => __( 'Website Showcase', 'gravityview' ), + 'description' => __( 'Feature submitted websites with screenshots.', 'gravityview' ), + 'logo' => plugins_url( 'includes/presets/website-showcase/logo-website-showcase.png', GRAVITYVIEW_FILE ), + 'preview' => 'http://demo.gravityview.co/blog/view/website-showcase/', + 'preset_form' => GRAVITYVIEW_DIR . 'includes/presets/website-showcase/form-website-showcase.xml', + 'preset_fields' => GRAVITYVIEW_DIR . 'includes/presets/website-showcase/fields-website-showcase.xml' + ); + + parent::__construct( $id, $settings ); + + } +} + +new GravityView_Preset_Website_Showcase; diff --git a/includes/widgets/class-gravityview-widget-custom-content.php b/includes/widgets/class-gravityview-widget-custom-content.php index 56cd1efa01..553cadfecf 100644 --- a/includes/widgets/class-gravityview-widget-custom-content.php +++ b/includes/widgets/class-gravityview-widget-custom-content.php @@ -74,9 +74,14 @@ public function render_frontend( $widget_args, $content = '', $context = '') { $widget_args['content'] = wpautop( $widget_args['content'] ); } + $content = $widget_args['content']; + + $content = GravityView_Merge_Tags::replace_variables( $content ); + // Enqueue scripts needed for Gravity Form display, if form shortcode exists. // Also runs `do_shortcode()` - $content = GFCommon::gform_do_shortcode( $widget_args['content'] ); + $content = GFCommon::gform_do_shortcode( $content ); + // Add custom class $class = !empty( $widget_args['custom_class'] ) ? $widget_args['custom_class'] : ''; diff --git a/includes/widgets/search-widget/class-search-widget.php b/includes/widgets/search-widget/class-search-widget.php index 2b714b9ba3..eb982a4066 100644 --- a/includes/widgets/search-widget/class-search-widget.php +++ b/includes/widgets/search-widget/class-search-widget.php @@ -768,7 +768,7 @@ public static function get_search_class( $custom_class = '' ) { $search_class = apply_filters( 'gravityview_search_class', $search_class ); // Is there an active search being performed? Used by fe-views.js - $search_class .= GravityView_frontend::getInstance()->is_search ? ' gv-is-search' : ''; + $search_class .= GravityView_frontend::getInstance()->isSearch() ? ' gv-is-search' : ''; return gravityview_sanitize_html_class( $search_class ); } diff --git a/includes/wordpress-widgets/class-gravityview-search-wp-widget.php b/includes/wordpress-widgets/class-gravityview-search-wp-widget.php index ba146a260d..b8fa5fee03 100644 --- a/includes/wordpress-widgets/class-gravityview-search-wp-widget.php +++ b/includes/wordpress-widgets/class-gravityview-search-wp-widget.php @@ -101,6 +101,7 @@ public function update( $new_instance, $old_instance ) { 'view_id' => 0, 'post_id' => '', 'search_fields' => '', + 'search_clear' => 0 ); $new_instance = wp_parse_args( (array) $new_instance, $defaults ); @@ -109,6 +110,7 @@ public function update( $new_instance, $old_instance ) { $instance['view_id'] = absint( $new_instance['view_id'] ); $instance['search_fields'] = $new_instance['search_fields']; $instance['post_id'] = $new_instance['post_id']; + $instance['search_clear'] = $new_instance['search_clear']; $is_valid_embed_id = GravityView_View_Data::is_valid_embed_id( $new_instance['post_id'], $instance['view_id'] ); @@ -140,7 +142,8 @@ public function form( $instance ) { 'title' => '', 'view_id' => 0, 'post_id' => '', - 'search_fields' => '' + 'search_fields' => '', + 'search_clear' => 0 ); $instance = wp_parse_args( (array) $instance, $defaults ); @@ -149,6 +152,7 @@ public function form( $instance ) { $view_id = $instance['view_id']; $post_id = $instance['post_id']; $search_fields = $instance['search_fields']; + $search_clear = $instance['search_clear']; $views = GVCommon::get_all_views(); @@ -216,6 +220,12 @@ public function form( $instance ) { ?>

    +

    + + + > +

    +
    diff --git a/includes/wordpress-widgets/register-wordpress-widgets.php b/includes/wordpress-widgets/register-wordpress-widgets.php index 99b4f3f470..1348a04394 100644 --- a/includes/wordpress-widgets/register-wordpress-widgets.php +++ b/includes/wordpress-widgets/register-wordpress-widgets.php @@ -20,6 +20,7 @@ */ function gravityview_register_widgets() { + /** @define "GRAVITYVIEW_DIR" "../../" */ require_once( GRAVITYVIEW_DIR . 'includes/wordpress-widgets/class-gravityview-recent-entries-widget.php' ); register_widget( 'GravityView_Recent_Entries_Widget' ); diff --git a/languages/gravityview.pot b/languages/gravityview.pot index f6a3d2678e..2cf8ae13e6 100644 --- a/languages/gravityview.pot +++ b/languages/gravityview.pot @@ -2,7 +2,7 @@ # This file is distributed under the GPLv2 or later. msgid "" msgstr "" -"Project-Id-Version: GravityView 1.14\n" +"Project-Id-Version: GravityView 1.15\n" "Report-Msgid-Bugs-To: https://gravityview.co/support/\n" "POT-Creation-Date: 2015-04-10 17:11-0700\n" "MIME-Version: 1.0\n" @@ -24,15 +24,15 @@ msgstr "" "X-Poedit-Bookmarks: \n" "X-Textdomain-Support: yes\n" -#: gravityview.php:342 +#: gravityview.php:348 msgid "Top" msgstr "" -#: gravityview.php:343 +#: gravityview.php:349 msgid "Left" msgstr "" -#: gravityview.php:343 +#: gravityview.php:349 msgid "Right" msgstr "" @@ -215,7 +215,7 @@ msgstr "" #: includes/class-gravityview-admin-bar.php:62 #: includes/extensions/edit-entry/class-edit-entry-admin.php:58 #: includes/extensions/edit-entry/class-edit-entry-admin.php:116 -#: includes/extensions/edit-entry/class-edit-entry-render.php:529 +#: includes/extensions/edit-entry/class-edit-entry-render.php:528 #: includes/extensions/edit-entry/class-edit-entry-shortcode.php:186 #: includes/extensions/edit-entry/fields/edit_link.php:14 msgid "Edit Entry" @@ -368,30 +368,32 @@ msgid "%s disapproved." msgstr "" #: includes/class-admin-approve-entries.php:310 +#: tests/unit-tests/GravityView_Uninstall_Test.php:177 uninstall.php:84 msgid "Disapproved the Entry for GravityView" msgstr "" #: includes/class-admin-approve-entries.php:310 +#: tests/unit-tests/GravityView_Uninstall_Test.php:178 uninstall.php:85 msgid "Approved the Entry for GravityView" msgstr "" -#: includes/class-admin-approve-entries.php:532 +#: includes/class-admin-approve-entries.php:533 msgid "Approve" msgstr "" -#: includes/class-admin-approve-entries.php:533 +#: includes/class-admin-approve-entries.php:534 msgid "Disapprove" msgstr "" -#: includes/class-admin-approve-entries.php:535 +#: includes/class-admin-approve-entries.php:536 msgid "Entry not approved for directory viewing. Click to approve this entry." msgstr "" -#: includes/class-admin-approve-entries.php:536 +#: includes/class-admin-approve-entries.php:537 msgid "Entry approved for directory viewing. Click to disapprove this entry." msgstr "" -#: includes/class-admin-approve-entries.php:537 +#: includes/class-admin-approve-entries.php:538 msgid "Show entry in directory view?" msgstr "" @@ -457,7 +459,7 @@ msgstr "" msgid "Entries" msgstr "" -#: includes/class-admin-views.php:306 includes/class-admin.php:91 +#: includes/class-admin-views.php:306 includes/class-admin.php:92 #: includes/class-settings.php:237 includes/class-settings.php:263 msgid "Settings" msgstr "" @@ -673,33 +675,33 @@ msgstr "" msgid "What’s New" msgstr "" -#: includes/class-admin-welcome.php:490 +#: includes/class-admin-welcome.php:500 msgid "GravityView is brought to you by:" msgstr "" -#: includes/class-admin-welcome.php:515 +#: includes/class-admin-welcome.php:525 msgid "Contributors" msgstr "" -#: includes/class-admin-welcome.php:536 +#: includes/class-admin-welcome.php:546 msgid "Want to contribute?" msgstr "" -#: includes/class-admin-welcome.php:537 +#: includes/class-admin-welcome.php:547 msgid "" "If you want to contribute to the code, %syou can on Github%s. If your " "contributions are accepted, you will be thanked here." msgstr "" -#: includes/class-admin.php:92 +#: includes/class-admin.php:93 msgid "Support" msgstr "" -#: includes/class-admin.php:138 +#: includes/class-admin.php:139 msgid "A new form was created for this View: \"%s\"" msgstr "" -#: includes/class-admin.php:139 +#: includes/class-admin.php:140 msgid "" "%sThere are no entries for the new form, so the View will also be empty.%s " "To start collecting entries, you can add submissions through %sthe preview " @@ -711,70 +713,70 @@ msgid "" "\t\t\t\t\t" msgstr "" -#: includes/class-admin.php:151 includes/class-admin.php:152 -#: includes/class-admin.php:154 +#: includes/class-admin.php:152 includes/class-admin.php:153 +#: includes/class-admin.php:155 msgid "View updated. %sView on website.%s" msgstr "" -#: includes/class-admin.php:153 +#: includes/class-admin.php:154 msgid "View deleted." msgstr "" -#: includes/class-admin.php:156 +#: includes/class-admin.php:157 #. translators: %s: date and time of the revision msgid "View restored to revision from %s" msgstr "" -#: includes/class-admin.php:157 +#: includes/class-admin.php:158 msgid "View published. %sView on website.%s" msgstr "" -#: includes/class-admin.php:158 +#: includes/class-admin.php:159 msgid "View saved. %sView on website.%s" msgstr "" -#: includes/class-admin.php:159 +#: includes/class-admin.php:160 msgid "View submitted." msgstr "" -#: includes/class-admin.php:161 +#: includes/class-admin.php:162 msgid "View scheduled for: %1$s." msgstr "" -#: includes/class-admin.php:163 +#: includes/class-admin.php:164 #. translators: Publish box date format, see http:php.net/date msgid "M j, Y @ G:i" msgstr "" -#: includes/class-admin.php:165 +#: includes/class-admin.php:166 msgid "View draft updated. %sView on website.%s" msgstr "" -#: includes/class-admin.php:171 +#: includes/class-admin.php:172 msgid "%s View updated." msgid_plural "%s Views updated." msgstr[0] "" msgstr[1] "" -#: includes/class-admin.php:172 +#: includes/class-admin.php:173 msgid "%s View not updated, somebody is editing it." msgid_plural "%s Views not updated, somebody is editing them." msgstr[0] "" msgstr[1] "" -#: includes/class-admin.php:173 +#: includes/class-admin.php:174 msgid "%s View permanently deleted." msgid_plural "%s Views permanently deleted." msgstr[0] "" msgstr[1] "" -#: includes/class-admin.php:174 +#: includes/class-admin.php:175 msgid "%s View moved to the Trash." msgid_plural "%s Views moved to the Trash." msgstr[0] "" msgstr[1] "" -#: includes/class-admin.php:175 +#: includes/class-admin.php:176 msgid "%s View restored from the Trash." msgid_plural "%s Views restored from the Trash." msgstr[0] "" @@ -792,7 +794,7 @@ msgstr "" msgid "← Go back" msgstr "" -#: includes/class-api.php:1164 +#: includes/class-api.php:1045 msgid "Map It" msgstr "" @@ -1178,15 +1180,15 @@ msgid "" "plugin." msgstr "" -#: includes/class-gravityview-extension.php:349 +#: includes/class-gravityview-extension.php:343 msgid "Could not activate the %s Extension; GravityView is not active." msgstr "" -#: includes/class-gravityview-extension.php:353 +#: includes/class-gravityview-extension.php:347 msgid "The %s Extension requires GravityView Version %s or newer." msgstr "" -#: includes/class-gravityview-extension.php:357 +#: includes/class-gravityview-extension.php:351 msgid "" "The %s Extension requires PHP Version %s or newer. Please ask your host to " "upgrade your server's PHP." @@ -1208,83 +1210,83 @@ msgstr "" msgid "Entry Creator: User ID" msgstr "" -#: includes/class-gv-license-handler.php:71 -#: includes/class-gv-license-handler.php:360 +#: includes/class-gv-license-handler.php:77 +#: includes/class-gv-license-handler.php:367 msgid "Activate License" msgstr "" -#: includes/class-gv-license-handler.php:72 -#: includes/class-gv-license-handler.php:86 -#: includes/class-gv-license-handler.php:359 includes/class-settings.php:532 +#: includes/class-gv-license-handler.php:78 +#: includes/class-gv-license-handler.php:92 +#: includes/class-gv-license-handler.php:366 includes/class-settings.php:533 msgid "Verifying license…" msgstr "" -#: includes/class-gv-license-handler.php:78 -#: includes/class-gv-license-handler.php:361 +#: includes/class-gv-license-handler.php:84 +#: includes/class-gv-license-handler.php:368 msgid "Deactivate License" msgstr "" -#: includes/class-gv-license-handler.php:79 +#: includes/class-gv-license-handler.php:85 msgid "Deactivating license…" msgstr "" -#: includes/class-gv-license-handler.php:85 +#: includes/class-gv-license-handler.php:91 msgid "Check License" msgstr "" -#: includes/class-gv-license-handler.php:347 +#: includes/class-gv-license-handler.php:354 msgid "Status" msgstr "" -#: includes/class-gv-license-handler.php:348 +#: includes/class-gv-license-handler.php:355 msgid "There was an error processing the request." msgstr "" -#: includes/class-gv-license-handler.php:349 +#: includes/class-gv-license-handler.php:356 msgid "" "Could not deactivate the license. The license key you attempted to " "deactivate may not be active or valid." msgstr "" -#: includes/class-gv-license-handler.php:350 +#: includes/class-gv-license-handler.php:357 msgid "The license key is valid, but it has not been activated for this site." msgstr "" -#: includes/class-gv-license-handler.php:351 +#: includes/class-gv-license-handler.php:358 msgid "Invalid: this license has reached its activation limit." msgstr "" -#: includes/class-gv-license-handler.php:351 +#: includes/class-gv-license-handler.php:358 msgid "You can manage license activations %son your GravityView account page%s." msgstr "" -#: includes/class-gv-license-handler.php:352 +#: includes/class-gv-license-handler.php:359 msgid "The license has been deactivated." msgstr "" -#: includes/class-gv-license-handler.php:353 +#: includes/class-gv-license-handler.php:360 msgid "The license key is valid and active." msgstr "" -#: includes/class-gv-license-handler.php:354 +#: includes/class-gv-license-handler.php:361 msgid "The license key entered is invalid." msgstr "" -#: includes/class-gv-license-handler.php:355 +#: includes/class-gv-license-handler.php:362 msgid "The license key was not defined." msgstr "" -#: includes/class-gv-license-handler.php:356 +#: includes/class-gv-license-handler.php:363 msgid "This license key has been revoked." msgstr "" -#: includes/class-gv-license-handler.php:357 +#: includes/class-gv-license-handler.php:364 msgid "" "This license key has expired. %sRenew your license on the GravityView " "website%s to receive updates and support." msgstr "" -#: includes/class-gv-license-handler.php:362 +#: includes/class-gv-license-handler.php:369 msgid "Verify License" msgstr "" @@ -1342,7 +1344,7 @@ msgstr "" msgid "Create views based on a Gravity Forms form" msgstr "" -#: includes/class-post-types.php:150 +#: includes/class-post-types.php:151 msgid "" "%sYou don't have any active views. Let’s go %screate one%s!%s\n" "\n" @@ -1384,48 +1386,94 @@ msgstr "" msgid "Update Settings" msgstr "" -#: includes/class-settings.php:499 +#: includes/class-settings.php:500 msgid "" "The license key you entered has been saved, but not activated. Please " "activate the license." msgstr "" -#: includes/class-settings.php:514 +#: includes/class-settings.php:515 msgid "Required" msgstr "" -#: includes/class-settings.php:529 +#: includes/class-settings.php:530 msgid "License Key" msgstr "" -#: includes/class-settings.php:530 +#: includes/class-settings.php:531 msgid "" "Enter the license key that was sent to you on purchase. This enables plugin " "updates & support." msgstr "" -#: includes/class-settings.php:551 +#: includes/class-settings.php:552 msgid "Support Email" msgstr "" -#: includes/class-settings.php:552 +#: includes/class-settings.php:553 msgid "" "In order to provide responses to your support requests, please provide your " "email address." msgstr "" -#: includes/class-settings.php:558 +#: includes/class-settings.php:559 msgid "No-Conflict Mode" msgstr "" -#: includes/class-settings.php:571 +#: includes/class-settings.php:572 msgid "" "Set this to ON to prevent extraneous scripts and styles from being printed " "on GravityView admin pages, reducing conflicts with other plugins and " "themes." msgstr "" -#: includes/class-settings.php:590 +#: includes/class-settings.php:572 +msgid "If your Edit View tabs are ugly, enable this setting." +msgstr "" + +#: includes/class-settings.php:577 +msgid "Remove Data on Uninstall?" +msgstr "" + +#: includes/class-settings.php:584 +msgid "Delete all GravityView content and settings" +msgstr "" + +#: includes/class-settings.php:584 +msgid "" +"If you delete then re-install GravityView, it will be like installing " +"GravityView for the first time." +msgstr "" + +#: includes/class-settings.php:584 +msgid "" +"When GravityView is uninstalled and deleted, delete all Views, GravityView " +"entry approvals, GravityView-generated entry notes (including approval and " +"entry creator changes), and GravityView plugin settings. No Gravity Forms " +"data will be touched." +msgstr "" + +#: includes/class-settings.php:589 +msgid "Keep GravityView content and settings" +msgstr "" + +#: includes/class-settings.php:589 +msgid "" +"If you delete then re-install the plugin, all GravityView data will be " +"kept. Views, settings, etc. will be untouched." +msgstr "" + +#: includes/class-settings.php:592 +msgid "" +"Should GravityView content and entry approval status be removed from the " +"site when the GravityView plugin is deleted?" +msgstr "" + +#: includes/class-settings.php:592 +msgid "Permanently Delete" +msgstr "" + +#: includes/class-settings.php:612 msgid "You are running GravityView version %s" msgstr "" @@ -1660,35 +1708,35 @@ msgstr "" msgid "Make field editable to:" msgstr "" -#: includes/extensions/edit-entry/class-edit-entry-render.php:541 +#: includes/extensions/edit-entry/class-edit-entry-render.php:542 msgid "There was a problem with your submission." msgstr "" -#: includes/extensions/edit-entry/class-edit-entry-render.php:541 +#: includes/extensions/edit-entry/class-edit-entry-render.php:542 msgid "Errors have been highlighted below." msgstr "" -#: includes/extensions/edit-entry/class-edit-entry-render.php:547 +#: includes/extensions/edit-entry/class-edit-entry-render.php:548 msgid "Entry Updated. %sReturn to Entry%s" msgstr "" -#: includes/extensions/edit-entry/class-edit-entry-render.php:1033 +#: includes/extensions/edit-entry/class-edit-entry-render.php:1034 msgid "Maximum number of files reached" msgstr "" -#: includes/extensions/edit-entry/class-edit-entry-render.php:1410 +#: includes/extensions/edit-entry/class-edit-entry-render.php:1411 msgid "The link to edit this entry is not valid; it may have expired." msgstr "" -#: includes/extensions/edit-entry/class-edit-entry-render.php:1416 +#: includes/extensions/edit-entry/class-edit-entry-render.php:1417 msgid "You do not have permission to edit this entry." msgstr "" -#: includes/extensions/edit-entry/class-edit-entry-render.php:1420 +#: includes/extensions/edit-entry/class-edit-entry-render.php:1421 msgid "You cannot edit the entry; it is in the trash." msgstr "" -#: includes/extensions/edit-entry/class-edit-entry-render.php:1460 +#: includes/extensions/edit-entry/class-edit-entry-render.php:1461 msgid "You do not have permission to edit this field." msgstr "" @@ -2326,12 +2374,12 @@ msgctxt "Debugging output data is empty." msgid "Empty" msgstr "" -#: includes/class-frontend-views.php:1136 +#: includes/class-frontend-views.php:1187 msgctxt "Clear all data from the form" msgid "Clear" msgstr "" -#: includes/class-frontend-views.php:1137 +#: includes/class-frontend-views.php:1188 msgctxt "Reset the search form to the state that existed on page load" msgid "Reset" msgstr "" @@ -2356,23 +2404,33 @@ msgctxt "View Item" msgid "View" msgstr "" -#: includes/class-settings.php:563 +#: includes/class-settings.php:564 msgctxt "Setting: On or off" msgid "On" msgstr "" -#: includes/class-settings.php:567 +#: includes/class-settings.php:568 msgctxt "Setting: On or off" msgid "Off" msgstr "" +#: includes/class-settings.php:582 +msgctxt "Setting: what to do when uninstalling plugin" +msgid "Permanently Delete" +msgstr "" + +#: includes/class-settings.php:587 +msgctxt "Setting: what to do when uninstalling plugin" +msgid "Keep GravityView Data" +msgstr "" + #: includes/extensions/delete-entry/class-delete-entry.php:199 #: includes/extensions/edit-entry/class-edit-entry-admin.php:89 msgctxt "User capability" msgid "Entry Creator" msgstr "" -#: includes/extensions/edit-entry/class-edit-entry-render.php:1436 +#: includes/extensions/edit-entry/class-edit-entry-render.php:1437 msgctxt "Link shown when invalid Edit Entry link is clicked" msgid "Go back." msgstr "" diff --git a/phpunit.xml b/phpunit.xml index fb717743ca..bb8432923c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,6 +8,9 @@ convertWarningsToExceptions="true" verbose="true" > + + + ./tests/unit-tests diff --git a/readme.txt b/readme.txt index e84aa2d43e..7b8ade6f68 100644 --- a/readme.txt +++ b/readme.txt @@ -20,9 +20,38 @@ Beautifully display your Gravity Forms entries. Learn more on [gravityview.co](h == Changelog == -= 1.14.4-beta on September 30 = -* Fixed: Only process "Update" Gravity Forms User Registration Addon feeds += 1.15 on October 15 = +* Added: `{get}` Merge Tag that allows passing data via URL to be safely displayed in Merge Tags. [Learn how this works](http://docs.gravityview.co/article/314-the-get-merge-tag). + - Example: When adding `?first-name=Floaty` to a URL, the Custom Content `My name is {get:first-name}` would be replaced with `My name is Floaty` +* Added: GravityView Capabilities: restrict access to GravityView functionality to certain users and roles. [Learn more](http://docs.gravityview.co/article/311-gravityview-capabilities). + - Fixed: Users without the ability to create Gravity Forms forms are able to create a new form via "Start Fresh" + - Only add the Approve Entries column if user has the `gravityview_moderate_entries` capability (defaults to Editor role or higher) + - Fixed: Contributors now have access to the GravityView "Getting Started" screen +* Added: `[gv_entry_link]` shortcode to link directly to an entry. [Learn more](http://docs.gravityview.co/article/287-edit-entry-and-delete-entry-shortcodes). + - Existing `[gv_delete_entry_link]` and `[gv_edit_entry_link]` shortcodes will continue to work +* Added: Ability to filter View by form in the Admin. [Learn more](http://docs.gravityview.co/article/313-the-views-list-on-the-dashboard). +* Added: Option to delete GravityView data when the plugin is uninstalled, then deleted. [Learn more](http://docs.gravityview.co/article/312-how-to-delete-the-gravityview-data-when-the-plugin-is-uninstalled). +* Added: New support "Beacon" to easily search documentation and ask support questions +* Added: Clear search button to the Search Widget (WP widget) +* Fixed: `number_format()` PHP warning on blank Number fields +* Fixed: `{created_by}` merge tags weren't being escaped using `esc_html()` +* Fixed: Checkmark icons weren't always available when displaying checkbox input field +* Fixed: When "Shorten Link Display" was enabled for Website fields, "Link Text" wasn't respected +* Fixed: Only process "Create" Gravity Forms User Registration Addon feeds, by default the user role and the user display name format persist +* Fixed: Error with List field `Call to undefined method GF_Field::get_input_type()` +* Fixed: BuddyPress/bbPress `bbp_setup_current_user()` warning +* Fixed: `gravityview_is_admin_page()` wasn't recognizing the Settings page as a GravityView admin page +* Fixed: Custom Content Widgets didn't replace Merge Tags * Fixed: PHP Warnings +* Fixed: WordPress Multisite fatal error when Gravity Forms not Network Activated +* Tweak: Don't show Data Source column in Views screen to users who don't have permissions to see any of the data anyway +* Tweak: Entry notes are now created using `GravityView_Entry_Notes` class +* Tweak: Improved automated code testing +* Tweak: Added `gravityview/support_port/display` filter to enable/disable displaying Support Port +* Tweak: Added `gravityview/support_port/show_profile_setting` filter to disable adding the Support Port setting on User Profile pages +* Tweak: Removed `gravityview/admin/display_live_chat` filter +* Tweak: Removed `gravityview_settings_capability` filter +* Tweak: Escape form name in dropdowns = 1.14.2 & 1.14.3 on September 17 = * Fixed: Issue affecting Gravity Forms User Registration Addon. Passwords were being reset when an user edited their own entry. diff --git a/templates/fields/gquiz_grade.php b/templates/fields/gquiz_grade.php index 0899135841..d3ab780bf3 100644 --- a/templates/fields/gquiz_grade.php +++ b/templates/fields/gquiz_grade.php @@ -20,7 +20,7 @@ if( 'letter' === $grading_type_enabled ) { echo $field['value']; -} elseif( GFCommon::current_user_can_any( 'manage_options' ) ) { +} elseif( GVCommon::has_cap( 'manage_options' ) ) { $grade_type = __( 'Letter', 'gravityview' ); printf( esc_html_x( '%s grading is disabled for this form. %sChange the setting%s', '%s is the current Quiz field type ("Letter" or "Pass/Fail")', 'gravityview' ), $grade_type, '', '' ); } diff --git a/templates/fields/gquiz_is_pass.php b/templates/fields/gquiz_is_pass.php index 84847027fd..a8e6cd85b0 100644 --- a/templates/fields/gquiz_is_pass.php +++ b/templates/fields/gquiz_is_pass.php @@ -23,7 +23,7 @@ // By default, the field value is "1" for Pass and "0" for Fail. We want the text. echo GFCommon::replace_variables( '{quiz_passfail}', $gravityview_view->getForm(), $gravityview_view->getCurrentEntry() ); -} elseif( GFCommon::current_user_can_any( 'manage_options' ) ) { +} elseif( GVCommon::has_cap( 'manage_options' ) ) { $grade_type = __( 'Pass/Fail', 'gravityview' ); printf( esc_html_x( '%s grading is disabled for this form. %sChange the setting%s', '%s is the current Quiz field type ("Letter" or "Pass/Fail")', 'gravityview' ), $grade_type, '', '' ); } diff --git a/templates/fields/number.php b/templates/fields/number.php index 8561219f9e..0dedb68038 100644 --- a/templates/fields/number.php +++ b/templates/fields/number.php @@ -11,12 +11,12 @@ $gravityview_view = GravityView_View::getInstance(); /** - * @var double|int $value - * @var double|int $display_value + * @var double|int|string $value + * @var double|int|string $display_value */ extract( $gravityview_view->getCurrentField() ); -if( !empty( $field_settings['number_format'] ) ) { +if( $value !== '' && !empty( $field_settings['number_format'] ) ) { $decimals = ( isset( $field_settings['decimals'] ) && $field_settings['decimals'] !== '' ) ? $field_settings['decimals'] : ''; echo gravityview_number_format( $value, $decimals ); } else { diff --git a/templates/fields/website.php b/templates/fields/website.php index f893512f1f..7e5e97226b 100644 --- a/templates/fields/website.php +++ b/templates/fields/website.php @@ -10,27 +10,25 @@ extract( $gravityview_view->getCurrentField() ); -if( !empty( $field_settings['truncatelink'] ) && function_exists( 'gravityview_format_link' ) ) { - if( !empty( $value ) ) { +if( !empty( $value ) && function_exists( 'gravityview_format_link' ) ) { - /** @since 1.8 */ - $anchor_text = !empty( $field_settings['anchor_text'] ) ? trim( rtrim( $field_settings['anchor_text'] ) ) : false; + /** @since 1.8 */ + $anchor_text = !empty( $field_settings['anchor_text'] ) ? trim( rtrim( $field_settings['anchor_text'] ) ) : false; - // Check empty again, just incase trim removed whitespace didn't work - if( !empty( $anchor_text ) ) { + // Check empty again, just in case trim removed whitespace didn't work + if( !empty( $anchor_text ) ) { - // Replace the variables - $anchor_text = GravityView_API::replace_variables( $anchor_text, $form, $entry ); + // Replace the variables + $anchor_text = GravityView_API::replace_variables( $anchor_text, $form, $entry ); - } else { - $anchor_text = gravityview_format_link( $value ); - } + } else { + $anchor_text = empty( $field_settings['truncatelink'] ) ? $value : gravityview_format_link( $value ); + } - $attributes = empty( $field_settings['open_same_window'] ) ? 'target=_blank' : ''; + $attributes = empty( $field_settings['open_same_window'] ) ? 'target=_blank' : ''; - echo gravityview_get_link( $value, $anchor_text, $attributes ); + echo gravityview_get_link( $value, $anchor_text, $attributes ); - } } else { echo $display_value; } diff --git a/tests/GV_UnitTestCase.php b/tests/GV_UnitTestCase.php new file mode 100644 index 0000000000..1b4084c83c --- /dev/null +++ b/tests/GV_UnitTestCase.php @@ -0,0 +1,34 @@ +factory = new GF_UnitTest_Factory( $this ); + } + + /** + * @inheritDoc + */ + function tearDown() { + /** @see https://core.trac.wordpress.org/ticket/29712 */ + wp_set_current_user( 0 ); + parent::tearDown(); + } + +} \ No newline at end of file diff --git a/tests/bin/install.sh b/tests/bin/install.sh index a8c913b9e5..8326df7ba8 100755 --- a/tests/bin/install.sh +++ b/tests/bin/install.sh @@ -9,7 +9,7 @@ DB_NAME=$1 DB_USER=$2 DB_PASS=$3 DB_HOST=${4-localhost} -WP_VERSION=${5-latest} +WP_VERSION=${5-nightly} WP_TESTS_DIR="${PWD}/tmp/wordpress-tests-lib" WP_CORE_DIR="${PWD}/tmp/wordpress/" @@ -19,25 +19,32 @@ set -ex install_wp() { mkdir -p $WP_CORE_DIR - if [ $WP_VERSION == 'latest' ]; then - local ARCHIVE_NAME='latest' + if [ $WP_VERSION == 'nightly' ]; then + local ARCHIVE_NAME="master" else - local ARCHIVE_NAME="wordpress-$WP_VERSION" + local ARCHIVE_NAME="$WP_VERSION" fi - wget -nv -O /tmp/wordpress.tar.gz https://wordpress.org/${ARCHIVE_NAME}.tar.gz + curl -L https://github.com/WordPress/WordPress/archive/${ARCHIVE_NAME}.tar.gz --output /tmp/wordpress.tar.gz --silent tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR wget -nv -O $WP_CORE_DIR/wp-content/db.php https://raw.github.com/markoheijnen/wp-mysqli/master/db.php } -install_depencency(){ +install_gravity_forms(){ curl -L https://github.com/gravityforms/gravityforms/archive/develop.tar.gz --output /tmp/gravityforms.tar.gz --silent mkdir -p $PWD/tmp/gravityforms tar --strip-components=1 -zxf /tmp/gravityforms.tar.gz -C $PWD/tmp/gravityforms } +install_rest_api() { + curl -L https://github.com/WP-API/api-core/archive/develop.tar.gz --output /tmp/api-core.tar.gz --silent + + mkdir -p $PWD/tmp/api-core + tar --strip-components=1 -zxf /tmp/api-core.tar.gz -C $PWD/tmp/api-core +} + install_test_suite() { # portable in-place argument for both GNU sed and Mac OSX sed if [[ $(uname -s) == 'Darwin' ]]; then @@ -81,15 +88,8 @@ install_db() { mysqladmin CREATE $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA; } -# Create xdebug log, if not correctly installed by Vagrant -# See https://github.com/Varying-Vagrant-Vagrants/VVV/issues/621 -fix_vagrant_permissions() { - touch /tmp/xdebug-remote.log; - chmod 666 /tmp/xdebug-remote.log; -} - install_wp -install_depencency +install_gravity_forms +install_rest_api install_test_suite install_db -fix_vagrant_permissions \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 18c8ce1d96..162731bdff 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -67,6 +67,8 @@ public function __construct() { // load the WP testing environment require_once( $this->wp_tests_dir . '/includes/bootstrap.php' ); + require_once $this->tests_dir . '/GV_UnitTestCase.php'; + require_once $this->tests_dir . '/factory.php'; // set up GravityView @@ -89,6 +91,11 @@ public function test_print_log( $message = '', $data = null ) { */ public function load() { require_once $this->plugin_dir . '/tmp/gravityforms/gravityforms.php'; + + if( ! defined( 'REST_API_VERSION' ) ) { + require_once $this->plugin_dir . '/tmp/api-core/rest-api.php'; + } + require_once $this->plugin_dir . '/gravityview.php'; /* Remove temporary tables which causes problems with GF */ @@ -133,6 +140,8 @@ public function get_form_id() { */ private function create_stubs() { + add_role( 'zero', "No Capabilities", array() ); + $this->form_id = GFAPI::add_form( array( 'title' => 'This is the form title', 'fields' => array( diff --git a/tests/factory.php b/tests/factory.php index 2d498c7c17..71725fe877 100644 --- a/tests/factory.php +++ b/tests/factory.php @@ -50,22 +50,41 @@ function __construct( $factory = null ) { $this->default_generation_definitions = array( 'post_status' => 'publish', 'post_title' => new WP_UnitTest_Generator_Sequence( 'GravityView title %s' ), - 'post_content' => '', - 'post_excerpt' => '', + 'post_content' => new WP_UnitTest_Generator_Sequence( 'Post content %s' ), + 'post_excerpt' => new WP_UnitTest_Generator_Sequence( 'Post excerpt %s' ), + 'post_author' => '', 'post_type' => 'gravityview', 'form_id' => $form['id'], - 'settings' => GravityView_View_Data::get_default_args(), ); } + /** + * Alias for parent method + * Only purpose is to add return values for IDE + * @return array|null|WP_Post + */ + function create_and_get( $args = array(), $generation_definitions = null ) { + return parent::create_and_get( $args, $generation_definitions ); + } + + /** + * @param $args + * + * @return int|WP_Error + */ function create_object( $args ) { + + $form_id = $args['form_id']; + $settings = isset( $args['settings'] ) ? $args['settings'] : GravityView_View_Data::get_default_args(); + $fields = isset( $args['fields'] ) ? $args['fields'] : array(); + $insert_post_response = parent::create_object( $args ); - if( $insert_post_response && !is_wp_error( $insert_post_response ) ) { + if( $insert_post_response && ! is_wp_error( $insert_post_response ) ) { $view_meta = array( - '_gravityview_form_id' => $args['form_id'], - '_gravityview_template_settings' => $args['settings'], + '_gravityview_form_id' => $form_id, + '_gravityview_template_settings' => $settings, '_gravityview_directory_template' => 'preset_business_data', '_gravityview_directory_widgets' => 'a:0:{}', '_gravityview_directory_fields' => 'a:1:{s:23:"directory_table-columns";a:3:{s:13:"535d63d1488b0";a:9:{s:2:"id";s:1:"4";s:5:"label";s:13:"Business Name";s:10:"show_label";s:1:"1";s:12:"custom_label";s:0:"";s:12:"custom_class";s:0:"";s:12:"show_as_link";s:1:"0";s:13:"search_filter";s:1:"0";s:13:"only_loggedin";s:1:"0";s:17:"only_loggedin_cap";s:4:"read";}s:13:"535d63d379a3c";a:9:{s:2:"id";s:2:"12";s:5:"label";s:20:"Business Description";s:10:"show_label";s:1:"1";s:12:"custom_label";s:0:"";s:12:"custom_class";s:0:"";s:12:"show_as_link";s:1:"0";s:13:"search_filter";s:1:"0";s:13:"only_loggedin";s:1:"0";s:17:"only_loggedin_cap";s:4:"read";}s:13:"535d63dc735a6";a:9:{s:2:"id";s:1:"2";s:5:"label";s:7:"Address";s:10:"show_label";s:1:"1";s:12:"custom_label";s:0:"";s:12:"custom_class";s:0:"";s:12:"show_as_link";s:1:"0";s:13:"search_filter";s:1:"0";s:13:"only_loggedin";s:1:"0";s:17:"only_loggedin_cap";s:4:"read";}}}', @@ -89,10 +108,46 @@ function create( $args = array(), $generation_definitions = array() ) { if( ! empty( $args['user_login'] ) ) { $user = get_user_by( 'login', $args['user_login'] ); } else if( ! empty( $args['id'] ) ) { - $user = get_user_by( 'id', $args['id'] ); + $user = $this->get_object_by_id( $args['id'] ); + } + + if( ! $user ) { + $user_id = parent::create( $args, $generation_definitions ); + $user = $this->get_object_by_id( $user_id ); } - return $user ? $user->ID : parent::create( $args, $generation_definitions ); + $this->_add_gravityview_caps( $user ); + + return $user->ID; + } + + function create_object( $args ) { + return wp_insert_user( $args ); + } + + /** + * Add GravityView user caps based on role + * @since 1.15 + * @param WP_User $user + */ + function _add_gravityview_caps( WP_User $user ) { + foreach( $user->roles as $role ) { + $capabilities = GravityView_Roles_Capabilities::all_caps( $role ); + + foreach ( $capabilities as $cap ) { + $user->add_cap( $cap, true ); + } + } + } + + /** + * @param array $args + * @param null $generation_definitions + * + * @return WP_User + */ + function create_and_get( $args = array(), $generation_definitions = null ) { + return parent::create_and_get( $args, $generation_definitions ); } /** @@ -124,7 +179,10 @@ function create_and_set( $args = array(), $generation_definitions = null ) { * @return WP_User */ function set( $user_id ) { - return wp_set_current_user( $user_id ); + $user = wp_set_current_user( $user_id ); + + $this->_add_gravityview_caps( $user ); + return $user; } } @@ -155,6 +213,7 @@ function __construct( $factory = null ) { } function create_object( $args ) { + $args = wp_parse_args( $args, $this->default_generation_definitions ); return GFAPI::add_entry( $args ); } diff --git a/tests/unit-tests/GravityView_AJAX_Test.php b/tests/unit-tests/GravityView_AJAX_Test.php new file mode 100644 index 0000000000..74a9353ba2 --- /dev/null +++ b/tests/unit-tests/GravityView_AJAX_Test.php @@ -0,0 +1,134 @@ +AJAX = new GravityView_Ajax; + $this->create_test_nonce(); + $this->GravityView_Preset_Business_Data = new GravityView_Preset_Business_Data; + + require_once( GFCommon::get_base_path() . '/export.php' ); + } + + /** + * Set a valid "gravityview_ajaxviews" $_POST['nonce'] value + * @see GravityView_Ajax::check_ajax_nonce() + */ + function create_test_nonce() { + $_POST['nonce'] = wp_create_nonce( 'gravityview_ajaxviews' ); + } + + /** + * @covers GravityView_Ajax::pre_get_form_fields() + * @group gvajax + */ + function test_get_available_fields_html() { + + $_POST['template_id'] = $this->GravityView_Preset_Business_Data->template_id; + + // Test form generation and default context + add_action( 'gravityview_render_available_fields', array( $this, 'get_available_fields_html_DEFAULT' ), 10, 2 ); + $this->AJAX->get_available_fields_html(); + remove_action( 'gravityview_render_available_fields', array( $this, 'get_available_fields_html_DEFAULT' ), 10 ); + + // Test SINGLE context being set + $_POST['context'] = 'single'; + add_action( 'gravityview_render_available_fields', array( $this, 'get_available_fields_html_SINGLE_CONTEXT' ), 10, 2 ); + $this->AJAX->get_available_fields_html(); + remove_action( 'gravityview_render_available_fields', array( $this, 'get_available_fields_html_SINGLE_CONTEXT' ), 10 ); + + // Test EDIT context being set + $_POST['context'] = 'edit'; + add_action( 'gravityview_render_available_fields', array( $this, 'get_available_fields_html_EDIT_CONTEXT' ), 10, 2 ); + $this->AJAX->get_available_fields_html(); + remove_action( 'gravityview_render_available_fields', array( $this, 'get_available_fields_html_EDIT_CONTEXT' ), 10 ); + + } + + + + /** + * @param array $form + * @param string $context + */ + function get_available_fields_html_DEFAULT( $form, $context ) { + + $this->assertEquals( GravityView_Ajax::pre_get_form_fields( $this->GravityView_Preset_Business_Data->template_id ), $form ); + + // When not defined, default to directory + $this->assertEquals( 'directory', $context ); + } + + /** + * @param array $form + * @param string $context + */ + function get_available_fields_html_SINGLE_CONTEXT( $form, $context ) { + + // When not defined, default to directory + $this->assertEquals( 'single', $context ); + } + + /** + * @param array $form + * @param string $context + */ + function get_available_fields_html_EDIT_CONTEXT( $form, $context ) { + + // When not defined, default to directory + $this->assertEquals( 'edit', $context ); + } + + /** + * @covers GravityView_Ajax::pre_get_form_fields() + * @group gvajax + */ + function test_pre_get_form_fields() { + + $imported_form = $this->AJAX->import_form( $this->GravityView_Preset_Business_Data->settings['preset_form'] ); + + $not_imported_form = GravityView_Ajax::pre_get_form_fields( $this->GravityView_Preset_Business_Data->template_id ); + + // We don't test exact equality, since the import_form will return GF_Field objects for field items, and other + // differences. We just want to make sure most stuff matches close enough to suggest it's working! + $this->assertEquals( count( $imported_form['fields'] ), count( $not_imported_form['fields'] ) ); + $this->assertEquals( $imported_form['title'], $not_imported_form['title'] ); + } + + /** + * @covers GravityView_Ajax::import_form() + * @group gvajax + */ + function test_import_form() { + /** @define "GRAVITYVIEW_DIR" "../../" */ + + $forms = $this->AJAX->import_form( $this->GravityView_Preset_Business_Data->settings['preset_form'] ); + + $this->assertNotEmpty( $forms ); + $this->assertEquals( 'GravityView - Business Data', $forms['title'] ); + $this->assertEquals( 14, sizeof( $forms['fields'] ) ); + + $GravityView_Preset_Business_Listings = new GravityView_Preset_Business_Listings; + + $forms = $this->AJAX->import_form( $GravityView_Preset_Business_Listings->settings['preset_form'] ); + + $this->assertNotEmpty( $forms ); + $this->assertEquals( 'GravityView - Business Listing', $forms['title'] ); + $this->assertEquals( 13, sizeof( $forms['fields'] ) ); + } +} \ No newline at end of file diff --git a/tests/unit-tests/GravityView_API_Test.php b/tests/unit-tests/GravityView_API_Test.php index 5eb7967d86..9a58d9d169 100644 --- a/tests/unit-tests/GravityView_API_Test.php +++ b/tests/unit-tests/GravityView_API_Test.php @@ -1,6 +1,11 @@ form = GV_Unit_Tests_Bootstrap::instance()->get_form(); $this->form_id = GV_Unit_Tests_Bootstrap::instance()->get_form_id(); @@ -39,8 +39,6 @@ function setUp() { $this->entry = GV_Unit_Tests_Bootstrap::instance()->get_entry(); $this->entry_id = GV_Unit_Tests_Bootstrap::instance()->get_entry_id(); - $this->factory = new GF_UnitTest_Factory( $this ); - } /** diff --git a/tests/unit-tests/GravityView_Edit_Entry_Test.php b/tests/unit-tests/GravityView_Edit_Entry_Test.php index f1ec35a8db..92cb64c7b5 100644 --- a/tests/unit-tests/GravityView_Edit_Entry_Test.php +++ b/tests/unit-tests/GravityView_Edit_Entry_Test.php @@ -1,6 +1,11 @@ factory = new GF_UnitTest_Factory( $this ); - } - /** * @covers GravityView_Edit_Entry::getInstance() */ @@ -72,8 +60,11 @@ function test_get_edit_link() { ), )); + $this->assertNotEmpty( $view, 'There was an error creating the View' ); + + $post_title = new WP_UnitTest_Generator_Sequence( __METHOD__ . ' %s' ); $post_id = $this->factory->post->create(array( - 'post_title' => new WP_UnitTest_Generator_Sequence( __METHOD__ . ' %s' ), + 'post_title' => $post_title->next(), 'post_content' => sprintf( '[gravityview id="%d"]', $view->ID ), )); @@ -86,6 +77,7 @@ function test_get_edit_link() { ### $edit_link_no_post = GravityView_Edit_Entry::get_edit_link( $entry, $view->ID ); + // A link to the raw $this->assertEquals( '?page=gf_entries&view=entry&edit='.$nonce, $edit_link_no_post ); $args = array( @@ -97,7 +89,7 @@ function test_get_edit_link() { 'edit' => $nonce, ); - // The test thinks we have multiple Views. Correct that. + // When running all tests, this test thinks we have multiple Views. Correct that. GravityView_View::getInstance()->setViewId( $view->ID ); ### @@ -106,7 +98,6 @@ function test_get_edit_link() { $edit_link_with_post = GravityView_Edit_Entry::get_edit_link( $entry, $view->ID, $post_id ); $this->assertEquals( add_query_arg( $args, 'http://example.org/' ), $edit_link_with_post ); - } /** @@ -124,6 +115,7 @@ public function test_add_template_path() { } /** + * @group capabilities * @covers GravityView_Edit_Entry::check_user_cap_edit_entry() */ public function test_check_user_cap_edit_entry() { @@ -144,21 +136,20 @@ public function test_check_user_cap_edit_entry() { ), )); - $author_id = $this->factory->user->create( array( + $author = $this->factory->user->create_and_get( array( 'user_login' => 'author', 'role' => 'author' ) ); - $subscriber_id = $this->factory->user->create( array( - 'user_login' => 'subscriber', - 'role' => 'subscriber' - ) ); + $author_id = $author->ID; - $contributor_id = $this->factory->user->create( array( + $contributor = $this->factory->user->create_and_get( array( 'user_login' => 'contributor', 'role' => 'contributor' ) ); + $contributor_id = $contributor->ID; + $editor_id = $this->factory->user->create( array( 'user_login' => 'editor', 'role' => 'editor' @@ -166,14 +157,25 @@ public function test_check_user_cap_edit_entry() { $entry = $this->factory->entry->create_and_get( array( 'form_id' => $form['id'], - 'created_by' => $editor_id + 'created_by' => $contributor_id ) ); + + $subscriber_id = $this->factory->user->create( array( + 'user_login' => 'subscriber', + 'role' => 'subscriber' + ) ); + + ##### + ##### Test Caps & Permissions always being able to edit + ##### + $this->_add_and_remove_caps_test( $entry, $view_user_edit_enabled ); + ##### ##### Test Entry with "Created By" ##### - $this->factory->user->set( $editor_id ); + $this->factory->user->set( $contributor_id ); // User Edit Enabled $this->assertTrue( GravityView_Edit_Entry::check_user_cap_edit_entry( $entry, $view_user_edit_enabled->ID ) ); @@ -183,12 +185,10 @@ public function test_check_user_cap_edit_entry() { /** @var WP_User $admin */ $admin = $this->factory->user->create_and_get( array( - 'user_login' => 'admin', + 'user_login' => 'administrator', 'role' => 'administrator' ) ); - $admin->add_cap('gravityforms_edit_entries'); - $admin_id = $admin->ID; ##### @@ -203,13 +203,14 @@ public function test_check_user_cap_edit_entry() { // Admin always can edit $this->assertTrue( GravityView_Edit_Entry::check_user_cap_edit_entry( $entry, $view_user_edit_disabled->ID ) ); + ##### ##### Test Entry _without_ "Created By" ##### $entry_without_created_by = $this->factory->entry->create_and_get( array( 'form_id' => $form['id'], - 'created_by' => $editor_id + 'created_by' => $contributor_id ) ); unset( $entry_without_created_by['created_by'] ); @@ -219,7 +220,7 @@ public function test_check_user_cap_edit_entry() { // Admin always can edit, even without "created_by" $this->assertTrue( GravityView_Edit_Entry::check_user_cap_edit_entry( $entry_without_created_by, $view_user_edit_disabled->ID ) ); - $this->factory->user->set( $editor_id ); + $this->factory->user->set( $contributor_id ); $this->assertFalse( GravityView_Edit_Entry::check_user_cap_edit_entry( $entry_without_created_by, $view->ID ) ); @@ -270,6 +271,42 @@ public function test_check_user_cap_edit_entry() { } + /** + * Test Caps & Permissions always being able to edit + * + * @param $entry + * @param $view_user_edit_enabled + */ + public function _add_and_remove_caps_test( $entry, $view_user_edit_enabled ) { + + $user = $this->factory->user->create_and_set( array( 'role' => 'zero' ) ); + + $current_user = wp_get_current_user(); + + $this->assertEquals( $user->ID, $current_user->ID ); + + $full_access = array( + 'gravityview_full_access', + 'gform_full_access', + 'gravityview_edit_others_entries', + ); + + foreach( $full_access as $cap ) { + + $user->remove_all_caps(); + + // Can't edit now + $this->assertFalse( current_user_can( $cap ), $cap ); + $this->assertFalse( GravityView_Edit_Entry::check_user_cap_edit_entry( $entry, $view_user_edit_enabled->ID ), $cap ); + + $user->add_cap( $cap ); + + // Can edit now + $this->assertTrue( current_user_can( $cap ), $cap ); + $this->assertTrue( GravityView_Edit_Entry::check_user_cap_edit_entry( $entry, $view_user_edit_enabled->ID ), $cap ); + } + } + /** * @covers GravityView_Edit_Entry::get_nonce_key() */ diff --git a/tests/unit-tests/GravityView_Entry_Link_Shortcode_Test.php b/tests/unit-tests/GravityView_Entry_Link_Shortcode_Test.php new file mode 100644 index 0000000000..afe853d586 --- /dev/null +++ b/tests/unit-tests/GravityView_Entry_Link_Shortcode_Test.php @@ -0,0 +1,156 @@ +object = new GravityView_Entry_Link_Shortcode; + } + + /** + * @covers GravityView_Entry_Link_Shortcode::__construct + * @covers GravityView_Entry_Link_Shortcode::add_hooks + */ + public function test_add_hooks() { + $this->assertTrue( shortcode_exists('gv_entry_link') ); + $this->assertTrue( shortcode_exists('gv_edit_entry_link') ); + $this->assertTrue( shortcode_exists('gv_delete_entry_link') ); + } + + /** + * @covers GravityView_Entry_Link_Shortcode::shortcode + */ + public function test_shortcode() { + + $form = $this->factory->form->create_and_get(); + + $editor = $this->factory->user->create_and_set( array( + 'user_login' => 'editor', + 'role' => 'editor' + ) ); + + $entry = $this->factory->entry->create_and_get( array( + 'form_id' => $form['id'], + 'created_by' => $editor->ID, + ) ); + + $view = $this->factory->view->create_and_get(array( + 'form_id' => $form['id'], + 'settings' => array( + 'user_edit' => 1 + ), + )); + + $this->assertNotEmpty( $view, 'There was an error creating the View' ); + + $post_title = new WP_UnitTest_Generator_Sequence( __METHOD__ . ' %s' ); + $post_id = $this->factory->post->create(array( + 'post_title' => $post_title->next(), + 'post_content' => sprintf( '[gravityview id="%d"]', $view->ID ), + )); + + $atts = array( + 'post_id' => $post_id, + 'entry_id' => $entry['id'], + 'view_id' => $view->ID, + ); + + + + $this->_test_read( $view, $entry, $atts ); + $this->_test_edit( $view, $entry, $atts ); + $this->_test_delete( $view, $entry, $atts ); + } + + /** + * @covers GravityView_Entry_Link_Shortcode::read_shortcode + */ + function _test_read( $view, $entry, $atts ) { + + $link = $this->object->read_shortcode( $atts ); + + $gvid = GravityView_View_Data::getInstance()->has_multiple_views() ? '&gvid='.gravityview_get_view_id() : ''; + + $this->assertEquals( 'View Details', $link, 'no action' ); + + $atts['return'] = 'url'; + $link_return_url = $this->object->read_shortcode( $atts ); + $this->assertEquals( 'http://example.org/?p='.$atts['post_id'].'&entry='.$atts['entry_id'] . $gvid, $link_return_url, 'no action, url only' ); + } + + /** + * @covers GravityView_Entry_Link_Shortcode::delete_shortcode + */ + function _test_delete( $view, $entry, $atts ) { + + // NO CAPS + $this->factory->user->create_and_set(array( 'user_login' => 'zero', 'role' => 'zero')); + + $zero_link = $this->object->delete_shortcode( $atts ); + $this->assertNull( $zero_link, 'user without caps shouldn\'t see delete link' ); + + // ADMIN + $this->factory->user->create_and_set(array( 'user_login' => 'administrator', 'role' => 'administrator') ); + + $delete_entry_delete_link = GravityView_Delete_Entry::get_delete_link( $entry, $view->ID, $atts['post_id'] ); + + $atts['return'] = 'html'; + $delete_link = $this->object->delete_shortcode( $atts ); + $atts['action'] = 'delete'; + $delete_link_backward_compat = $this->object->read_shortcode( $atts ); + $this->assertEquals( 'Delete Entry', $delete_link, 'delete link' ); + $this->assertEquals( $delete_link, $delete_link_backward_compat ); + + $atts['return'] = 'url'; + $delete_link_return_url = $this->object->delete_shortcode( $atts ); + $this->assertEquals( $delete_entry_delete_link, $delete_link_return_url, 'delete link URL only' ); + } + + /** + * @covers GravityView_Entry_Link_Shortcode::edit_shortcode + */ + function _test_edit( $view, $entry, $atts ) { + + $nonce_key = GravityView_Edit_Entry::get_nonce_key( $view->ID, $entry['form_id'], $entry['id'] ); + + $nonce = wp_create_nonce( $nonce_key ); + + $gvid = GravityView_View_Data::getInstance()->has_multiple_views() ? '&gvid='.gravityview_get_view_id() : ''; + + $atts['return'] = 'html'; + $edit_link = $this->object->edit_shortcode( $atts ); + $atts['action'] = 'edit'; + $edit_link_backward_compat = $this->object->read_shortcode( $atts ); + $this->assertEquals( $edit_link, $edit_link_backward_compat ); + $this->assertEquals( 'Edit Entry', $edit_link, 'edit link' ); + + $atts['return'] = 'url'; + $edit_link_return_url = $this->object->edit_shortcode( $atts ); + $this->assertEquals( 'http://example.org/?p='.$atts['post_id'].'&entry='.$atts['entry_id']. $gvid . '&page=gf_entries&view=entry&edit='.$nonce , $edit_link_return_url, 'edit link URL only' ); + + $atts['return'] = 'html'; + $atts['link_atts'] = 'target="_blank"&title="check me out!"'; + $edit_link_link_atts = $this->object->edit_shortcode( $atts ); + $this->assertEquals( 'Edit Entry', $edit_link_link_atts, 'edit link, return html, with link_atts target="_blank"&title="check me out!"' ); + + $atts['return'] = 'html'; + $atts['link_atts'] = 'target=_blank&title=check me out!'; + $edit_link_link_atts = $this->object->edit_shortcode( $atts ); + $this->assertEquals( 'Edit Entry', $edit_link_link_atts, 'edit link return html with link atts target=_blank&title=check me out!' ); + + $zero = $this->factory->user->create_and_set(array('role' => 'zero')); + + // User without edit entry caps should not be able to see link + $this->assertNull( $this->object->edit_shortcode( $atts ), 'user with no caps shouldn\'t be able to see link' ); + } +} diff --git a/tests/unit-tests/GravityView_Merge_Tags_Test.php b/tests/unit-tests/GravityView_Merge_Tags_Test.php index fdb1f2499c..c509f5ef60 100644 --- a/tests/unit-tests/GravityView_Merge_Tags_Test.php +++ b/tests/unit-tests/GravityView_Merge_Tags_Test.php @@ -1,20 +1,16 @@ assertTrue( is_a( $Roles_Caps, 'GravityView_Roles_Capabilities' ) ); + } + + /** + * @covers GravityView_Roles_Capabilities::merge_with_all_caps + */ + public function test_merge_with_all_caps() { + + $new_caps = array( + 'example', + 'example2' + ); + + $all_caps = GravityView_Roles_Capabilities::all_caps(); + + $merged_caps = GravityView_Roles_Capabilities::merge_with_all_caps( $new_caps ); + + $this->assertEquals( array_merge( $new_caps, GravityView_Roles_Capabilities::all_caps() ), $merged_caps ); + + + $merged_caps_all_caps = GravityView_Roles_Capabilities::merge_with_all_caps( $all_caps ); + + // Check that array_unique works properly + $this->assertEquals( GravityView_Roles_Capabilities::all_caps(), $merged_caps_all_caps ); + + } + + /** + * @covers GravityView_Roles_Capabilities::all_caps + */ + public function test_all_caps() { + + $all_roles_array = GravityView_Roles_Capabilities::all_caps( false, false ); + + foreach( array( 'all', 'administrator', 'editor', 'author', 'contributor', 'subscriber' ) as $role ) { + ${"$role"} = GravityView_Roles_Capabilities::all_caps( $role ); + } + + $this->assertEquals( $subscriber, $all_roles_array['subscriber'] ); + $this->assertEquals( $contributor, $all_roles_array['contributor'] ); + $this->assertEquals( $author, $all_roles_array['author'] ); + $this->assertEquals( $editor, $all_roles_array['editor'] ); + $this->assertEquals( $administrator, $all_roles_array['administrator'] ); + $this->assertEquals( $administrator, $all ); + } + + /** + * @covers GravityView_Roles_Capabilities::maybe_add_full_access_caps + */ + public function test_maybe_add_full_access_caps() { + + // Add GV global + $gv_cap = array('gravityview_edit_settings'); + $merged_gv = GravityView_Roles_Capabilities::maybe_add_full_access_caps( $gv_cap ); + $this->assertEquals( array( 'gravityview_edit_settings', 'gravityview_full_access' ), $merged_gv ); + + // Add GF global + $gf_cap = array('gravityforms_edit_entries'); + $merged_gf = GravityView_Roles_Capabilities::maybe_add_full_access_caps( $gf_cap ); + $this->assertEquals( array( 'gravityforms_edit_entries', 'gform_full_access' ), $merged_gf ); + + // Don't dupe + $gv_full_cap = array('gform_full_access'); + $merged_gv_full = GravityView_Roles_Capabilities::maybe_add_full_access_caps( $gv_full_cap ); + $this->assertEquals( $gv_full_cap, $merged_gv_full ); + } + + /** + * @covers GravityView_Roles_Capabilities::has_cap + * @covers GVCommon::has_cap + */ + public function test_has_cap_cap_parameter() { + + $default_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' ); + + foreach( $default_roles as $role ) { + + // Create a user with the default roles + $user = $this->factory->user->create_and_set( array( 'role' => $role ) ); + + $this->assertNotEmpty( $user->ID ); + + // Get all the caps for that role + $role_caps = GravityView_Roles_Capabilities::all_caps( $role ); + + // Make sure that the roles have each of the caps they should have + foreach( $role_caps as $cap ) { + $this->assertTrue( GravityView_Roles_Capabilities::has_cap( $cap ), "Checking {$role} for {$cap} capability" ); + } + + } + } + + function authorless_view_statuses() { + return array( array( 'draft' ), array( 'private' ), array( 'publish' ) ); + } + + /** + * @ticket 27020 + * @dataProvider authorless_view_statuses + */ + function test_authorless_view( $status ) { + // Make a post without an author + $post = $this->factory->view->create( array( 'post_author' => 0, 'post_type' => 'gravityview', 'post_status' => $status ) ); + + // Add an editor and contributor + $editor = $this->factory->user->create_and_get( array( 'role' => 'editor' ) ); + $contributor = $this->factory->user->create_and_get( array( 'role' => 'contributor' ) ); + + // editor can edit, view, and trash + $this->assertTrue( $editor->has_cap( 'edit_gravityview', $post ) ); + $this->assertTrue( $editor->has_cap( 'delete_gravityview', $post ) ); + $this->assertTrue( $editor->has_cap( 'read_gravityview', $post ) ); + + // a contributor cannot (except read a published post) + $this->assertFalse( $contributor->has_cap( 'edit_gravityview', $post ) ); + $this->assertFalse( $contributor->has_cap( 'delete_gravityview', $post ) ); + $this->assertEquals( $status === 'publish', $contributor->has_cap( 'read_gravityview', $post ) ); + } + + /** + * Test using the third $user->ID parameter for has_cap(), which checks against a provided $user_id + * We create a user with no caps, check + * @covers GravityView_Roles_Capabilities::has_cap + * @covers GVCommon::has_cap + */ + public function test_has_cap_user_id_parameter() { + + // Create a user with no capabilities + $zero = $this->factory->user->create_and_set( array( + 'user_login' => 'zero', + 'role' => 'zero', + ) ); + + $this->assertEquals( $zero, wp_get_current_user() ); + + foreach( $default_roles as $role ) { + + $user_id = $this->factory->user->create( array( + 'user_login' => $role, + 'role' => $role + ) ); + + $role_caps = GravityView_Roles_Capabilities::all_caps( $role ); + + foreach( $role_caps as $cap ) { + $this->assertTrue( GravityView_Roles_Capabilities::has_cap( $cap, NULL, $user_id ), "Checking {$role} for {$cap} capability with user #{$user_id}" ); + } + + $this->assertEquals( $zero, wp_get_current_user() ); + } + + $this->assertEquals( $zero, wp_get_current_user() ); + } + + /** + * Test global gravityview_full_access permissions using the + * We create a user with no caps, check + * @covers GravityView_Roles_Capabilities::has_cap + * @covers GVCommon::has_cap + */ + public function test_has_cap_gravityview_full_access() { + + // Create a user with no capabilities + $zero = $this->factory->user->create_and_set( array( + 'user_login' => 'zero', + 'role' => 'zero', + ) ); + + $role_caps = GravityView_Roles_Capabilities::all_caps( 'all' ); + + // Zero can't access anything by default + foreach( $role_caps as $cap ) { + $this->assertFalse( GravityView_Roles_Capabilities::has_cap( $cap ) ); + } + + $zero->add_cap( 'gravityview_full_access' ); + + // With GV full access, $zero is a $hero + foreach( $role_caps as $cap ) { + $this->assertTrue( GravityView_Roles_Capabilities::has_cap( $cap ) ); + } + } + + /** + * @covers GravityView_Roles_Capabilities::has_cap + * @covers GVCommon::has_cap + * @group metacaps + */ + public function test_has_cap_single_post_cap() { + + $admin_id = $this->factory->user->create( array( + 'user_login' => 'administrator', + 'role' => 'administrator', + )); + + // Create a user with no capabilities + $zero = $this->factory->user->create_and_set( array( + 'user_login' => 'zero', + 'role' => 'zero', + ) ); + + $admin_view_id = $this->factory->view->create( array( 'post_author' => $admin_id ) ); + $admin_private_view_id = $this->factory->view->create( array( 'post_author' => $admin_id, 'post_status' => 'private' ) ); + $this->assertTrue( ! empty( $admin_view_id ) ); + + $zero_view_id = $this->factory->view->create( array( 'post_author' => $zero->ID ) ); + $this->assertTrue( ! empty( $zero_view_id ) ); + + $this->assertFalse( GravityView_Roles_Capabilities::has_cap( array( 'edit_others_gravityviews', 'edit_gravityviews' ) ) ); + + // Can't edit own View + $this->assertFalse( GravityView_Roles_Capabilities::has_cap( 'edit_gravityview', $zero_view_id ) ); + + // Can't edit others' View + $this->assertFalse( GravityView_Roles_Capabilities::has_cap( 'edit_gravityview', $admin_view_id ) ); + $this->assertFalse( GravityView_Roles_Capabilities::has_cap( 'edit_gravityview', $admin_private_view_id ) ); + + $zero->add_cap( 'edit_gravityviews' ); + $zero->add_cap( 'edit_published_gravityviews' ); + + // CAN edit own view + $this->assertTrue( GravityView_Roles_Capabilities::has_cap( 'edit_gravityview', $zero_view_id ) ); + + // Still can't edit others' View + $this->assertFalse( GravityView_Roles_Capabilities::has_cap( 'edit_gravityview', $admin_view_id ) ); + + $zero->add_cap( 'edit_others_gravityviews' ); + + // CAN edit others' View + $this->assertTrue( GravityView_Roles_Capabilities::has_cap( 'edit_gravityview', $admin_view_id ) ); + + // Still can't edit other's PRIVATE View + $this->assertFalse( GravityView_Roles_Capabilities::has_cap( 'edit_gravityview', $admin_private_view_id ) ); + + $zero->add_cap( 'edit_private_gravityviews' ); + + // And now user can edit other's PRIVATE View + $this->assertTrue( GravityView_Roles_Capabilities::has_cap( 'edit_gravityview', $admin_private_view_id ) ); + + + ### + ### RESET $zero + ### + $zero->remove_all_caps(); + + $zero->add_cap( 'gravityview_full_access' ); + + // With GV full access, $zero is a $hero + $this->assertTrue( GravityView_Roles_Capabilities::has_cap( 'edit_gravityview', $admin_private_view_id ) ); + } +} \ No newline at end of file diff --git a/tests/unit-tests/GravityView_Uninstall_Test.php b/tests/unit-tests/GravityView_Uninstall_Test.php index cf34f29862..f08b0e1cf3 100644 --- a/tests/unit-tests/GravityView_Uninstall_Test.php +++ b/tests/unit-tests/GravityView_Uninstall_Test.php @@ -1,50 +1,274 @@ factory->form->create_and_get(); + + $all_forms = GFAPI::get_forms(); + + $views = $this->factory->view->create_many( $create_count, array( 'form_id' => $form['id'] ) ); + + $entry_ids = $this->factory->entry->create_many( $create_count, array( 'form_id' => $form['id'] ) ); + + $connected = gravityview_get_connected_views( $form['id'] ); + + $entry_count = GFAPI::count_entries( $form['id'] ); + + // Make sure the objects were created and connected + $this->assertEquals( $create_count, count( array_filter( $views ) ) ); + $this->assertEquals( $create_count, count( array_filter( $connected ) ) ); + $this->assertEquals( $create_count, count( array_filter( $entry_ids ) ) ); + + $this->_set_up_expected_options(); + + ### DO NOT DELETE WHEN THE USER DOESN'T HAVE THE CAPABILITY + + $user = $this->factory->user->create_and_set(array( + 'user_login' => 'administrator', + 'user_pass' => 'administrator', + 'role' => 'administrator', + )); + + $this->assertTrue( GVCommon::has_cap( 'gravityview_uninstall' ) ); + + ### DO NOT DELETE WHEN IT IS NOT SET OR SET TO FALSE + + // TRY deleting when the settings aren't configured. + $this->_set_up_gravityview_settings( NULL ); + $this->uninstall(); + $this->_check_deleted_options( false ); + + // TRY deleting when the Delete setting is set to No + $this->_set_up_gravityview_settings( '0' ); + $this->uninstall(); + $this->_check_deleted_options( false ); + + ### REALLY DELETE NOW + + // Create the items + $this->_set_up_gravityview_settings( 'delete' ); + $this->_set_up_notes( $entry_ids ); + $this->_set_up_entry_meta( $entry_ids, $form ); + + $this->uninstall(); + + // No Forms should be deleted + $this->assertEquals( $all_forms, GFAPI::get_forms() ); + + $this->_check_posts(); + $this->_check_entries( $form, $entry_count ); + $this->_check_deleted_options(); + $this->_check_deleted_entry_notes( $entry_ids ); + $this->_check_deleted_entry_meta( $entry_ids ); + + } + + /** + * Make sure that the GV approval entry meta has been deleted, but not other meta + * @since 1.15 + * @param $entry_ids + */ + function _check_deleted_entry_meta( $entry_ids ) { + + $values = gform_get_meta_values_for_entries( $entry_ids, array( 'is_approved', 'do_not_delete' ) ); + + foreach ( $values as $value ) { + $this->assertFalse( $value->is_approved ); + $this->assertEquals( "DO NOT DELETE", $value->do_not_delete ); + } + } + + /** + * @since 1.15 + */ + function _check_posts() { + // All the Views should be deleted + $views = get_posts( array( + 'post_type' => 'gravityview', + )); + $this->assertEquals( array(), $views ); + } /** - * @var int + * No entries should be deleted + * @since 1.15 + * @param $form + * @param $create_count */ - var $form_id = 0; + function _check_entries( $form, $create_count ) { + $entries = GFAPI::get_entries( $form['id'] ); + $this->assertEquals( sizeof( $entries ), $create_count ); + } /** - * @var array GF Form array + * There should only be ONE NOTE not deleted + * @since 1.15 + * @param $entry_ids */ - var $form = array(); + function _check_deleted_entry_notes( $entry_ids ) { + + foreach( $entry_ids as $entry_id ) { + + $notes = GravityView_Entry_Notes::get_notes( $entry_id ); + + $this->assertEquals( sizeof( $notes ), 1 ); + $this->assertEquals( 'NOT DELETED', $notes[0]->value ); + } + } /** - * @var int + * Make sure the settings and transients have been deleted + * @since 1.15 */ - var $entry_id = 0; + function _check_deleted_options( $should_be_empty = true ) { + + $options = array( + 'gravityformsaddon_gravityview_app_settings', + 'gravityformsaddon_gravityview_version', + 'gravityview_cache_blacklist', + ); + + foreach( $options as $option ) { + if( $should_be_empty ) { + $this->assertEmpty( get_option( $option ) ); + } else { + $this->assertNotEmpty( get_option( $option ) ); + } + } + + $transients = array( + 'gravityview_edd-activate_valid', + 'gravityview_edd-deactivate_valid', + 'gravityview_dismissed_notices', + ); + + foreach( $transients as $transient ) { + if( $should_be_empty ) { + $this->assertEmpty( get_transient( $transient ) ); + } else { + $this->assertNotEmpty( get_transient( $transient ) ); + } + } + + if( $should_be_empty ) { + $this->assertEmpty( get_site_transient( 'gravityview_related_plugins' ) ); + } else { + $this->assertNotEmpty( get_site_transient( 'gravityview_related_plugins' ) ); + } + } /** - * @var array GF Entry array + * @since 1.15 + * @param $entry_ids + * @param $form */ - var $entry = array(); + function _set_up_entry_meta( $entry_ids, $form ) { - function setUp() { - parent::setUp(); + foreach( $entry_ids as $entry_id ) { + GravityView_Admin_ApproveEntries::update_approved( $entry_id, 1, $form['id'] ); + $this->assertEquals( gform_get_meta( $entry_id, 'is_approved' ), 1 ); + gform_add_meta( $entry_id, 'do_not_delete', 'DO NOT DELETE' ); + } + } - define( 'WP_UNINSTALL_PLUGIN', true ); + /** + * @since 1.15 + * @param $entry_ids + */ + function _set_up_notes( $entry_ids ) { - require_once GV_Unit_Tests_Bootstrap::instance()->plugin_dir . '/uninstall.php'; + $disapproved = __('Disapproved the Entry for GravityView', 'gravityview'); + $approved = __('Approved the Entry for GravityView', 'gravityview'); + foreach( $entry_ids as $entry_id ) { - $this->form = GV_Unit_Tests_Bootstrap::instance()->get_form(); - $this->form_id = GV_Unit_Tests_Bootstrap::instance()->get_form_id(); + $added_notes = 0; - $this->entry = GV_Unit_Tests_Bootstrap::instance()->get_entry(); - $this->entry_id = GV_Unit_Tests_Bootstrap::instance()->get_entry_id(); + // Deleted because it's "gravityview" note type + GravityView_Entry_Notes::add_note( $entry_id, -1, new WP_UnitTest_Generator_Sequence( 'To be deleted %s' ), 'NOTE!', 'gravityview' ); // TO BE DELETED + $added_notes++; - do_action( 'deactivate_gravityview/gravityview.php' ); + // Deleted because it's the same value as $approved + GravityView_Entry_Notes::add_note( $entry_id, -1, new WP_UnitTest_Generator_Sequence( 'To be deleted %s' ), $approved, 'note' ); + $added_notes++; + + // Deleted because it's the same value as $disapproved + GravityView_Entry_Notes::add_note( $entry_id, -1, new WP_UnitTest_Generator_Sequence( 'To be deleted %s' ), $disapproved, 'note' ); + $added_notes++; + + // NOT DELETED + GravityView_Entry_Notes::add_note( $entry_id, -1, new WP_UnitTest_Generator_Sequence( 'NOT DELETED %s' ), 'NOT DELETED', 'note' ); // NOT DELETED ("note" type) + $added_notes++; + + $notes = GravityView_Entry_Notes::get_notes( $entry_id ); + + $this->assertEquals( sizeof( $notes ), $added_notes ); + } + } + + /** + * Get the script and process uninstall + * @since 1.15 + */ + function uninstall() { + if( ! defined('WP_UNINSTALL_PLUGIN') ) { + define( 'WP_UNINSTALL_PLUGIN', true ); + } + if( ! class_exists('GravityView_Uninstall' ) ) { + require_once GV_Unit_Tests_Bootstrap::instance()->plugin_dir . '/uninstall.php'; + } else { + new GravityView_Uninstall; + } } /** - * @group uninstall + * Set delete to true + * @since 1.15 */ - function test_gravityview_has_shortcode_r() { + function _set_up_gravityview_settings( $delete_on_uninstall ) { + $defaults = GravityView_Settings::get_instance()->get_app_settings(); + if( NULL === $delete_on_uninstall ) { + unset( $defaults['delete-on-uninstall'] ); + } else { + $defaults['delete-on-uninstall'] = $delete_on_uninstall; + } + update_option( 'gravityformsaddon_gravityview_app_settings', $defaults ); + + if( NULL !== $delete_on_uninstall ) { + $this->assertEquals( $delete_on_uninstall, GravityView_Settings::get_instance()->get_app_setting( 'delete-on-uninstall' ) ); + } + } + + /** + * @since 1.15 + */ + function _set_up_expected_options() { + update_option( 'gravityformsaddon_gravityview_version', 1 ); + update_option( 'gravityview_cache_blacklist', 1 ); + + set_transient( 'gravityview_edd-activate_valid', 1 ); + set_transient( 'gravityview_edd-deactivate_valid', 1 ); + set_transient( 'gravityview_dismissed_notices', 1 ); + + set_site_transient( 'gravityview_related_plugins', 1 ); } + } diff --git a/tests/unit-tests/connector-functions_Test.php b/tests/unit-tests/connector-functions_Test.php index 2d7b2f3d40..166923e7f4 100644 --- a/tests/unit-tests/connector-functions_Test.php +++ b/tests/unit-tests/connector-functions_Test.php @@ -1,5 +1,7 @@ assertEquals( '', gravityview_strip_whitespace( ' ' ) ); + $this->assertEquals( '', gravityview_strip_whitespace( ' ' ) ); + $this->assertEquals( '', gravityview_strip_whitespace( "\t" ) ); + $this->assertEquals( '', gravityview_strip_whitespace( "\t\t" ) ); + $this->assertEquals( '', gravityview_strip_whitespace( "\n" ) ); + $this->assertEquals( '', gravityview_strip_whitespace( "\n\n" ) ); + $this->assertEquals( '', gravityview_strip_whitespace( "\r" ) ); + $this->assertEquals( '', gravityview_strip_whitespace( "\r\r" ) ); + $this->assertEquals( '', gravityview_strip_whitespace( "\r\n\t " ) ); + + $this->assertEquals( 'Word', gravityview_strip_whitespace( "\nWord\n" ) ); + $this->assertEquals( 'Word Word', gravityview_strip_whitespace( "Word\nWord\n" ) ); + $this->assertEquals( 'Word Word Word', gravityview_strip_whitespace( "Word\nWord\nWord\n" ) ); + $this->assertEquals( 'Word Word Word', gravityview_strip_whitespace( "Word Word Word " ) ); + $this->assertEquals( 'Word Word Word Word', gravityview_strip_whitespace( "Word\n\tWord\n\tWord\n\tWord" ) ); + } + + /** + * @group helperfunctions + * @covers ::gravityview_is_not_empty_string + */ + public function test_gravityview_is_not_empty_string() { + + $not_empty_strings = array( + array(), + true, + false, + null, + 0, + '0', + 'asdsad', + ' ', + ); + + foreach ( $not_empty_strings as $not_empty_string ) { + $this->assertTrue( gravityview_is_not_empty_string( $not_empty_string ) ); + } + + // The one true empty string + $this->assertFalse( gravityview_is_not_empty_string( '' ) ); + } + + /** + * We only test gravityview_number_format() without a decimal defined; otherwise it's an alias for number_format_i18n() + * + * @see number_format_i18n() + * @group helperfunctions + * @covers ::gravityview_number_format() + */ + public function test_gravityview_number_format() { + + $numbers = array( + '0' => '1,000', + '1' => '1,000.0', + '2' => '1,000.00', + '7' => '1,000,000.0000000', + '17' => '1.00000000000000000', + ); + + foreach( $numbers as $expected_decimals => $number ) { + $this->assertEquals( number_format_i18n( $number, $expected_decimals ), gravityview_number_format( $number ) ); + } + + } /** * @group helperfunctions + * @covers ::gravityview_sanitize_html_class() */ public function test_gravityview_sanitize_html_class() { @@ -39,6 +114,8 @@ public function test_gravityview_sanitize_html_class() { /** * @group helperfunctions + * @covers ::gravityview_format_link() + * @covers :: _gravityview_strip_subdomain() */ public function test_gravityview_format_link_DEFAULT() { @@ -133,6 +210,7 @@ public function test_gravityview_format_link_DEFAULT() { /** * @group helperfunctions + * @covers ::gravityview_format_link() */ public function test_gravityview_format_link_WHEN_FILTER_ROOTONLY_FALSE() { @@ -163,6 +241,7 @@ public function test_gravityview_format_link_WHEN_FILTER_ROOTONLY_FALSE() { /** * @group helperfunctions + * @covers ::gravityview_format_link() */ public function test_gravityview_format_link_WHEN_FILTER_NOSUBDOMAIN_FALSE() { @@ -210,6 +289,7 @@ public function test_gravityview_format_link_WHEN_FILTER_NOSUBDOMAIN_FALSE() { /** * @group helperfunctions + * @covers ::gravityview_format_link() */ public function test_gravityview_format_link_WHEN_FILTER_NOQUERYSTRING_FALSE() { diff --git a/uninstall.php b/uninstall.php index bf21391aa6..434cd48d4a 100644 --- a/uninstall.php +++ b/uninstall.php @@ -16,33 +16,48 @@ /** * Delete GravityView content when GravityView is uninstalled, if the setting is set to "Delete on Uninstall" - * @since 1.14 + * @since 1.15 */ class GravityView_Uninstall { + private $settings_name = 'gravityformsaddon_gravityview_app_settings'; + public function __construct() { /** @define "$file_path" "./" */ $file_path = plugin_dir_path( __FILE__ ); - include_once $file_path . 'includes/class-settings.php'; - include_once $file_path . 'includes/class-gravityview-roles.php'; + include_once $file_path . 'includes/class-gravityview-roles-capabilities.php'; /** * Only delete content and settings if "Delete on Uninstall?" setting is "Permanently Delete" - * @var string|null $delete NULL if not configured (previous versions); "0" if false, "delete" if delete */ - $delete = GravityView_Settings::get_instance()->get_app_setting('delete-on-uninstall'); + $delete = $this->get_delete_setting(); - if( 'delete' === $delete ) { + if( GravityView_Roles_Capabilities::has_cap( 'gravityview_uninstall' ) && 'delete' === $delete ) { $this->fire_everything(); } } + /** + * Get the GravityView setting for whether to delete all View settings on uninstall + * + * @since 1.15 + * + * @return string|null Returns NULL if not configured (pre-1.15 settings); "0" if false, "delete" if delete + */ + private function get_delete_setting() { + + $settings = get_option( $this->settings_name, array() ); + + return isset( $settings[ 'delete-on-uninstall' ] ) ? $settings[ 'delete-on-uninstall' ] : null; + } + /** * Delete GravityView Views, settings, roles, caps, etc. * @see https://youtu.be/FXy_DO6IZOA?t=35s - * @since 1.14 + * @since 1.15 + * @return void */ private function fire_everything() { $this->delete_options(); @@ -54,7 +69,8 @@ private function fire_everything() { /** * Delete GravityView "approved entry" meta - * @since 1.14 + * @since 1.15 + * @return void */ private function delete_entry_meta() { global $wpdb; @@ -73,7 +89,8 @@ private function delete_entry_meta() { /** * Delete all GravityView-generated entry notes - * @since 1.14 + * @since 1.15 + * @return void */ private function delete_entry_notes() { global $wpdb; @@ -97,7 +114,8 @@ private function delete_entry_notes() { /** * Delete capabilities added by GravityView - * @since 1.14 + * @since 1.15 + * @return void */ private function delete_capabilities() { GravityView_Roles_Capabilities::get_instance()->remove_caps(); @@ -105,7 +123,8 @@ private function delete_capabilities() { /** * Delete all the GravityView custom post type posts - * @since 1.14 + * @since 1.15 + * @return void */ private function delete_posts() { @@ -125,7 +144,8 @@ private function delete_posts() { /** * Delete GravityView options - * @since 1.14 + * @since 1.15 + * @return void */ private function delete_options() { @@ -136,6 +156,8 @@ private function delete_options() { delete_transient( 'gravityview_edd-activate_valid' ); delete_transient( 'gravityview_edd-deactivate_valid' ); delete_transient( 'gravityview_dismissed_notices' ); + + delete_site_transient( 'gravityview_related_plugins' ); } }